installed plugin `Jetpack Protect` version 1.0.2

This commit is contained in:
KawaiiPunk 2022-07-28 18:42:13 +00:00 committed by Gitium
parent d55c4af45c
commit a3483bf62f
286 changed files with 64090 additions and 0 deletions

View File

@ -0,0 +1,120 @@
# 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).
## 1.0.2 - 2022-07-27
### Changed
- Updated package dependencies.
### Fixed
- Fix protect admin toolbar icon display when Jetpack enabled and connected
- Minor bug fix - added isset() checks for report data properties
## 1.0.1 - 2022-07-07
### Added
- Added two new FAQ entries
- Protect: record even just after the site is registered
### Changed
- Moved normalization of the Protect status report data to the server side.
- Renamed `master` references to `trunk`
- Reorder JS imports for `import/order` eslint rule.
### Fixed
- Fixed bug that would not display Core vulnerabilities.
- Removed legacy code and documentation and adds new docs for the debug helper plugin.
- Fixed recommendation for plugins that don't have a fix yet
- Protect: Fix visual issue of the Interstitial page
## 1.0.0 - 2022-05-31
### Added
- Add additional tracking events
- Add Alert icon to the error admin page
- Add checks to the Site Health page
- Add custom hook to handle viewport sizes via CSS
- Add error message when adding plugin fails
- Add first approach of Interstitial page
- Add Jetpack Scan to promotion section when site doesn't have Security bundle
- Add missing prop-types module dependency
- Add Navigation dropdown mode and use it for small/medium screens
- Add ProductOffer component
- Add product offer component
- Add title and redirect for vul at wpscan
- Add 'get themes' to synced callables in Protect
- Add installedThemes to the initial state
- Add notifications to the menu item and the admin bar
- Add status polling to the Protect admin page.
- Added details to the FAQ question on how Jetpack Protect is different from other Jetpack products.
- Added Jetpack Protect readme file for the plugin listing.
- Added option to display terms of service below product activation button.
- Added Social card to My Jetpack.
- Added the list of installed plugins to the initial state
- Change ConnectScreen by the Interstitial component
- Creates Status Class
- Empty state screen
- Expose and use IconsCard component
- Flush cache on plugin deactivation
- Footer component
- Handle error in the UI
- Hooks on plugin activation and deactivation
- Hook to read data from the initial state
- Implement Footer
- Implement Add Security bundle workflow
- Introduce Accordion component
- Introduce Navigation component
- Introduce Summary component
- Introduce VulnerabilitiesList component
- JS Components: Introduce Alert component. Add error to ProductOffer components
- More options to the testing api responses
- Record admin page-view and get security from footer events
- Render Security component with data provided by wpcom
- Request and expose to the client the Security bundle data
- Update logo
### Changed
- Add empty state for no vuls
- Add popover at Badge
- Cache empty statuses for a short period of time
- Changed connection screen to the one that does not require a product
- Changed the method used to disconnect
- Changed the wording for the initial screen.
- Change expiration time of plugin cache
- Clean connection data. Update to latest connection API
- Configure Sync to only what we need to sync
- Janitorial: require a more recent version of WordPress now that WP 6.0 is coming out.
- JS Components: Add subTitle prop to ProductOffer component
- JS Components: iterate over Dialog component
- Improve Dialog layout in medium viewport size
- Move VulnerabilitiesList to section hero
- New VulsList
- Redesign Summary component
- Re-implement "Your results will be ready soon" layout
- Re-implement Admin page by using Dialog component
- Remove use of `pnpx` in preparation for pnpm 7.0.
- Replace deprecated external-link variation by using isExternalLink prop
- Require only site level connection
- Truncate items at NavigationGroup
- Tweak footer
- Update Footer and VulsList for small/medium viewport
- Update Navigation to be external controlled
- Update Protect icon
- Update VulnerabilitiesList to remove severity and add fixed in
- Updated package dependencies.
- Update package.json metadata.
- Updates CTA wording to reduce confusion when user already has Jetpack Security Bundle which includes Jetpack Scan
- Update the Status when connection is established
- Use data provided by My Jetpack to render Product offer
- Use weight Button prop to style the "learn more" footer link
- Use a different copy when there are no found vulnerabilities but there are still some unchecked items
### Removed
- Removed Full Sync from loaded modules as Full Sync Immediately is present by default now
### Fixed
- Adjust spacing and overflow properties of the navigation labels to improve display of long names.
- Fix Connectino initialization
- Fix fatal when checking whether site site has vuls
- Fix right margin in primary layout

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,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="jetpack-protect" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="protect" d="M891.73 737.552l-376.745 171.248-376.744-171.248v-255.284c0-217.615 139.767-423.66 336.854-488.663 25.898-8.54 53.883-8.54 79.78 0 197.089 65.004 336.855 271.048 336.855 488.663v255.284zM221.962 482.268v201.375l293.023 133.193 293.023-133.193v-201.375c0-184.909-119.726-356.506-279.357-409.155-8.863-2.924-18.465-2.924-27.333 0-159.627 52.649-279.355 224.246-279.355 409.155zM547.958 377.569h-65.252l-6.214 248.056h77.682l-6.216-248.056zM557.537 289.531c0-22.784-18.642-40.648-42.204-40.648-23.564 0-42.207 17.864-42.207 40.648 0 22.789 18.643 40.653 42.207 40.653 23.562 0 42.204-17.864 42.204-40.653z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,30 @@
@font-face {
font-family: 'jetpack-protect';
src: url('fonts/jetpack-protect.eot?31wpn');
src: url('fonts/jetpack-protect.eot?31wpn#iefix') format('embedded-opentype'),
url('fonts/jetpack-protect.ttf?31wpn') format('truetype'),
url('fonts/jetpack-protect.woff?31wpn') format('woff'),
url('fonts/jetpack-protect.svg?31wpn#jetpack-protect') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
[class^="jp-protect-icon"], [class*=" jp-protect-icon"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'jetpack-protect' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.jp-protect-icon:before {
content: "\e900";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
/*
* 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
*/

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,69 @@
{
"name": "automattic/jetpack-protect",
"description": "Social plugin",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": {
"ext-json": "*",
"automattic/jetpack-assets": "^1.17",
"automattic/jetpack-admin-ui": "^0.2.11",
"automattic/jetpack-autoloader": "^2.11",
"automattic/jetpack-composer-plugin": "^1.1",
"automattic/jetpack-config": "^1.9",
"automattic/jetpack-identity-crisis": "^0.8.18",
"automattic/jetpack-my-jetpack": "^1.8",
"automattic/jetpack-plugins-installer": "^0.1.4",
"automattic/jetpack-sync": "^1.37"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2",
"automattic/wordbless": "0.3.1"
},
"autoload": {
"classmap": [
"src/"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
],
"build-development": [
"pnpm run build"
],
"build-production": [
"pnpm run build-production-concurrently"
],
"watch": [
"Composer\\Config::disableProcessTimeout",
"pnpm run watch"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
},
"repositories": [],
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"mirror-repo": "Automattic/jetpack-protect-plugin",
"release-branch-prefix": "protect",
"wp-plugin-slug": "jetpack-protect",
"version-constants": {
"JETPACK_PROTECT_VERSION": "jetpack-protect.php"
}
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true,
"automattic/jetpack-autoloader": true,
"automattic/jetpack-composer-plugin": true
},
"autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_protectⓥ1_0_2"
}
}

View File

@ -0,0 +1,120 @@
<?php
/**
*
* Plugin Name: Jetpack Protect
* Plugin URI: https://wordpress.org/plugins/jetpack-protect
* Description: Security tools that keep your site safe and sound, from posts to plugins.
* Version: 1.0.2
* Author: Automattic
* Author URI: https://jetpack.com/
* License: GPLv2 or later
* Text Domain: jetpack-protect
*
* @package automattic/jetpack-protect
*/
/*
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.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'JETPACK_PROTECT_VERSION', '1.0.2' );
define( 'JETPACK_PROTECT_DIR', plugin_dir_path( __FILE__ ) );
define( 'JETPACK_PROTECT_ROOT_FILE', __FILE__ );
define( 'JETPACK_PROTECT_ROOT_FILE_RELATIVE_PATH', plugin_basename( __FILE__ ) );
define( 'JETPACK_PROTECT_SLUG', 'jetpack-protect' );
define( 'JETPACK_PROTECT_NAME', 'Jetpack Protect' );
define( 'JETPACK_PROTECT_URI', 'https://jetpack.com/jetpack-protect' );
define( 'JETPACK_PROTECT_FOLDER', dirname( plugin_basename( __FILE__ ) ) );
define( 'JETPACK_PROTECT_BASE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
// Jetpack Autoloader.
$jetpack_autoloader = JETPACK_PROTECT_DIR . 'vendor/autoload_packages.php';
if ( is_readable( $jetpack_autoloader ) ) {
require_once $jetpack_autoloader;
if ( method_exists( \Automattic\Jetpack\Assets::class, 'alias_textdomains_from_file' ) ) {
\Automattic\Jetpack\Assets::alias_textdomains_from_file( JETPACK_PROTECT_DIR . 'jetpack_vendor/i18n-map.php' );
}
} else { // Something very unexpected. Error out gently with an admin_notice and exit loading.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
__( 'Error loading autoloader file for Jetpack Protect plugin', 'jetpack-protect' )
);
}
add_action(
'admin_notices',
function () {
?>
<div class="notice notice-error is-dismissible">
<p>
<?php
printf(
wp_kses(
/* translators: Placeholder is a link to a support document. */
__( 'Your installation of Jetpack Protect is incomplete. If you installed Jetpack Protect from GitHub, please refer to <a href="%1$s" target="_blank" rel="noopener noreferrer">this document</a> to set up your development environment. Jetpack Protect must have Composer dependencies installed and built via the build command.', 'jetpack-protect' ),
array(
'a' => array(
'href' => array(),
'target' => array(),
'rel' => array(),
),
)
),
'https://github.com/Automattic/jetpack/blob/trunk/docs/development-environment.md#building-your-project'
);
?>
</p>
</div>
<?php
}
);
return;
}
// Redirect to plugin page when the plugin is activated.
add_action( 'activated_plugin', 'jetpack_protect_plugin_activation' );
/**
* Redirects to plugin page when the plugin is activated
*
* @param string $plugin Path to the plugin file relative to the plugins directory.
*/
function jetpack_protect_plugin_activation( $plugin ) {
if ( JETPACK_PROTECT_ROOT_FILE_RELATIVE_PATH === $plugin ) {
wp_safe_redirect( esc_url( admin_url( 'admin.php?page=jetpack-protect' ) ) );
exit;
}
}
// Add "Settings" link to plugins page.
add_filter(
'plugin_action_links_' . JETPACK_PROTECT_FOLDER . '/jetpack-protect.php',
function ( $actions ) {
$settings_link = '<a href="' . esc_url( admin_url( 'admin.php?page=jetpack-protect' ) ) . '">' . __( 'Dashboard', 'jetpack-protect' ) . '</a>';
array_unshift( $actions, $settings_link );
return $actions;
}
);
register_deactivation_hook( __FILE__, array( 'Jetpack_Protect', 'plugin_deactivation' ) );
// Main plugin class.
new Jetpack_Protect();

View File

@ -0,0 +1,121 @@
# 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).
## [1.4.15] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [1.4.14] - 2022-06-21
### Changed
- Renaming master to trunk.
## [1.4.13] - 2022-04-26
### Changed
- Updated package dependencies.
## [1.4.12] - 2022-01-25
### Changed
- Updated package dependencies.
## [1.4.11] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
## [1.4.10] - 2021-12-14
### Changed
- Updated package dependencies.
## [1.4.9] - 2021-11-02
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [1.4.8] - 2021-10-13
### Changed
- Updated package dependencies.
## [1.4.7] - 2021-10-12
### Changed
- Updated package dependencies
## [1.4.6] - 2021-09-28
### Changed
- Updated package dependencies.
## [1.4.5] - 2021-08-30
### Changed
- Run composer update on test-php command instead of phpunit
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- updated annotations versions
## [1.4.4] - 2021-05-25
### Changed
- Updated package dependencies.
## [1.4.3] - 2021-04-08
### Changed
- Packaging and build changes, no change to the package itself.
## [1.4.2] - 2021-03-30
### Added
- Composer alias for dev-master, to improve dependencies
### Changed
- Update package dependencies.
### Fixed
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
## [1.4.1] - 2021-02-05
- CI: Make tests more generic
## [1.4.0] - 2021-01-20
- Add mirror-repo information to all current composer packages
## [1.3.0] - 2020-12-17
- Coverage Update whitelist for backend tests
- Pin dependencies
- Packages: Update for PHP 8 testing
## [1.2.0] - 2020-09-17
## [1.1.1] - 2020-09-17
- a8c-mc-stats: Do not distribute test files
## [1.1.0] - 2020-08-13
- CI: Try collect js coverage
## 1.0.0 - 2020-07-27
- Creates the MC Stats package
[1.4.15]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.14...v1.4.15
[1.4.14]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.13...v1.4.14
[1.4.13]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.12...v1.4.13
[1.4.12]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.11...v1.4.12
[1.4.11]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.10...v1.4.11
[1.4.10]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.9...v1.4.10
[1.4.9]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.8...v1.4.9
[1.4.8]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.7...v1.4.8
[1.4.7]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.6...v1.4.7
[1.4.6]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.5...v1.4.6
[1.4.5]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.4...v1.4.5
[1.4.4]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.3...v1.4.4
[1.4.3]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.2...v1.4.3
[1.4.2]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.0.0...v1.1.0

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,39 @@
{
"name": "automattic/jetpack-a8c-mc-stats",
"description": "Used to record internal usage stats for Automattic. Not visible to site owners.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2"
},
"autoload": {
"classmap": [
"src/"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-a8c-mc-stats",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.4.x-dev"
}
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* Jetpack MC Stats package.
*
* @package automattic/jetpack-mc-stats
*/
namespace Automattic\Jetpack;
/**
* Class MC Stats, used to record stats using https://pixel.wp.com/g.gif
*/
class A8c_Mc_Stats {
/**
* Holds the stats to be processed
*
* @var array
*/
private $stats = array();
/**
* Indicates whether to use the transparent pixel (b.gif) instead of the regular smiley (g.gif)
*
* @var boolean
*/
public $use_transparent_pixel = true;
/**
* Class Constructor
*
* @param boolean $use_transparent_pixel Use the transparent pixel instead of the smiley.
*/
public function __construct( $use_transparent_pixel = true ) {
$this->use_transparent_pixel = $use_transparent_pixel;
}
/**
* Store a stat for later output.
*
* @param string $group The stat group.
* @param string $name The stat name to bump.
*
* @return boolean true if stat successfully added
*/
public function add( $group, $name ) {
if ( ! \is_string( $group ) || ! \is_string( $name ) ) {
return false;
}
if ( ! isset( $this->stats[ $group ] ) ) {
$this->stats[ $group ] = array();
}
if ( \in_array( $name, $this->stats[ $group ], true ) ) {
return false;
}
$this->stats[ $group ][] = $name;
return true;
}
/**
* Gets current stats stored to be processed
*
* @return array $stats
*/
public function get_current_stats() {
return $this->stats;
}
/**
* Return the stats from a group in an array ready to be added as parameters in a query string
*
* @param string $group_name The name of the group to retrieve.
* @return array Array with one item, where the key is the prefixed group and the value are all stats concatenated with a comma. If group not found, an empty array will be returned
*/
public function get_group_query_args( $group_name ) {
$stats = $this->get_current_stats();
if ( isset( $stats[ $group_name ] ) && ! empty( $stats[ $group_name ] ) ) {
return array( "x_jetpack-{$group_name}" => implode( ',', $stats[ $group_name ] ) );
}
return array();
}
/**
* Gets a list of trac URLs for every stored URL
*
* @return array An array of URLs
*/
public function get_stats_urls() {
$urls = array();
foreach ( $this->get_current_stats() as $group => $stat ) {
$group_query_string = $this->get_group_query_args( $group );
$urls[] = $this->build_stats_url( $group_query_string );
}
return $urls;
}
/**
* Outputs the tracking pixels for the current stats and empty the stored stats from the object
*
* @return void
*/
public function do_stats() {
$urls = $this->get_stats_urls();
foreach ( $urls as $url ) {
echo '<img src="' . esc_url( $url ) . '" width="1" height="1" style="display:none;" />';
}
$this->stats = array();
}
/**
* Pings the stats server for the current stats and empty the stored stats from the object
*
* @return void
*/
public function do_server_side_stats() {
$urls = $this->get_stats_urls();
foreach ( $urls as $url ) {
$this->do_server_side_stat( $url );
}
$this->stats = array();
}
/**
* Runs stats code for a one-off, server-side.
*
* @param string $url string The URL to be pinged. Should include `x_jetpack-{$group}={$stats}` or whatever we want to store.
*
* @return bool If it worked.
*/
public function do_server_side_stat( $url ) {
$response = wp_remote_get( esc_url_raw( $url ) );
if ( is_wp_error( $response ) ) {
return false;
}
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
return false;
}
return true;
}
/**
* Builds the stats url.
*
* @param array $args array|string The arguments to append to the URL.
*
* @return string The URL to be pinged.
*/
public function build_stats_url( $args ) {
$defaults = array(
'v' => 'wpcom2',
'rand' => md5( wp_rand( 0, 999 ) . time() ),
);
$args = wp_parse_args( $args, $defaults );
$gifname = true === $this->use_transparent_pixel ? 'b.gif' : 'g.gif';
/**
* Filter the URL used as the Stats tracking pixel.
*
* @since-jetpack 2.3.2
* @since 1.0.0
*
* @param string $url Base URL used as the Stats tracking pixel.
*/
$base_url = apply_filters(
'jetpack_stats_base_url',
'https://pixel.wp.com/' . $gifname
);
$url = add_query_arg( $args, $base_url );
return $url;
}
}

View File

@ -0,0 +1,87 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.11] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [0.2.10] - 2022-07-12
### Changed
- Updated package dependencies.
## [0.2.9] - 2022-06-21
### Changed
- Renaming master to trunk.
## [0.2.8] - 2022-06-14
### Changed
- Updated package dependencies.
## [0.2.7] - 2022-04-26
### Changed
- Update package.json metadata.
## [0.2.6] - 2022-04-05
### Changed
- Updated package dependencies.
## [0.2.5] - 2022-03-08
### Fixed
- Do not handle Akismet submenu if Jetpack plugin is present
## [0.2.4] - 2022-02-09
### Added
- Support for akismet menu with stand-alone plugins
### Fixed
- Fixes menu order working around a bug in add_submenu_page
## [0.2.3] - 2022-01-25
### Changed
- Updated package dependencies.
## [0.2.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
## [0.2.1] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
## [0.2.0] - 2021-12-14
### Added
- New method to get the top level menu item
## [0.1.1] - 2021-11-17
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## 0.1.0 - 2021-10-13
### Added
- Created the package.
### Changed
- Updated package dependencies.
### Fixed
- Fixing menu visibility issues.
[0.2.11]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.10...0.2.11
[0.2.10]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.9...0.2.10
[0.2.9]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.8...0.2.9
[0.2.8]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.7...0.2.8
[0.2.7]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.6...0.2.7
[0.2.6]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.5...0.2.6
[0.2.5]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.4...0.2.5
[0.2.4]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.3...0.2.4
[0.2.3]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.2...0.2.3
[0.2.2]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.1...0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-admin-ui/compare/0.1.1...0.2.0
[0.1.1]: https://github.com/Automattic/jetpack-admin-ui/compare/0.1.0...0.1.1

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,50 @@
{
"name": "automattic/jetpack-admin-ui",
"description": "Generic Jetpack wp-admin UI elements",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2",
"automattic/wordbless": "dev-master"
},
"autoload": {
"classmap": [
"src/"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\""
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-admin-ui",
"textdomain": "jetpack-admin-ui",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}"
},
"branch-alias": {
"dev-trunk": "0.2.x-dev"
},
"version-constants": {
"::PACKAGE_VERSION": "src/class-admin-menu.php"
}
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true
}
}
}

View File

@ -0,0 +1,214 @@
<?php
/**
* Admin Menu Registration
*
* @package automattic/jetpack-admin-ui
*/
namespace Automattic\Jetpack\Admin_UI;
/**
* This class offers a wrapper to add_submenu_page and makes sure stand-alone plugin's menu items are always added under the Jetpack top level menu.
* If the Jetpack top level was not previously registered by other plugin, it will be registered here.
*/
class Admin_Menu {
const PACKAGE_VERSION = '0.2.11';
/**
* Whether this class has been initialized
*
* @var boolean
*/
private static $initialized = false;
/**
* List of menu items enqueued to be added
*
* @var array
*/
private static $menu_items = array();
/**
* Initialize the class and set up the main hook
*
* @return void
*/
public static function init() {
if ( ! self::$initialized ) {
self::$initialized = true;
self::handle_akismet_menu();
add_action( 'admin_menu', array( __CLASS__, 'admin_menu_hook_callback' ), 1000 ); // Jetpack uses 998.
}
}
/**
* Handles the Akismet menu item when used alongside other stand-alone plugins
*
* When Jetpack plugin is present, Akismet menu item is moved under the Jetpack top level menu, but if Akismet is active alongside other stand-alone plugins,
* we use this method to move the menu item.
*/
private static function handle_akismet_menu() {
if ( ! class_exists( 'Jetpack' ) && class_exists( 'Akismet_Admin' ) ) {
// Prevent Akismet from adding a menu item.
add_action(
'admin_menu',
function () {
remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
},
4
);
// Add an Anti-spam menu item for Jetpack.
self::add_menu( __( 'Anti-Spam', 'jetpack-admin-ui' ), __( 'Anti-Spam', 'jetpack-admin-ui' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
}
}
/**
* Enqueue styles for the top level menu
*
* @return void
*/
public static function enqueue_style() {
wp_enqueue_style(
'jetpack-admin-ui',
plugin_dir_url( __FILE__ ) . 'css/jetpack-icon.css',
array(),
self::PACKAGE_VERSION
);
}
/**
* Callback to the admin_menu hook that will register the enqueued menu items
*
* @return void
*/
public static function admin_menu_hook_callback() {
$can_see_toplevel_menu = true;
$jetpack_plugin_present = class_exists( 'Jetpack_React_Page' );
if ( ! $jetpack_plugin_present ) {
add_action( 'admin_print_scripts', array( __CLASS__, 'enqueue_style' ) );
add_menu_page(
'Jetpack',
'Jetpack',
'read',
'jetpack',
'__return_null',
'div',
3
);
// If Jetpack plugin is not present, user will only be able to see this menu if they have enough capability to at least one of the sub menus being added.
$can_see_toplevel_menu = false;
}
/**
* The add_sub_menu function has a bug and will not keep the right order of menu items.
*
* @see https://core.trac.wordpress.org/ticket/52035
* Let's order the items before registering them.
* Since this all happens after the Jetpack plugin menu items were added, all items will be added after Jetpack plugin items - unless position is very low number (smaller than the number of menu items present in Jetpack plugin).
*/
usort(
self::$menu_items,
function ( $a, $b ) {
$position_a = empty( $a['position'] ) ? 0 : $a['position'];
$position_b = empty( $b['position'] ) ? 0 : $b['position'];
return $position_a - $position_b;
}
);
foreach ( self::$menu_items as $menu_item ) {
if ( ! current_user_can( $menu_item['capability'] ) ) {
continue;
}
$can_see_toplevel_menu = true;
add_submenu_page(
'jetpack',
$menu_item['page_title'],
$menu_item['menu_title'],
$menu_item['capability'],
$menu_item['menu_slug'],
$menu_item['function'],
$menu_item['position']
);
}
if ( ! $jetpack_plugin_present ) {
remove_submenu_page( 'jetpack', 'jetpack' );
}
if ( ! $can_see_toplevel_menu ) {
remove_menu_page( 'jetpack' );
}
}
/**
* Adds a new submenu to the Jetpack Top level menu
*
* The parameters this method accepts are the same as @see add_submenu_page. This class will
* aggreagate all menu items registered by stand-alone plugins and make sure they all go under the same
* Jetpack top level menu. It will also handle the top level menu registration in case the Jetpack plugin is not present.
*
* @param string $page_title The text to be displayed in the title tags of the page when the menu
* is selected.
* @param string $menu_title The text to be used for the menu.
* @param string $capability The capability required for this menu to be displayed to the user.
* @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu
* and only include lowercase alphanumeric, dashes, and underscores characters
* to be compatible with sanitize_key().
* @param callable $function The function to be called to output the content for this page.
* @param int $position The position in the menu order this item should appear.
*
* @return string The resulting page's hook_suffix
*/
public static function add_menu( $page_title, $menu_title, $capability, $menu_slug, $function, $position = null ) {
self::init();
self::$menu_items[] = compact( 'page_title', 'menu_title', 'capability', 'menu_slug', 'function', 'position' );
/**
* Let's return the page hook so consumers can use.
* We know all pages will be under Jetpack top level menu page, so we can hardcode the first part of the string.
* Using get_plugin_page_hookname here won't work because the top level page is not registered yet.
*/
return 'jetpack_page_' . $menu_slug;
}
/**
* Gets the slug for the first item under the Jetpack top level menu
*
* @return string|null
*/
public static function get_top_level_menu_item_slug() {
global $submenu;
if ( ! empty( $submenu['jetpack'] ) ) {
$item = reset( $submenu['jetpack'] );
if ( isset( $item[2] ) ) {
return $item[2];
}
}
}
/**
* Gets the URL for the first item under the Jetpack top level menu
*
* @param string $fallback If Jetpack menu is not there or no children is found, return this fallback instead. Default to admin_url().
* @return string
*/
public static function get_top_level_menu_item_url( $fallback = false ) {
$slug = self::get_top_level_menu_item_slug();
if ( $slug ) {
$url = menu_page_url( $slug, false );
return $url;
}
$url = $fallback ? $fallback : admin_url();
return $url;
}
}

View File

@ -0,0 +1,14 @@
@font-face {
font-family: 'jetpack';
src: url( '../fonts/jetpack/jetpack.eot' );
src: url( '../fonts/jetpack/jetpack.eot?#iefix' ) format( 'embedded-opentype' ),
url( '../fonts/jetpack/jetpack.woff' ) format( 'woff' ),
url( '../fonts/jetpack/jetpack.ttf' ) format( 'truetype' );
font-weight: normal;
font-style: normal;
}
li.toplevel_page_jetpack .wp-menu-image:before {
font-family: 'jetpack' !important;
content: '\f100';
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2017-12-4: Created with FontForge (http://fontforge.org)
-->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata>
Created by FontForge 20170925 at Mon Dec 4 15:18:51 2017
By Michael Arestad
</metadata>
<defs>
<font id="jetpack" horiz-adv-x="200" >
<font-face
font-family="jetpack"
font-weight="400"
font-stretch="normal"
units-per-em="512"
panose-1="2 0 5 3 0 0 0 0 0 0"
ascent="448"
descent="-64"
bbox="51.2002 -12.7998 460.8 396.8"
underline-thickness="25.6"
underline-position="-51.2"
unicode-range="U+0020-F100"
/>
<missing-glyph />
<glyph glyph-name="space" unicode=" "
/>
<glyph glyph-name="jetpack-logo__icon" unicode="&#xf100;" horiz-adv-x="512"
d="M256 396.8c112.64 0 204.8 -92.1602 204.8 -204.8s-92.1602 -204.8 -204.8 -204.8s-204.8 92.1602 -204.8 204.8s92.1602 204.8 204.8 204.8zM230.4 166.4v179.199l-102.4 -179.199h102.4zM281.6 38.4004l102.4 179.199h-102.4v-179.199z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,312 @@
# 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).
## [1.17.20] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [1.17.19] - 2022-07-12
### Changed
- Updated package dependencies.
## [1.17.18] - 2022-07-06
### Changed
- Updated package dependencies
## [1.17.17] - 2022-06-21
### Changed
- Renaming master to trunk.
## [1.17.16] - 2022-06-14
## [1.17.15] - 2022-06-08
### Changed
- Reorder JS imports for `import/order` eslint rule. [#24601]
## [1.17.14] - 2022-05-18
### Changed
- Updated package dependencies [#24372]
## [1.17.13] - 2022-05-10
### Changed
- Updated package dependencies. [#24302]
## [1.17.12] - 2022-05-04
### Added
- Add missing JavaScript dependencies, and fix a test. [#24096]
## [1.17.11] - 2022-04-26
### Changed
- Updated package dependencies.
## [1.17.10] - 2022-04-19
### Fixed
- Assets: Defer the enqueued script instead of its translations
## [1.17.9] - 2022-04-05
### Changed
- Updated package dependencies.
## [1.17.8] - 2022-03-29
### Changed
- Updated package dependencies.
## [1.17.7] - 2022-03-23
### Changed
- Updated package dependencies.
## [1.17.6] - 2022-03-02
### Changed
- Updated package dependencies.
## [1.17.5] - 2022-02-16
### Changed
- Updated package dependencies.
## [1.17.4] - 2022-02-09
### Changed
- Updated package dependencies.
## [1.17.3] - 2022-02-02
### Fixed
- Fixed minor coding standard violation.
## [1.17.2] - 2022-02-01
### Changed
- Build: remove unneeded files from production build.
## [1.17.1] - 2022-01-27
### Changed
- Updated package dependencies.
## [1.17.0] - 2022-01-25
### Added
- Accept package path prefixes from jetpack-composer-plugin and use them when lazy-loading JS translations.
- Generate the `wp-jp-i18n-loader` module needed by the new i18n-loader-webpack-plugin.
### Deprecated
- Deprecated the `wp-jp-i18n-state` module.
## [1.16.2] - 2022-01-18
### Fixed
- Handle the case where `WP_LANG_DIR` is in `WP_CONTENT_DIR`, but `WP_CONTENT_DIR` is not in `ABSPATH`.
## [1.16.1] - 2022-01-05
### Fixed
- Don't issue a "doing it wrong" warning for registering aliases during plugin activation.
## [1.16.0] - 2022-01-04
### Added
- Document use of jetpack-assets, jetpack-composer-plugin, and i18n-loader-webpack-plugin together.
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
- Updated package textdomain from `jetpack` to `jetpack-assets`.
## [1.15.0] - 2021-12-20
### Added
- Add `alias_textdomain()`.
## [1.14.0] - 2021-12-14
### Added
- Generate `wp-jp-i18n-state` script.
## [1.13.1] - 2021-11-22
### Fixed
- Call `_doing_it_wrong` correctly.
## [1.13.0] - 2021-11-22
### Added
- Have `Assets::register_script()` accept a textdomain for `wp_set_script_translations` (and complain if no textdomain is passed when `wp-i18n` is depended on).
### Changed
- Updated package dependencies
### Fixed
- Added missing option doc for `Assets::register_script()`.
## [1.12.0] - 2021-11-15
### Added
- Add `Assets::register_script()` for easier loading of Webpack-built scripts.
## [1.11.10] - 2021-11-02
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [1.11.9] - 2021-10-13
### Changed
- Updated package dependencies.
## [1.11.8] - 2021-10-06
### Changed
- Updated package dependencies
## [1.11.7] - 2021-09-28
### Changed
- Updated package dependencies.
## [1.11.6] - 2021-08-30
### Changed
- Run composer update on test-php command instead of phpunit
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- update annotations versions
## [1.11.5] - 2021-05-25
### Changed
- Updated package dependencies.
## [1.11.4] - 2021-04-08
### Changed
- Packaging and build changes, no change to the package itself.
## [1.11.3] - 2021-03-30
### Added
- Composer alias for dev-master, to improve dependencies
### Changed
- Update package dependencies.
### Fixed
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
## [1.11.2] - 2021-02-23
- CI: Make tests more generic
## [1.11.1] - 2021-01-26
- Add mirror-repo information to all current composer packages
- Monorepo: Reorganize all projects
## [1.11.0] - 2021-01-05
- Update dependency brain/monkey to v2.6.0
## [1.10.0] - 2020-12-08
- Assets: introduce new method to process static resources
- Assets: Use defer for script tags
- Pin dependencies
- Packages: Update for PHP 8 testing
## [1.9.1] - 2020-11-24
- Update dependency brain/monkey to v2.5.0
- Updated PHPCS: Packages and Debugger
## [1.9.0] - 2020-10-27
- Instagram oEmbed: Simplify
## [1.8.0] - 2020-09-29
- Consolidate the Lazy Images package to rely on the Assets package
## [1.7.0] - 2020-08-25
- Packages: Update filenames after #16810
- CI: Try collect js coverage
- Docker: Add package testing shortcut
## [1.6.0] - 2020-07-28
- Various: Use wp_resource_hints
## [1.5.0] - 2020-06-30
- PHPCS: Clean up the packages
- WooCommerce Analytics: avoid 404 error when enqueuing script
## [1.4.0] - 2020-05-26
- Add Jetpack Scan threat notifications
## [1.3.0] - 2020-04-28
- Update dependencies to latest stable
## [1.2.0] - 2020-03-31
- Update dependencies to latest stable
## [1.1.1] - 2020-01-27
- Pin dependency brain/monkey to 2.4.0
## [1.1.0] - 2020-01-14
- Packages: Various improvements for wp.com or self-contained consumers
## [1.0.3] - 2019-11-08
- Packages: Use classmap instead of PSR-4
## [1.0.1] - 2019-10-28
- PHPCS: JITM and Assets packages
- Packages: Add gitattributes files to all packages that need th…
## 1.0.0 - 2019-09-14
- Statically access asset tools
[1.17.20]: https://github.com/Automattic/jetpack-assets/compare/v1.17.19...v1.17.20
[1.17.19]: https://github.com/Automattic/jetpack-assets/compare/v1.17.18...v1.17.19
[1.17.18]: https://github.com/Automattic/jetpack-assets/compare/v1.17.17...v1.17.18
[1.17.17]: https://github.com/Automattic/jetpack-assets/compare/v1.17.16...v1.17.17
[1.17.16]: https://github.com/Automattic/jetpack-assets/compare/v1.17.15...v1.17.16
[1.17.15]: https://github.com/Automattic/jetpack-assets/compare/v1.17.14...v1.17.15
[1.17.14]: https://github.com/Automattic/jetpack-assets/compare/v1.17.13...v1.17.14
[1.17.13]: https://github.com/Automattic/jetpack-assets/compare/v1.17.12...v1.17.13
[1.17.12]: https://github.com/Automattic/jetpack-assets/compare/v1.17.11...v1.17.12
[1.17.11]: https://github.com/Automattic/jetpack-assets/compare/v1.17.10...v1.17.11
[1.17.10]: https://github.com/Automattic/jetpack-assets/compare/v1.17.9...v1.17.10
[1.17.9]: https://github.com/Automattic/jetpack-assets/compare/v1.17.8...v1.17.9
[1.17.8]: https://github.com/Automattic/jetpack-assets/compare/v1.17.7...v1.17.8
[1.17.7]: https://github.com/Automattic/jetpack-assets/compare/v1.17.6...v1.17.7
[1.17.6]: https://github.com/Automattic/jetpack-assets/compare/v1.17.5...v1.17.6
[1.17.5]: https://github.com/Automattic/jetpack-assets/compare/v1.17.4...v1.17.5
[1.17.4]: https://github.com/Automattic/jetpack-assets/compare/v1.17.3...v1.17.4
[1.17.3]: https://github.com/Automattic/jetpack-assets/compare/v1.17.2...v1.17.3
[1.17.2]: https://github.com/Automattic/jetpack-assets/compare/v1.17.1...v1.17.2
[1.17.1]: https://github.com/Automattic/jetpack-assets/compare/v1.17.0...v1.17.1
[1.17.0]: https://github.com/Automattic/jetpack-assets/compare/v1.16.2...v1.17.0
[1.16.2]: https://github.com/Automattic/jetpack-assets/compare/v1.16.1...v1.16.2
[1.16.1]: https://github.com/Automattic/jetpack-assets/compare/v1.16.0...v1.16.1
[1.16.0]: https://github.com/Automattic/jetpack-assets/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/Automattic/jetpack-assets/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/Automattic/jetpack-assets/compare/v1.13.1...v1.14.0
[1.13.1]: https://github.com/Automattic/jetpack-assets/compare/v1.13.0...v1.13.1
[1.13.0]: https://github.com/Automattic/jetpack-assets/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/Automattic/jetpack-assets/compare/v1.11.10...v1.12.0
[1.11.10]: https://github.com/Automattic/jetpack-assets/compare/v1.11.9...v1.11.10
[1.11.9]: https://github.com/Automattic/jetpack-assets/compare/v1.11.8...v1.11.9
[1.11.8]: https://github.com/Automattic/jetpack-assets/compare/v1.11.7...v1.11.8
[1.11.7]: https://github.com/Automattic/jetpack-assets/compare/v1.11.6...v1.11.7
[1.11.6]: https://github.com/Automattic/jetpack-assets/compare/v1.11.5...v1.11.6
[1.11.5]: https://github.com/Automattic/jetpack-assets/compare/v1.11.4...v1.11.5
[1.11.4]: https://github.com/Automattic/jetpack-assets/compare/v1.11.3...v1.11.4
[1.11.3]: https://github.com/Automattic/jetpack-assets/compare/v1.11.2...v1.11.3
[1.11.2]: https://github.com/Automattic/jetpack-assets/compare/v1.11.1...v1.11.2
[1.11.1]: https://github.com/Automattic/jetpack-assets/compare/v1.11.0...v1.11.1
[1.11.0]: https://github.com/Automattic/jetpack-assets/compare/v1.10.0...v1.11.0
[1.10.0]: https://github.com/Automattic/jetpack-assets/compare/v1.9.1...v1.10.0
[1.9.1]: https://github.com/Automattic/jetpack-assets/compare/v1.9.0...v1.9.1
[1.9.0]: https://github.com/Automattic/jetpack-assets/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/Automattic/jetpack-assets/compare/v1.7.0...v1.8.0
[1.7.0]: https://github.com/Automattic/jetpack-assets/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/Automattic/jetpack-assets/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/Automattic/jetpack-assets/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/Automattic/jetpack-assets/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/Automattic/jetpack-assets/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Automattic/jetpack-assets/compare/v1.1.1...v1.2.0
[1.1.1]: https://github.com/Automattic/jetpack-assets/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/Automattic/jetpack-assets/compare/v1.0.3...v1.1.0
[1.0.3]: https://github.com/Automattic/jetpack-assets/compare/v1.0.1...v1.0.3
[1.0.1]: https://github.com/Automattic/jetpack-assets/compare/v1.0.0...v1.0.1

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,19 @@
<?php
/**
* Action Hooks for Jetpack Assets module.
*
* @package automattic/jetpack-assets
*/
// If WordPress's plugin API is available already, use it. If not,
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
if ( function_exists( 'add_action' ) ) {
add_action( 'wp_default_scripts', array( Automattic\Jetpack\Assets::class, 'wp_default_scripts_hook' ) );
} else {
global $wp_filter;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter['wp_default_scripts'][10][] = array(
'accepted_args' => 1,
'function' => array( Automattic\Jetpack\Assets::class, 'wp_default_scripts_hook' ),
);
}

View File

@ -0,0 +1 @@
<?php return array('dependencies' => array('wp-i18n'), 'version' => 'ee939953aa2115e2ca59');

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
{
"name": "automattic/jetpack-assets",
"description": "Asset management utilities for Jetpack ecosystem packages",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-constants": "^1.6"
},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2",
"wikimedia/testing-access-wrapper": "^1.0 || ^2.0"
},
"autoload": {
"files": [
"actions.php"
],
"classmap": [
"src/"
]
},
"scripts": {
"build-development": [
"pnpm run build"
],
"build-production": [
"pnpm run build-production"
],
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/php/clover.xml\"",
"pnpm run test-coverage"
],
"test-js": [
"pnpm run test"
],
"test-php": [
"@composer phpunit"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-assets",
"textdomain": "jetpack-assets",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.17.x-dev"
}
}
}

View File

@ -0,0 +1,738 @@
<?php
/**
* Jetpack Assets package.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack;
use Automattic\Jetpack\Assets\Semver;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use InvalidArgumentException;
/**
* Class Assets
*/
class Assets {
/**
* Holds all the scripts handles that should be loaded in a deferred fashion.
*
* @var array
*/
private $defer_script_handles = array();
/**
* The singleton instance of this class.
*
* @var Assets
*/
protected static $instance;
/**
* The registered textdomain mappings.
*
* @var array `array( mapped_domain => array( string target_domain, string target_type, string semver, string path_prefix ) )`.
*/
private static $domain_map = array();
/**
* Constructor.
*
* Static-only class, so nothing here.
*/
private function __construct() {}
// ////////////////////
// region Async script loading
/**
* Get the singleton instance of the class.
*
* @return Assets
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new Assets();
self::$instance->init_hooks();
}
return self::$instance;
}
/**
* Initalize the hooks as needed.
*/
private function init_hooks() {
/*
* Load some scripts asynchronously.
*/
add_filter( 'script_loader_tag', array( $this, 'script_add_async' ), 10, 2 );
}
/**
* A public method for adding the async script.
*
* @param string $script_handle Script handle.
*/
public function add_async_script( $script_handle ) {
$this->defer_script_handles[] = $script_handle;
}
/**
* Add an async attribute to scripts that can be loaded deferred.
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*
* @param string $tag The <script> tag for the enqueued script.
* @param string $handle The script's registered handle.
*/
public function script_add_async( $tag, $handle ) {
if ( empty( $this->defer_script_handles ) ) {
return $tag;
}
if ( in_array( $handle, $this->defer_script_handles, true ) ) {
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
return preg_replace( '/<script( [^>]*)? src=/i', '<script defer$1 src=', $tag );
}
return $tag;
}
/**
* A helper function that lets you enqueue scripts in an async fashion.
*
* @param string $handle Name of the script. Should be unique.
* @param string $min_path Minimized script path.
* @param string $non_min_path Full Script path.
* @param array $deps Array of script dependencies.
* @param bool $ver The script version.
* @param bool $in_footer Should the script be included in the footer.
*/
public static function enqueue_async_script( $handle, $min_path, $non_min_path, $deps = array(), $ver = false, $in_footer = true ) {
$assets_instance = self::instance();
$assets_instance->add_async_script( $handle );
wp_enqueue_script( $handle, self::get_file_url_for_environment( $min_path, $non_min_path ), $deps, $ver, $in_footer );
}
// endregion .
// ////////////////////
// region Utils
/**
* Given a minified path, and a non-minified path, will return
* a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
*
* If $package_path is provided, then the minified or non-minified file URL will be generated
* relative to the root package directory.
*
* Both `$min_base` and `$non_min_base` can be either full URLs, or are expected to be relative to the
* root Jetpack directory.
*
* @param string $min_path minified path.
* @param string $non_min_path non-minified path.
* @param string $package_path Optional. A full path to a file inside a package directory
* The URL will be relative to its directory. Default empty.
* Typically this is done by passing __FILE__ as the argument.
*
* @return string The URL to the file
* @since 1.0.3
* @since-jetpack 5.6.0
*/
public static function get_file_url_for_environment( $min_path, $non_min_path, $package_path = '' ) {
$path = ( Jetpack_Constants::is_defined( 'SCRIPT_DEBUG' ) && Jetpack_Constants::get_constant( 'SCRIPT_DEBUG' ) )
? $non_min_path
: $min_path;
/*
* If the path is actually a full URL, keep that.
* We look for a host value, since enqueues are sometimes without a scheme.
*/
$file_parts = wp_parse_url( $path );
if ( ! empty( $file_parts['host'] ) ) {
$url = $path;
} else {
$plugin_path = empty( $package_path ) ? Jetpack_Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) : $package_path;
$url = plugins_url( $path, $plugin_path );
}
/**
* Filters the URL for a file passed through the get_file_url_for_environment function.
*
* @since 1.0.3
*
* @package assets
*
* @param string $url The URL to the file.
* @param string $min_path The minified path.
* @param string $non_min_path The non-minified path.
*/
return apply_filters( 'jetpack_get_file_for_environment', $url, $min_path, $non_min_path );
}
/**
* Passes an array of URLs to wp_resource_hints.
*
* @since 1.5.0
*
* @param string|array $urls URLs to hint.
* @param string $type One of the supported resource types: dns-prefetch (default), preconnect, prefetch, or prerender.
*/
public static function add_resource_hint( $urls, $type = 'dns-prefetch' ) {
add_filter(
'wp_resource_hints',
function ( $hints, $resource_type ) use ( $urls, $type ) {
if ( $resource_type === $type ) {
// Type casting to array required since the function accepts a single string.
foreach ( (array) $urls as $url ) {
$hints[] = $url;
}
}
return $hints;
},
10,
2
);
}
/**
* Serve a WordPress.com static resource via a randomized wp.com subdomain.
*
* @since 1.9.0
*
* @param string $url WordPress.com static resource URL.
*
* @return string $url
*/
public static function staticize_subdomain( $url ) {
// Extract hostname from URL.
$host = wp_parse_url( $url, PHP_URL_HOST );
// Explode hostname on '.'.
$exploded_host = explode( '.', $host );
// Retrieve the name and TLD.
if ( count( $exploded_host ) > 1 ) {
$name = $exploded_host[ count( $exploded_host ) - 2 ];
$tld = $exploded_host[ count( $exploded_host ) - 1 ];
// Rebuild domain excluding subdomains.
$domain = $name . '.' . $tld;
} else {
$domain = $host;
}
// Array of Automattic domains.
$domains_allowed = array( 'wordpress.com', 'wp.com' );
// Return $url if not an Automattic domain.
if ( ! in_array( $domain, $domains_allowed, true ) ) {
return $url;
}
if ( \is_ssl() ) {
return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
}
/*
* Generate a random subdomain id by taking the modulus of the crc32 value of the URL.
* Valid values are 0, 1, and 2.
*/
$static_counter = abs( crc32( basename( $url ) ) % 3 );
return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
}
/**
* Resolve '.' and '..' components in a path or URL.
*
* @since 1.12.0
* @param string $path Path or URL.
* @return string Normalized path or URL.
*/
public static function normalize_path( $path ) {
$parts = wp_parse_url( $path );
if ( ! isset( $parts['path'] ) ) {
return $path;
}
$ret = '';
$ret .= isset( $parts['scheme'] ) ? $parts['scheme'] . '://' : '';
if ( isset( $parts['user'] ) || isset( $parts['pass'] ) ) {
$ret .= isset( $parts['user'] ) ? $parts['user'] : '';
$ret .= isset( $parts['pass'] ) ? ':' . $parts['pass'] : '';
$ret .= '@';
}
$ret .= isset( $parts['host'] ) ? $parts['host'] : '';
$ret .= isset( $parts['port'] ) ? ':' . $parts['port'] : '';
$pp = explode( '/', $parts['path'] );
if ( '' === $pp[0] ) {
$ret .= '/';
array_shift( $pp );
}
$i = 0;
while ( $i < count( $pp ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
if ( '' === $pp[ $i ] || '.' === $pp[ $i ] || 0 === $i && '..' === $pp[ $i ] ) {
array_splice( $pp, $i, 1 );
} elseif ( '..' === $pp[ $i ] ) {
array_splice( $pp, --$i, 2 );
} else {
$i++;
}
}
$ret .= join( '/', $pp );
$ret .= isset( $parts['query'] ) ? '?' . $parts['query'] : '';
$ret .= isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '';
return $ret;
}
// endregion .
// ////////////////////
// region Webpack-built script registration
/**
* Register a Webpack-built script.
*
* Our Webpack-built scripts tend to need a bunch of boilerplate:
* - A call to `Assets::get_file_url_for_environment()` for possible debugging.
* - A call to `wp_register_style()` for extracted CSS, possibly with detection of RTL.
* - Loading of dependencies and version provided by `@wordpress/dependency-extraction-webpack-plugin`.
* - Avoiding WPCom's broken minifier.
*
* This wrapper handles all of that.
*
* @since 1.12.0
* @param string $handle Name of the script. Should be unique across both scripts and styles.
* @param string $path Minimized script path.
* @param string $relative_to File that `$path` is relative to. Pass `__FILE__`.
* @param array $options Additional options:
* - `asset_path`: (string|null) `.asset.php` to load. Default is to base it on `$path`.
* - `async`: (bool) Set true to register the script as async, like `Assets::enqueue_async_script()`
* - `css_dependencies`: (string[]) Additional style dependencies to queue.
* - `css_path`: (string|null) `.css` to load. Default is to base it on `$path`.
* - `dependencies`: (string[]) Additional script dependencies to queue.
* - `enqueue`: (bool) Set true to enqueue the script immediately.
* - `in_footer`: (bool) Set true to register script for the footer.
* - `media`: (string) Media for the css file. Default 'all'.
* - `minify`: (bool|null) Set true to pass `minify=true` in the query string, or `null` to suppress the normal `minify=false`.
* - `nonmin_path`: (string) Non-minified script path.
* - `textdomain`: (string) Text domain for the script. Required if the script depends on wp-i18n.
* - `version`: (string) Override the version from the `asset_path` file.
* @throws \InvalidArgumentException If arguments are invalid.
*/
public static function register_script( $handle, $path, $relative_to, array $options = array() ) {
if ( substr( $path, -3 ) !== '.js' ) {
throw new \InvalidArgumentException( '$path must end in ".js"' );
}
$dir = dirname( $relative_to );
$base = substr( $path, 0, -3 );
$options += array(
'asset_path' => "$base.asset.php",
'async' => false,
'css_dependencies' => array(),
'css_path' => "$base.css",
'dependencies' => array(),
'enqueue' => false,
'in_footer' => false,
'media' => 'all',
'minify' => false,
'textdomain' => null,
);
if ( $options['css_path'] && substr( $options['css_path'], -4 ) !== '.css' ) {
throw new \InvalidArgumentException( '$options[\'css_path\'] must end in ".css"' );
}
if ( isset( $options['nonmin_path'] ) ) {
$url = self::get_file_url_for_environment( $path, $options['nonmin_path'], $relative_to );
} else {
$url = plugins_url( $path, $relative_to );
}
$url = self::normalize_path( $url );
if ( null !== $options['minify'] ) {
$url = add_query_arg( 'minify', $options['minify'] ? 'true' : 'false', $url );
}
if ( $options['asset_path'] && file_exists( "$dir/{$options['asset_path']}" ) ) {
$asset = require "$dir/{$options['asset_path']}"; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
$options['dependencies'] = array_merge( $asset['dependencies'], $options['dependencies'] );
$options['css_dependencies'] = array_merge(
array_filter(
$asset['dependencies'],
function ( $d ) {
return wp_style_is( $d, 'registered' );
}
),
$options['css_dependencies']
);
$ver = isset( $options['version'] ) ? $options['version'] : $asset['version'];
} else {
$ver = isset( $options['version'] ) ? $options['version'] : filemtime( "$dir/$path" );
}
wp_register_script( $handle, $url, $options['dependencies'], $ver, $options['in_footer'] );
if ( $options['async'] ) {
self::instance()->add_async_script( $handle );
}
if ( $options['textdomain'] ) {
// phpcs:ignore Jetpack.Functions.I18n.DomainNotLiteral
wp_set_script_translations( $handle, $options['textdomain'] );
} elseif ( in_array( 'wp-i18n', $options['dependencies'], true ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s is the script handle. */
esc_html( sprintf( __( 'Script "%s" depends on wp-i18n but does not specify "textdomain"', 'jetpack-assets' ), $handle ) ),
''
);
}
if ( $options['css_path'] && file_exists( "$dir/{$options['css_path']}" ) ) {
$csspath = $options['css_path'];
if ( is_rtl() ) {
$rtlcsspath = substr( $csspath, 0, -4 ) . '.rtl.css';
if ( file_exists( "$dir/$rtlcsspath" ) ) {
$csspath = $rtlcsspath;
}
}
$url = self::normalize_path( plugins_url( $csspath, $relative_to ) );
if ( null !== $options['minify'] ) {
$url = add_query_arg( 'minify', $options['minify'] ? 'true' : 'false', $url );
}
wp_register_style( $handle, $url, $options['css_dependencies'], $ver, $options['media'] );
wp_script_add_data( $handle, 'Jetpack::Assets::hascss', true );
} else {
wp_script_add_data( $handle, 'Jetpack::Assets::hascss', false );
}
if ( $options['enqueue'] ) {
self::enqueue_script( $handle );
}
}
/**
* Enqueue a script registered with `Assets::register_script`.
*
* @since 1.12.0
* @param string $handle Name of the script. Should be unique across both scripts and styles.
*/
public static function enqueue_script( $handle ) {
wp_enqueue_script( $handle );
if ( wp_scripts()->get_data( $handle, 'Jetpack::Assets::hascss' ) ) {
wp_enqueue_style( $handle );
}
}
/**
* 'wp_default_scripts' action handler.
*
* This registers the `wp-jp-i18n-loader` script for use by Webpack bundles built with
* `@automattic/i18n-loader-webpack-plugin`.
*
* @since 1.14.0
* @param \WP_Scripts $wp_scripts WP_Scripts instance.
*/
public static function wp_default_scripts_hook( $wp_scripts ) {
$data = array(
'baseUrl' => false,
'locale' => determine_locale(),
'domainMap' => array(),
'domainPaths' => array(),
);
$lang_dir = Jetpack_Constants::get_constant( 'WP_LANG_DIR' );
$content_dir = Jetpack_Constants::get_constant( 'WP_CONTENT_DIR' );
$abspath = Jetpack_Constants::get_constant( 'ABSPATH' );
if ( strpos( $lang_dir, $content_dir ) === 0 ) {
$data['baseUrl'] = content_url( substr( trailingslashit( $lang_dir ), strlen( trailingslashit( $content_dir ) ) ) );
} elseif ( strpos( $lang_dir, $abspath ) === 0 ) {
$data['baseUrl'] = site_url( substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) ) );
}
foreach ( self::$domain_map as $from => list( $to, $type, , $path ) ) {
$data['domainMap'][ $from ] = ( 'core' === $type ? '' : "{$type}/" ) . $to;
if ( '' !== $path ) {
$data['domainPaths'][ $from ] = trailingslashit( $path );
}
}
/**
* Filters the i18n state data for use by Webpack bundles built with
* `@automattic/i18n-loader-webpack-plugin`.
*
* @since 1.14.0
* @package assets
* @param array $data The state data to generate. Expected fields are:
* - `baseUrl`: (string|false) The URL to the languages directory. False if no URL could be determined.
* - `locale`: (string) The locale for the page.
* - `domainMap`: (string[]) A mapping from Composer package textdomains to the corresponding
* `plugins/textdomain` or `themes/textdomain` (or core `textdomain`, but that's unlikely).
* - `domainPaths`: (string[]) A mapping from Composer package textdomains to the corresponding package
* paths.
*/
$data = apply_filters( 'jetpack_i18n_state', $data );
// Can't use self::register_script(), this action is called too early.
if ( file_exists( __DIR__ . '/../build/i18n-loader.asset.php' ) ) {
$path = '../build/i18n-loader.js';
$asset = require __DIR__ . '/../build/i18n-loader.asset.php';
} else {
$path = 'js/i18n-loader.js';
$asset = array(
'dependencies' => array( 'wp-i18n' ),
'version' => filemtime( __DIR__ . "/$path" ),
);
}
$url = self::normalize_path( plugins_url( $path, __FILE__ ) );
$url = add_query_arg( 'minify', 'true', $url );
$wp_scripts->add( 'wp-jp-i18n-loader', $url, $asset['dependencies'], $asset['version'] );
if ( ! is_array( $data ) ||
! isset( $data['baseUrl'] ) || ! ( is_string( $data['baseUrl'] ) || false === $data['baseUrl'] ) ||
! isset( $data['locale'] ) || ! is_string( $data['locale'] ) ||
! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] ) ||
! isset( $data['domainPaths'] ) || ! is_array( $data['domainPaths'] )
) {
$wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );' );
} elseif ( ! $data['baseUrl'] ) {
$wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );' );
} else {
$data['domainMap'] = (object) $data['domainMap']; // Ensure it becomes a json object.
$data['domainPaths'] = (object) $data['domainPaths']; // Ensure it becomes a json object.
$wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'wp.jpI18nLoader.state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';' );
}
// Deprecated state module: Depend on wp-i18n to ensure global `wp` exists and because anything needing this will need that too.
$wp_scripts->add( 'wp-jp-i18n-state', null, array( 'wp-deprecated', 'wp-jp-i18n-loader' ) );
$wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.deprecated( "wp-jp-i18n-state", { alternative: "wp-jp-i18n-loader" } );' );
$wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.jpI18nState = wp.jpI18nLoader.state;' );
}
// endregion .
// ////////////////////
// region Textdomain aliasing
/**
* Register a textdomain alias.
*
* Composer packages included in plugins will likely not use the textdomain of the plugin, while
* WordPress's i18n infrastructure will include the translations in the plugin's domain. This
* allows for mapping the package's domain to the plugin's.
*
* Since multiple plugins may use the same package, we include the package's version here so
* as to choose the most recent translations (which are most likely to match the package
* selected by jetpack-autoloader).
*
* @since 1.15.0
* @param string $from Domain to alias.
* @param string $to Domain to alias it to.
* @param string $totype What is the target of the alias: 'plugins', 'themes', or 'core'.
* @param string $ver Version of the `$from` domain.
* @param string $path Path to prepend when lazy-loading from JavaScript.
* @throws InvalidArgumentException If arguments are invalid.
*/
public static function alias_textdomain( $from, $to, $totype, $ver, $path = '' ) {
if ( ! in_array( $totype, array( 'plugins', 'themes', 'core' ), true ) ) {
throw new InvalidArgumentException( 'Type must be "plugins", "themes", or "core"' );
}
if (
did_action( 'wp_default_scripts' ) &&
// Don't complain during plugin activation.
! defined( 'WP_SANDBOX_SCRAPING' )
) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: wp_default_scripts. 2: Name of the domain being aliased. */
esc_html__( 'Textdomain aliases should be registered before the %1$s hook. This notice was triggered by the %2$s domain.', 'jetpack-assets' ),
'<code>wp_default_scripts</code>',
'<code>' . esc_html( $from ) . '</code>'
),
''
);
}
if ( empty( self::$domain_map[ $from ] ) ) {
self::init_domain_map_hooks( $from, array() === self::$domain_map );
self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
} elseif ( Semver::compare( $ver, self::$domain_map[ $from ][2] ) > 0 ) {
self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
}
}
/**
* Register textdomain aliases from a mapping file.
*
* The mapping file is simply a PHP file that returns an array
* with the following properties:
* - 'domain': String, `$to`
* - 'type': String, `$totype`
* - 'packages': Array, mapping `$from` to `array( 'path' => $path, 'ver' => $ver )` (or to the string `$ver` for back compat).
*
* @since 1.15.0
* @param string $file Mapping file.
*/
public static function alias_textdomains_from_file( $file ) {
$data = require $file;
foreach ( $data['packages'] as $from => $fromdata ) {
if ( ! is_array( $fromdata ) ) {
$fromdata = array(
'path' => '',
'ver' => $fromdata,
);
}
self::alias_textdomain( $from, $data['domain'], $data['type'], $fromdata['ver'], $fromdata['path'] );
}
}
/**
* Register the hooks for textdomain aliasing.
*
* @param string $domain Domain to alias.
* @param bool $firstcall If this is the first call.
*/
private static function init_domain_map_hooks( $domain, $firstcall ) {
// If WordPress's plugin API is available already, use it. If not,
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
if ( function_exists( 'add_filter' ) ) {
$add_filter = 'add_filter';
} else {
$add_filter = function ( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
global $wp_filter;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter[ $hook_name ][ $priority ][] = array(
'accepted_args' => $accepted_args,
'function' => $callback,
);
};
}
$add_filter( "gettext_{$domain}", array( self::class, 'filter_gettext' ), 10, 3 );
$add_filter( "ngettext_{$domain}", array( self::class, 'filter_ngettext' ), 10, 5 );
$add_filter( "gettext_with_context_{$domain}", array( self::class, 'filter_gettext_with_context' ), 10, 4 );
$add_filter( "ngettext_with_context_{$domain}", array( self::class, 'filter_ngettext_with_context' ), 10, 6 );
if ( $firstcall ) {
$add_filter( 'load_script_translation_file', array( self::class, 'filter_load_script_translation_file' ), 10, 3 );
}
}
/**
* Filter for `gettext`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $text Text to translate.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_gettext( $translation, $text, $domain ) {
if ( $translation === $text ) {
// phpcs:ignore WordPress.WP.I18n
$newtext = __( $text, self::$domain_map[ $domain ][0] );
if ( $newtext !== $text ) {
return $newtext;
}
}
return $translation;
}
/**
* Filter for `ngettext`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $single The text to be used if the number is singular.
* @param string $plural The text to be used if the number is plural.
* @param string $number The number to compare against to use either the singular or plural form.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_ngettext( $translation, $single, $plural, $number, $domain ) {
if ( $translation === $single || $translation === $plural ) {
// phpcs:ignore WordPress.WP.I18n
$translation = _n( $single, $plural, $number, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `gettext_with_context`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $text Text to translate.
* @param string $context Context information for the translators.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_gettext_with_context( $translation, $text, $context, $domain ) {
if ( $translation === $text ) {
// phpcs:ignore WordPress.WP.I18n
$translation = _x( $text, $context, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `ngettext_with_context`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $single The text to be used if the number is singular.
* @param string $plural The text to be used if the number is plural.
* @param string $number The number to compare against to use either the singular or plural form.
* @param string $context Context information for the translators.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_ngettext_with_context( $translation, $single, $plural, $number, $context, $domain ) {
if ( $translation === $single || $translation === $plural ) {
// phpcs:ignore WordPress.WP.I18n
$translation = _nx( $single, $plural, $number, $context, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `load_script_translation_file`.
*
* @since 1.15.0
* @param string|false $file Path to the translation file to load. False if there isn't one.
* @param string $handle Name of the script to register a translation domain to.
* @param string $domain The text domain.
*/
public static function filter_load_script_translation_file( $file, $handle, $domain ) {
if ( false !== $file && isset( self::$domain_map[ $domain ] ) && ! is_readable( $file ) ) {
// Determine the part of the filename after the domain.
$suffix = basename( $file );
$l = strlen( $domain );
if ( substr( $suffix, 0, $l ) !== $domain || '-' !== $suffix[ $l ] ) {
return $file;
}
$suffix = substr( $suffix, $l );
$lang_dir = Jetpack_Constants::get_constant( 'WP_LANG_DIR' );
// Look for replacement files.
list( $newdomain, $type ) = self::$domain_map[ $domain ];
$newfile = $lang_dir . ( 'core' === $type ? '/' : "/{$type}/" ) . $newdomain . $suffix;
if ( is_readable( $newfile ) ) {
return $newfile;
}
}
return $file;
}
// endregion .
}
// Enable section folding in vim:
// vim: foldmarker=//\ region,//\ endregion foldmethod=marker
// .

View File

@ -0,0 +1,122 @@
<?php
/**
* Simple semver version handling.
*
* We use this instead of something like `composer/semver` to avoid
* plugins needing to include yet-another dependency package. The
* amount of code we need here is pretty small.
*
* We use this instead of PHP's `version_compare()` because that doesn't
* handle prerelease versions in the way anyone other than PHP devs would
* expect, and silently breaks on various unexpected input.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack\Assets;
use InvalidArgumentException;
/**
* Simple semver version handling.
*/
class Semver {
/**
* Parse a semver version.
*
* @param string $version Version.
* @return array With components:
* - major: (int) Major version.
* - minor: (int) Minor version.
* - patch: (int) Patch version.
* - version: (string) Major.minor.patch.
* - prerelease: (string|null) Pre-release string.
* - buildinfo: (string|null) Build metadata string.
* @throws InvalidArgumentException If the version number is not in a recognized format.
*/
public static function parse( $version ) {
// This is slightly looser than the official version from semver.org, in that leading zeros are allowed.
if ( ! preg_match( '/^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<prerelease>(?:[0-9a-zA-Z-]+)(?:\.(?:[0-9a-zA-Z-]+))*))?(?:\+(?P<buildinfo>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/', $version, $m ) ) {
throw new InvalidArgumentException( "Version number \"$version\" is not in a recognized format." );
}
$info = array(
'major' => (int) $m['major'],
'minor' => (int) $m['minor'],
'patch' => (int) $m['patch'],
'version' => sprintf( '%d.%d.%d', $m['major'], $m['minor'], $m['patch'] ),
'prerelease' => isset( $m['prerelease'] ) && '' !== $m['prerelease'] ? $m['prerelease'] : null,
'buildinfo' => isset( $m['buildinfo'] ) && '' !== $m['buildinfo'] ? $m['buildinfo'] : null,
);
if ( null !== $info['prerelease'] ) {
$sep = '';
$prerelease = '';
foreach ( explode( '.', $info['prerelease'] ) as $part ) {
if ( ctype_digit( $part ) ) {
$part = (int) $part;
}
$prerelease .= $sep . $part;
$sep = '.';
}
$info['prerelease'] = $prerelease;
}
return $info;
}
/**
* Compare two version numbers.
*
* @param string $a First version.
* @param string $b Second version.
* @return int Less than, equal to, or greater than 0 depending on whether `$a` is less than, equal to, or greater than `$b`.
* @throws InvalidArgumentException If the version numbers are not in a recognized format.
*/
public static function compare( $a, $b ) {
$aa = self::parse( $a );
$bb = self::parse( $b );
if ( $aa['major'] !== $bb['major'] ) {
return $aa['major'] - $bb['major'];
}
if ( $aa['minor'] !== $bb['minor'] ) {
return $aa['minor'] - $bb['minor'];
}
if ( $aa['patch'] !== $bb['patch'] ) {
return $aa['patch'] - $bb['patch'];
}
if ( null === $aa['prerelease'] ) {
return null === $bb['prerelease'] ? 0 : 1;
}
if ( null === $bb['prerelease'] ) {
return -1;
}
$aaa = explode( '.', $aa['prerelease'] );
$bbb = explode( '.', $bb['prerelease'] );
$al = count( $aaa );
$bl = count( $bbb );
for ( $i = 0; $i < $al && $i < $bl; $i++ ) {
$a = $aaa[ $i ];
$b = $bbb[ $i ];
if ( ctype_digit( $a ) ) {
if ( ctype_digit( $b ) ) {
if ( (int) $a !== (int) $b ) {
return $a - $b;
}
} else {
return -1;
}
} elseif ( ctype_digit( $b ) ) {
return 1;
} else {
$tmp = strcmp( $a, $b );
if ( 0 !== $tmp ) {
return $tmp;
}
}
}
return $al - $bl;
}
}

View File

@ -0,0 +1,76 @@
const i18n = require( '@wordpress/i18n' );
const { default: md5 } = require( 'md5-es' );
const locationMap = {
plugin: 'plugins/',
theme: 'themes/',
core: '',
};
const hasOwn = ( obj, prop ) => Object.prototype.hasOwnProperty.call( obj, prop );
module.exports = {
state: {
baseUrl: null,
locale: null,
domainMap: {},
domainPaths: {},
},
/**
* Download and register translations for a bundle.
*
* @param {string} path - Bundle path being fetched. May have a query part.
* @param {string} domain - Text domain to register into.
* @param {string} location - Location for the translation: 'plugin', 'theme', or 'core'.
* @returns {Promise} Resolved when the translations are registered, or rejected with an `Error`.
*/
async downloadI18n( path, domain, location ) {
const state = this.state;
if ( ! state || typeof state.baseUrl !== 'string' ) {
throw new Error( 'wp.jpI18nLoader.state is not set' );
}
// "en_US" is the default, no translations are needed.
if ( state.locale === 'en_US' ) {
return;
}
// Check that fetch is available.
if ( typeof fetch === 'undefined' ) {
throw new Error( 'Fetch API is not available.' );
}
// Extract any query part and hash the script name like WordPress does.
const pathPrefix = hasOwn( state.domainPaths, domain ) ? state.domainPaths[ domain ] : '';
let hash, query;
const i = path.indexOf( '?' );
if ( i >= 0 ) {
hash = md5.hash( pathPrefix + path.substring( 0, i ) );
query = path.substring( i );
} else {
hash = md5.hash( pathPrefix + path );
query = '';
}
// Download.
const locationAndDomain = hasOwn( state.domainMap, domain )
? state.domainMap[ domain ]
: locationMap[ location ] + domain;
const res = await fetch(
// prettier-ignore
`${ state.baseUrl }${ locationAndDomain }-${ state.locale }-${ hash }.json${ query }`
);
if ( ! res.ok ) {
throw new Error( `HTTP request failed: ${ res.status } ${ res.statusText }` );
}
const data = await res.json();
// Extract the messages from the file and register them.
const localeData = hasOwn( data.locale_data, domain )
? data.locale_data[ domain ]
: data.locale_data.messages;
localeData[ '' ].domain = domain;
i18n.setLocaleData( localeData, domain );
},
};

View File

@ -0,0 +1,161 @@
# 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).
## [1.9.3] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [1.9.2] - 2022-06-29
- Updated package dependencies.
## [1.9.1] - 2022-06-21
### Changed
- Renaming master to trunk.
## [1.9.0] - 2022-05-18
### Added
- Configuration for waf package [#24153]
## [1.8.0] - 2022-04-26
### Added
- Added the publicize package to be configured via the config package.
### Changed
- Updated package dependencies.
## [1.7.2] - 2022-04-19
### Added
- Enable WordAds from Config class
## [1.7.1] - 2022-04-06
### Removed
- Removed tracking dependency.
## [1.7.0] - 2022-03-23
### Added
- Search: added search initialization
## [1.6.1] - 2022-02-09
### Added
- Allow sync package consumers to provide custom data settings.
## [1.6.0] - 2022-01-04
### Added
- Accept options for the IDC package.
### Changed
- Updated package textdomain from `jetpack` to `jetpack-config`.
## [1.5.4] - 2021-12-14
### Changed
- Updated package dependencies.
## [1.5.3] - 2021-10-13
### Changed
- Updated package dependencies.
## [1.5.2] - 2021-10-12
### Added
- Add support for the identity-crisis package
## [1.5.1] - 2021-09-28
### Changed
- Updated package dependencies.
## [1.5.0] - 2021-09-22
### Added
- Allow for enabling and initializing new Post_List package from Config package.
## [1.4.7] - 2021-08-31
### Changed
- updates annotations versions.
## [1.4.6] - 2021-05-25
### Changed
- Updated package dependencies.
## [1.4.5] - 2021-04-27
### Changed
- Updated package dependencies.
## [1.4.4] - 2021-03-30
### Added
- Composer alias for dev-master, to improve dependencies
### Changed
- Update package dependencies.
## [1.4.3] - 2021-01-19
- Add mirror-repo information to all current composer packages
- Monorepo: Reorganize all projects
## [1.4.2] - 2020-10-28
- Updated PHPCS: Packages and Debugger
- Config: remove tos and tracking features
- Config: add info about the package dependencies to the package docs
## [1.4.1] - 2020-09-15
- Config: remove tos and tracking features
## [1.4.0] - 2020-08-26
- Config: Remove composer dependencies
- Config: Add connection status check
## [1.3.0] - 2020-06-26
- Config: check for both JITM namespaces
## [1.2.0] - 2020-05-20
- Store the list of active plugins that uses connection in an option
- Implement pre-connection JITMs
- Connection Package: Handle disconnections gracefully
## [1.1.0] - 2020-01-23
- Moved JITM initialization to plugins_loaded.
## [1.0.1] - 2020-01-20
- Move connection manager related logic to after plugins_loaded.
## 1.0.0 - 2020-01-14
- Trying to add deterministic initialization.
[1.9.3]: https://github.com/Automattic/jetpack-config/compare/v1.9.2...v1.9.3
[1.9.2]: https://github.com/Automattic/jetpack-config/compare/v1.9.1...v1.9.2
[1.9.1]: https://github.com/Automattic/jetpack-config/compare/v1.9.0...v1.9.1
[1.9.0]: https://github.com/Automattic/jetpack-config/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/Automattic/jetpack-config/compare/v1.7.2...v1.8.0
[1.7.2]: https://github.com/Automattic/jetpack-config/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/Automattic/jetpack-config/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/Automattic/jetpack-config/compare/v1.6.1...v1.7.0
[1.6.1]: https://github.com/Automattic/jetpack-config/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/Automattic/jetpack-config/compare/v1.5.4...v1.6.0
[1.5.4]: https://github.com/Automattic/jetpack-config/compare/v1.5.3...v1.5.4
[1.5.3]: https://github.com/Automattic/jetpack-config/compare/v1.5.2...v1.5.3
[1.5.2]: https://github.com/Automattic/jetpack-config/compare/v1.5.1...v1.5.2
[1.5.1]: https://github.com/Automattic/jetpack-config/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/Automattic/jetpack-config/compare/v1.4.7...v1.5.0
[1.4.7]: https://github.com/Automattic/jetpack-config/compare/v1.4.6...v1.4.7
[1.4.6]: https://github.com/Automattic/jetpack-config/compare/v1.4.5...v1.4.6
[1.4.5]: https://github.com/Automattic/jetpack-config/compare/v1.4.4...v1.4.5
[1.4.4]: https://github.com/Automattic/jetpack-config/compare/v1.4.3...v1.4.4
[1.4.3]: https://github.com/Automattic/jetpack-config/compare/v1.4.2...v1.4.3
[1.4.2]: https://github.com/Automattic/jetpack-config/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/Automattic/jetpack-config/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/Automattic/jetpack-config/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/Automattic/jetpack-config/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Automattic/jetpack-config/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/Automattic/jetpack-config/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/Automattic/jetpack-config/compare/v1.0.0...v1.0.1

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,28 @@
{
"name": "automattic/jetpack-config",
"description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require-dev": {
"automattic/jetpack-changelogger": "^3.2"
},
"autoload": {
"classmap": [
"src/"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-config",
"textdomain": "jetpack-config",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.9.x-dev"
}
}
}

View File

@ -0,0 +1,364 @@
<?php
/**
* The base Jetpack configuration class file.
*
* @package automattic/jetpack-config
*/
namespace Automattic\Jetpack;
/*
* The Config package does not require the composer packages that
* contain the package classes shown below. The consumer plugin
* must require the corresponding packages to use these features.
*/
use Automattic\Jetpack\Connection\Manager;
use Automattic\Jetpack\Connection\Plugin;
use Automattic\Jetpack\JITM as JITM;
use Automattic\Jetpack\JITMS\JITM as JITMS_JITM;
use Automattic\Jetpack\Post_List\Post_List as Post_List;
use Automattic\Jetpack\Publicize\Publicize_Setup as Publicize_Setup;
use Automattic\Jetpack\Search\Initializer as Jetpack_Search_Main;
use Automattic\Jetpack\Sync\Main as Sync_Main;
use Automattic\Jetpack\Waf\Waf_Initializer as Jetpack_Waf_Main;
use Automattic\Jetpack\WordAds\Initializer as Jetpack_WordAds_Main;
/**
* The configuration class.
*/
class Config {
const FEATURE_ENSURED = 1;
const FEATURE_NOT_AVAILABLE = 0;
const FEATURE_ALREADY_ENSURED = -1;
/**
* The initial setting values.
*
* @var Array
*/
protected $config = array(
'jitm' => false,
'connection' => false,
'sync' => false,
'post_list' => false,
'identity_crisis' => false,
'search' => false,
'publicize' => false,
'wordads' => false,
'waf' => false,
);
/**
* Initialization options stored here.
*
* @var array
*/
protected $feature_options = array();
/**
* Creates the configuration class instance.
*/
public function __construct() {
/**
* Adding the config handler to run on priority 2 because the class itself is
* being constructed on priority 1.
*/
add_action( 'plugins_loaded', array( $this, 'on_plugins_loaded' ), 2 );
}
/**
* Require a feature to be initialized. It's up to the package consumer to actually add
* the package to their composer project. Declaring a requirement using this method
* instructs the class to initialize it.
*
* @param String $feature the feature slug.
* @param array $options Additional options, optional.
*/
public function ensure( $feature, array $options = array() ) {
$this->config[ $feature ] = true;
$this->set_feature_options( $feature, $options );
}
/**
* Runs on plugins_loaded hook priority with priority 2.
*
* @action plugins_loaded
*/
public function on_plugins_loaded() {
if ( $this->config['connection'] ) {
$this->ensure_class( 'Automattic\Jetpack\Connection\Manager' )
&& $this->ensure_feature( 'connection' );
}
if ( $this->config['sync'] ) {
$this->ensure_class( 'Automattic\Jetpack\Sync\Main' )
&& $this->ensure_feature( 'sync' );
}
if ( $this->config['jitm'] ) {
// Check for the JITM class in both namespaces. The namespace was changed in jetpack-jitm v1.6.
( $this->ensure_class( 'Automattic\Jetpack\JITMS\JITM', false )
|| $this->ensure_class( 'Automattic\Jetpack\JITM' ) )
&& $this->ensure_feature( 'jitm' );
}
if ( $this->config['post_list'] ) {
$this->ensure_class( 'Automattic\Jetpack\Post_List\Post_List' )
&& $this->ensure_feature( 'post_list' );
}
if ( $this->config['identity_crisis'] ) {
$this->ensure_class( 'Automattic\Jetpack\Identity_Crisis' )
&& $this->ensure_feature( 'identity_crisis' );
}
if ( $this->config['search'] ) {
$this->ensure_class( 'Automattic\Jetpack\Search\Initializer' )
&& $this->ensure_feature( 'search' );
}
if ( $this->config['publicize'] ) {
$this->ensure_class( 'Automattic\Jetpack\Publicize\Publicize_UI' ) && $this->ensure_class( 'Automattic\Jetpack\Publicize\Publicize' )
&& $this->ensure_feature( 'publicize' );
}
if ( $this->config['wordads'] ) {
$this->ensure_class( 'Automattic\Jetpack\WordAds\Initializer' )
&& $this->ensure_feature( 'wordads' );
}
if ( $this->config['waf'] ) {
$this->ensure_class( 'Automattic\Jetpack\Waf\Waf_Initializer' )
&& $this->ensure_feature( 'waf' );
}
}
/**
* Returns true if the required class is available and alerts the user if it's not available
* in case the site is in debug mode.
*
* @param String $classname a fully qualified class name.
* @param Boolean $log_notice whether the E_USER_NOTICE should be generated if the class is not found.
*
* @return Boolean whether the class is available.
*/
protected function ensure_class( $classname, $log_notice = true ) {
$available = class_exists( $classname );
if ( $log_notice && ! $available && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
sprintf(
/* translators: %1$s is a PHP class name. */
esc_html__(
'Unable to load class %1$s. Please add the package that contains it using composer and make sure you are requiring the Jetpack autoloader',
'jetpack-config'
),
esc_html( $classname )
),
E_USER_NOTICE
);
}
return $available;
}
/**
* Ensures a feature is enabled, sets it up if it hasn't already been set up.
* Run the options method (if exists) every time the method is called.
*
* @param String $feature slug of the feature.
* @return Integer either FEATURE_ENSURED, FEATURE_ALREADY_ENSURED or FEATURE_NOT_AVAILABLE constants.
*/
protected function ensure_feature( $feature ) {
$method = 'enable_' . $feature;
if ( ! method_exists( $this, $method ) ) {
return self::FEATURE_NOT_AVAILABLE;
}
$method_options = 'ensure_options_' . $feature;
if ( method_exists( $this, $method_options ) ) {
$this->{ $method_options }();
}
if ( did_action( 'jetpack_feature_' . $feature . '_enabled' ) ) {
return self::FEATURE_ALREADY_ENSURED;
}
$this->{ $method }();
/**
* Fires when a specific Jetpack package feature is initalized using the Config package.
*
* @since 1.1.0
*/
do_action( 'jetpack_feature_' . $feature . '_enabled' );
return self::FEATURE_ENSURED;
}
/**
* Enables the JITM feature.
*/
protected function enable_jitm() {
if ( class_exists( 'Automattic\Jetpack\JITMS\JITM' ) ) {
JITMS_JITM::configure();
} else {
// Provides compatibility with jetpack-jitm <v1.6.
JITM::configure();
}
return true;
}
/**
* Enables the Post_List feature.
*/
protected function enable_post_list() {
Post_List::configure();
return true;
}
/**
* Enables the Sync feature.
*/
protected function enable_sync() {
Sync_Main::configure();
return true;
}
/**
* Enables the Connection feature.
*/
protected function enable_connection() {
Manager::configure();
return true;
}
/**
* Enables the identity-crisis feature.
*/
protected function enable_identity_crisis() {
Identity_Crisis::init();
}
/**
* Enables the search feature.
*/
protected function enable_search() {
Jetpack_Search_Main::init();
}
/**
* Enables the Publicize feature.
*/
protected function enable_publicize() {
Publicize_Setup::configure();
return true;
}
/**
* Enables WordAds.
*/
protected function enable_wordads() {
Jetpack_WordAds_Main::init();
}
/**
* Enables Waf.
*/
protected function enable_waf() {
Jetpack_Waf_Main::init();
return true;
}
/**
* Setup the Connection options.
*/
protected function ensure_options_connection() {
$options = $this->get_feature_options( 'connection' );
if ( ! empty( $options['slug'] ) ) {
// The `slug` and `name` are removed from the options because they need to be passed as arguments.
$slug = $options['slug'];
unset( $options['slug'] );
$name = $slug;
if ( ! empty( $options['name'] ) ) {
$name = $options['name'];
unset( $options['name'] );
}
( new Plugin( $slug ) )->add( $name, $options );
}
return true;
}
/**
* Setup the Identity Crisis options.
*
* @return bool
*/
protected function ensure_options_identity_crisis() {
$options = $this->get_feature_options( 'identity_crisis' );
if ( is_array( $options ) && count( $options ) ) {
add_filter(
'jetpack_idc_consumers',
function ( $consumers ) use ( $options ) {
$consumers[] = $options;
return $consumers;
}
);
}
return true;
}
/**
* Setup the Sync options.
*/
protected function ensure_options_sync() {
$options = $this->get_feature_options( 'sync' );
if ( method_exists( 'Automattic\Jetpack\Sync\Main', 'set_sync_data_options' ) ) {
Sync_Main::set_sync_data_options( $options );
}
return true;
}
/**
* Temporary save initialization options for a feature.
*
* @param string $feature The feature slug.
* @param array $options The options.
*
* @return bool
*/
protected function set_feature_options( $feature, array $options ) {
if ( $options ) {
$this->feature_options[ $feature ] = $options;
}
return true;
}
/**
* Get initialization options for a feature from the temporary storage.
*
* @param string $feature The feature slug.
*
* @return array
*/
protected function get_feature_options( $feature ) {
return empty( $this->feature_options[ $feature ] ) ? array() : $this->feature_options[ $feature ];
}
}

View File

@ -0,0 +1,763 @@
# 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).
## [1.41.7] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
### Fixed
- Tokens: edit return doc to highlight possibility of returning WP_Error. [#25127]
## [1.41.6] - 2022-07-19
### Changed
- Updated package dependencies.
## [1.41.5] - 2022-07-12
### Changed
- Updated package dependencies.
## [1.41.4] - 2022-06-29
- Updated package dependencies.
## [1.41.3] - 2022-06-28
### Fixed
- Connection: fix fatal error due to undefined constant in remote_provision()
## [1.41.2] - 2022-06-28
### Removed
- Removed use of autounit tag [#24845]
## [1.41.1] - 2022-06-21
### Changed
- Renaming master to trunk.
- Renaming `master` references to `trunk`
## [1.41.0] - 2022-06-14
### Changed
- Updated package dependencies. [#24529]
### Fixed
- Fixed old tk_ai regex to accurately match tk_ai ids. [#24697]
- Moved the connection_url_redirect action handling to the connection package. [#24529]
## [1.40.5] - 2022-06-08
### Changed
- Reorder JS imports for `import/order` eslint rule. [#24601]
## [1.40.4] - 2022-05-24
### Added
- Allow plugins to filter the list of available modules. Only activate and consider active modules that are available [#24454]
## [1.40.3] - 2022-05-19
### Added
- PHPCS updates. [#24418]
## [1.40.2] - 2022-05-18
### Changed
- Updated package dependencies [#24372]
### Fixed
- Fix new PHPCS sniffs. [#24366]
## [1.40.1] - 2022-05-10
### Added
- Bundle and transpile JavaScript with Webpack. [#24216]
### Changed
- Deprecate soft disconnect [#24105]
## [1.40.0] - 2022-05-04
### Added
- Connection: Expose wpVersion and siteSuffix in the global initial state var [#24137]
### Deprecated
- Moved the options class into Connection. [#24095]
## [1.39.2] - 2022-05-19
### Added
- PHPCS updates.
## [1.39.1] - 2022-04-27
### Fixed
- Reverts soft disconnect deprecation
## [1.39.0] - 2022-04-26
### Changed
- Make remove_connection a proxy method to ensure all trackings are triggered
- Updated package dependencies.
### Deprecated
- Removed Heartbeat by hoisting it into Connection.
### Removed
- Deprecated Soft disconnect
## [1.38.0] - 2022-04-19
### Added
- Added list of connected list to the connection initial state
- Add token lock functionality.
### Changed
- PHPCS: Fix `WordPress.Security.ValidatedSanitizedInput`
### Deprecated
- Deprecated in-place connection.
## [1.37.6] - 2022-04-12
### Changed
- Updated package dependencies.
## [1.37.5] - 2022-04-06
### Removed
- Removed tracking dependency.
## [1.37.4] - 2022-04-05
### Changed
- Updated package dependencies.
## [1.37.3] - 2022-03-29
### Changed
- Microperformance: Use === null instead of is_null
### Fixed
- Fix regression added to Jetpack webhooks handling
## [1.37.2] - 2022-03-23
### Changed
- Jetpack now relies on Connection Webooks for authorize and authorize_redirect actions
### Removed
- Removed a reference to the terms-of-service package.
## [1.37.1] - 2022-03-15
### Added
- Handle the Authorization Redirect from the Connection package
## [1.37.0] - 2022-03-02
### Added
- Moved the ToS package to Connection.
## [1.36.4] - 2022-02-22
### Changed
- Updated package dependencies.
## [1.36.3] - 2022-02-16
### Added
- Add the 'jetpack_site_before_disconnected' action hook.
## [1.36.2] - 2022-02-09
### Fixed
- Fixed some new PHPCS warnings.
## [1.36.1] - 2022-01-25
### Changed
- Updated package dependencies.
## [1.36.0] - 2022-01-18
### Added
- Debugging: Add a filter to add XDEBUG_PROFILE to requests made to the sandbox.
## [1.35.0] - 2022-01-13
### Changed
- Added user data to initial state
## [1.34.0] - 2022-01-04
### Added
- Jetpack Connection: Added fallback for keeping `jetpack_connection_active_plugins` consistent on WPCOM when Sync is not present.
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
- Updated package textdomain from `jetpack` to `jetpack-connection`.
## [1.33.0] - 2021-12-14
### Changed
- Jetpack Connection: handle package versions on site registration.
## [1.32.0] - 2021-11-30
### Added
- Added a way to set the has_seen_wc_connection_modal option from the API
- Provides an Initial State that can be used by JS packages
### Changed
- Updated package dependencies.
## [1.31.0] - 2021-11-22
### Added
- Added plugin_slug parameter to the v4/register endpoint
### Changed
- Updated package dependencies
## [1.30.13] - 2021-11-09
### Fixed
- Fix PHP 8.1 deprecation warning.
## [1.30.12] - 2021-11-02
### Added
- Client: add IDC query args to remote requests
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [1.30.11] - 2021-10-26
### Changed
- Change the error code returned when a remoteRegister XMLRPC call is executed to the more helpful "already_registered" when the blog is already registered
- Updated package dependencies.
## [1.30.10] - 2021-10-19
### Deprecated
- General: remove numerous long-deprecated functions.
### Fixed
- Fix permission check for authorization_url endpoint.
## [1.30.9] - 2021-10-13
### Changed
- Updated package dependencies.
## [1.30.8] - 2021-10-12
### Added
- Add a new action to the Client::remote_request method, jetpack_received_remote_request_response
### Changed
- Updated package dependencies
## [1.30.7] - 2021-10-04
### Added
- Sandbox Server: add the sandbox-server class to the connection package.
## [1.30.6] - 2021-09-30
### Changed
- Moved the Package Tracker execution to the shutdown hook for performance improvement.
## [1.30.5] - 2021-09-28
### Changed
- Package Version Tracker: send package versions to wpcom on the init hook instead of plugins_loaded
- Updated package dependencies.
### Fixed
- Load WordPress's IXR classes on demand.
## [1.30.4] - 2021-09-02
### Fixed
- Remove invalid user token before reconnect.
## [1.30.3] - 2021-08-30
### Added
- Limit repeated failed attempts to update remote DNA package versions.
### Changed
- Make sure generated secrets have the required length
- Remove tracked package versions when disconnecting the site.
- Run composer update on test-php command instead of phpunit
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- update annotations versions
## [1.30.2] - 2021-08-12
### Added
- Add package version tracking.
## [1.30.1] - 2021-07-27
### Added
- Add a package version constant.
### Changed
- Move connection/data endpoint to Connection package.
- Move site disconnection endpoint to Connection package.
### Fixed
- Fix `@covers` directives in tests.
## [1.30.0] - 2021-07-13
### Added
- Added second parameter to Tokens::get_connected_users to allow any connected user to be returned.
### Changed
- Moved the get_connected_users logic back to the Manager class
## [1.29.0] - 2021-06-29
### Changed
- Implement disconnect_site function.
- Updated package dependencies.
## [1.28.0] - 2021-06-15
### Added
- Added Urls class, migrated from Sync Functions.
- Adding new REST endpoint /jetpack/v4/user-token that allows us to add/update user tokens remotely.
- Add new 'connection/authorize_url' endpoint.
- Adds information received from the server to the register_site REST response.
- Enable site-level authentication (blog token) for REST API endpoints.
- Move 'connection/owner' endpoint to Connection package.
## [1.27.0] - 2021-05-25
### Added
- Add "isUserConnected" to the connection status data.
- Connection: add the default value of JETPACK__WPCOM_JSON_API_BASE to the Connection Utils class.
### Changed
- Connection package independence: Move a Jetpack specfic connection routine out of the package and into the plugin
- Package Independence: Add a filter to the remote_uri returned by remote_register XMLRPC method
### Removed
- Removed "user-less" jargon from code
- Remove do_post_authorization routine and add a hook instead
- Remove onboarding_token logic in the Remote provision XMLRPC method from the Connection package and add it to the Jetpack plugin
### Fixed
- Disconnection flow: disconnect users from WordPress.com before to delete data locally.
## [1.26.0] - 2021-04-27
### Added
- Adds segmentation "from" parameter to the registration flow
- Connection: moving the registration REST endpoint to the package.
### Changed
- Added "userless" parameter to the authorization URL.
- Updated package dependencies.
## [1.25.2] - 2021-04-13
### Fixed
- Connection: nonce cleanup safeguard against accidental option removal.
## [1.25.1] - 2021-04-08
### Fixed
- Avoid determine_current_user going through infinite loops
- Tokens: Fix token validation logic.
## [1.25.0] - 2021-03-30
### Added
- Add new test for blog token health to support user-less sites
- Composer alias for dev-master, to improve dependencies
### Changed
- API Nonces: performance optimization and refactoring
- Replace is_active usage towards gradually deprecating it.
- Do not use is_active to determine the XMLRPC methods that should be registered
- Make connected_plugins REST endpoint available for the Jetpack Debugger
- Move Jetpack specific XMLRPC methods from the Connection package into the plugin
- Update package dependencies.
- User-less connection: Reconnect without asking the user to connect their WPCOM account
### Deprecated
- add deprecation notice and remove user-less check in is_active
### Fixed
- Only check offline mode when needed in map_meta_cap filters
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
## [1.24.0] - 2021-02-23
- Refactor secrets and tokens
- User-less connection: Restrict first connection to admins only
- Connection: Prevent pointless calls to the test API
- CI: Make tests more generic
- Connection: extracting the Jetpack's authorization webhook
- codesniffer: Update mediawiki-codesniffer dep to v35.0
## [1.23.2] - 2021-02-08
- Connection: Prevent pointless calls to the test API
## [1.23.1] - 2021-01-28
- Update dependencies to latest stable
## [1.23.0] - 2021-01-26
- Sync Concurrency / Race Conditions
- Add mirror-repo information to all current composer packages
- Mirroring: Fix auth, attempt 2
- Monorepo: Reorganize all projects
## [1.22.0] - 2021-01-05
- Connection: setting valid connection owner.
- userless testing mode: do not discard user tokens
- Pin dependencies
- Packages: Update for PHP 8 testing
- Connection: New methods and tests to the manager
- Connection: refreshing connected plugin storage on multisite networks
- Build: migrate from Travis to GH Actions
- fix typos and add section to error handling docs
## [1.21.1] - 2020-11-24
- Version packages for release
## [1.21.0] - 2020-11-24
- Handle empty SERVER_PORT information on signature checks
- Fix remaining phpcs warnings in most of requirelist
- Add the no_user_testing mode
- Clarified error message for non-writable options table.
- Pass HTTP POST when making a wp.com api request
## [1.20.0] - 2020-10-29
- Connection: Plugin Tracking
- Connection Package: Ensure a text/xml header is set
- Updated PHPCS: Packages and Debugger
## [1.19.2] - 2020-11-05
- Pass HTTP POST when making a wp.com api request
## [1.19.1] - 2020-10-29
- Connection: Plugin Tracking
## [1.19.0] - 2020-10-27
- Connection Errors: Tracking additional error data
- Replaced intval() with (int) as part of issue #17432.
- Replaced strval() with type casting (string) as part of issue #17432.
- Connection: Add wp.com function
- Instagram oEmbed: Simplify
- Client: Extract `validate_args_for_wpcom_json_api_request` helper.
- Connection: add the constant filter hooks directly
- Make XMLRPC methods available for blog token
- API Nonces: Revert of the Runtime Cleanup.
- PHPCS: Update directory structure, and modernize class usage
- deprecates JETPACK_MASTER_USER and adds linter
- API: Remove the constant `JETPACK_CLIENT__HTTPS`.
## [1.18.4] - 2020-10-14
- Connection: Add wp.com function
## [1.18.3] - 2020-10-09
- Connection: add the constant filter hooks directly
## [1.18.2] - 2020-10-06
- API Nonces: Revert of the Runtime Cleanup.
## [1.18.1] - 2020-10-01
- deprecates JETPACK_MASTER_USER and adds linter
## [1.18.0] - 2020-09-29
- API Nonces: Runtime cleanup
- Packages: update list of files distributed in production packages
- Connection: Add success message on completed partial reconnection
- Jetpack_IXR_ClientMulticall: Fix sort_calls() producing undefined relative order between equal items
- Connection: Initializing default constants for the REST authorization.
- Trigger action jetpack_user_authorize after we save the token
- Partial reconnect: Cleanup redundant actions
- Signature Class: Adds support for nested query strings
## [1.17.2] - 2020-09-16
- Connection: Initializing default constants for the REST authorization.
## [1.17.1] - 2020-09-09
- Update dependencies to latest stable
## [1.17.0] - 2020-08-26
- Connection: move the rest authentication methods to the connection package
- Connection: use heartbeat to send connected plugins info
- clear xmlrpc error on site disconnect
- add new features to debug helper
- remove no longer needed check
- disable xmlrpc errors out of JP dashboard
- Connection REST API: Unit test for the `remote_authorize` request.
- Simplify error notices for broken connections
- Unit Tests: Fixing a failing `remote_authorize` test.
- Reconnect Process: Partial Reconnect
- Packages: Update filenames after #16810
- CI: Try collect js coverage
- Docker: Add package testing shortcut
- Remove usages of removed HTTP_RAW_POST_DATA
- adds tracking for deleted but active master users
- New class to handle async XML-RPC requests
- Connection Register: Add current user email to connection register request
## [1.15.2] - 2020-08-10
- Connection Register: Add current user email to connection register request
## [1.15.1] - 2020-08-10
- adds tracking for deleted but active master users
## [1.15.0] - 2020-07-28
- Core Compat: Site Environment
- Reconnect Notice: In-place Reconnect
- add messages for some common connection errors
- REST API: Move some endpoints to `jetpack-connection` package.
- REST API: Add permission callback to all that lack one.
- Secondary user in-place connection flow
- Tests: Update WorDBless location
- Connection Errors: Clear when there's a successful request to /sites
- Connection: add unit tests for is_registered
- only display connection errors to allowed users
## [1.14.2] - 2020-07-06
- Connection Errors: Clear when there's a successful request to /sites
## [1.14.1] - 2020-07-01
- only display connection errors to allowed users
## [1.14.0] - 2020-06-30
- Jetpack_XMLRPC_Server: set up jsonAPI and testConnection endpoint when Jetpack is active
- Connection Error Handling
- Connection: Update XMLRPC overload
- Connection: move connection custom caps to the Connection package
- Connection package: Don't throw warnings if the database is corrupted somehow
- Check blog token when computing is_registered
- Connection: add the api_constant filter before setup_xmlrpc_handlers is called
- Connection Package: Soft Disconnects
- Remove usage of the Jetpack_Error class in the connection package
- Connection: Fix issue where ABSPATH not included with register
## [1.13.1] - 2020-06-01
- Connection: Fix issue where ABSPATH not included with register
## [1.13.0] - 2020-05-26
- Store the list of active plugins that uses connection in an option
- Connection: increase timeout on the token request
- Connection Package: Handle disconnections gracefully
## [1.12.0] - 2020-04-28
- Correct inline documentation "Array" type
- Use jp.com redirect in all links
- Docs: Update the Connection Manager namespace, a minor typo
- Connection: add a filter for setting Jetpack api constants
## [1.11.0] - 2020-03-31
- Update dependencies to latest stable
## [1.10.0] - 2020-03-31
- Connection: move post authorization work to package
## [1.9.0] - 2020-02-25
## [1.8.3] - 2020-02-14
## [1.8.2] - 2020-02-12
## [1.8.1] - 2020-02-12
- Added a specific filter to enable iframe authorization API URL.
- Added better defaults for the connection package.
## [1.8.0] - 2020-01-27
- Connection\Utils: Add a new function which provides the Jetpack API version
- Connection\Manager: Use jetpack_master_user class constant
## [1.7.2] - 2020-01-20
- Move connection manager related logic to after plugins_loaded.
## [1.7.1] - 2020-01-14
- Packages: Various improvements for wp.com or self-contained consumers
## [1.7.0] - 2019-11-26
- Connection package: Add new methods to for disconnecting/delet…
## [1.6.1] - 2019-11-25
- Connection: Loose Comparison for Port Number in Signatures
## [1.6.0] - 2019-11-19
## [1.5.0] - 2019-11-15
- Removed Jetpack references in the IXR client.
- Connection: Move get_token() to Connection package
## [1.4.0] - 2019-11-15
- Add connection authentication URL.
- Connection: Move the authorize() method to the connection package
- Connection: Move update_user_token to the Connection package
- Connection: Set the value of user_id in Manager::generate_secrets
## [1.3.1] - 2019-11-08
- Packages: Use classmap instead of PSR-4
- Moved tracking code to the Tracking file.
## [1.3.0] - 2019-11-08
- Move fix_url_for_bad_hosts from Jetpack class to Connection pa…
## [1.2.0] - 2019-11-07
- Connection: call verify_secrets() in verify_action()
## [1.1.0] - 2019-10-31
- Adds filter capability to the api_url method.
## [1.0.7] - 2019-10-28
- Packages: Add gitattributes files to all packages that need th…
- Replace parse_url with wp_parse_url
## [1.0.6] - 2019-10-07
- Update dependency phpcompatibility/phpcompatibility-wp to v2.1.0
## [1.0.5] - 2019-09-26
- XMLRPC: Fires clean_nonce action in all cases
## [1.0.4] - 2019-09-24
- Connection: Make sure port is an integer
## [1.0.3] - 2019-09-23
- Moves unreachable code to where it would be run.
- Connection: Deprecate Manager interface
## [1.0.2] - 2019-09-23
- Connection: Fix all PHPCS errors in the connection package
## [1.0.1] - 2019-09-20
- Various: Remove pre-PHP 5.6 shims and fallbacks
- Store "Assumed site creation date" in transient
- Sync: Move sync_object XML-RPC method from connection to sync
- Connection: Fix PHPCS errors in Jetpack_Signature
- Docs: Unify usage of @package phpdoc tags
- Janitorial: Remove the leading backslash from namespaces
## 1.0.0 - 2019-09-14
- Separate the connection library into its own package.
[1.41.7]: https://github.com/Automattic/jetpack-connection/compare/v1.41.6...v1.41.7
[1.41.6]: https://github.com/Automattic/jetpack-connection/compare/v1.41.5...v1.41.6
[1.41.5]: https://github.com/Automattic/jetpack-connection/compare/v1.41.4...v1.41.5
[1.41.4]: https://github.com/Automattic/jetpack-connection/compare/v1.41.3...v1.41.4
[1.41.3]: https://github.com/Automattic/jetpack-connection/compare/v1.41.2...v1.41.3
[1.41.2]: https://github.com/Automattic/jetpack-connection/compare/v1.41.1...v1.41.2
[1.41.1]: https://github.com/Automattic/jetpack-connection/compare/v1.41.0...v1.41.1
[1.41.0]: https://github.com/Automattic/jetpack-connection/compare/v1.40.5...v1.41.0
[1.40.5]: https://github.com/Automattic/jetpack-connection/compare/v1.40.4...v1.40.5
[1.40.4]: https://github.com/Automattic/jetpack-connection/compare/v1.40.3...v1.40.4
[1.40.3]: https://github.com/Automattic/jetpack-connection/compare/v1.40.2...v1.40.3
[1.40.2]: https://github.com/Automattic/jetpack-connection/compare/v1.40.1...v1.40.2
[1.40.1]: https://github.com/Automattic/jetpack-connection/compare/v1.40.0...v1.40.1
[1.40.0]: https://github.com/Automattic/jetpack-connection/compare/v1.39.1...v1.40.0
[1.39.2]: https://github.com/Automattic/jetpack-connection/compare/v1.39.1...v1.39.2
[1.39.1]: https://github.com/Automattic/jetpack-connection/compare/v1.39.0...v1.39.1
[1.39.0]: https://github.com/Automattic/jetpack-connection/compare/v1.38.0...v1.39.0
[1.38.0]: https://github.com/Automattic/jetpack-connection/compare/v1.37.6...v1.38.0
[1.37.6]: https://github.com/Automattic/jetpack-connection/compare/v1.37.5...v1.37.6
[1.37.5]: https://github.com/Automattic/jetpack-connection/compare/v1.37.4...v1.37.5
[1.37.4]: https://github.com/Automattic/jetpack-connection/compare/v1.37.3...v1.37.4
[1.37.3]: https://github.com/Automattic/jetpack-connection/compare/v1.37.2...v1.37.3
[1.37.2]: https://github.com/Automattic/jetpack-connection/compare/v1.37.1...v1.37.2
[1.37.1]: https://github.com/Automattic/jetpack-connection/compare/v1.37.0...v1.37.1
[1.37.0]: https://github.com/Automattic/jetpack-connection/compare/v1.36.4...v1.37.0
[1.36.4]: https://github.com/Automattic/jetpack-connection/compare/v1.36.3...v1.36.4
[1.36.3]: https://github.com/Automattic/jetpack-connection/compare/v1.36.2...v1.36.3
[1.36.2]: https://github.com/Automattic/jetpack-connection/compare/v1.36.1...v1.36.2
[1.36.1]: https://github.com/Automattic/jetpack-connection/compare/v1.36.0...v1.36.1
[1.36.0]: https://github.com/Automattic/jetpack-connection/compare/v1.35.0...v1.36.0
[1.35.0]: https://github.com/Automattic/jetpack-connection/compare/v1.34.0...v1.35.0
[1.34.0]: https://github.com/Automattic/jetpack-connection/compare/v1.33.0...v1.34.0
[1.33.0]: https://github.com/Automattic/jetpack-connection/compare/v1.32.0...v1.33.0
[1.32.0]: https://github.com/Automattic/jetpack-connection/compare/v1.31.0...v1.32.0
[1.31.0]: https://github.com/Automattic/jetpack-connection/compare/v1.30.13...v1.31.0
[1.30.13]: https://github.com/Automattic/jetpack-connection/compare/v1.30.12...v1.30.13
[1.30.12]: https://github.com/Automattic/jetpack-connection/compare/v1.30.11...v1.30.12
[1.30.11]: https://github.com/Automattic/jetpack-connection/compare/v1.30.10...v1.30.11
[1.30.10]: https://github.com/Automattic/jetpack-connection/compare/v1.30.9...v1.30.10
[1.30.9]: https://github.com/Automattic/jetpack-connection/compare/v1.30.8...v1.30.9
[1.30.8]: https://github.com/Automattic/jetpack-connection/compare/v1.30.7...v1.30.8
[1.30.7]: https://github.com/Automattic/jetpack-connection/compare/v1.30.6...v1.30.7
[1.30.6]: https://github.com/Automattic/jetpack-connection/compare/v1.30.5...v1.30.6
[1.30.5]: https://github.com/Automattic/jetpack-connection/compare/v1.30.4...v1.30.5
[1.30.4]: https://github.com/Automattic/jetpack-connection/compare/v1.30.3...v1.30.4
[1.30.3]: https://github.com/Automattic/jetpack-connection/compare/v1.30.2...v1.30.3
[1.30.2]: https://github.com/Automattic/jetpack-connection/compare/v1.30.1...v1.30.2
[1.30.1]: https://github.com/Automattic/jetpack-connection/compare/v1.30.0...v1.30.1
[1.30.0]: https://github.com/Automattic/jetpack-connection/compare/v1.29.0...v1.30.0
[1.29.0]: https://github.com/Automattic/jetpack-connection/compare/v1.28.0...v1.29.0
[1.28.0]: https://github.com/Automattic/jetpack-connection/compare/v1.27.0...v1.28.0
[1.27.0]: https://github.com/Automattic/jetpack-connection/compare/v1.26.0...v1.27.0
[1.26.0]: https://github.com/Automattic/jetpack-connection/compare/v1.25.2...v1.26.0
[1.25.2]: https://github.com/Automattic/jetpack-connection/compare/v1.25.1...v1.25.2
[1.25.1]: https://github.com/Automattic/jetpack-connection/compare/v1.25.0...v1.25.1
[1.25.0]: https://github.com/Automattic/jetpack-connection/compare/v1.24.0...v1.25.0
[1.24.0]: https://github.com/Automattic/jetpack-connection/compare/v1.23.2...v1.24.0
[1.23.2]: https://github.com/Automattic/jetpack-connection/compare/v1.23.1...v1.23.2
[1.23.1]: https://github.com/Automattic/jetpack-connection/compare/v1.23.0...v1.23.1
[1.23.0]: https://github.com/Automattic/jetpack-connection/compare/v1.22.0...v1.23.0
[1.22.0]: https://github.com/Automattic/jetpack-connection/compare/v1.21.1...v1.22.0
[1.21.1]: https://github.com/Automattic/jetpack-connection/compare/v1.21.0...v1.21.1
[1.21.0]: https://github.com/Automattic/jetpack-connection/compare/v1.20.0...v1.21.0
[1.20.0]: https://github.com/Automattic/jetpack-connection/compare/v1.19.2...v1.20.0
[1.19.2]: https://github.com/Automattic/jetpack-connection/compare/v1.19.1...v1.19.2
[1.19.1]: https://github.com/Automattic/jetpack-connection/compare/v1.19.0...v1.19.1
[1.19.0]: https://github.com/Automattic/jetpack-connection/compare/v1.18.4...v1.19.0
[1.18.4]: https://github.com/Automattic/jetpack-connection/compare/v1.18.3...v1.18.4
[1.18.3]: https://github.com/Automattic/jetpack-connection/compare/v1.18.2...v1.18.3
[1.18.2]: https://github.com/Automattic/jetpack-connection/compare/v1.18.1...v1.18.2
[1.18.1]: https://github.com/Automattic/jetpack-connection/compare/v1.18.0...v1.18.1
[1.18.0]: https://github.com/Automattic/jetpack-connection/compare/v1.17.2...v1.18.0
[1.17.2]: https://github.com/Automattic/jetpack-connection/compare/v1.17.1...v1.17.2
[1.17.1]: https://github.com/Automattic/jetpack-connection/compare/v1.17.0...v1.17.1
[1.17.0]: https://github.com/Automattic/jetpack-connection/compare/v1.15.2...v1.17.0
[1.15.2]: https://github.com/Automattic/jetpack-connection/compare/v1.15.1...v1.15.2
[1.15.1]: https://github.com/Automattic/jetpack-connection/compare/v1.15.0...v1.15.1
[1.15.0]: https://github.com/Automattic/jetpack-connection/compare/v1.14.2...v1.15.0
[1.14.2]: https://github.com/Automattic/jetpack-connection/compare/v1.14.1...v1.14.2
[1.14.1]: https://github.com/Automattic/jetpack-connection/compare/v1.14.0...v1.14.1
[1.14.0]: https://github.com/Automattic/jetpack-connection/compare/v1.13.1...v1.14.0
[1.13.1]: https://github.com/Automattic/jetpack-connection/compare/v1.13.0...v1.13.1
[1.13.0]: https://github.com/Automattic/jetpack-connection/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/Automattic/jetpack-connection/compare/v1.11.0...v1.12.0
[1.11.0]: https://github.com/Automattic/jetpack-connection/compare/1.10.0...v1.11.0
[1.10.0]: https://github.com/Automattic/jetpack-connection/compare/v1.9.0...1.10.0
[1.9.0]: https://github.com/Automattic/jetpack-connection/compare/v1.8.3...v1.9.0
[1.8.3]: https://github.com/Automattic/jetpack-connection/compare/v1.8.2...v1.8.3
[1.8.2]: https://github.com/Automattic/jetpack-connection/compare/v1.8.1...v1.8.2
[1.8.1]: https://github.com/Automattic/jetpack-connection/compare/v1.8.0...v1.8.1
[1.8.0]: https://github.com/Automattic/jetpack-connection/compare/v1.7.2...v1.8.0
[1.7.2]: https://github.com/Automattic/jetpack-connection/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/Automattic/jetpack-connection/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/Automattic/jetpack-connection/compare/v1.6.1...v1.7.0
[1.6.1]: https://github.com/Automattic/jetpack-connection/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/Automattic/jetpack-connection/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/Automattic/jetpack-connection/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/Automattic/jetpack-connection/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/Automattic/jetpack-connection/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/Automattic/jetpack-connection/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Automattic/jetpack-connection/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/Automattic/jetpack-connection/compare/v1.0.7...v1.1.0
[1.0.7]: https://github.com/Automattic/jetpack-connection/compare/v1.0.6...v1.0.7
[1.0.6]: https://github.com/Automattic/jetpack-connection/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/Automattic/jetpack-connection/compare/v1.0.4...v1.0.5
[1.0.4]: https://github.com/Automattic/jetpack-connection/compare/v1.0.3...v1.0.4
[1.0.3]: https://github.com/Automattic/jetpack-connection/compare/v1.0.2...v1.0.3
[1.0.2]: https://github.com/Automattic/jetpack-connection/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Automattic/jetpack-connection/compare/v1.0.0...v1.0.1

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,122 @@
# Jetpack Tracking package
A package containing functionality to track events to the a8c Tracks system
## Usage
There are several ways to track events using this package.
* Ajax: Probably the easiest one. You can simply add a class to a link and it will be tracked or you can make your own ajax call
* PHP: Track an event on the backend
* Tracking pixel: An alternative way to track events by dynamically adding a pixel to the DOM
### Tracking via Ajax
This is useful to track simple click events without the need of any additional js. Just add the appropriate class to your links and it will be tracked.
#### 1. enqueue script
Note: Not needed if you are using the Jetpack plugin in the admin context as the script is already loaded by Jetpack.
See `Automattic\Jetpack\Tracking::enqueue_tracks_scripts()`
```PHP
add_action( 'admin_enqueue_scripts', array( new Tracking( 'plugin-slug' ), 'enqueue_tracks_scripts' ) );
```
#### 2. Add the class and the event attributes.
Add the `jptracks` class to any `a` element or to its parent element.
The event needs a name. This can be informed with the `data-jptracks-name` attritbute.
```HTML
<a class="jptracks" data-jptracks-name="my-awesome-event">Click me</a>
```
And that's it. Your event will be tracked. Every time this element is clicked an ajax call will be triggered to the Tracking package and it will send it to wpcom.
**Note:** Event name will be automatically prefixed with `jetpack_`.
#### 3. Additional parameters
You can also inform additional parameters to your event using the `data-jptracks-prop` attribute. Anything in this attr will be stored in the `clicked` attribute in the event.
#### 4. Making your own ajax calls
In your JS you can set up your own ajax calls. Example:
```JS
window.jpTracksAJAX.record_ajax_event( 'my_event_name', 'click', { prop1: value1, prop2: value2 } );
```
**Note:** Event name will be automatically prefixed with `jetpack_`.
##### Waiting for the ajax call to complete before doing anything else
If you need to do a subsequent action but wants to wait for this event to be tracked, you can do the following:
```JS
window.jpTracksAJAX
.record_ajax_event( 'my_event_name', 'click', { prop1: value1, prop2: value2 } )
.always( function() {
// do something
} );
```
### Tracking in PHP
Use `Automattic\Jetpack\Tracking::record_user_event()` to track events on the backend.
```PHP
$connection_manager = new Automattic\Jetpack\Connection\Manager( 'plugin-slug' );
$tracking = new Tracking( 'plugin-slug', $connection_manager );
$tracking->record_user_event(
$event_name,
array(
'property_key' => 'value',
)
);
```
### Tracking pixel
This approach to track events uses `//stats.wp.com/w.js` and dynamically adds a tracking pixel to the DOM to do the tracking.
#### 1. Enqueue the scripts
```PHP
Tracking::register_tracks_functions_scripts( true );
```
#### 2. Inform the user data
```PHP
wp_localize_script(
'my_script',
'varname',
array(
'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
)
);
```
#### 3. Track!
In your JS:
```JS
var tracksUser = varname.tracksUserData;
analytics.initialize( tracksUser.userid, tracksUser.username );
analytics.tracks.recordEvent( 'jetpack_my_event_name', { prop1: value1, prop2: value2 } );
```
## Debugging
You can watch your events being tracked in the browser console. In order to activate that, run the following command in the console:
```JS
localStorage.setItem( 'debug', 'dops:analytics*' );
```

View File

@ -0,0 +1,66 @@
{
"name": "automattic/jetpack-connection",
"description": "Everything needed to connect to the Jetpack infrastructure",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-a8c-mc-stats": "^1.4",
"automattic/jetpack-admin-ui": "^0.2",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-roles": "^1.4",
"automattic/jetpack-status": "^1.14",
"automattic/jetpack-redirect": "^1.7"
},
"require-dev": {
"automattic/wordbless": "@dev",
"yoast/phpunit-polyfills": "1.0.3",
"brain/monkey": "2.6.1",
"automattic/jetpack-changelogger": "^3.2"
},
"autoload": {
"classmap": [
"legacy",
"src/",
"src/webhooks"
]
},
"scripts": {
"build-production": [
"pnpm run build-production"
],
"build-development": [
"pnpm run build"
],
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-connection",
"textdomain": "jetpack-connection",
"version-constants": {
"::PACKAGE_VERSION": "src/class-package-version.php"
},
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.41.x-dev"
}
},
"config": {
"allow-plugins": {
"roots/wordpress-core-installer": true
}
}
}

View File

@ -0,0 +1 @@
<?php return array('dependencies' => array(), 'version' => '2c644cc46566ed615c42');

View File

@ -0,0 +1 @@
!function(t,a){window.jpTracksAJAX=window.jpTracksAJAX||{};const c="dops:analytics"===localStorage.getItem("debug");window.jpTracksAJAX.record_ajax_event=function(n,e,r){const o={tracksNonce:a.jpTracksAJAX_nonce,action:"jetpack_tracks",tracksEventType:e,tracksEventName:n,tracksEventProp:r||!1};return t.ajax({type:"POST",url:a.ajaxurl,data:o,success:function(t){c&&console.log("AJAX tracks event recorded: ",o,t)}})},t(document).ready((function(){t("body").on("click",".jptracks a, a.jptracks",(function(a){const c=t(a.target),n=c.closest(".jptracks"),e=n.attr("data-jptracks-name");if(void 0===e)return;const r=n.attr("data-jptracks-prop")||!1,o=c.attr("href"),s=c.get(0).target;let i=null;o&&s&&"_self"!==s&&(i=window.open("",s),i.opener=null),a.preventDefault(),window.jpTracksAJAX.record_ajax_event(e,"click",r).always((function(){if(o&&!c.hasClass("thickbox")){if(i)return void(i.location=o);window.location=o}}))}))}))}(jQuery,jpTracksAJAX);

View File

@ -0,0 +1 @@
<?php return array('dependencies' => array(), 'version' => 'd9dbf909a3d10fb26f39');

View File

@ -0,0 +1 @@
(()=>{var e={775:e=>{let n;window._tkq=window._tkq||[];const t=console.error;const o={initialize:function(e,n){o.setUser(e,n),o.identifyUser()},mc:{bumpStat:function(e,n){const t=function(e,n){let t="";if("object"==typeof e)for(const n in e)t+="&x_"+encodeURIComponent(n)+"="+encodeURIComponent(e[n]);else t="&x_"+encodeURIComponent(e)+"="+encodeURIComponent(n);return t}(e,n);(new Image).src=document.location.protocol+"//pixel.wp.com/g.gif?v=wpcom-no-pv"+t+"&t="+Math.random()}},tracks:{recordEvent:function(e,n){n=n||{},0===e.indexOf("jetpack_")?window._tkq.push(["recordEvent",e,n]):t('- Event name must be prefixed by "jetpack_"')},recordPageView:function(e){o.tracks.recordEvent("jetpack_page_view",{path:e})}},setUser:function(e,t){n={ID:e,username:t}},identifyUser:function(){n&&window._tkq.push(["identifyUser",n.ID,n.username])},clearedIdentity:function(){window._tkq.push(["clearIdentity"])}};e.exports=o}},n={};var t=function t(o){var r=n[o];if(void 0!==r)return r.exports;var i=n[o]={exports:{}};return e[o](i,i.exports,t),i.exports}(775);window.analytics=t})();

View File

@ -0,0 +1,157 @@
<?php
/**
* IXR_Client
*
* @package automattic/jetpack-connection
*
* @since 1.7.0
* @since-jetpack 1.5
* @since-jetpack 7.7 Moved to the jetpack-connection package.
*/
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager;
if ( ! class_exists( IXR_Client::class ) ) {
require_once ABSPATH . WPINC . '/class-IXR.php';
}
/**
* A Jetpack implementation of the WordPress core IXR client.
*/
class Jetpack_IXR_Client extends IXR_Client {
/**
* Jetpack args, used for the remote requests.
*
* @var array
*/
public $jetpack_args = null;
/**
* Remote Response Headers.
*
* @var array
*/
public $response_headers = null;
/**
* Constructor.
* Initialize a new Jetpack IXR client instance.
*
* @param array $args Jetpack args, used for the remote requests.
* @param string|bool $path Path to perform the reuqest to.
* @param int $port Port number.
* @param int $timeout The connection timeout, in seconds.
*/
public function __construct( $args = array(), $path = false, $port = 80, $timeout = 15 ) {
$connection = new Manager();
$defaults = array(
'url' => $connection->xmlrpc_api_url(),
'user_id' => 0,
'headers' => array(),
);
$args = wp_parse_args( $args, $defaults );
$args['headers'] = array_merge( array( 'Content-Type' => 'text/xml' ), (array) $args['headers'] );
$this->jetpack_args = $args;
$this->IXR_Client( $args['url'], $path, $port, $timeout );
}
/**
* Perform the IXR request.
*
* @param string[] ...$args IXR args.
*
* @return bool True if request succeeded, false otherwise.
*/
public function query( ...$args ) {
$method = array_shift( $args );
$request = new IXR_Request( $method, $args );
$xml = trim( $request->getXml() );
$response = Client::remote_request( $this->jetpack_args, $xml );
// Store response headers.
$this->response_headers = wp_remote_retrieve_headers( $response );
if ( is_wp_error( $response ) ) {
$this->error = new IXR_Error( -10520, sprintf( 'Jetpack: [%s] %s', $response->get_error_code(), $response->get_error_message() ) );
return false;
}
if ( ! $response ) {
$this->error = new IXR_Error( -10520, 'Jetpack: Unknown Error' );
return false;
}
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
$this->error = new IXR_Error( -32300, 'transport error - HTTP status code was not 200' );
return false;
}
$content = wp_remote_retrieve_body( $response );
// Now parse what we've got back.
$this->message = new IXR_Message( $content );
if ( ! $this->message->parse() ) {
// XML error.
$this->error = new IXR_Error( -32700, 'parse error. not well formed' );
return false;
}
// Is the message a fault?
if ( 'fault' === $this->message->messageType ) {
$this->error = new IXR_Error( $this->message->faultCode, $this->message->faultString );
return false;
}
// Message must be OK.
return true;
}
/**
* Retrieve the Jetpack error from the result of the last request.
*
* @param int $fault_code Fault code.
* @param string $fault_string Fault string.
* @return WP_Error Error object.
*/
public function get_jetpack_error( $fault_code = null, $fault_string = null ) {
if ( $fault_code === null ) {
$fault_code = $this->error->code;
}
if ( $fault_string === null ) {
$fault_string = $this->error->message;
}
if ( preg_match( '#jetpack:\s+\[(\w+)\]\s*(.*)?$#i', $fault_string, $match ) ) {
$code = $match[1];
$message = $match[2];
$status = $fault_code;
return new \WP_Error( $code, $message, $status );
}
return new \WP_Error( "IXR_{$fault_code}", $fault_string );
}
/**
* Retrieve a response header if set.
*
* @param string $name header name.
* @return string|bool Header value if set, false if not set.
*/
public function get_response_header( $name ) {
if ( isset( $this->response_headers[ $name ] ) ) {
return $this->response_headers[ $name ];
}
// case-insensitive header names: http://www.ietf.org/rfc/rfc2616.txt.
if ( isset( $this->response_headers[ strtolower( $name ) ] ) ) {
return $this->response_headers[ strtolower( $name ) ];
}
return false;
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* IXR_ClientMulticall
*
* @package automattic/jetpack-connection
*
* @since 1.7.0
* @since-jetpack 1.5
* @since-jetpack 7.7 Moved to the jetpack-connection package.
*/
/**
* A Jetpack implementation of the WordPress core IXR client, capable of multiple calls in a single request.
*/
class Jetpack_IXR_ClientMulticall extends Jetpack_IXR_Client {
/**
* Storage for the IXR calls.
*
* @var array
*/
public $calls = array();
/**
* Add a IXR call to the client.
* First argument is the method name.
* The rest of the arguments are the params specified to the method.
*
* @param string[] ...$args IXR args.
*/
public function addCall( ...$args ) {
$method_name = array_shift( $args );
$struct = array(
'methodName' => $method_name,
'params' => $args,
);
$this->calls[] = $struct;
}
/**
* Perform the IXR multicall request.
*
* @param string[] ...$args IXR args.
*
* @return bool True if request succeeded, false otherwise.
*/
public function query( ...$args ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$this->calls = $this->sort_calls( $this->calls );
// Prepare multicall, then call the parent::query() method.
return parent::query( 'system.multicall', $this->calls );
}
/**
* Sort the IXR calls.
* Make sure syncs are always done first preserving relative order.
*
* @param array $calls Calls to sort.
* @return array Sorted calls.
*/
public function sort_calls( $calls ) {
$sync_calls = array();
$other_calls = array();
foreach ( $calls as $call ) {
if ( 'jetpack.syncContent' === $call['methodName'] ) {
$sync_calls[] = $call;
} else {
$other_calls[] = $call;
}
}
return array_merge( $sync_calls, $other_calls );
}
}

View File

@ -0,0 +1,692 @@
<?php
/**
* Legacy Jetpack_Options class.
*
* @package automattic/jetpack-connection
*/
use Automattic\Jetpack\Constants;
/**
* Class Jetpack_Options
*/
class Jetpack_Options {
/**
* An array that maps a grouped option type to an option name.
*
* @var array
*/
private static $grouped_options = array(
'compact' => 'jetpack_options',
'private' => 'jetpack_private_options',
);
/**
* Returns an array of option names for a given type.
*
* @param string $type The type of option to return. Defaults to 'compact'.
*
* @return array
*/
public static function get_option_names( $type = 'compact' ) {
switch ( $type ) {
case 'non-compact':
case 'non_compact':
return array(
'activated',
'active_modules',
'active_modules_initialized', // (bool) used to determine that all the default modules were activated, so we know how to act on a reconnection.
'allowed_xsite_search_ids', // (array) Array of WP.com blog ids that are allowed to search the content of this site
'available_modules',
'do_activate',
'edit_links_calypso_redirect', // (bool) Whether post/page edit links on front end should point to Calypso.
'log',
'slideshow_background_color',
'widget_twitter',
'wpcc_options',
'relatedposts',
'file_data',
'autoupdate_plugins', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated
'autoupdate_plugins_translations', // (array) An array of plugin ids ( eg. jetpack/jetpack ) that should be autoupdated translation files.
'autoupdate_themes', // (array) An array of theme ids ( eg. twentyfourteen ) that should be autoupdated
'autoupdate_themes_translations', // (array) An array of theme ids ( eg. twentyfourteen ) that should autoupdated translation files.
'autoupdate_core', // (bool) Whether or not to autoupdate core
'autoupdate_translations', // (bool) Whether or not to autoupdate all translations
'json_api_full_management', // (bool) Allow full management (eg. Activate, Upgrade plugins) of the site via the JSON API.
'sync_non_public_post_stati', // (bool) Allow synchronisation of posts and pages with non-public status.
'site_icon_url', // (string) url to the full site icon
'site_icon_id', // (int) Attachment id of the site icon file
'dismissed_manage_banner', // (bool) Dismiss Jetpack manage banner allows the user to dismiss the banner permanently
'unique_connection', // (array) A flag to determine a unique connection to wordpress.com two values "connected" and "disconnected" with values for how many times each has occured
'unique_registrations', // (integer) A counter of how many times the site was registered
'protect_whitelist', // (array) IP Address for the Protect module to ignore
'sync_error_idc', // (bool|array) false or array containing the site's home and siteurl at time of IDC error
'sync_health_status', // (bool|array) An array of data relating to Jetpack's sync health.
'safe_mode_confirmed', // (bool) True if someone confirms that this site was correctly put into safe mode automatically after an identity crisis is discovered.
'migrate_for_idc', // (bool) True if someone confirms that this site should migrate stats and subscribers from its previous URL
'dismissed_connection_banner', // (bool) True if the connection banner has been dismissed
'ab_connect_banner_green_bar', // (int) Version displayed of the A/B test for the green bar at the top of the connect banner.
'onboarding', // (string) Auth token to be used in the onboarding connection flow
'tos_agreed', // (bool) Whether or not the TOS for connection has been agreed upon.
'static_asset_cdn_files', // (array) An nested array of files that we can swap out for cdn versions.
'mapbox_api_key', // (string) Mapbox API Key, for use with Map block.
'mailchimp', // (string) Mailchimp keyring data, for mailchimp block.
'xmlrpc_errors', // (array) Keys are XML-RPC signature error codes. Values are truthy.
'dismissed_wizard_banner', // (int) (DEPRECATED) True if the Wizard banner has been dismissed.
);
case 'private':
return array(
'blog_token', // (string) The Client Secret/Blog Token of this site.
'user_token', // (string) The User Token of this site. (deprecated)
'user_tokens', // (array) User Tokens for each user of this site who has connected to jetpack.wordpress.com.
'purchase_token', // (string) Token for logged out user purchases.
'token_lock', // (string) Token lock in format `expiration_date|||site_url`.
);
case 'network':
return array(
'onboarding', // (string) Auth token to be used in the onboarding connection flow
'file_data', // (array) List of absolute paths to all Jetpack modules
);
}
return array(
'id', // (int) The Client ID/WP.com Blog ID of this site.
'publicize_connections', // (array) An array of Publicize connections from WordPress.com.
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time.
'old_version', // (string) Used to determine which modules are the most recently added. previous_version:time.
'fallback_no_verify_ssl_certs', // (int) Flag for determining if this host must skip SSL Certificate verification due to misconfigured SSL.
'time_diff', // (int) Offset between Jetpack server's clocks and this server's clocks. Jetpack Server Time = time() + (int) Jetpack_Options::get_option( 'time_diff' )
'public', // (int|bool) If we think this site is public or not (1, 0), false if we haven't yet tried to figure it out.
'videopress', // (array) VideoPress options array.
'is_network_site', // (int|bool) If we think this site is a network or a single blog (1, 0), false if we haven't yet tried to figue it out.
'social_links', // (array) The specified links for each social networking site.
'identity_crisis_whitelist', // (array) An array of options, each having an array of the values whitelisted for it.
'gplus_authors', // (array) The Google+ authorship information for connected users.
'last_heartbeat', // (int) The timestamp of the last heartbeat that fired.
'hide_jitm', // (array) A list of just in time messages that we should not show because they have been dismissed by the user.
'custom_css_4.7_migration', // (bool) Whether Custom CSS has scanned for and migrated any legacy CSS CPT entries to the new Core format.
'image_widget_migration', // (bool) Whether any legacy Image Widgets have been converted to the new Core widget.
'gallery_widget_migration', // (bool) Whether any legacy Gallery Widgets have been converted to the new Core widget.
'sso_first_login', // (bool) Is this the first time the user logins via SSO.
'dismissed_hints', // (array) Part of Plugin Search Hints. List of cards that have been dismissed.
'first_admin_view', // (bool) Set to true the first time the user views the admin. Usually after the initial connection.
'setup_wizard_questionnaire', // (array) (DEPRECATED) List of user choices from the setup wizard.
'setup_wizard_status', // (string) (DEPRECATED) Status of the setup wizard.
'licensing_error', // (string) Last error message occurred while attaching licenses that is yet to be surfaced to the user.
'recommendations_banner_dismissed', // (bool) Determines if the recommendations dashboard banner is dismissed or not.
'recommendations_banner_enabled', // (bool) Whether the recommendations are enabled or not.
'recommendations_data', // (array) The user choice and other data for the recommendations.
'recommendations_step', // (string) The current step of the recommendations.
'recommendations_conditional', // (array) An array of action-based recommendations.
'licensing_activation_notice_dismiss', // (array) The `last_detached_count` and the `last_dismissed_time` for the user-license activation notice.
'has_seen_wc_connection_modal', // (bool) Whether the site has displayed the WooCommerce Connection modal
'partner_coupon', // (string) A Jetpack partner issued coupon to promote a sale together with Jetpack.
'partner_coupon_added', // (string) A date for when `partner_coupon` was added, so we can auto-purge after a certain time interval.
'dismissed_backup_review_request', // (bool) Determines if the plugin review request is dismissed.
);
}
/**
* Is the option name valid?
*
* @param string $name The name of the option.
* @param string|null $group The name of the group that the option is in. Default to null, which will search non_compact.
*
* @return bool Is the option name valid?
*/
public static function is_valid( $name, $group = null ) {
if ( is_array( $name ) ) {
$compact_names = array();
foreach ( array_keys( self::$grouped_options ) as $_group ) {
$compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
}
$result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
return empty( $result );
}
if ( $group === null || 'non_compact' === $group ) {
if ( in_array( $name, self::get_option_names( $group ), true ) ) {
return true;
}
}
foreach ( array_keys( self::$grouped_options ) as $_group ) {
if ( $group === null || $group === $_group ) {
if ( in_array( $name, self::get_option_names( $_group ), true ) ) {
return true;
}
}
}
return false;
}
/**
* Checks if an option must be saved for the whole network in WP Multisite
*
* @param string $option_name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
*
* @return bool
*/
public static function is_network_option( $option_name ) {
if ( ! is_multisite() ) {
return false;
}
return in_array( $option_name, self::get_option_names( 'network' ), true );
}
/**
* Filters the requested option.
* This is a wrapper around `get_option_from_database` so that we can filter the option.
*
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
* @param mixed $default (optional).
*
* @return mixed
*/
public static function get_option( $name, $default = false ) {
/**
* Filter Jetpack Options.
* Can be useful in environments when Jetpack is running with a different setup
*
* @since 1.7.0
*
* @param string $value The value from the database.
* @param string $name Option name, _without_ `jetpack_%` prefix.
* @return string $value, unless the filters modify it.
*/
return apply_filters( 'jetpack_options', self::get_option_from_database( $name, $default ), $name );
}
/**
* Returns the requested option. Looks in jetpack_options or jetpack_$name as appropriate.
*
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
* @param mixed $default (optional).
*
* @return mixed
*/
private static function get_option_from_database( $name, $default = false ) {
if ( self::is_valid( $name, 'non_compact' ) ) {
if ( self::is_network_option( $name ) ) {
return get_site_option( "jetpack_$name", $default );
}
return get_option( "jetpack_$name", $default );
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( self::is_valid( $name, $group ) ) {
return self::get_grouped_option( $group, $name, $default );
}
}
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't wish to change legacy behavior.
return $default;
}
/**
* Returns the requested option, and ensures it's autoloaded in the future.
* This does _not_ adjust the prefix in any way (does not prefix jetpack_%)
*
* @param string $name Option name.
* @param mixed $default (optional).
*
* @return mixed
*/
public static function get_option_and_ensure_autoload( $name, $default ) {
// In this function the name is not adjusted by prefixing jetpack_
// so if it has already prefixed, we'll replace it and then
// check if the option name is a network option or not.
$jetpack_name = preg_replace( '/^jetpack_/', '', $name, 1 );
$is_network_option = self::is_network_option( $jetpack_name );
$value = $is_network_option ? get_site_option( $name ) : get_option( $name );
if ( false === $value && false !== $default ) {
if ( $is_network_option ) {
add_site_option( $name, $default );
} else {
add_option( $name, $default );
}
$value = $default;
}
return $value;
}
/**
* Update grouped option
*
* @param string $group Options group.
* @param string $name Options name.
* @param mixed $value Options value.
*
* @return bool Success or failure.
*/
private static function update_grouped_option( $group, $name, $value ) {
$options = get_option( self::$grouped_options[ $group ] );
if ( ! is_array( $options ) ) {
$options = array();
}
$options[ $name ] = $value;
return update_option( self::$grouped_options[ $group ], $options );
}
/**
* Updates the single given option. Updates jetpack_options or jetpack_$name as appropriate.
*
* @param string $name Option name. It must come _without_ `jetpack_%` prefix. The method will prefix the option name.
* @param mixed $value Option value.
* @param string $autoload If not compact option, allows specifying whether to autoload or not.
*
* @return bool Was the option successfully updated?
*/
public static function update_option( $name, $value, $autoload = null ) {
/**
* Fires before Jetpack updates a specific option.
*
* @since 1.1.2
* @since-jetpack 3.0.0
*
* @param str $name The name of the option being updated.
* @param mixed $value The new value of the option.
*/
do_action( 'pre_update_jetpack_option_' . $name, $name, $value );
if ( self::is_valid( $name, 'non_compact' ) ) {
if ( self::is_network_option( $name ) ) {
return update_site_option( "jetpack_$name", $value );
}
return update_option( "jetpack_$name", $value, $autoload );
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( self::is_valid( $name, $group ) ) {
return self::update_grouped_option( $group, $name, $value );
}
}
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't want to change legacy behavior.
return false;
}
/**
* Updates the multiple given options. Updates jetpack_options and/or jetpack_$name as appropriate.
*
* @param array $array array( option name => option value, ... ).
*/
public static function update_options( $array ) {
$names = array_keys( $array );
foreach ( array_diff( $names, self::get_option_names(), self::get_option_names( 'non_compact' ), self::get_option_names( 'private' ) ) as $unknown_name ) {
trigger_error( sprintf( 'Invalid Jetpack option name: %s', esc_html( $unknown_name ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Don't change legacy behavior.
unset( $array[ $unknown_name ] );
}
foreach ( $names as $name ) {
self::update_option( $name, $array[ $name ] );
}
}
/**
* Deletes the given option. May be passed multiple option names as an array.
* Updates jetpack_options and/or deletes jetpack_$name as appropriate.
*
* @param string|array $names Option names. They must come _without_ `jetpack_%` prefix. The method will prefix the option names.
*
* @return bool Was the option successfully deleted?
*/
public static function delete_option( $names ) {
$result = true;
$names = (array) $names;
if ( ! self::is_valid( $names ) ) {
// phpcs:disable -- This line triggers a handful of errors; ignoring to avoid changing legacy behavior.
trigger_error( sprintf( 'Invalid Jetpack option names: %s', print_r( $names, 1 ) ), E_USER_WARNING );
// phpcs:enable
return false;
}
foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
if ( self::is_network_option( $name ) ) {
$result = delete_site_option( "jetpack_$name" );
} else {
$result = delete_option( "jetpack_$name" );
}
}
foreach ( array_keys( self::$grouped_options ) as $group ) {
if ( ! self::delete_grouped_option( $group, $names ) ) {
$result = false;
}
}
return $result;
}
/**
* Get group option.
*
* @param string $group Option group name.
* @param string $name Option name.
* @param mixed $default Default option value.
*
* @return mixed Option.
*/
private static function get_grouped_option( $group, $name, $default ) {
$options = get_option( self::$grouped_options[ $group ] );
if ( is_array( $options ) && isset( $options[ $name ] ) ) {
return $options[ $name ];
}
return $default;
}
/**
* Delete grouped option.
*
* @param string $group Option group name.
* @param array $names Option names.
*
* @return bool Success or failure.
*/
private static function delete_grouped_option( $group, $names ) {
$options = get_option( self::$grouped_options[ $group ], array() );
$to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
if ( $to_delete ) {
foreach ( $to_delete as $name ) {
unset( $options[ $name ] );
}
return update_option( self::$grouped_options[ $group ], $options );
}
return true;
}
/*
* Raw option methods allow Jetpack to get / update / delete options via direct DB queries, including options
* that are not created by the Jetpack plugin. This is helpful only in rare cases when we need to bypass
* cache and filters.
*/
/**
* Deletes an option via $wpdb query.
*
* @param string $name Option name.
*
* @return bool Is the option deleted?
*/
public static function delete_raw_option( $name ) {
if ( self::bypass_raw_option( $name ) ) {
return delete_option( $name );
}
global $wpdb;
$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $name ) );
return $result;
}
/**
* Updates an option via $wpdb query.
*
* @param string $name Option name.
* @param mixed $value Option value.
* @param bool $autoload Specifying whether to autoload or not.
*
* @return bool Is the option updated?
*/
public static function update_raw_option( $name, $value, $autoload = false ) {
if ( self::bypass_raw_option( $name ) ) {
return update_option( $name, $value, $autoload );
}
global $wpdb;
$autoload_value = $autoload ? 'yes' : 'no';
$old_value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
$name
)
);
if ( $old_value === $value ) {
return false;
}
$serialized_value = maybe_serialize( $value );
// below we used "insert ignore" to at least suppress the resulting error.
$updated_num = $wpdb->query(
$wpdb->prepare(
"UPDATE $wpdb->options SET option_value = %s WHERE option_name = %s",
$serialized_value,
$name
)
);
// Try inserting the option if the value doesn't exits.
if ( ! $updated_num ) {
$updated_num = $wpdb->query(
$wpdb->prepare(
"INSERT IGNORE INTO $wpdb->options ( option_name, option_value, autoload ) VALUES ( %s, %s, %s )",
$name,
$serialized_value,
$autoload_value
)
);
}
return (bool) $updated_num;
}
/**
* Gets an option via $wpdb query.
*
* @since 1.1.2
* @since-jetpack 5.4.0
*
* @param string $name Option name.
* @param mixed $default Default option value if option is not found.
*
* @return mixed Option value, or null if option is not found and default is not specified.
*/
public static function get_raw_option( $name, $default = null ) {
if ( self::bypass_raw_option( $name ) ) {
return get_option( $name, $default );
}
global $wpdb;
$value = $wpdb->get_var(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
$name
)
);
$value = maybe_unserialize( $value );
if ( null === $value && null !== $default ) {
return $default;
}
return $value;
}
/**
* This function checks for a constant that, if present, will disable direct DB queries Jetpack uses to manage certain options and force Jetpack to always use Options API instead.
* Options can be selectively managed via a blocklist by filtering option names via the jetpack_disabled_raw_option filter.
*
* @param string $name Option name.
*
* @return bool
*/
public static function bypass_raw_option( $name ) {
if ( Constants::get_constant( 'JETPACK_DISABLE_RAW_OPTIONS' ) ) {
return true;
}
/**
* Allows to disable particular raw options.
*
* @since 1.1.2
* @since-jetpack 5.5.0
*
* @param array $disabled_raw_options An array of option names that you can selectively blocklist from being managed via direct database queries.
*/
$disabled_raw_options = apply_filters( 'jetpack_disabled_raw_options', array() );
return isset( $disabled_raw_options[ $name ] );
}
/**
* Gets all known options that are used by Jetpack and managed by Jetpack_Options.
*
* @since 1.1.2
* @since-jetpack 5.4.0
*
* @param boolean $strip_unsafe_options If true, and by default, will strip out options necessary for the connection to WordPress.com.
* @return array An array of all options managed via the Jetpack_Options class.
*/
public static function get_all_jetpack_options( $strip_unsafe_options = true ) {
$jetpack_options = self::get_option_names();
$jetpack_options_non_compat = self::get_option_names( 'non_compact' );
$jetpack_options_private = self::get_option_names( 'private' );
$all_jp_options = array_merge( $jetpack_options, $jetpack_options_non_compat, $jetpack_options_private );
if ( $strip_unsafe_options ) {
// Flag some Jetpack options as unsafe.
$unsafe_options = array(
'id', // (int) The Client ID/WP.com Blog ID of this site.
'master_user', // (int) The local User ID of the user who connected this site to jetpack.wordpress.com.
'version', // (string) Used during upgrade procedure to auto-activate new modules. version:time
// non_compact.
'activated',
// private.
'register',
'blog_token', // (string) The Client Secret/Blog Token of this site.
'user_token', // (string) The User Token of this site. (deprecated)
'user_tokens',
);
// Remove the unsafe Jetpack options.
foreach ( $unsafe_options as $unsafe_option ) {
$key = array_search( $unsafe_option, $all_jp_options, true );
if ( false !== $key ) {
unset( $all_jp_options[ $key ] );
}
}
}
return $all_jp_options;
}
/**
* Get all options that are not managed by the Jetpack_Options class that are used by Jetpack.
*
* @since 1.1.2
* @since-jetpack 5.4.0
*
* @return array
*/
public static function get_all_wp_options() {
// A manual build of the wp options.
return array(
'sharing-options',
'disabled_likes',
'disabled_reblogs',
'jetpack_comments_likes_enabled',
'stats_options',
'stats_dashboard_widget',
'safecss_preview_rev',
'safecss_rev',
'safecss_revision_migrated',
'nova_menu_order',
'jetpack_portfolio',
'jetpack_portfolio_posts_per_page',
'jetpack_testimonial',
'jetpack_testimonial_posts_per_page',
'sharedaddy_disable_resources',
'sharing-options',
'sharing-services',
'site_icon_temp_data',
'featured-content',
'site_logo',
'jetpack_dismissed_notices',
'jetpack-twitter-cards-site-tag',
'jetpack-sitemap-state',
'jetpack_sitemap_post_types',
'jetpack_sitemap_location',
'jetpack_protect_key',
'jetpack_protect_blocked_attempts',
'jetpack_protect_activating',
'jetpack_connection_banner_ab',
'jetpack_active_plan',
'jetpack_activation_source',
'jetpack_site_products',
'jetpack_sso_match_by_email',
'jetpack_sso_require_two_step',
'jetpack_sso_remove_login_form',
'jetpack_last_connect_url_check',
'jpo_business_address',
'jpo_site_type',
'jpo_homepage_format',
'jpo_contact_page',
'jetpack_excluded_extensions',
);
}
/**
* Gets all options that can be safely reset by CLI.
*
* @since 1.1.2
* @since-jetpack 5.4.0
*
* @return array array Associative array containing jp_options which are managed by the Jetpack_Options class and wp_options which are not.
*/
public static function get_options_for_reset() {
$all_jp_options = self::get_all_jetpack_options();
$wp_options = self::get_all_wp_options();
$options = array(
'jp_options' => $all_jp_options,
'wp_options' => $wp_options,
);
return $options;
}
/**
* Delete all known options
*
* @since 1.1.2
* @since-jetpack 5.4.0
*
* @return void
*/
public static function delete_all_known_options() {
// Delete all compact options.
foreach ( (array) self::$grouped_options as $option_name ) {
delete_option( $option_name );
}
// Delete all non-compact Jetpack options.
foreach ( (array) self::get_option_names( 'non-compact' ) as $option_name ) {
self::delete_option( $option_name );
}
// Delete all options that can be reset via CLI, that aren't Jetpack options.
foreach ( (array) self::get_all_wp_options() as $option_name ) {
delete_option( $option_name );
}
}
}

View File

@ -0,0 +1,405 @@
<?php
/**
* The Jetpack Connection signature class file.
*
* @package automattic/jetpack-connection
*/
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
/**
* The Jetpack Connection signature class that is used to sign requests.
*/
class Jetpack_Signature {
/**
* Token part of the access token.
*
* @access public
* @var string
*/
public $token;
/**
* Access token secret.
*
* @access public
* @var string
*/
public $secret;
/**
* The current request URL.
*
* @access public
* @var string
*/
public $current_request_url;
/**
* Constructor.
*
* @param array $access_token Access token.
* @param int $time_diff Timezone difference (in seconds).
*/
public function __construct( $access_token, $time_diff = 0 ) {
$secret = explode( '.', $access_token );
if ( 2 !== count( $secret ) ) {
return;
}
$this->token = $secret[0];
$this->secret = $secret[1];
$this->time_diff = $time_diff;
}
/**
* Sign the current request.
*
* @todo Implement a proper nonce verification.
*
* @param array $override Optional arguments to override the ones from the current request.
* @return string|WP_Error Request signature, or a WP_Error on failure.
*/
public function sign_current_request( $override = array() ) {
if ( isset( $override['scheme'] ) ) {
$scheme = $override['scheme'];
if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
return new WP_Error( 'invalid_scheme', 'Invalid URL scheme' );
}
} else {
if ( is_ssl() ) {
$scheme = 'https';
} else {
$scheme = 'http';
}
}
$port = $this->get_current_request_port();
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidatedNotSanitized -- Sniff misses the esc_url_raw wrapper.
$this->current_request_url = esc_url_raw( wp_unslash( "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . ( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ) ) );
if ( array_key_exists( 'body', $override ) && ! empty( $override['body'] ) ) {
$body = $override['body'];
} elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
$body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null;
// Convert the $_POST to the body, if the body was empty. This is how arrays are hashed
// and encoded on the Jetpack side.
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( empty( $body ) && is_array( $_POST ) && count( $_POST ) > 0 ) {
$body = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
}
} elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'PUT' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
// This is a little strange-looking, but there doesn't seem to be another way to get the PUT body.
$raw_put_data = file_get_contents( 'php://input' );
parse_str( $raw_put_data, $body );
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
$put_data = json_decode( $raw_put_data, true );
if ( is_array( $put_data ) && count( $put_data ) > 0 ) {
$body = $put_data;
}
}
} else {
$body = null;
}
if ( empty( $body ) ) {
$body = null;
}
$a = array();
foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) {
if ( isset( $override[ $parameter ] ) ) {
$a[ $parameter ] = $override[ $parameter ];
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$a[ $parameter ] = isset( $_GET[ $parameter ] ) ? filter_var( wp_unslash( $_GET[ $parameter ] ) ) : '';
}
}
$method = isset( $override['method'] ) ? $override['method'] : ( isset( $_SERVER['REQUEST_METHOD'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : null );
return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $this->current_request_url, $body, true );
}
/**
* Sign a specified request.
*
* @todo Having body_hash v. body-hash is annoying. Refactor to accept an array?
* @todo Use wp_json_encode() instead of json_encode()?
*
* @param string $token Request token.
* @param int $timestamp Timestamp of the request.
* @param string $nonce Request nonce.
* @param string $body_hash Request body hash.
* @param string $method Request method.
* @param string $url Request URL.
* @param mixed $body Request body.
* @param bool $verify_body_hash Whether to verify the body hash against the body.
* @return string|WP_Error Request signature, or a WP_Error on failure.
*/
public function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true ) {
if ( ! $this->secret ) {
return new WP_Error( 'invalid_secret', 'Invalid secret' );
}
if ( ! $this->token ) {
return new WP_Error( 'invalid_token', 'Invalid token' );
}
list( $token ) = explode( '.', $token );
$signature_details = compact( 'token', 'timestamp', 'nonce', 'body_hash', 'method', 'url' );
if ( 0 !== strpos( $token, "$this->token:" ) ) {
return new WP_Error( 'token_mismatch', 'Incorrect token', compact( 'signature_details' ) );
}
// If we got an array at this point, let's encode it, so we can see what it looks like as a string.
if ( is_array( $body ) ) {
if ( count( $body ) > 0 ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
$body = json_encode( $body );
} else {
$body = '';
}
}
$required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' );
if ( $body !== null ) {
$required_parameters[] = 'body_hash';
if ( ! is_string( $body ) ) {
return new WP_Error( 'invalid_body', 'Body is malformed.', compact( 'signature_details' ) );
}
}
foreach ( $required_parameters as $required ) {
if ( ! is_scalar( $$required ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
}
if ( ! strlen( $$required ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ), compact( 'signature_details' ) );
}
}
if ( empty( $body ) ) {
if ( $body_hash ) {
return new WP_Error( 'invalid_body_hash', 'Invalid body hash for empty body.', compact( 'signature_details' ) );
}
} else {
$connection = new Connection_Manager();
if ( $verify_body_hash && $connection->sha1_base64( $body ) !== $body_hash ) {
return new WP_Error( 'invalid_body_hash', 'The body hash does not match.', compact( 'signature_details' ) );
}
}
$parsed = wp_parse_url( $url );
if ( ! isset( $parsed['host'] ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ), compact( 'signature_details' ) );
}
if ( ! empty( $parsed['port'] ) ) {
$port = $parsed['port'];
} else {
if ( 'http' === $parsed['scheme'] ) {
$port = 80;
} elseif ( 'https' === $parsed['scheme'] ) {
$port = 443;
} else {
return new WP_Error( 'unknown_scheme_port', "The scheme's port is unknown", compact( 'signature_details' ) );
}
}
if ( ! ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug.
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'timestamp' ), compact( 'signature_details' ) );
}
$local_time = $timestamp - $this->time_diff;
if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
return new WP_Error( 'invalid_signature', 'The timestamp is too old.', compact( 'signature_details' ) );
}
if ( 12 < strlen( $nonce ) || preg_match( '/[^a-zA-Z0-9]/', $nonce ) ) {
return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ), compact( 'signature_details' ) );
}
$normalized_request_pieces = array(
$token,
$timestamp,
$nonce,
$body_hash,
strtoupper( $method ),
strtolower( $parsed['host'] ),
$port,
empty( $parsed['path'] ) ? '' : $parsed['path'],
// Normalized Query String.
);
$normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) );
$flat_normalized_request_pieces = array();
foreach ( $normalized_request_pieces as $piece ) {
if ( is_array( $piece ) ) {
foreach ( $piece as $subpiece ) {
$flat_normalized_request_pieces[] = $subpiece;
}
} else {
$flat_normalized_request_pieces[] = $piece;
}
}
$normalized_request_pieces = $flat_normalized_request_pieces;
$normalized_request_string = join( "\n", $normalized_request_pieces ) . "\n";
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $this->secret, true ) );
}
/**
* Retrieve and normalize the parameters from a query string.
*
* @param string $query_string Query string.
* @return array Normalized query string parameters.
*/
public function normalized_query_parameters( $query_string ) {
parse_str( $query_string, $array );
unset( $array['signature'] );
$names = array_keys( $array );
$values = array_values( $array );
$names = array_map( array( $this, 'encode_3986' ), $names );
$values = array_map( array( $this, 'encode_3986' ), $values );
$pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values );
sort( $pairs );
return $pairs;
}
/**
* Encodes a string or array of strings according to RFC 3986.
*
* @param string|array $string_or_array String or array to encode.
* @return string|array URL-encoded string or array.
*/
public function encode_3986( $string_or_array ) {
if ( is_array( $string_or_array ) ) {
return array_map( array( $this, 'encode_3986' ), $string_or_array );
}
return rawurlencode( $string_or_array );
}
/**
* Concatenates a parameter name and a parameter value with an equals sign between them.
*
* @param string $name Parameter name.
* @param string|array $value Parameter value.
* @return string|array A string pair (e.g. `name=value`) or an array of string pairs.
*/
public function join_with_equal_sign( $name, $value ) {
if ( is_array( $value ) ) {
return $this->join_array_with_equal_sign( $name, $value );
}
return "{$name}={$value}";
}
/**
* Helper function for join_with_equal_sign for handling arrayed values.
* Explicitly supports nested arrays.
*
* @param string $name Parameter name.
* @param array $value Parameter value.
* @return array An array of string pairs (e.g. `[ name[example]=value ]`).
*/
private function join_array_with_equal_sign( $name, $value ) {
$result = array();
foreach ( $value as $value_key => $value_value ) {
$joined_value = $this->join_with_equal_sign( $name . '[' . $value_key . ']', $value_value );
if ( is_array( $joined_value ) ) {
foreach ( array_values( $joined_value ) as $individual_joined_value ) {
$result[] = $individual_joined_value;
}
} elseif ( is_string( $joined_value ) ) {
$result[] = $joined_value;
}
}
sort( $result );
return $result;
}
/**
* Gets the port that should be considered to sign the current request.
*
* It will analyze the current request, as well as some Jetpack constants, to return the string
* to be concatenated in the URL representing the port of the current request.
*
* @since 1.8.4
*
* @return string The port to be used in the signature
*/
public function get_current_request_port() {
$host_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? $this->sanitize_host_post( $_SERVER['HTTP_X_FORWARDED_PORT'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
if ( '' === $host_port && isset( $_SERVER['SERVER_PORT'] ) ) {
$host_port = $this->sanitize_host_post( $_SERVER['SERVER_PORT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
}
/**
* Note: This port logic is tested in the Jetpack_Cxn_Tests->test__server_port_value() test.
* Please update the test if any changes are made in this logic.
*/
if ( is_ssl() ) {
// 443: Standard Port
// 80: Assume we're behind a proxy without X-Forwarded-Port. Hardcoding "80" here means most sites
// with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with
// the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a
// site at https://example.com:80/ (which would be a strange configuration).
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
// if the site is behind a proxy running on port 443 without
// X-Forwarded-Port and the back end's port is *not* 80. It's better,
// though, to configure the proxy to send X-Forwarded-Port.
$https_port = defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ? $this->sanitize_host_post( JETPACK_SIGNATURE__HTTPS_PORT ) : '443';
$port = in_array( $host_port, array( '443', '80', $https_port ), true ) ? '' : $host_port;
} else {
// 80: Standard Port
// JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port
// if the site is behind a proxy running on port 80 without
// X-Forwarded-Port. It's better, though, to configure the proxy to
// send X-Forwarded-Port.
$http_port = defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ? $this->sanitize_host_post( JETPACK_SIGNATURE__HTTP_PORT ) : '80';
$port = in_array( $host_port, array( '80', $http_port ), true ) ? '' : $host_port;
}
return (string) $port;
}
/**
* Sanitizes a variable checking if it's a valid port number, which can be an integer or a numeric string
*
* @since 1.8.4
*
* @param mixed $port_number Variable representing a port number.
* @return string Always a string with a valid port number, or an empty string if input is invalid
*/
public function sanitize_host_post( $port_number ) {
if ( ! is_int( $port_number ) && ! is_string( $port_number ) ) {
return '';
}
if ( is_string( $port_number ) && ! ctype_digit( $port_number ) ) {
return '';
}
if ( 0 >= (int) $port_number || 65535 < $port_number ) {
return '';
}
return (string) $port_number;
}
}

View File

@ -0,0 +1,228 @@
<?php
/**
* Legacy Jetpack Tracks Client
*
* @package automattic/jetpack-tracking
*/
use Automattic\Jetpack\Connection\Manager;
/**
* Jetpack_Tracks_Client
*
* Send Tracks events on behalf of a user
*
* Example Usage:
```php
require( dirname(__FILE__).'path/to/tracks/class-jetpack-tracks-client.php' );
$result = Jetpack_Tracks_Client::record_event( array(
'_en' => $event_name, // required
'_ui' => $user_id, // required unless _ul is provided
'_ul' => $user_login, // required unless _ui is provided
// Optional, but recommended
'_ts' => $ts_in_ms, // Default: now
'_via_ip' => $client_ip, // we use it for geo, etc.
// Possibly useful to set some context for the event
'_via_ua' => $client_user_agent,
'_via_url' => $client_url,
'_via_ref' => $client_referrer,
// For user-targeted tests
'abtest_name' => $abtest_name,
'abtest_variation' => $abtest_variation,
// Your application-specific properties
'custom_property' => $some_value,
) );
if ( is_wp_error( $result ) ) {
// Handle the error in your app
}
```
*/
class Jetpack_Tracks_Client {
const PIXEL = 'https://pixel.wp.com/t.gif';
const BROWSER_TYPE = 'php-agent';
const USER_AGENT_SLUG = 'tracks-client';
const VERSION = '0.3';
/**
* Stores the Terms of Service Object Reference.
*
* @var null
*/
private static $terms_of_service = null;
/**
* Record an event.
*
* @param mixed $event Event object to send to Tracks. An array will be cast to object. Required.
* Properties are included directly in the pixel query string after light validation.
* @return mixed True on success, WP_Error on failure
*/
public static function record_event( $event ) {
if ( ! self::$terms_of_service ) {
self::$terms_of_service = new \Automattic\Jetpack\Terms_Of_Service();
}
// Don't track users who have opted out or not agreed to our TOS, or are not running an active Jetpack.
if ( ! self::$terms_of_service->has_agreed() || ! empty( $_COOKIE['tk_opt-out'] ) ) {
return false;
}
if ( ! $event instanceof Jetpack_Tracks_Event ) {
$event = new Jetpack_Tracks_Event( $event );
}
if ( is_wp_error( $event ) ) {
return $event;
}
$pixel = $event->build_pixel_url( $event );
if ( ! $pixel ) {
return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
}
return self::record_pixel( $pixel );
}
/**
* Synchronously request the pixel.
*
* @param string $pixel The wp.com tracking pixel.
* @return array|bool|WP_Error True if successful. wp_remote_get response or WP_Error if not.
*/
public static function record_pixel( $pixel ) {
// Add the Request Timestamp and URL terminator just before the HTTP request.
$pixel .= '&_rt=' . self::build_timestamp() . '&_=_';
$response = wp_remote_get(
$pixel,
array(
'blocking' => true, // The default, but being explicit here :).
'timeout' => 1,
'redirection' => 2,
'httpversion' => '1.1',
'user-agent' => self::get_user_agent(),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0;
if ( 200 !== $code ) {
return new WP_Error( 'request_failed', 'Tracks pixel request failed', $code );
}
return true;
}
/**
* Get the user agent.
*
* @return string The user agent.
*/
public static function get_user_agent() {
return self::USER_AGENT_SLUG . '-v' . self::VERSION;
}
/**
* Build an event and return its tracking URL
*
* @deprecated Call the `build_pixel_url` method on a Jetpack_Tracks_Event object instead.
* @param array $event Event keys and values.
* @return string URL of a tracking pixel.
*/
public static function build_pixel_url( $event ) {
$_event = new Jetpack_Tracks_Event( $event );
return $_event->build_pixel_url();
}
/**
* Validate input for a tracks event.
*
* @deprecated Instantiate a Jetpack_Tracks_Event object instead
* @param array $event Event keys and values.
* @return mixed Validated keys and values or WP_Error on failure
*/
private static function validate_and_sanitize( $event ) {
$_event = new Jetpack_Tracks_Event( $event );
if ( is_wp_error( $_event ) ) {
return $_event;
}
return get_object_vars( $_event );
}
/**
* Builds a timestamp.
*
* Milliseconds since 1970-01-01.
*
* @return string
*/
public static function build_timestamp() {
$ts = round( microtime( true ) * 1000 );
return number_format( $ts, 0, '', '' );
}
/**
* Grabs the user's anon id from cookies, or generates and sets a new one
*
* @return string An anon id for the user
*/
public static function get_anon_id() {
static $anon_id = null;
if ( ! isset( $anon_id ) ) {
// Did the browser send us a cookie?
if ( isset( $_COOKIE['tk_ai'] ) && preg_match( '#^[a-z]+:[A-Za-z0-9+/=]{24}$#', $_COOKIE['tk_ai'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
$anon_id = $_COOKIE['tk_ai']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
} else {
$binary = '';
// Generate a new anonId and try to save it in the browser's cookies.
// Note that base64-encoding an 18 character string generates a 24-character anon id.
for ( $i = 0; $i < 18; ++$i ) {
$binary .= chr( wp_rand( 0, 255 ) );
}
$anon_id = 'jetpack:' . base64_encode( $binary ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
if ( ! headers_sent()
&& ! ( defined( 'REST_REQUEST' ) && REST_REQUEST )
&& ! ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
) {
setcookie( 'tk_ai', $anon_id, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- This is a random value and should be fine.
}
}
}
return $anon_id;
}
/**
* Gets the WordPress.com user's Tracks identity, if connected.
*
* @return array|bool
*/
public static function get_connected_user_tracks_identity() {
$user_data = ( new Manager() )->get_connected_user_data();
if ( ! $user_data ) {
return false;
}
return array(
'blogid' => Jetpack_Options::get_option( 'id', 0 ),
'userid' => $user_data['ID'],
'username' => $user_data['login'],
);
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* Class Jetpack_Tracks_Event. Legacy.
*
* @package automattic/jetpack-sync
*/
/*
* Example Usage:
```php
require_once( dirname(__FILE__) . 'path/to/tracks/class-jetpack-tracks-event.php' );
$event = new Jetpack_Tracks_Event( array(
'_en' => $event_name, // required
'_ui' => $user_id, // required unless _ul is provided
'_ul' => $user_login, // required unless _ui is provided
// Optional, but recommended
'_via_ip' => $client_ip, // for geo, etc.
// Possibly useful to set some context for the event
'_via_ua' => $client_user_agent,
'_via_url' => $client_url,
'_via_ref' => $client_referrer,
// For user-targeted tests
'abtest_name' => $abtest_name,
'abtest_variation' => $abtest_variation,
// Your application-specific properties
'custom_property' => $some_value,
) );
if ( is_wp_error( $event->error ) ) {
// Handle the error in your app
}
$bump_and_redirect_pixel = $event->build_signed_pixel_url();
```
*/
/**
* Class Jetpack_Tracks_Event
*/
class Jetpack_Tracks_Event {
const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/';
const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
/**
* Tracks Event Error.
*
* @var mixed Error.
*/
public $error;
/**
* Jetpack_Tracks_Event constructor.
*
* @param object $event Tracks event.
*/
public function __construct( $event ) {
$_event = self::validate_and_sanitize( $event );
if ( is_wp_error( $_event ) ) {
$this->error = $_event;
return;
}
foreach ( $_event as $key => $value ) {
$this->{$key} = $value;
}
}
/**
* Record a track event.
*/
public function record() {
return Jetpack_Tracks_Client::record_event( $this );
}
/**
* Annotate the event with all relevant info.
*
* @param mixed $event Object or (flat) array.
* @return mixed The transformed event array or WP_Error on failure.
*/
public static function validate_and_sanitize( $event ) {
$event = (object) $event;
// Required.
if ( ! $event->_en ) {
return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 );
}
// delete non-routable addresses otherwise geoip will discard the record entirely.
if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
unset( $event->_via_ip );
}
$validated = array(
'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE,
'_aua' => Jetpack_Tracks_Client::get_user_agent(),
);
$_event = (object) array_merge( (array) $event, $validated );
// If you want to block property names, do it here.
// Make sure we have an event timestamp.
if ( ! isset( $_event->_ts ) ) {
$_event->_ts = Jetpack_Tracks_Client::build_timestamp();
}
return $_event;
}
/**
* Build a pixel URL that will send a Tracks event when fired.
* On error, returns an empty string ('').
*
* @return string A pixel URL or empty string ('') if there were invalid args.
*/
public function build_pixel_url() {
if ( $this->error ) {
return '';
}
$args = get_object_vars( $this );
// Request Timestamp and URL Terminator must be added just before the HTTP request or not at all.
unset( $args['_rt'] );
unset( $args['_'] );
$validated = self::validate_and_sanitize( $args );
if ( is_wp_error( $validated ) ) {
return '';
}
return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated );
}
/**
* Validate the event name.
*
* @param string $name Event name.
* @return false|int
*/
public static function event_name_is_valid( $name ) {
return preg_match( self::EVENT_NAME_REGEX, $name );
}
/**
* Validates prop name
*
* @param string $name Property name.
*
* @return false|int Truthy value.
*/
public static function prop_name_is_valid( $name ) {
return preg_match( self::PROP_NAME_REGEX, $name );
}
/**
* Scrutinize event name.
*
* @param object $event Tracks event.
*/
public static function scrutinize_event_names( $event ) {
if ( ! self::event_name_is_valid( $event->_en ) ) {
return;
}
$whitelisted_key_names = array(
'anonId',
'Browser_Type',
);
foreach ( array_keys( (array) $event ) as $key ) {
if ( in_array( $key, $whitelisted_key_names, true ) ) {
continue;
}
if ( ! self::prop_name_is_valid( $key ) ) {
return;
}
}
}
}

View File

@ -0,0 +1,867 @@
<?php
/**
* Jetpack XMLRPC Server.
*
* @package automattic/jetpack-connection
*/
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Connection\Secrets;
use Automattic\Jetpack\Connection\Tokens;
use Automattic\Jetpack\Connection\Urls;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Roles;
/**
* Just a sack of functions. Not actually an IXR_Server
*/
class Jetpack_XMLRPC_Server {
/**
* The current error object
*
* @var \WP_Error
*/
public $error = null;
/**
* The current user
*
* @var \WP_User
*/
public $user = null;
/**
* The connection manager object.
*
* @var Automattic\Jetpack\Connection\Manager
*/
private $connection;
/**
* Creates a new XMLRPC server object.
*/
public function __construct() {
$this->connection = new Connection_Manager();
}
/**
* Whitelist of the XML-RPC methods available to the Jetpack Server. If the
* user is not authenticated (->login()) then the methods are never added,
* so they will get a "does not exist" error.
*
* @param array $core_methods Core XMLRPC methods.
*/
public function xmlrpc_methods( $core_methods ) {
$jetpack_methods = array(
'jetpack.verifyAction' => array( $this, 'verify_action' ),
'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
'jetpack.unlinkUser' => array( $this, 'unlink_user' ),
'jetpack.testConnection' => array( $this, 'test_connection' ),
);
$jetpack_methods = array_merge( $jetpack_methods, $this->provision_xmlrpc_methods() );
$this->user = $this->login();
if ( $this->user ) {
$jetpack_methods = array_merge(
$jetpack_methods,
array(
'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
)
);
if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
}
/**
* Filters the XML-RPC methods available to Jetpack for authenticated users.
*
* @since 1.7.0
* @since-jetpack 1.1.0
*
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
* @param array $core_methods Available core XML-RPC methods.
* @param \WP_User $user Information about the user authenticated in the request.
*/
$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
}
/**
* Filters the XML-RPC methods available to Jetpack for requests signed both with a blog token or a user token.
*
* @since 1.7.0
* @since 1.9.5 Introduced the $user parameter.
* @since-jetpack 3.0.0
*
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
* @param array $core_methods Available core XML-RPC methods.
* @param \WP_User|bool $user Information about the user authenticated in the request. False if authenticated with blog token.
*/
return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods, $this->user );
}
/**
* Whitelist of the bootstrap XML-RPC methods
*/
public function bootstrap_xmlrpc_methods() {
return array(
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
'jetpack.remoteRegister' => array( $this, 'remote_register' ),
);
}
/**
* Additional method needed for authorization calls.
*/
public function authorize_xmlrpc_methods() {
return array(
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
'jetpack.remoteRegister' => array( $this, 'remote_already_registered' ),
);
}
/**
* Remote provisioning methods.
*/
public function provision_xmlrpc_methods() {
return array(
'jetpack.remoteRegister' => array( $this, 'remote_register' ),
'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
'jetpack.remoteConnect' => array( $this, 'remote_connect' ),
'jetpack.getUser' => array( $this, 'get_user' ),
);
}
/**
* Used to verify whether a local user exists and what role they have.
*
* @param int|string|array $request One of:
* int|string The local User's ID, username, or email address.
* array A request array containing:
* 0: int|string The local User's ID, username, or email address.
*
* @return array|\IXR_Error Information about the user, or error if no such user found:
* roles: string[] The user's rols.
* login: string The user's username.
* email_hash string[] The MD5 hash of the user's normalized email address.
* caps string[] The user's capabilities.
* allcaps string[] The user's granular capabilities, merged from role capabilities.
* token_key string The Token Key of the user's Jetpack token. Empty string if none.
*/
public function get_user( $request ) {
$user_id = is_array( $request ) ? $request[0] : $request;
if ( ! $user_id ) {
return $this->error(
new \WP_Error(
'invalid_user',
__( 'Invalid user identifier.', 'jetpack-connection' ),
400
),
'get_user'
);
}
$user = $this->get_user_by_anything( $user_id );
if ( ! $user ) {
return $this->error(
new \WP_Error(
'user_unknown',
__( 'User not found.', 'jetpack-connection' ),
404
),
'get_user'
);
}
$user_token = ( new Tokens() )->get_access_token( $user->ID );
if ( $user_token ) {
list( $user_token_key ) = explode( '.', $user_token->secret );
if ( $user_token_key === $user_token->secret ) {
$user_token_key = '';
}
} else {
$user_token_key = '';
}
return array(
'id' => $user->ID,
'login' => $user->user_login,
'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
'roles' => $user->roles,
'caps' => $user->caps,
'allcaps' => $user->allcaps,
'token_key' => $user_token_key,
);
}
/**
* Remote authorization XMLRPC method handler.
*
* @param array $request the request.
*/
public function remote_authorize( $request ) {
$user = get_user_by( 'id', $request['state'] );
/**
* Happens on various request handling events in the Jetpack XMLRPC server.
* The action combines several types of events:
* - remote_authorize
* - remote_provision
* - get_user.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param String $action the action name, i.e., 'remote_authorize'.
* @param String $stage the execution stage, can be 'begin', 'success', 'error', etc.
* @param array $parameters extra parameters from the event.
* @param WP_User $user the acting user.
*/
do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
return $this->error(
new \WP_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
'remote_authorize'
);
}
}
if ( ! $user ) {
return $this->error( new \WP_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
}
if ( $this->connection->has_connected_owner() && $this->connection->is_user_connected( $request['state'] ) ) {
return $this->error( new \WP_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
}
$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
if ( is_a( $verified, 'IXR_Error' ) ) {
return $this->error( $verified, 'remote_authorize' );
}
wp_set_current_user( $request['state'] );
$result = $this->connection->authorize( $request );
if ( is_wp_error( $result ) ) {
return $this->error( $result, 'remote_authorize' );
}
// This action is documented in class.jetpack-xmlrpc-server.php.
do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
return array(
'result' => $result,
);
}
/**
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
* register this site so that a plan can be provisioned.
*
* @param array $request An array containing at minimum nonce and local_user keys.
*
* @return \WP_Error|array
*/
public function remote_register( $request ) {
// This action is documented in class.jetpack-xmlrpc-server.php.
do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
$user = $this->fetch_and_verify_local_user( $request );
if ( ! $user ) {
return $this->error(
new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack-connection' ), 400 ),
'remote_register'
);
}
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
return $this->error( $user, 'remote_register' );
}
if ( empty( $request['nonce'] ) ) {
return $this->error(
new \WP_Error(
'nonce_missing',
__( 'The required "nonce" parameter is missing.', 'jetpack-connection' ),
400
),
'remote_register'
);
}
$nonce = sanitize_text_field( $request['nonce'] );
unset( $request['nonce'] );
$api_url = $this->connection->api_url( 'partner_provision_nonce_check' );
$response = Client::_wp_remote_request(
esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
array( 'method' => 'GET' ),
true
);
if (
200 !== wp_remote_retrieve_response_code( $response ) ||
'OK' !== trim( wp_remote_retrieve_body( $response ) )
) {
return $this->error(
new \WP_Error(
'invalid_nonce',
__( 'There was an issue validating this request.', 'jetpack-connection' ),
400
),
'remote_register'
);
}
if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) {
wp_set_current_user( $user->ID );
// This code mostly copied from Jetpack::admin_page_load.
if ( isset( $request['from'] ) ) {
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
}
$registered = $this->connection->try_registration();
if ( is_wp_error( $registered ) ) {
return $this->error( $registered, 'remote_register' );
} elseif ( ! $registered ) {
return $this->error(
new \WP_Error(
'registration_error',
__( 'There was an unspecified error registering the site', 'jetpack-connection' ),
400
),
'remote_register'
);
}
}
// This action is documented in class.jetpack-xmlrpc-server.php.
do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
return array(
'client_id' => Jetpack_Options::get_option( 'id' ),
);
}
/**
* This is a substitute for remote_register() when the blog is already registered which returns an error code
* signifying that state.
* This is an unauthorized call and we should not be responding with any data other than the error code.
*
* @return \IXR_Error
*/
public function remote_already_registered() {
return $this->error(
new \WP_Error( 'already_registered', __( 'Blog is already registered', 'jetpack-connection' ), 400 ),
'remote_register'
);
}
/**
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
* register this site so that a plan can be provisioned.
*
* @param array $request An array containing at minimum a nonce key and a local_username key.
*
* @return \WP_Error|array
*/
public function remote_provision( $request ) {
$user = $this->fetch_and_verify_local_user( $request );
if ( ! $user ) {
return $this->error(
new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack-connection' ), 400 ),
'remote_provision'
);
}
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
return $this->error( $user, 'remote_provision' );
}
$site_icon = get_site_icon_url();
/**
* Filters the Redirect URI returned by the remote_register XMLRPC method
*
* @param string $redirect_uri The Redirect URI
*
* @since 1.9.7
*/
$redirect_uri = apply_filters( 'jetpack_xmlrpc_remote_register_redirect_uri', admin_url() );
// Generate secrets.
$roles = new Roles();
$role = $roles->translate_user_to_role( $user );
$secrets = ( new Secrets() )->generate( 'authorize', $user->ID );
$response = array(
'jp_version' => Constants::get_constant( 'JETPACK__VERSION' ),
'redirect_uri' => $redirect_uri,
'user_id' => $user->ID,
'user_email' => $user->user_email,
'user_login' => $user->user_login,
'scope' => $this->connection->sign_role( $role, $user->ID ),
'secret' => $secrets['secret_1'],
'is_active' => $this->connection->has_connected_owner(),
);
if ( $site_icon ) {
$response['site_icon'] = $site_icon;
}
/**
* Filters the response of the remote_provision XMLRPC method
*
* @param array $response The response.
* @param array $request An array containing at minimum a nonce key and a local_username key.
* @param \WP_User $user The local authenticated user.
*
* @since 1.9.7
*/
$response = apply_filters( 'jetpack_remote_xmlrpc_provision_response', $response, $request, $user );
return $response;
}
/**
* Given an array containing a local user identifier and a nonce, will attempt to fetch and set
* an access token for the given user.
*
* @param array $request An array containing local_user and nonce keys at minimum.
* @param \IXR_Client $ixr_client The client object, optional.
* @return mixed
*/
public function remote_connect( $request, $ixr_client = false ) {
if ( $this->connection->has_connected_owner() ) {
return $this->error(
new WP_Error(
'already_connected',
__( 'Jetpack is already connected.', 'jetpack-connection' ),
400
),
'remote_connect'
);
}
$user = $this->fetch_and_verify_local_user( $request );
if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
return $this->error(
new WP_Error(
'input_error',
__( 'Valid user is required.', 'jetpack-connection' ),
400
),
'remote_connect'
);
}
if ( empty( $request['nonce'] ) ) {
return $this->error(
new WP_Error(
'input_error',
__( 'A non-empty nonce must be supplied.', 'jetpack-connection' ),
400
),
'remote_connect'
);
}
if ( ! $ixr_client ) {
$ixr_client = new Jetpack_IXR_Client();
}
// TODO: move this query into the Tokens class?
$ixr_client->query(
'jetpack.getUserAccessToken',
array(
'nonce' => sanitize_text_field( $request['nonce'] ),
'external_user_id' => $user->ID,
)
);
$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
if ( empty( $token ) ) {
return $this->error(
new WP_Error(
'token_fetch_failed',
__( 'Failed to fetch user token from WordPress.com.', 'jetpack-connection' ),
400
),
'remote_connect'
);
}
$token = sanitize_text_field( $token );
( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
/**
* Hook fired at the end of the jetpack.remoteConnect XML-RPC callback
*
* @since 1.9.7
*/
do_action( 'jetpack_remote_connect_end' );
return $this->connection->has_connected_owner();
}
/**
* Getter for the local user to act as.
*
* @param array $request the current request data.
*/
private function fetch_and_verify_local_user( $request ) {
if ( empty( $request['local_user'] ) ) {
return $this->error(
new \WP_Error(
'local_user_missing',
__( 'The required "local_user" parameter is missing.', 'jetpack-connection' ),
400
),
'remote_provision'
);
}
// Local user is used to look up by login, email or ID.
$local_user_info = $request['local_user'];
return $this->get_user_by_anything( $local_user_info );
}
/**
* Gets the user object by its data.
*
* @param string $user_id can be any identifying user data.
*/
private function get_user_by_anything( $user_id ) {
$user = get_user_by( 'login', $user_id );
if ( ! $user ) {
$user = get_user_by( 'email', $user_id );
}
if ( ! $user ) {
$user = get_user_by( 'ID', $user_id );
}
return $user;
}
/**
* Possible error_codes:
*
* - verify_secret_1_missing
* - verify_secret_1_malformed
* - verify_secrets_missing: verification secrets are not found in database
* - verify_secrets_incomplete: verification secrets are only partially found in database
* - verify_secrets_expired: verification secrets have expired
* - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
* - state_missing: required parameter of state not found
* - state_malformed: state is not a digit
* - invalid_state: state in request does not match the stored state
*
* The 'authorize' and 'register' actions have additional error codes
*
* state_missing: a state ( user id ) was not supplied
* state_malformed: state is not the correct data type
* invalid_state: supplied state does not match the stored state
*
* @param array $params action An array of 3 parameters:
* [0]: string action. Possible values are `authorize`, `publicize` and `register`.
* [1]: string secret_1.
* [2]: int state.
* @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
*/
public function verify_action( $params ) {
$action = isset( $params[0] ) ? $params[0] : '';
$verify_secret = isset( $params[1] ) ? $params[1] : '';
$state = isset( $params[2] ) ? $params[2] : '';
$result = ( new Secrets() )->verify( $action, $verify_secret, $state );
if ( is_wp_error( $result ) ) {
return $this->error( $result );
}
return $result;
}
/**
* Wrapper for wp_authenticate( $username, $password );
*
* @return \WP_User|bool
*/
public function login() {
$this->connection->require_jetpack_authentication();
$user = wp_authenticate( 'username', 'password' );
if ( is_wp_error( $user ) ) {
if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
} else {
$this->error = $user;
}
return false;
} elseif ( ! $user ) { // Shouldn't happen.
$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
return false;
}
wp_set_current_user( $user->ID );
return $user;
}
/**
* Returns the current error as an \IXR_Error
*
* @param \WP_Error|\IXR_Error $error The error object, optional.
* @param string $event_name The event name.
* @param \WP_User $user The user object.
* @return bool|\IXR_Error
*/
public function error( $error = null, $event_name = null, $user = null ) {
if ( null !== $event_name ) {
// This action is documented in class.jetpack-xmlrpc-server.php.
do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
}
if ( $error !== null ) {
$this->error = $error;
}
if ( is_wp_error( $this->error ) ) {
$code = $this->error->get_error_data();
if ( ! $code ) {
$code = -10520;
}
$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
if ( ! class_exists( \IXR_Error::class ) ) {
require_once ABSPATH . WPINC . '/class-IXR.php';
}
return new \IXR_Error( $code, $message );
} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
return $this->error;
}
return false;
}
/* API Methods */
/**
* Just authenticates with the given Jetpack credentials.
*
* @return string A success string. The Jetpack plugin filters it and make it return the Jetpack plugin version.
*/
public function test_connection() {
/**
* Filters the successful response of the XMLRPC test_connection method
*
* @param string $response The response string.
*/
return apply_filters( 'jetpack_xmlrpc_test_connection_response', 'success' );
}
/**
* Test the API user code.
*
* @param array $args arguments identifying the test site.
*/
public function test_api_user_code( $args ) {
$client_id = (int) $args[0];
$user_id = (int) $args[1];
$nonce = (string) $args[2];
$verify = (string) $args[3];
if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
return false;
}
$user = get_user_by( 'id', $user_id );
if ( ! $user || is_wp_error( $user ) ) {
return false;
}
/* phpcs:ignore
debugging
error_log( "CLIENT: $client_id" );
error_log( "USER: $user_id" );
error_log( "NONCE: $nonce" );
error_log( "VERIFY: $verify" );
*/
$jetpack_token = ( new Tokens() )->get_access_token( $user_id );
$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
if ( ! $api_user_code ) {
return false;
}
$hmac = hash_hmac(
'md5',
json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
(object) array(
'client_id' => (int) $client_id,
'user_id' => (int) $user_id,
'nonce' => (string) $nonce,
'code' => (string) $api_user_code,
)
),
$jetpack_token->secret
);
if ( ! hash_equals( $hmac, $verify ) ) {
return false;
}
return $user_id;
}
/**
* Unlink a user from WordPress.com
*
* When the request is done without any parameter, this XMLRPC callback gets an empty array as input.
*
* If $user_id is not provided, it will try to disconnect the current logged in user. This will fail if called by the Master User.
*
* If $user_id is is provided, it will try to disconnect the informed user, even if it's the Master User.
*
* @param mixed $user_id The user ID to disconnect from this site.
*/
public function unlink_user( $user_id = array() ) {
$user_id = (int) $user_id;
if ( $user_id < 1 ) {
$user_id = null;
}
/**
* Fired when we want to log an event to the Jetpack event log.
*
* @since 1.7.0
* @since-jetpack 7.7.0
*
* @param string $code Unique name for the event.
* @param string $data Optional data about the event.
*/
do_action( 'jetpack_event_log', 'unlink' );
return $this->connection->disconnect_user(
$user_id,
(bool) $user_id
);
}
/**
* Returns the home URL and site URL for the current site which can be used on the WPCOM side for
* IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
* and the remote Jetpack site.
*
* @return array
*/
public function validate_urls_for_idc_mitigation() {
return array(
'home' => Urls::home_url(),
'siteurl' => Urls::site_url(),
);
}
/**
* Updates the attachment parent object.
*
* @param array $args attachment and parent identifiers.
*/
public function update_attachment_parent( $args ) {
$attachment_id = (int) $args[0];
$parent_id = (int) $args[1];
return wp_update_post(
array(
'ID' => $attachment_id,
'post_parent' => $parent_id,
)
);
}
/**
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
*
* Disconnect this blog from the connected wordpress.com account
*
* @deprecated since 1.25.0
* @see Jetpack_XMLRPC_Methods::disconnect_blog() in the Jetpack plugin
*
* @return boolean
*/
public function disconnect_blog() {
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::disconnect_blog()' );
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
return Jetpack_XMLRPC_Methods::disconnect_blog();
}
return false;
}
/**
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
*
* Returns what features are available. Uses the slug of the module files.
*
* @deprecated since 1.25.0
* @see Jetpack_XMLRPC_Methods::features_available() in the Jetpack plugin
*
* @return array
*/
public function features_available() {
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_available()' );
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
return Jetpack_XMLRPC_Methods::features_available();
}
return array();
}
/**
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
*
* Returns what features are enabled. Uses the slug of the modules files.
*
* @deprecated since 1.25.0
* @see Jetpack_XMLRPC_Methods::features_enabled() in the Jetpack plugin
*
* @return array
*/
public function features_enabled() {
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::features_enabled()' );
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
return Jetpack_XMLRPC_Methods::features_enabled();
}
return array();
}
/**
* Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
*
* Serve a JSON API request.
*
* @deprecated since 1.25.0
* @see Jetpack_XMLRPC_Methods::json_api() in the Jetpack plugin
*
* @param array $args request arguments.
*/
public function json_api( $args = array() ) {
_deprecated_function( __METHOD__, '1.25.0', 'Jetpack_XMLRPC_Methods::json_api()' );
if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
return Jetpack_XMLRPC_Methods::json_api( $args );
}
return array();
}
}

View File

@ -0,0 +1,488 @@
<?php
/**
* The Connection Client class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
/**
* The Client class that is used to connect to WordPress.com Jetpack API.
*/
class Client {
const WPCOM_JSON_API_VERSION = '1.1';
/**
* Makes an authorized remote request using Jetpack_Signature
*
* @param array $args the arguments for the remote request.
* @param array|String $body the request body.
* @return array|WP_Error WP HTTP response on success
*/
public static function remote_request( $args, $body = null ) {
if ( isset( $args['url'] ) ) {
/**
* Filters the remote request url.
*
* @since 1.30.12
*
* @param string The remote request url.
*/
$args['url'] = apply_filters( 'jetpack_remote_request_url', $args['url'] );
}
$result = self::build_signed_request( $args, $body );
if ( ! $result || is_wp_error( $result ) ) {
return $result;
}
$response = self::_wp_remote_request( $result['url'], $result['request'] );
/**
* Fired when the remote request response has been received.
*
* @since 1.30.8
*
* @param array|WP_Error The HTTP response.
*/
do_action( 'jetpack_received_remote_request_response', $response );
return $response;
}
/**
* Adds authorization signature to a remote request using Jetpack_Signature
*
* @param array $args the arguments for the remote request.
* @param array|String $body the request body.
* @return WP_Error|array {
* An array containing URL and request items.
*
* @type String $url The request URL.
* @type array $request Request arguments.
* }
*/
public static function build_signed_request( $args, $body = null ) {
add_filter(
'jetpack_constant_default_value',
__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
10,
2
);
$defaults = array(
'url' => '',
'user_id' => 0,
'blog_id' => 0,
'auth_location' => Constants::get_constant( 'JETPACK_CLIENT__AUTH_LOCATION' ),
'method' => 'POST',
'timeout' => 10,
'redirection' => 0,
'headers' => array(),
'stream' => false,
'filename' => null,
'sslverify' => true,
);
$args = wp_parse_args( $args, $defaults );
$args['blog_id'] = (int) $args['blog_id'];
if ( 'header' !== $args['auth_location'] ) {
$args['auth_location'] = 'query_string';
}
$token = ( new Tokens() )->get_access_token( $args['user_id'] );
if ( ! $token ) {
return new \WP_Error( 'missing_token' );
}
$method = strtoupper( $args['method'] );
$timeout = (int) $args['timeout'];
$redirection = $args['redirection'];
$stream = $args['stream'];
$filename = $args['filename'];
$sslverify = $args['sslverify'];
$request = compact( 'method', 'body', 'timeout', 'redirection', 'stream', 'filename', 'sslverify' );
@list( $token_key, $secret ) = explode( '.', $token->secret ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( empty( $token ) || empty( $secret ) ) {
return new \WP_Error( 'malformed_token' );
}
$token_key = sprintf(
'%s:%d:%d',
$token_key,
Constants::get_constant( 'JETPACK__API_VERSION' ),
$token->external_user_id
);
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
$timestamp = time() + $time_diff;
if ( function_exists( 'wp_generate_password' ) ) {
$nonce = wp_generate_password( 10, false );
} else {
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
}
// Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing.
if ( $body === null ) {
$body_hash = '';
} else {
// Allow arrays to be used in passing data.
$body_to_hash = $body;
if ( is_array( $body ) ) {
// We cast this to a new variable, because the array form of $body needs to be
// maintained so it can be passed into the request later on in the code.
if ( count( $body ) > 0 ) {
$body_to_hash = wp_json_encode( self::_stringify_data( $body ) );
} else {
$body_to_hash = '';
}
}
if ( ! is_string( $body_to_hash ) ) {
return new \WP_Error( 'invalid_body', 'Body is malformed.' );
}
$body_hash = base64_encode( sha1( $body_to_hash, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
$auth = array(
'token' => $token_key,
'timestamp' => $timestamp,
'nonce' => $nonce,
'body-hash' => $body_hash,
);
if ( false !== strpos( $args['url'], 'xmlrpc.php' ) ) {
$url_args = array(
'for' => 'jetpack',
'wpcom_blog_id' => \Jetpack_Options::get_option( 'id' ),
);
} else {
$url_args = array();
}
if ( 'header' !== $args['auth_location'] ) {
$url_args += $auth;
}
$url = add_query_arg( urlencode_deep( $url_args ), $args['url'] );
$signature = $jetpack_signature->sign_request( $token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false );
if ( ! $signature || is_wp_error( $signature ) ) {
return $signature;
}
// Send an Authorization header so various caches/proxies do the right thing.
$auth['signature'] = $signature;
$auth['version'] = Constants::get_constant( 'JETPACK__VERSION' );
$header_pieces = array();
foreach ( $auth as $key => $value ) {
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
}
$request['headers'] = array_merge(
$args['headers'],
array(
'Authorization' => 'X_JETPACK ' . join( ' ', $header_pieces ),
)
);
if ( 'header' !== $args['auth_location'] ) {
$url = add_query_arg( 'signature', rawurlencode( $signature ), $url );
}
return compact( 'url', 'request' );
}
/**
* Wrapper for wp_remote_request(). Turns off SSL verification for certain SSL errors.
* This is lame, but many, many, many hosts have misconfigured SSL.
*
* When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
* 1. a certificate error is found AND
* 2. not verifying the certificate works around the problem.
*
* The option is checked on each request.
*
* @internal
*
* @param String $url the request URL.
* @param array $args request arguments.
* @param Boolean $set_fallback whether to allow flagging this request to use a fallback certficate override.
* @return array|WP_Error WP HTTP response on success
*/
public static function _wp_remote_request( $url, $args, $set_fallback = false ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
$fallback = \Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
if ( false === $fallback ) {
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', 0 );
}
/**
* SSL verification (`sslverify`) for the JetpackClient remote request
* defaults to off, use this filter to force it on.
*
* Return `true` to ENABLE SSL verification, return `false`
* to DISABLE SSL verification.
*
* @since 1.7.0
* @since-jetpack 3.6.0
*
* @param bool Whether to force `sslverify` or not.
*/
if ( apply_filters( 'jetpack_client_verify_ssl_certs', false ) ) {
return wp_remote_request( $url, $args );
}
if ( (int) $fallback ) {
// We're flagged to fallback.
$args['sslverify'] = false;
}
$response = wp_remote_request( $url, $args );
if (
! $set_fallback // We're not allowed to set the flag on this request, so whatever happens happens.
||
isset( $args['sslverify'] ) && ! $args['sslverify'] // No verification - no point in doing it again.
||
! is_wp_error( $response ) // Let it ride.
) {
self::set_time_diff( $response, $set_fallback );
return $response;
}
// At this point, we're not flagged to fallback and we are allowed to set the flag on this request.
$message = $response->get_error_message();
// Is it an SSL Certificate verification error?
if (
false === strpos( $message, '14090086' ) // OpenSSL SSL3 certificate error.
&&
false === strpos( $message, '1407E086' ) // OpenSSL SSL2 certificate error.
&&
false === strpos( $message, 'error setting certificate verify locations' ) // cURL CA bundle not found.
&&
false === strpos( $message, 'Peer certificate cannot be authenticated with' ) // cURL CURLE_SSL_CACERT: CA bundle found, but not helpful
// Different versions of curl have different error messages
// this string should catch them all.
&&
false === strpos( $message, 'Problem with the SSL CA cert' ) // cURL CURLE_SSL_CACERT_BADFILE: probably access rights.
) {
// No, it is not.
return $response;
}
// Redo the request without SSL certificate verification.
$args['sslverify'] = false;
$response = wp_remote_request( $url, $args );
if ( ! is_wp_error( $response ) ) {
// The request went through this time, flag for future fallbacks.
\Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', time() );
self::set_time_diff( $response, $set_fallback );
}
return $response;
}
/**
* Sets the time difference for correct signature computation.
*
* @param HTTP_Response $response the response object.
* @param Boolean $force_set whether to force setting the time difference.
*/
public static function set_time_diff( &$response, $force_set = false ) {
$code = wp_remote_retrieve_response_code( $response );
// Only trust the Date header on some responses.
if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
return;
}
$date = wp_remote_retrieve_header( $response, 'date' );
if ( ! $date ) {
return;
}
$time = (int) strtotime( $date );
if ( 0 >= $time ) {
return;
}
$time_diff = $time - time();
if ( $force_set ) { // During register.
\Jetpack_Options::update_option( 'time_diff', $time_diff );
} else { // Otherwise.
$old_diff = \Jetpack_Options::get_option( 'time_diff' );
if ( false === $old_diff || abs( $time_diff - (int) $old_diff ) > 10 ) {
\Jetpack_Options::update_option( 'time_diff', $time_diff );
}
}
}
/**
* Validate and build arguments for a WordPress.com REST API request.
*
* @param string $path REST API path.
* @param string $version REST API version. Default is `2`.
* @param array $args Arguments to {@see WP_Http}. Default is `array()`.
* @param string $base_api_path REST API root. Default is `wpcom`.
*
* @return array|WP_Error $response Response data, else {@see WP_Error} on failure.
*/
public static function validate_args_for_wpcom_json_api_request(
$path,
$version = '2',
$args = array(),
$base_api_path = 'wpcom'
) {
$base_api_path = trim( $base_api_path, '/' );
$version = ltrim( $version, 'v' );
$path = ltrim( $path, '/' );
$filtered_args = array_intersect_key(
$args,
array(
'headers' => 'array',
'method' => 'string',
'timeout' => 'int',
'redirection' => 'int',
'stream' => 'boolean',
'filename' => 'string',
'sslverify' => 'boolean',
)
);
// Use GET by default whereas `remote_request` uses POST.
$request_method = isset( $filtered_args['method'] ) ? strtoupper( $filtered_args['method'] ) : 'GET';
$url = sprintf(
'%s/%s/v%s/%s',
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
$base_api_path,
$version,
$path
);
$validated_args = array_merge(
$filtered_args,
array(
'url' => $url,
'method' => $request_method,
)
);
return $validated_args;
}
/**
* Queries the WordPress.com REST API with a user token.
*
* @param string $path REST API path.
* @param string $version REST API version. Default is `2`.
* @param array $args Arguments to {@see WP_Http}. Default is `array()`.
* @param string $body Body passed to {@see WP_Http}. Default is `null`.
* @param string $base_api_path REST API root. Default is `wpcom`.
*
* @return array|WP_Error $response Response data, else {@see WP_Error} on failure.
*/
public static function wpcom_json_api_request_as_user(
$path,
$version = '2',
$args = array(),
$body = null,
$base_api_path = 'wpcom'
) {
$args = self::validate_args_for_wpcom_json_api_request( $path, $version, $args, $base_api_path );
$args['user_id'] = get_current_user_id();
if ( isset( $body ) && ! isset( $args['headers'] ) && in_array( $args['method'], array( 'POST', 'PUT', 'PATCH' ), true ) ) {
$args['headers'] = array( 'Content-Type' => 'application/json' );
}
if ( isset( $body ) && ! is_string( $body ) ) {
$body = wp_json_encode( $body );
}
return self::remote_request( $args, $body );
}
/**
* Query the WordPress.com REST API using the blog token
*
* @param String $path The API endpoint relative path.
* @param String $version The API version.
* @param array $args Request arguments.
* @param String $body Request body.
* @param String $base_api_path (optional) the API base path override, defaults to 'rest'.
* @return array|WP_Error $response Data.
*/
public static function wpcom_json_api_request_as_blog(
$path,
$version = self::WPCOM_JSON_API_VERSION,
$args = array(),
$body = null,
$base_api_path = 'rest'
) {
$validated_args = self::validate_args_for_wpcom_json_api_request( $path, $version, $args, $base_api_path );
$validated_args['blog_id'] = (int) \Jetpack_Options::get_option( 'id' );
// For Simple sites get the response directly without any HTTP requests.
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
add_filter( 'is_jetpack_authorized_for_site', '__return_true' );
require_lib( 'wpcom-api-direct' );
return \WPCOM_API_Direct::do_request( $validated_args, $body );
}
return self::remote_request( $validated_args, $body );
}
/**
* Takes an array or similar structure and recursively turns all values into strings. This is used to
* make sure that body hashes are made ith the string version, which is what will be seen after a
* server pulls up the data in the $_POST array.
*
* @param array|Mixed $data the data that needs to be stringified.
*
* @return array|string
*/
public static function _stringify_data( $data ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
// Booleans are special, lets just makes them and explicit 1/0 instead of the 0 being an empty string.
if ( is_bool( $data ) ) {
return $data ? '1' : '0';
}
// Cast objects into arrays.
if ( is_object( $data ) ) {
$data = (array) $data;
}
// Non arrays at this point should be just converted to strings.
if ( ! is_array( $data ) ) {
return (string) $data;
}
foreach ( $data as &$value ) {
$value = self::_stringify_data( $value );
}
return $data;
}
}

View File

@ -0,0 +1,690 @@
<?php
/**
* The Jetpack Connection error class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The Jetpack Connection Errors that handles errors
*
* This class handles the following workflow:
*
* 1. A XML-RCP request with an invalid signature triggers a error
* 2. Applies a gate to only process each error code once an hour to avoid overflow
* 3. It stores the error on the database, but we don't know yet if this is a valid error, because
* we can't confirm it came from WP.com.
* 4. It encrypts the error details and send it to thw wp.com server
* 5. wp.com checks it and, if valid, sends a new request back to this site using the verify_xml_rpc_error REST endpoint
* 6. This endpoint add this error to the Verified errors in the database
* 7. Triggers a workflow depending on the error (display user an error message, do some self healing, etc.)
*
* Errors are stored in the database as options in the following format:
*
* [
* $error_code => [
* $user_id => [
* $error_details
* ]
* ]
* ]
*
* For each error code we store a maximum of 5 errors for 5 different user ids.
*
* An user ID can be
* * 0 for blog tokens
* * positive integer for user tokens
* * 'invalid' for malformed tokens
*
* @since 1.14.2
*/
class Error_Handler {
/**
* The name of the option that stores the errors
*
* @since 1.14.2
*
* @var string
*/
const STORED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_errors';
/**
* The name of the option that stores the errors
*
* @since 1.14.2
*
* @var string
*/
const STORED_VERIFIED_ERRORS_OPTION = 'jetpack_connection_xmlrpc_verified_errors';
/**
* The prefix of the transient that controls the gate for each error code
*
* @since 1.14.2
*
* @var string
*/
const ERROR_REPORTING_GATE = 'jetpack_connection_error_reporting_gate_';
/**
* Time in seconds a test should live in the database before being discarded
*
* @since 1.14.2
*/
const ERROR_LIFE_TIME = DAY_IN_SECONDS;
/**
* The error code for event tracking purposes.
* If there are many, only the first error code will be tracked.
*
* @var string
*/
private $error_code;
/**
* List of known errors. Only error codes in this list will be handled
*
* @since 1.14.2
*
* @var array
*/
public $known_errors = array(
'malformed_token',
'malformed_user_id',
'unknown_user',
'no_user_tokens',
'empty_master_user_option',
'no_token_for_user',
'token_malformed',
'user_id_mismatch',
'no_possible_tokens',
'no_valid_user_token',
'no_valid_blog_token',
'unknown_token',
'could_not_sign',
'invalid_scheme',
'invalid_secret',
'invalid_token',
'token_mismatch',
'invalid_body',
'invalid_signature',
'invalid_body_hash',
'invalid_nonce',
'signature_mismatch',
);
/**
* Holds the instance of this singleton class
*
* @since 1.14.2
*
* @var Error_Handler $instance
*/
public static $instance = null;
/**
* Initialize instance, hookds and load verified errors handlers
*
* @since 1.14.2
*/
private function __construct() {
defined( 'JETPACK__ERRORS_PUBLIC_KEY' ) || define( 'JETPACK__ERRORS_PUBLIC_KEY', 'KdZY80axKX+nWzfrOcizf0jqiFHnrWCl9X8yuaClKgM=' );
add_action( 'rest_api_init', array( $this, 'register_verify_error_endpoint' ) );
$this->handle_verified_errors();
// If the site gets reconnected, clear errors.
add_action( 'jetpack_site_registered', array( $this, 'delete_all_errors' ) );
add_action( 'jetpack_get_site_data_success', array( $this, 'delete_all_errors' ) );
add_filter( 'jetpack_connection_disconnect_site_wpcom', array( $this, 'delete_all_errors_and_return_unfiltered_value' ) );
add_filter( 'jetpack_connection_delete_all_tokens', array( $this, 'delete_all_errors_and_return_unfiltered_value' ) );
add_action( 'jetpack_unlinked_user', array( $this, 'delete_all_errors' ) );
add_action( 'jetpack_updated_user_token', array( $this, 'delete_all_errors' ) );
}
/**
* Gets the list of verified errors and act upon them
*
* @since 1.14.2
*
* @return void
*/
public function handle_verified_errors() {
$verified_errors = $this->get_verified_errors();
foreach ( array_keys( $verified_errors ) as $error_code ) {
switch ( $error_code ) {
case 'malformed_token':
case 'token_malformed':
case 'no_possible_tokens':
case 'no_valid_user_token':
case 'no_valid_blog_token':
case 'unknown_token':
case 'could_not_sign':
case 'invalid_token':
case 'token_mismatch':
case 'invalid_signature':
case 'signature_mismatch':
case 'no_user_tokens':
case 'no_token_for_user':
add_action( 'admin_notices', array( $this, 'generic_admin_notice_error' ) );
add_action( 'react_connection_errors_initial_state', array( $this, 'jetpack_react_dashboard_error' ) );
$this->error_code = $error_code;
// Since we are only generically handling errors, we don't need to trigger error messages for each one of them.
break 2;
}
}
}
/**
* Gets the instance of this singleton class
*
* @since 1.14.2
*
* @return Error_Handler $instance
*/
public static function get_instance() {
if ( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Keep track of a connection error that was encountered
*
* @since 1.14.2
*
* @param \WP_Error $error the error object.
* @param boolean $force Force the report, even if should_report_error is false.
* @return void
*/
public function report_error( \WP_Error $error, $force = false ) {
if ( in_array( $error->get_error_code(), $this->known_errors, true ) && $this->should_report_error( $error ) || $force ) {
$stored_error = $this->store_error( $error );
if ( $stored_error ) {
$this->send_error_to_wpcom( $stored_error );
}
}
}
/**
* Checks the status of the gate
*
* This protects the site (and WPCOM) against over loads.
*
* @since 1.14.2
*
* @param \WP_Error $error the error object.
* @return boolean $should_report True if gate is open and the error should be reported.
*/
public function should_report_error( \WP_Error $error ) {
if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
return true;
}
/**
* Whether to bypass the gate for XML-RPC error handling
*
* By default, we only process XML-RPC errors once an hour for each error code.
* This is done to avoid overflows. If you need to disable this gate, you can set this variable to true.
*
* This filter is useful for unit testing
*
* @since 1.14.2
*
* @param boolean $bypass_gate whether to bypass the gate. Default is false, do not bypass.
*/
$bypass_gate = apply_filters( 'jetpack_connection_bypass_error_reporting_gate', false );
if ( true === $bypass_gate ) {
return true;
}
$transient = self::ERROR_REPORTING_GATE . $error->get_error_code();
if ( get_transient( $transient ) ) {
return false;
}
set_transient( $transient, true, HOUR_IN_SECONDS );
return true;
}
/**
* Stores the error in the database so we know there is an issue and can inform the user
*
* @since 1.14.2
*
* @param \WP_Error $error the error object.
* @return boolean|array False if stored errors were not updated and the error array if it was successfully stored.
*/
public function store_error( \WP_Error $error ) {
$stored_errors = $this->get_stored_errors();
$error_array = $this->wp_error_to_array( $error );
$error_code = $error->get_error_code();
$user_id = $error_array['user_id'];
if ( ! isset( $stored_errors[ $error_code ] ) || ! is_array( $stored_errors[ $error_code ] ) ) {
$stored_errors[ $error_code ] = array();
}
$stored_errors[ $error_code ][ $user_id ] = $error_array;
// Let's store a maximum of 5 different user ids for each error code.
if ( count( $stored_errors[ $error_code ] ) > 5 ) {
// array_shift will destroy keys here because they are numeric, so manually remove first item.
$keys = array_keys( $stored_errors[ $error_code ] );
unset( $stored_errors[ $error_code ][ $keys[0] ] );
}
if ( update_option( self::STORED_ERRORS_OPTION, $stored_errors ) ) {
return $error_array;
}
return false;
}
/**
* Converts a WP_Error object in the array representation we store in the database
*
* @since 1.14.2
*
* @param \WP_Error $error the error object.
* @return boolean|array False if error is invalid or the error array
*/
public function wp_error_to_array( \WP_Error $error ) {
$data = $error->get_error_data();
if ( ! isset( $data['signature_details'] ) || ! is_array( $data['signature_details'] ) ) {
return false;
}
$data = $data['signature_details'];
if ( ! isset( $data['token'] ) || empty( $data['token'] ) ) {
return false;
}
$user_id = $this->get_user_id_from_token( $data['token'] );
$error_array = array(
'error_code' => $error->get_error_code(),
'user_id' => $user_id,
'error_message' => $error->get_error_message(),
'error_data' => $data,
'timestamp' => time(),
'nonce' => wp_generate_password( 10, false ),
);
return $error_array;
}
/**
* Sends the error to WP.com to be verified
*
* @since 1.14.2
*
* @param array $error_array The array representation of the error as it is stored in the database.
* @return bool
*/
public function send_error_to_wpcom( $error_array ) {
$blog_id = \Jetpack_Options::get_option( 'id' );
$encrypted_data = $this->encrypt_data_to_wpcom( $error_array );
if ( false === $encrypted_data ) {
return false;
}
$args = array(
'body' => array(
'error_data' => $encrypted_data,
),
);
// send encrypted data to WP.com Public-API v2.
wp_remote_post( "https://public-api.wordpress.com/wpcom/v2/sites/{$blog_id}/jetpack-report-error/", $args );
return true;
}
/**
* Encrypt data to be sent over to WP.com
*
* @since 1.14.2
*
* @param array|string $data the data to be encoded.
* @return boolean|string The encoded string on success, false on failure
*/
public function encrypt_data_to_wpcom( $data ) {
try {
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$encrypted_data = base64_encode( sodium_crypto_box_seal( wp_json_encode( $data ), base64_decode( JETPACK__ERRORS_PUBLIC_KEY ) ) );
// phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
// phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
} catch ( \SodiumException $e ) {
// error encrypting data.
return false;
}
return $encrypted_data;
}
/**
* Extracts the user ID from a token
*
* @since 1.14.2
*
* @param string $token the token used to make the xml-rpc request.
* @return string $the user id or `invalid` if user id not present.
*/
public function get_user_id_from_token( $token ) {
$parsed_token = explode( ':', wp_unslash( $token ) );
if ( isset( $parsed_token[2] ) && ctype_digit( $parsed_token[2] ) ) {
$user_id = $parsed_token[2];
} else {
$user_id = 'invalid';
}
return $user_id;
}
/**
* Gets the reported errors stored in the database
*
* @since 1.14.2
*
* @return array $errors
*/
public function get_stored_errors() {
$stored_errors = get_option( self::STORED_ERRORS_OPTION );
if ( ! is_array( $stored_errors ) ) {
$stored_errors = array();
}
$stored_errors = $this->garbage_collector( $stored_errors );
return $stored_errors;
}
/**
* Gets the verified errors stored in the database
*
* @since 1.14.2
*
* @return array $errors
*/
public function get_verified_errors() {
$verified_errors = get_option( self::STORED_VERIFIED_ERRORS_OPTION );
if ( ! is_array( $verified_errors ) ) {
$verified_errors = array();
}
$verified_errors = $this->garbage_collector( $verified_errors );
return $verified_errors;
}
/**
* Removes expired errors from the array
*
* This method is called by get_stored_errors and get_verified errors and filters their result
* Whenever a new error is stored to the database or verified, this will be triggered and the
* expired error will be permantently removed from the database
*
* @since 1.14.2
*
* @param array $errors array of errors as stored in the database.
* @return array
*/
private function garbage_collector( $errors ) {
foreach ( $errors as $error_code => $users ) {
foreach ( $users as $user_id => $error ) {
if ( self::ERROR_LIFE_TIME < time() - (int) $error['timestamp'] ) {
unset( $errors[ $error_code ][ $user_id ] );
}
}
}
// Clear empty error codes.
$errors = array_filter(
$errors,
function ( $user_errors ) {
return ! empty( $user_errors );
}
);
return $errors;
}
/**
* Delete all stored and verified errors from the database
*
* @since 1.14.2
*
* @return void
*/
public function delete_all_errors() {
$this->delete_stored_errors();
$this->delete_verified_errors();
}
/**
* Delete all stored and verified errors from the database and returns unfiltered value
*
* This is used to hook into a couple of filters that expect true to not short circuit the disconnection flow
*
* @since 8.9.0
*
* @param mixed $check The input sent by the filter.
* @return boolean
*/
public function delete_all_errors_and_return_unfiltered_value( $check ) {
$this->delete_all_errors();
return $check;
}
/**
* Delete the reported errors stored in the database
*
* @since 1.14.2
*
* @return boolean True, if option is successfully deleted. False on failure.
*/
public function delete_stored_errors() {
return delete_option( self::STORED_ERRORS_OPTION );
}
/**
* Delete the verified errors stored in the database
*
* @since 1.14.2
*
* @return boolean True, if option is successfully deleted. False on failure.
*/
public function delete_verified_errors() {
return delete_option( self::STORED_VERIFIED_ERRORS_OPTION );
}
/**
* Gets an error based on the nonce
*
* Receives a nonce and finds the related error.
*
* @since 1.14.2
*
* @param string $nonce The nonce created for the error we want to get.
* @return null|array Returns the error array representation or null if error not found.
*/
public function get_error_by_nonce( $nonce ) {
$errors = $this->get_stored_errors();
foreach ( $errors as $user_group ) {
foreach ( $user_group as $error ) {
if ( $error['nonce'] === $nonce ) {
return $error;
}
}
}
return null;
}
/**
* Adds an error to the verified error list
*
* @since 1.14.2
*
* @param array $error The error array, as it was saved in the unverified errors list.
* @return void
*/
public function verify_error( $error ) {
$verified_errors = $this->get_verified_errors();
$error_code = $error['error_code'];
$user_id = $error['user_id'];
if ( ! isset( $verified_errors[ $error_code ] ) ) {
$verified_errors[ $error_code ] = array();
}
$verified_errors[ $error_code ][ $user_id ] = $error;
update_option( self::STORED_VERIFIED_ERRORS_OPTION, $verified_errors );
}
/**
* Register REST API end point for error hanlding.
*
* @since 1.14.2
*
* @return void
*/
public function register_verify_error_endpoint() {
register_rest_route(
'jetpack/v4',
'/verify_xmlrpc_error',
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'verify_xml_rpc_error' ),
'permission_callback' => '__return_true',
'args' => array(
'nonce' => array(
'required' => true,
'type' => 'string',
),
),
)
);
}
/**
* Handles verification that a xml rpc error is legit and came from WordPres.com
*
* @since 1.14.2
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return boolean
*/
public function verify_xml_rpc_error( \WP_REST_Request $request ) {
$error = $this->get_error_by_nonce( $request['nonce'] );
if ( $error ) {
$this->verify_error( $error );
return new \WP_REST_Response( true, 200 );
}
return new \WP_REST_Response( false, 200 );
}
/**
* Prints a generic error notice for all connection errors
*
* @since 8.9.0
*
* @return void
*/
public function generic_admin_notice_error() {
// do not add admin notice to the jetpack dashboard.
global $pagenow;
if ( 'admin.php' === $pagenow || isset( $_GET['page'] ) && 'jetpack' === $_GET['page'] ) { // phpcs:ignore
return;
}
if ( ! current_user_can( 'jetpack_connect' ) ) {
return;
}
/**
* Filters the message to be displayed in the admin notices area when there's a xmlrpc error.
*
* By default we don't display any errors.
*
* Return an empty value to disable the message.
*
* @since 8.9.0
*
* @param string $message The error message.
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
*/
$message = apply_filters( 'jetpack_connection_error_notice_message', '', $this->get_verified_errors() );
/**
* Fires inside the admin_notices hook just before displaying the error message for a broken connection.
*
* If you want to disable the default message from being displayed, return an emtpy value in the jetpack_connection_error_notice_message filter.
*
* @since 8.9.0
*
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
*/
do_action( 'jetpack_connection_error_notice', $this->get_verified_errors() );
if ( empty( $message ) ) {
return;
}
?>
<div class="notice notice-error is-dismissible jetpack-message jp-connect" style="display:block !important;">
<p><?php echo esc_html( $message ); ?></p>
</div>
<?php
}
/**
* Adds the error message to the Jetpack React Dashboard
*
* @since 8.9.0
*
* @param array $errors The array of errors. See Automattic\Jetpack\Connection\Error_Handler for details on the array structure.
* @return array
*/
public function jetpack_react_dashboard_error( $errors ) {
$errors[] = array(
'code' => 'xmlrpc_error',
'message' => __( 'Your connection with WordPress.com seems to be broken. If you\'re experiencing issues, please try reconnecting.', 'jetpack-connection' ),
'action' => 'reconnect',
'data' => array( 'api_error_code' => $this->error_code ),
);
return $errors;
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* Jetpack Heartbeat package.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
use Jetpack_Options;
use WP_CLI;
/**
* Heartbeat sends a batch of stats to wp.com once a day
*/
class Heartbeat {
/**
* Holds the singleton instance of this class
*
* @since 1.0.0
* @since-jetpack 2.3.3
* @var Heartbeat
*/
private static $instance = false;
/**
* Cronjob identifier
*
* @var string
*/
private $cron_name = 'jetpack_v2_heartbeat';
/**
* Singleton
*
* @since 1.0.0
* @since-jetpack 2.3.3
* @static
* @return Heartbeat
*/
public static function init() {
if ( ! self::$instance ) {
self::$instance = new Heartbeat();
}
return self::$instance;
}
/**
* Constructor for singleton
*
* @since 1.0.0
* @since-jetpack 2.3.3
*/
private function __construct() {
// Schedule the task.
add_action( $this->cron_name, array( $this, 'cron_exec' ) );
if ( ! wp_next_scheduled( $this->cron_name ) ) {
// Deal with the old pre-3.0 weekly one.
$timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
}
wp_schedule_event( time(), 'daily', $this->cron_name );
}
add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( __CLASS__, 'jetpack_xmlrpc_methods' ) );
if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'jetpack-heartbeat', array( $this, 'cli_callback' ) );
}
}
/**
* Method that gets executed on the wp-cron call
*
* @since 1.0.0
* @since-jetpack 2.3.3
* @global string $wp_version
*/
public function cron_exec() {
$a8c_mc_stats = new A8c_Mc_Stats();
/*
* This should run daily. Figuring in for variances in
* WP_CRON, don't let it run more than every 23 hours at most.
*
* i.e. if it ran less than 23 hours ago, fail out.
*/
$last = (int) Jetpack_Options::get_option( 'last_heartbeat' );
if ( $last && ( $last + DAY_IN_SECONDS - HOUR_IN_SECONDS > time() ) ) {
return;
}
/*
* Check for an identity crisis
*
* If one exists:
* - Bump stat for ID crisis
* - Email site admin about potential ID crisis
*/
// Coming Soon!
foreach ( self::generate_stats_array( 'v2-' ) as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $v ) {
$a8c_mc_stats->add( $key, (string) $v );
}
} else {
$a8c_mc_stats->add( $key, (string) $value );
}
}
Jetpack_Options::update_option( 'last_heartbeat', time() );
$a8c_mc_stats->do_server_side_stats();
/**
* Fires when we synchronize all registered options on heartbeat.
*
* @since 3.3.0
*/
do_action( 'jetpack_heartbeat' );
}
/**
* Generates heartbeat stats data.
*
* @param string $prefix Prefix to add before stats identifier.
*
* @return array The stats array.
*/
public static function generate_stats_array( $prefix = '' ) {
/**
* This filter is used to build the array of stats that are bumped once a day by Jetpack Heartbeat.
*
* Filter the array and add key => value pairs where
* * key is the stat group name
* * value is the stat name.
*
* Example:
* add_filter( 'jetpack_heartbeat_stats_array', function( $stats ) {
* $stats['is-https'] = is_ssl() ? 'https' : 'http';
* });
*
* This will bump the stats for the 'is-https/https' or 'is-https/http' stat.
*
* @param array $stats The stats to be filtered.
* @param string $prefix The prefix that will automatically be added at the begining at each stat group name.
*/
$stats = apply_filters( 'jetpack_heartbeat_stats_array', array(), $prefix );
$return = array();
// Apply prefix to stats.
foreach ( $stats as $stat => $value ) {
$return[ "$prefix$stat" ] = $value;
}
return $return;
}
/**
* Registers jetpack.getHeartbeatData xmlrpc method
*
* @param array $methods The list of methods to be filtered.
* @return array $methods
*/
public static function jetpack_xmlrpc_methods( $methods ) {
$methods['jetpack.getHeartbeatData'] = array( __CLASS__, 'xmlrpc_data_response' );
return $methods;
}
/**
* Handles the response for the jetpack.getHeartbeatData xmlrpc method
*
* @param array $params The parameters received in the request.
* @return array $params all the stats that heartbeat handles.
*/
public static function xmlrpc_data_response( $params = array() ) {
// The WordPress XML-RPC server sets a default param of array()
// if no argument is passed on the request and the method handlers get this array in $params.
// generate_stats_array() needs a string as first argument.
$params = empty( $params ) ? '' : $params;
return self::generate_stats_array( $params );
}
/**
* Clear scheduled events
*
* @return void
*/
public function deactivate() {
// Deal with the old pre-3.0 weekly one.
$timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
}
$timestamp = wp_next_scheduled( $this->cron_name );
wp_unschedule_event( $timestamp, $this->cron_name );
}
/**
* Interact with the Heartbeat
*
* ## OPTIONS
*
* inspect (default): Gets the list of data that is going to be sent in the heartbeat and the date/time of the last heartbeat
*
* @param array $args Arguments passed via CLI.
*
* @return void
*/
public function cli_callback( $args ) {
$allowed_args = array(
'inspect',
);
if ( isset( $args[0] ) && ! in_array( $args[0], $allowed_args, true ) ) {
/* translators: %s is a command like "prompt" */
WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack-connection' ), $args[0] ) );
}
$stats = self::generate_stats_array();
$formatted_stats = array();
foreach ( $stats as $stat_name => $bin ) {
$formatted_stats[] = array(
'Stat name' => $stat_name,
'Bin' => $bin,
);
}
WP_CLI\Utils\format_items( 'table', $formatted_stats, array( 'Stat name', 'Bin' ) );
$last_heartbeat = Jetpack_Options::get_option( 'last_heartbeat' );
if ( $last_heartbeat ) {
$last_date = gmdate( 'Y-m-d H:i:s', $last_heartbeat );
/* translators: %s is the full datetime of the last heart beat e.g. 2020-01-01 12:21:23 */
WP_CLI::line( sprintf( __( 'Last heartbeat sent at: %s', 'jetpack-connection' ), $last_date ) );
}
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* The React initial state.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Status;
/**
* The React initial state.
*/
class Initial_State {
/**
* Whether the initial state was already rendered
*
* @var boolean
*/
private static $rendered = false;
/**
* Get the initial state data.
*
* @return array
*/
private static function get_data() {
global $wp_version;
return array(
'apiRoot' => esc_url_raw( rest_url() ),
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'connectionStatus' => REST_Connector::connection_status( false ),
'userConnectionData' => REST_Connector::get_user_connection_data( false ),
'connectedPlugins' => REST_Connector::get_connection_plugins( false ),
'wpVersion' => $wp_version,
'siteSuffix' => ( new Status() )->get_site_suffix(),
);
}
/**
* Render the initial state into a JavaScript variable.
*
* @return string
*/
public static function render() {
if ( self::$rendered ) {
return null;
}
self::$rendered = true;
return 'var JP_CONNECTION_INITIAL_STATE=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( self::get_data() ) ) . '"));';
}
}

View File

@ -0,0 +1,213 @@
<?php
/**
* The nonce handler.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The nonce handler.
*/
class Nonce_Handler {
/**
* How long the scheduled cleanup can run (in seconds).
* Can be modified using the filter `jetpack_connection_nonce_scheduled_cleanup_limit`.
*/
const SCHEDULED_CLEANUP_TIME_LIMIT = 5;
/**
* How many nonces should be removed per batch during the `clean_all()` run.
*/
const CLEAN_ALL_LIMIT_PER_BATCH = 1000;
/**
* Nonce lifetime in seconds.
*/
const LIFETIME = HOUR_IN_SECONDS;
/**
* The nonces used during the request are stored here to keep them valid.
* The property is static to keep the nonces accessible between the `Nonce_Handler` instances.
*
* @var array
*/
private static $nonces_used_this_request = array();
/**
* The database object.
*
* @var \wpdb
*/
private $db;
/**
* Initializing the object.
*/
public function __construct() {
global $wpdb;
$this->db = $wpdb;
}
/**
* Scheduling the WP-cron cleanup event.
*/
public function init_schedule() {
add_action( 'jetpack_clean_nonces', array( __CLASS__, 'clean_scheduled' ) );
if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
}
}
/**
* Reschedule the WP-cron cleanup event to make it start sooner.
*/
public function reschedule() {
wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
}
/**
* Adds a used nonce to a list of known nonces.
*
* @param int $timestamp the current request timestamp.
* @param string $nonce the nonce value.
*
* @return bool whether the nonce is unique or not.
*/
public function add( $timestamp, $nonce ) {
if ( isset( static::$nonces_used_this_request[ "$timestamp:$nonce" ] ) ) {
return static::$nonces_used_this_request[ "$timestamp:$nonce" ];
}
// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp and $nonce.
$timestamp = (int) $timestamp;
$nonce = esc_sql( $nonce );
// Raw query so we can avoid races: add_option will also update.
$show_errors = $this->db->hide_errors();
// Running `try...finally` to make sure that we re-enable errors in case of an exception.
try {
$old_nonce = $this->db->get_row(
$this->db->prepare( "SELECT 1 FROM `{$this->db->options}` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
);
if ( $old_nonce === null ) {
$return = (bool) $this->db->query(
$this->db->prepare(
"INSERT INTO `{$this->db->options}` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
"jetpack_nonce_{$timestamp}_{$nonce}",
time(),
'no'
)
);
} else {
$return = false;
}
} finally {
$this->db->show_errors( $show_errors );
}
static::$nonces_used_this_request[ "$timestamp:$nonce" ] = $return;
return $return;
}
/**
* Removing all existing nonces, or at least as many as possible.
* Capped at 20 seconds to avoid breaking the site.
*
* @param int $cutoff_timestamp All nonces added before this timestamp will be removed.
* @param int $time_limit How long the cleanup can run (in seconds).
*
* @return true
*/
public function clean_all( $cutoff_timestamp = PHP_INT_MAX, $time_limit = 20 ) {
// phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
for ( $end_time = time() + $time_limit; time() < $end_time; ) {
$result = $this->delete( static::CLEAN_ALL_LIMIT_PER_BATCH, $cutoff_timestamp );
if ( ! $result ) {
break;
}
}
return true;
}
/**
* Scheduled clean up of the expired nonces.
*/
public static function clean_scheduled() {
/**
* Adjust the time limit for the scheduled cleanup.
*
* @since 9.5.0
*
* @param int $time_limit How long the cleanup can run (in seconds).
*/
$time_limit = apply_filters( 'jetpack_connection_nonce_cleanup_runtime_limit', static::SCHEDULED_CLEANUP_TIME_LIMIT );
( new static() )->clean_all( time() - static::LIFETIME, $time_limit );
}
/**
* Delete the nonces.
*
* @param int $limit How many nonces to delete.
* @param null|int $cutoff_timestamp All nonces added before this timestamp will be removed.
*
* @return int|false Number of removed nonces, or `false` if nothing to remove (or in case of a database error).
*/
public function delete( $limit = 10, $cutoff_timestamp = null ) {
global $wpdb;
$ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT option_id FROM `{$wpdb->options}`"
. " WHERE `option_name` >= 'jetpack_nonce_' AND `option_name` < %s"
. ' LIMIT %d',
'jetpack_nonce_' . $cutoff_timestamp,
$limit
)
);
if ( ! is_array( $ids ) ) {
// There's an error and we can't proceed.
return false;
}
// Removing zeroes in case AUTO_INCREMENT of the options table is broken, and all ID's are zeroes.
$ids = array_filter( $ids );
if ( ! count( $ids ) ) {
// There's nothing to remove.
return false;
}
$ids_fill = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
$args = $ids;
$args[] = 'jetpack_nonce_%';
// The Code Sniffer is unable to understand what's going on...
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
return $wpdb->query( $wpdb->prepare( "DELETE FROM `{$wpdb->options}` WHERE `option_id` IN ( {$ids_fill} ) AND option_name LIKE %s", $args ) );
}
/**
* Clean the cached nonces valid during the current request, therefore making them invalid.
*
* @return bool
*/
public static function invalidate_request_nonces() {
static::$nonces_used_this_request = array();
return true;
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* The Package_Version_Tracker class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The Package_Version_Tracker class.
*/
class Package_Version_Tracker {
const PACKAGE_VERSION_OPTION = 'jetpack_package_versions';
/**
* The cache key for storing a failed request to update remote package versions.
* The caching logic is that when a failed request occurs, we cache it temporarily
* with a set expiration time.
* Only after the key has expired, we'll be able to repeat a remote request.
* This also implies that the cached value is redundant, however we chose the datetime
* of the failed request to avoid using booleans.
*/
const CACHED_FAILED_REQUEST_KEY = 'jetpack_failed_update_remote_package_versions';
/**
* The min time difference in seconds for attempting to
* update remote tracked package versions after a failed remote request.
*/
const CACHED_FAILED_REQUEST_EXPIRATION = 1 * HOUR_IN_SECONDS;
/**
* Uses the jetpack_package_versions filter to obtain the package versions from packages that need
* version tracking. If the package versions have changed, updates the option and notifies WPCOM.
*/
public function maybe_update_package_versions() {
/**
* Obtains the package versions.
*
* @since 1.30.2
*
* @param array An associative array of Jetpack package slugs and their corresponding versions as key/value pairs.
*/
$filter_versions = apply_filters( 'jetpack_package_versions', array() );
if ( ! is_array( $filter_versions ) ) {
return;
}
$option_versions = get_option( self::PACKAGE_VERSION_OPTION, array() );
foreach ( $filter_versions as $package => $version ) {
if ( ! is_string( $package ) || ! is_string( $version ) ) {
unset( $filter_versions[ $package ] );
}
}
if ( ! is_array( $option_versions )
|| count( array_diff_assoc( $filter_versions, $option_versions ) )
|| count( array_diff_assoc( $option_versions, $filter_versions ) )
) {
$this->update_package_versions_option( $filter_versions );
}
}
/**
* Updates the package versions:
* - Sends the updated package versions to wpcom.
* - Updates the 'jetpack_package_versions' option.
*
* @param array $package_versions The package versions.
*/
protected function update_package_versions_option( $package_versions ) {
$connection = new Manager();
if ( ! $connection->is_connected() ) {
return;
}
$site_id = \Jetpack_Options::get_option( 'id' );
$last_failed_attempt_within_hour = get_transient( self::CACHED_FAILED_REQUEST_KEY );
if ( $last_failed_attempt_within_hour ) {
return;
}
$body = wp_json_encode(
array(
'package_versions' => $package_versions,
)
);
$response = Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/jetpack-package-versions', $site_id ),
'2',
array(
'headers' => array( 'content-type' => 'application/json' ),
'method' => 'POST',
),
$body,
'wpcom'
);
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
update_option( self::PACKAGE_VERSION_OPTION, $package_versions );
} else {
set_transient( self::CACHED_FAILED_REQUEST_KEY, time(), self::CACHED_FAILED_REQUEST_EXPIRATION );
}
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* The Package_Version class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The Package_Version class.
*/
class Package_Version {
const PACKAGE_VERSION = '1.41.7';
const PACKAGE_SLUG = 'connection';
/**
* Adds the package slug and version to the package version tracker's data.
*
* @param array $package_versions The package version array.
*
* @return array The packge version array.
*/
public static function send_package_version_to_tracker( $package_versions ) {
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
return $package_versions;
}
}

View File

@ -0,0 +1,269 @@
<?php
/**
* Storage for plugin connection information.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use WP_Error;
/**
* The class serves a single purpose - to store the data which plugins use the connection, along with some auxiliary information.
*/
class Plugin_Storage {
const ACTIVE_PLUGINS_OPTION_NAME = 'jetpack_connection_active_plugins';
/**
* Options where disabled plugins were stored
*
* @deprecated since 1.39.0.
* @var string
*/
const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
/**
* Whether this class was configured for the first time or not.
*
* @var boolean
*/
private static $configured = false;
/**
* Refresh list of connected plugins upon intialization.
*
* @var boolean
*/
private static $refresh_connected_plugins = false;
/**
* Connected plugins.
*
* @var array
*/
private static $plugins = array();
/**
* The blog ID the storage is setup for.
* The data will be refreshed if the blog ID changes.
* Used for the multisite networks.
*
* @var int
*/
private static $current_blog_id = null;
/**
* Add or update the plugin information in the storage.
*
* @param string $slug Plugin slug.
* @param array $args Plugin arguments, optional.
*
* @return bool
*/
public static function upsert( $slug, array $args = array() ) {
self::$plugins[ $slug ] = $args;
// if plugin is not in the list of active plugins, refresh the list.
if ( ! array_key_exists( $slug, (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) ) ) {
self::$refresh_connected_plugins = true;
}
return true;
}
/**
* Retrieve the plugin information by slug.
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
* so please make sure not to run the method too early in the code.
*
* @param string $slug The plugin slug.
*
* @return array|null|WP_Error
*/
public static function get_one( $slug ) {
$plugins = self::get_all();
if ( $plugins instanceof WP_Error ) {
return $plugins;
}
return empty( $plugins[ $slug ] ) ? null : $plugins[ $slug ];
}
/**
* Retrieve info for all plugins that use the connection.
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
* so please make sure not to run the method too early in the code.
*
* @since 1.39.0 deprecated the $connected_only argument.
*
* @param null $deprecated null plugins that were explicitly disconnected. Deprecated, there's no such a thing as disconnecting only specific plugins anymore.
*
* @return array|WP_Error
*/
public static function get_all( $deprecated = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$maybe_error = self::ensure_configured();
if ( $maybe_error instanceof WP_Error ) {
return $maybe_error;
}
return self::$plugins;
}
/**
* Remove the plugin connection info from Jetpack.
* WARNING: the method cannot be called until Plugin_Storage::configure is called, which happens on plugins_loaded
* Even if you don't use Jetpack Config, it may be introduced later by other plugins,
* so please make sure not to run the method too early in the code.
*
* @param string $slug The plugin slug.
*
* @return bool|WP_Error
*/
public static function delete( $slug ) {
$maybe_error = self::ensure_configured();
if ( $maybe_error instanceof WP_Error ) {
return $maybe_error;
}
if ( array_key_exists( $slug, self::$plugins ) ) {
unset( self::$plugins[ $slug ] );
}
return true;
}
/**
* The method makes sure that `Jetpack\Config` has finished, and it's now safe to retrieve the list of plugins.
*
* @return bool|WP_Error
*/
private static function ensure_configured() {
if ( ! self::$configured ) {
return new WP_Error( 'too_early', __( 'You cannot call this method until Jetpack Config is configured', 'jetpack-connection' ) );
}
if ( is_multisite() && get_current_blog_id() !== self::$current_blog_id ) {
self::$plugins = (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() );
self::$current_blog_id = get_current_blog_id();
}
return true;
}
/**
* Called once to configure this class after plugins_loaded.
*
* @return void
*/
public static function configure() {
if ( self::$configured ) {
return;
}
if ( is_multisite() ) {
self::$current_blog_id = get_current_blog_id();
}
// If a plugin was activated or deactivated.
// self::$plugins is populated in Config::ensure_options_connection().
$number_of_plugins_differ = count( self::$plugins ) !== count( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
if ( $number_of_plugins_differ || true === self::$refresh_connected_plugins ) {
self::update_active_plugins_option();
}
self::$configured = true;
}
/**
* Updates the active plugins option with current list of active plugins.
*
* @return void
*/
public static function update_active_plugins_option() {
// Note: Since this options is synced to wpcom, if you change its structure, you have to update the sanitizer at wpcom side.
update_option( self::ACTIVE_PLUGINS_OPTION_NAME, self::$plugins );
if ( ! class_exists( 'Automattic\Jetpack\Sync\Settings' ) || ! \Automattic\Jetpack\Sync\Settings::is_sync_enabled() ) {
self::update_active_plugins_wpcom_no_sync_fallback();
}
}
/**
* Add the plugin to the set of disconnected ones.
*
* @deprecated since 1.39.0.
*
* @param string $slug Plugin slug.
*
* @return bool
*/
public static function disable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return true;
}
/**
* Remove the plugin from the set of disconnected ones.
*
* @deprecated since 1.39.0.
*
* @param string $slug Plugin slug.
*
* @return bool
*/
public static function enable_plugin( $slug ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return true;
}
/**
* Get all plugins that were disconnected by user.
*
* @deprecated since 1.39.0.
*
* @return array
*/
public static function get_all_disabled_plugins() { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return array();
}
/**
* Update active plugins option with current list of active plugins on WPCOM.
* This is a fallback to ensure this option is always up to date on WPCOM in case
* Sync is not present or disabled.
*
* @since 1.34.0
*/
private static function update_active_plugins_wpcom_no_sync_fallback() {
$connection = new Manager();
if ( ! $connection->is_connected() ) {
return;
}
$site_id = \Jetpack_Options::get_option( 'id' );
$body = wp_json_encode(
array(
'active_connected_plugins' => self::$plugins,
)
);
Client::wpcom_json_api_request_as_blog(
sprintf( '/sites/%d/jetpack-active-connected-plugins', $site_id ),
'2',
array(
'headers' => array( 'content-type' => 'application/json' ),
'method' => 'POST',
),
$body,
'wpcom'
);
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* Plugin connection management class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* Plugin connection management class.
* The class represents a single plugin that uses Jetpack connection.
* Its functionality has been pretty simplistic so far: add to the storage (`Plugin_Storage`), remove it from there,
* and determine whether it's the last active connection. As the component grows, there'll be more functionality added.
*/
class Plugin {
/**
* List of the keys allowed as arguments
*
* @var array
*/
private $arguments_whitelist = array(
'url_info',
);
/**
* Plugin slug.
*
* @var string
*/
private $slug;
/**
* Initialize the plugin manager.
*
* @param string $slug Plugin slug.
*/
public function __construct( $slug ) {
$this->slug = $slug;
}
/**
* Get the plugin slug.
*
* @return string
*/
public function get_slug() {
return $this->slug;
}
/**
* Add the plugin connection info into Jetpack.
*
* @param string $name Plugin name, required.
* @param array $args Plugin arguments, optional.
*
* @return $this
* @see $this->arguments_whitelist
*/
public function add( $name, array $args = array() ) {
$args = compact( 'name' ) + array_intersect_key( $args, array_flip( $this->arguments_whitelist ) );
Plugin_Storage::upsert( $this->slug, $args );
return $this;
}
/**
* Remove the plugin connection info from Jetpack.
*
* @return $this
*/
public function remove() {
Plugin_Storage::delete( $this->slug );
return $this;
}
/**
* Determine if this plugin connection is the only one active at the moment, if any.
*
* @return bool
*/
public function is_only() {
$plugins = Plugin_Storage::get_all( true );
return ! $plugins || ( array_key_exists( $this->slug, $plugins ) && 1 === count( $plugins ) );
}
/**
* Add the plugin to the set of disconnected ones.
*
* @deprecated since 1.39.0.
*
* @return bool
*/
public function disable() {
return true;
}
/**
* Remove the plugin from the set of disconnected ones.
*
* @deprecated since 1.39.0.
*
* @return bool
*/
public function enable() {
return true;
}
/**
* Whether this plugin is allowed to use the connection.
*
* @deprecated since $next-version$$
* @return bool
*/
public function is_enabled() {
return true;
}
}

View File

@ -0,0 +1,220 @@
<?php
/**
* The Jetpack Connection Rest Authentication file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* The Jetpack Connection Rest Authentication class.
*/
class Rest_Authentication {
/**
* The rest authentication status.
*
* @since 1.17.0
* @var boolean
*/
private $rest_authentication_status = null;
/**
* The rest authentication type.
* Can be either 'user' or 'blog' depending on whether the request
* is signed with a user or a blog token.
*
* @since 1.29.0
* @var string
*/
private $rest_authentication_type = null;
/**
* The Manager object.
*
* @since 1.17.0
* @var Object
*/
private $connection_manager = null;
/**
* Holds the singleton instance of this class
*
* @since 1.17.0
* @var Object
*/
private static $instance = false;
/**
* Flag used to avoid determine_current_user filter to enter an infinite loop
*
* @since 1.26.0
* @var boolean
*/
private $doing_determine_current_user_filter = false;
/**
* The constructor.
*/
private function __construct() {
$this->connection_manager = new Manager();
}
/**
* Controls the single instance of this class.
*
* @static
*/
public static function init() {
if ( ! self::$instance ) {
self::$instance = new self();
add_filter( 'determine_current_user', array( self::$instance, 'wp_rest_authenticate' ) );
add_filter( 'rest_authentication_errors', array( self::$instance, 'wp_rest_authentication_errors' ) );
}
return self::$instance;
}
/**
* Authenticates requests from Jetpack server to WP REST API endpoints.
* Uses the existing XMLRPC request signing implementation.
*
* @param int|bool $user User ID if one has been determined, false otherwise.
*
* @return int|null The user id or null if the request was authenticated via blog token, or not authenticated at all.
*/
public function wp_rest_authenticate( $user ) {
if ( $this->doing_determine_current_user_filter ) {
return $user;
}
$this->doing_determine_current_user_filter = true;
try {
if ( ! empty( $user ) ) {
// Another authentication method is in effect.
return $user;
}
add_filter(
'jetpack_constant_default_value',
__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
10,
2
);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['_for'] ) || 'jetpack' !== $_GET['_for'] ) {
// Nothing to do for this authentication method.
return null;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
// Nothing to do for this authentication method.
return null;
}
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
$this->rest_authentication_status = new \WP_Error(
'rest_invalid_request',
__( 'The request method is missing.', 'jetpack-connection' ),
array( 'status' => 400 )
);
return null;
}
// Only support specific request parameters that have been tested and
// are known to work with signature verification. A different method
// can be passed to the WP REST API via the '?_method=' parameter if
// needed.
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
$this->rest_authentication_status = new \WP_Error(
'rest_invalid_request',
__( 'This request method is not supported.', 'jetpack-connection' ),
array( 'status' => 400 )
);
return null;
}
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! empty( file_get_contents( 'php://input' ) ) ) {
$this->rest_authentication_status = new \WP_Error(
'rest_invalid_request',
__( 'This request method does not support body parameters.', 'jetpack-connection' ),
array( 'status' => 400 )
);
return null;
}
$verified = $this->connection_manager->verify_xml_rpc_signature();
if (
$verified &&
isset( $verified['type'] ) &&
'blog' === $verified['type']
) {
// Site-level authentication successful.
$this->rest_authentication_status = true;
$this->rest_authentication_type = 'blog';
return null;
}
if (
$verified &&
isset( $verified['type'] ) &&
'user' === $verified['type'] &&
! empty( $verified['user_id'] )
) {
// User-level authentication successful.
$this->rest_authentication_status = true;
$this->rest_authentication_type = 'user';
return $verified['user_id'];
}
// Something else went wrong. Probably a signature error.
$this->rest_authentication_status = new \WP_Error(
'rest_invalid_signature',
__( 'The request is not signed correctly.', 'jetpack-connection' ),
array( 'status' => 400 )
);
return null;
} finally {
$this->doing_determine_current_user_filter = false;
}
}
/**
* Report authentication status to the WP REST API.
*
* @param WP_Error|mixed $value Error from another authentication handler, null if we should handle it, or another value if not.
* @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
*/
public function wp_rest_authentication_errors( $value ) {
if ( null !== $value ) {
return $value;
}
return $this->rest_authentication_status;
}
/**
* Resets the saved authentication state in between testing requests.
*/
public function reset_saved_auth_state() {
$this->rest_authentication_status = null;
$this->connection_manager->reset_saved_auth_state();
}
/**
* Whether the request was signed with a blog token.
*
* @since 1.29.0
*
* @return bool True if the request was signed with a valid blog token, false otherwise.
*/
public static function is_signed_with_blog_token() {
$instance = self::init();
return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
}
}

View File

@ -0,0 +1,851 @@
<?php
/**
* Sets up the Connection REST API endpoints.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Status;
use Jetpack_XMLRPC_Server;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
/**
* Registers the REST routes for Connections.
*/
class REST_Connector {
/**
* The Connection Manager.
*
* @var Manager
*/
private $connection;
/**
* This property stores the localized "Insufficient Permissions" error message.
*
* @var string Generic error message when user is not allowed to perform an action.
*/
private static $user_permissions_error_msg;
const JETPACK__DEBUGGER_PUBLIC_KEY = "\r\n" . '-----BEGIN PUBLIC KEY-----' . "\r\n"
. 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+uLLVoxGCY71LS6KFc6' . "\r\n"
. '1UnF6QGBAsi5XF8ty9kR3/voqfOkpW+gRerM2Kyjy6DPCOmzhZj7BFGtxSV2ZoMX' . "\r\n"
. '9ZwWxzXhl/Q/6k8jg8BoY1QL6L2K76icXJu80b+RDIqvOfJruaAeBg1Q9NyeYqLY' . "\r\n"
. 'lEVzN2vIwcFYl+MrP/g6Bc2co7Jcbli+tpNIxg4Z+Hnhbs7OJ3STQLmEryLpAxQO' . "\r\n"
. 'q8cbhQkMx+FyQhxzSwtXYI/ClCUmTnzcKk7SgGvEjoKGAmngILiVuEJ4bm7Q1yok' . "\r\n"
. 'xl9+wcfW6JAituNhml9dlHCWnn9D3+j8pxStHihKy2gVMwiFRjLEeD8K/7JVGkb/' . "\r\n"
. 'EwIDAQAB' . "\r\n"
. '-----END PUBLIC KEY-----' . "\r\n";
/**
* Constructor.
*
* @param Manager $connection The Connection Manager.
*/
public function __construct( Manager $connection ) {
$this->connection = $connection;
self::$user_permissions_error_msg = esc_html__(
'You do not have the correct user permissions to perform this action.
Please contact your site admin if you think this is a mistake.',
'jetpack-connection'
);
$jp_version = Constants::get_constant( 'JETPACK__VERSION' );
if ( ! $this->connection->has_connected_owner() ) {
// Register a site.
register_rest_route(
'jetpack/v4',
'/verify_registration',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'verify_registration' ),
'permission_callback' => '__return_true',
)
);
}
// Authorize a remote user.
register_rest_route(
'jetpack/v4',
'/remote_authorize',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::remote_authorize',
'permission_callback' => '__return_true',
)
);
// Get current connection status of Jetpack.
register_rest_route(
'jetpack/v4',
'/connection',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::connection_status',
'permission_callback' => '__return_true',
)
);
// Disconnect site.
register_rest_route(
'jetpack/v4',
'/connection',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::disconnect_site',
'permission_callback' => __CLASS__ . '::disconnect_site_permission_check',
'args' => array(
'isActive' => array(
'description' => __( 'Set to false will trigger the site to disconnect.', 'jetpack-connection' ),
'validate_callback' => function ( $value ) {
if ( false !== $value ) {
return new WP_Error(
'rest_invalid_param',
__( 'The isActive argument should be set to false.', 'jetpack-connection' ),
array( 'status' => 400 )
);
}
return true;
},
'required' => true,
),
),
)
);
// We are only registering this route if Jetpack-the-plugin is not active or it's version is ge 10.0-alpha.
// The reason for doing so is to avoid conflicts between the Connection package and
// older versions of Jetpack, registering the same route twice.
if ( empty( $jp_version ) || version_compare( $jp_version, '10.0-alpha', '>=' ) ) {
// Get current user connection data.
register_rest_route(
'jetpack/v4',
'/connection/data',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_user_connection_data',
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
)
);
}
// Get list of plugins that use the Jetpack connection.
register_rest_route(
'jetpack/v4',
'/connection/plugins',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( __CLASS__, 'get_connection_plugins' ),
'permission_callback' => __CLASS__ . '::connection_plugins_permission_check',
)
);
// Full or partial reconnect in case of connection issues.
register_rest_route(
'jetpack/v4',
'/connection/reconnect',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connection_reconnect' ),
'permission_callback' => __CLASS__ . '::jetpack_reconnect_permission_check',
)
);
// Register the site (get `blog_token`).
register_rest_route(
'jetpack/v4',
'/connection/register',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connection_register' ),
'permission_callback' => __CLASS__ . '::jetpack_register_permission_check',
'args' => array(
'from' => array(
'description' => __( 'Indicates where the registration action was triggered for tracking/segmentation purposes', 'jetpack-connection' ),
'type' => 'string',
),
'registration_nonce' => array(
'description' => __( 'The registration nonce', 'jetpack-connection' ),
'type' => 'string',
'required' => true,
),
'redirect_uri' => array(
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
'type' => 'string',
),
'plugin_slug' => array(
'description' => __( 'Indicates from what plugin the request is coming from', 'jetpack-connection' ),
'type' => 'string',
),
),
)
);
// Get authorization URL.
register_rest_route(
'jetpack/v4',
'/connection/authorize_url',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'connection_authorize_url' ),
'permission_callback' => __CLASS__ . '::user_connection_data_permission_check',
'args' => array(
'redirect_uri' => array(
'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
'type' => 'string',
),
),
)
);
register_rest_route(
'jetpack/v4',
'/user-token',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( static::class, 'update_user_token' ),
'permission_callback' => array( static::class, 'update_user_token_permission_check' ),
'args' => array(
'user_token' => array(
'description' => __( 'New user token', 'jetpack-connection' ),
'type' => 'string',
'required' => true,
),
'is_connection_owner' => array(
'description' => __( 'Is connection owner', 'jetpack-connection' ),
'type' => 'boolean',
),
),
),
)
);
// Set the connection owner.
register_rest_route(
'jetpack/v4',
'/connection/owner',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( static::class, 'set_connection_owner' ),
'permission_callback' => array( static::class, 'set_connection_owner_permission_check' ),
'args' => array(
'owner' => array(
'description' => __( 'New owner', 'jetpack-connection' ),
'type' => 'integer',
'required' => true,
),
),
)
);
}
/**
* Handles verification that a site is registered.
*
* @since 1.7.0
* @since-jetpack 5.4.0
*
* @param WP_REST_Request $request The request sent to the WP REST API.
*
* @return string|WP_Error
*/
public function verify_registration( WP_REST_Request $request ) {
$registration_data = array( $request['secret_1'], $request['state'] );
return $this->connection->handle_registration( $registration_data );
}
/**
* Handles verification that a site is registered
*
* @since 1.7.0
* @since-jetpack 5.4.0
*
* @param WP_REST_Request $request The request sent to the WP REST API.
*
* @return array|wp-error
*/
public static function remote_authorize( $request ) {
$xmlrpc_server = new Jetpack_XMLRPC_Server();
$result = $xmlrpc_server->remote_authorize( $request );
if ( is_a( $result, 'IXR_Error' ) ) {
$result = new WP_Error( $result->code, $result->message );
}
return $result;
}
/**
* Get connection status for this Jetpack site.
*
* @since 1.7.0
* @since-jetpack 4.3.0
*
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
*
* @return WP_REST_Response|array Connection information.
*/
public static function connection_status( $rest_response = true ) {
$status = new Status();
$connection = new Manager();
$connection_status = array(
'isActive' => $connection->is_active(), // TODO deprecate this.
'isStaging' => $status->is_staging_site(),
'isRegistered' => $connection->is_connected(),
'isUserConnected' => $connection->is_user_connected(),
'hasConnectedOwner' => $connection->has_connected_owner(),
'offlineMode' => array(
'isActive' => $status->is_offline_mode(),
'constant' => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
'url' => $status->is_local_site(),
/** This filter is documented in packages/status/src/class-status.php */
'filter' => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
),
'isPublic' => '1' == get_option( 'blog_public' ), // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
);
/**
* Filters the connection status data.
*
* @since 1.25.0
*
* @param array An array containing the connection status data.
*/
$connection_status = apply_filters( 'jetpack_connection_status', $connection_status );
if ( $rest_response ) {
return rest_ensure_response(
$connection_status
);
} else {
return $connection_status;
}
}
/**
* Get plugins connected to the Jetpack.
*
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
*
* @since 1.13.1
* @since 1.38.0 Added $rest_response param.
*
* @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
*/
public static function get_connection_plugins( $rest_response = true ) {
$plugins = ( new Manager() )->get_connected_plugins();
if ( is_wp_error( $plugins ) ) {
return $plugins;
}
array_walk(
$plugins,
function ( &$data, $slug ) {
$data['slug'] = $slug;
}
);
if ( $rest_response ) {
return rest_ensure_response( array_values( $plugins ) );
}
return array_values( $plugins );
}
/**
* Verify that user can view Jetpack admin page and can activate plugins.
*
* @since 1.15.0
*
* @return bool|WP_Error Whether user has the capability 'activate_plugins'.
*/
public static function activate_plugins_permission_check() {
if ( current_user_can( 'activate_plugins' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
/**
* Permission check for the connection_plugins endpoint
*
* @return bool|WP_Error
*/
public static function connection_plugins_permission_check() {
if ( true === static::activate_plugins_permission_check() ) {
return true;
}
if ( true === static::is_request_signed_by_jetpack_debugger() ) {
return true;
}
return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
/**
* Permission check for the disconnect site endpoint.
*
* @since 1.30.1
*
* @return bool|WP_Error True if user is able to disconnect the site.
*/
public static function disconnect_site_permission_check() {
if ( current_user_can( 'jetpack_disconnect' ) ) {
return true;
}
return new WP_Error(
'invalid_user_permission_jetpack_disconnect',
self::get_user_permissions_error_msg(),
array( 'status' => rest_authorization_required_code() )
);
}
/**
* Get miscellaneous user data related to the connection. Similar data available in old "My Jetpack".
* Information about the master/primary user.
* Information about the current user.
*
* @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
*
* @since 1.30.1
*
* @return \WP_REST_Response|array
*/
public static function get_user_connection_data( $rest_response = true ) {
$connection = new Manager();
$current_user = wp_get_current_user();
$connection_owner = $connection->get_connection_owner();
$owner_display_name = false === $connection_owner ? null : $connection_owner->display_name;
$is_user_connected = $connection->is_user_connected();
$is_master_user = false === $connection_owner ? false : ( $current_user->ID === $connection_owner->ID );
$wpcom_user_data = $connection->get_connected_user_data();
// Add connected user gravatar to the returned wpcom_user_data.
// Probably we shouldn't do this when $wpcom_user_data is false, but we have been since 2016 so
// clients probably expect that by now.
if ( false === $wpcom_user_data ) {
$wpcom_user_data = array();
}
$wpcom_user_data['avatar'] = ( ! empty( $wpcom_user_data['email'] ) ?
get_avatar_url(
$wpcom_user_data['email'],
array(
'size' => 64,
'default' => 'mysteryman',
)
)
: false );
$current_user_connection_data = array(
'isConnected' => $is_user_connected,
'isMaster' => $is_master_user,
'username' => $current_user->user_login,
'id' => $current_user->ID,
'wpcomUser' => $wpcom_user_data,
'gravatar' => get_avatar_url( $current_user->ID, 64, 'mm', '', array( 'force_display' => true ) ),
'permissions' => array(
'connect' => current_user_can( 'jetpack_connect' ),
'connect_user' => current_user_can( 'jetpack_connect_user' ),
'disconnect' => current_user_can( 'jetpack_disconnect' ),
),
);
/**
* Filters the current user connection data.
*
* @since 1.30.1
*
* @param array An array containing the current user connection data.
*/
$current_user_connection_data = apply_filters( 'jetpack_current_user_connection_data', $current_user_connection_data );
$response = array(
'currentUser' => $current_user_connection_data,
'connectionOwner' => $owner_display_name,
);
if ( $rest_response ) {
return rest_ensure_response( $response );
}
return $response;
}
/**
* Permission check for the connection/data endpoint
*
* @return bool|WP_Error
*/
public static function user_connection_data_permission_check() {
if ( current_user_can( 'jetpack_connect_user' ) ) {
return true;
}
return new WP_Error(
'invalid_user_permission_user_connection_data',
self::get_user_permissions_error_msg(),
array( 'status' => rest_authorization_required_code() )
);
}
/**
* Verifies if the request was signed with the Jetpack Debugger key
*
* @param string|null $pub_key The public key used to verify the signature. Default is the Jetpack Debugger key. This is used for testing purposes.
*
* @return bool
*/
public static function is_request_signed_by_jetpack_debugger( $pub_key = null ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'], $_GET['rest_route'] ) ) {
return false;
}
// signature timestamp must be within 5min of current time.
if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) {
return false;
}
$signature = base64_decode( filter_var( wp_unslash( $_GET['signature'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$signature_data = wp_json_encode(
array(
'rest_route' => filter_var( wp_unslash( $_GET['rest_route'] ) ),
'timestamp' => (int) $_GET['timestamp'],
'url' => filter_var( wp_unslash( $_GET['url'] ) ),
)
);
if (
! function_exists( 'openssl_verify' )
|| 1 !== openssl_verify(
$signature_data,
$signature,
$pub_key ? $pub_key : static::JETPACK__DEBUGGER_PUBLIC_KEY
)
) {
return false;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return true;
}
/**
* Verify that user is allowed to disconnect Jetpack.
*
* @since 1.15.0
*
* @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'.
*/
public static function jetpack_reconnect_permission_check() {
if ( current_user_can( 'jetpack_reconnect' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
/**
* Returns generic error message when user is not allowed to perform an action.
*
* @return string The error message.
*/
public static function get_user_permissions_error_msg() {
return self::$user_permissions_error_msg;
}
/**
* The endpoint tried to partially or fully reconnect the website to WP.com.
*
* @since 1.15.0
*
* @return \WP_REST_Response|WP_Error
*/
public function connection_reconnect() {
$response = array();
$next = null;
$result = $this->connection->restore();
if ( is_wp_error( $result ) ) {
$response = $result;
} elseif ( is_string( $result ) ) {
$next = $result;
} else {
$next = true === $result ? 'completed' : 'failed';
}
switch ( $next ) {
case 'authorize':
$response['status'] = 'in_progress';
$response['authorizeUrl'] = $this->connection->get_authorization_url();
break;
case 'completed':
$response['status'] = 'completed';
/**
* Action fired when reconnection has completed successfully.
*
* @since 1.18.1
*/
do_action( 'jetpack_reconnection_completed' );
break;
case 'failed':
$response = new WP_Error( 'Reconnect failed' );
break;
}
return rest_ensure_response( $response );
}
/**
* Verify that user is allowed to connect Jetpack.
*
* @since 1.26.0
*
* @return bool|WP_Error Whether user has the capability 'jetpack_connect'.
*/
public static function jetpack_register_permission_check() {
if ( current_user_can( 'jetpack_connect' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_jetpack_connect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
/**
* The endpoint tried to partially or fully reconnect the website to WP.com.
*
* @since 1.7.0
* @since-jetpack 7.7.0
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return \WP_REST_Response|WP_Error
*/
public function connection_register( $request ) {
if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack-connection' ), array( 'status' => 403 ) );
}
if ( isset( $request['from'] ) ) {
$this->connection->add_register_request_param( 'from', (string) $request['from'] );
}
if ( ! empty( $request['plugin_slug'] ) ) {
// If `plugin_slug` matches a plugin using the connection, let's inform the plugin that is establishing the connection.
$connected_plugin = Plugin_Storage::get_one( (string) $request['plugin_slug'] );
if ( ! is_wp_error( $connected_plugin ) && ! empty( $connected_plugin ) ) {
$this->connection->set_plugin_instance( new Plugin( (string) $request['plugin_slug'] ) );
}
}
$result = $this->connection->try_registration();
if ( is_wp_error( $result ) ) {
return $result;
}
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
if ( class_exists( 'Jetpack' ) ) {
$authorize_url = \Jetpack::build_authorize_url( $redirect_uri );
} else {
$authorize_url = $this->connection->get_authorization_url( null, $redirect_uri );
}
/**
* Filters the response of jetpack/v4/connection/register endpoint
*
* @param array $response Array response
* @since 1.27.0
*/
$response_body = apply_filters(
'jetpack_register_site_rest_response',
array()
);
// We manipulate the alternate URLs after the filter is applied, so they can not be overwritten.
$response_body['authorizeUrl'] = $authorize_url;
if ( ! empty( $response_body['alternateAuthorizeUrl'] ) ) {
$response_body['alternateAuthorizeUrl'] = Redirect::get_url( $response_body['alternateAuthorizeUrl'] );
}
return rest_ensure_response( $response_body );
}
/**
* Get the authorization URL.
*
* @since 1.27.0
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return \WP_REST_Response|WP_Error
*/
public function connection_authorize_url( $request ) {
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
$authorize_url = $this->connection->get_authorization_url( null, $redirect_uri );
return rest_ensure_response(
array(
'authorizeUrl' => $authorize_url,
)
);
}
/**
* The endpoint tried to partially or fully reconnect the website to WP.com.
*
* @since 1.29.0
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return \WP_REST_Response|WP_Error
*/
public static function update_user_token( $request ) {
$token_parts = explode( '.', $request['user_token'] );
if ( count( $token_parts ) !== 3 || ! (int) $token_parts[2] || ! ctype_digit( $token_parts[2] ) ) {
return new WP_Error( 'invalid_argument_user_token', esc_html__( 'Invalid user token is provided', 'jetpack-connection' ) );
}
$user_id = (int) $token_parts[2];
if ( false === get_userdata( $user_id ) ) {
return new WP_Error( 'invalid_argument_user_id', esc_html__( 'Invalid user id is provided', 'jetpack-connection' ) );
}
$connection = new Manager();
if ( ! $connection->is_connected() ) {
return new WP_Error( 'site_not_connected', esc_html__( 'Site is not connected', 'jetpack-connection' ) );
}
$is_connection_owner = isset( $request['is_connection_owner'] )
? (bool) $request['is_connection_owner']
: ( new Manager() )->get_connection_owner_id() === $user_id;
( new Tokens() )->update_user_token( $user_id, $request['user_token'], $is_connection_owner );
/**
* Fires when the user token gets successfully replaced.
*
* @since 1.29.0
*
* @param int $user_id User ID.
* @param string $token New user token.
*/
do_action( 'jetpack_updated_user_token', $user_id, $request['user_token'] );
return rest_ensure_response(
array(
'success' => true,
)
);
}
/**
* Disconnects Jetpack from the WordPress.com Servers
*
* @since 1.30.1
*
* @return bool|WP_Error True if Jetpack successfully disconnected.
*/
public static function disconnect_site() {
$connection = new Manager();
if ( $connection->is_connected() ) {
$connection->disconnect_site();
return rest_ensure_response( array( 'code' => 'success' ) );
}
return new WP_Error(
'disconnect_failed',
esc_html__( 'Failed to disconnect the site as it appears already disconnected.', 'jetpack-connection' ),
array( 'status' => 400 )
);
}
/**
* Verify that the API client is allowed to replace user token.
*
* @since 1.29.0
*
* @return bool|WP_Error.
*/
public static function update_user_token_permission_check() {
return Rest_Authentication::is_signed_with_blog_token()
? true
: new WP_Error( 'invalid_permission_update_user_token', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
/**
* Change the connection owner.
*
* @since 1.29.0
*
* @param WP_REST_Request $request The request sent to the WP REST API.
*
* @return \WP_REST_Response|WP_Error
*/
public static function set_connection_owner( $request ) {
$new_owner_id = $request['owner'];
$owner_set = ( new Manager() )->update_connection_owner( $new_owner_id );
if ( is_wp_error( $owner_set ) ) {
return $owner_set;
}
return rest_ensure_response(
array(
'code' => 'success',
)
);
}
/**
* Check that user has permission to change the master user.
*
* @since 1.7.0
* @since-jetpack 6.2.0
* @since-jetpack 7.7.0 Update so that any user with jetpack_disconnect privs can set owner.
*
* @return bool|WP_Error True if user is able to change master user.
*/
public static function set_connection_owner_permission_check() {
if ( current_user_can( 'jetpack_disconnect' ) ) {
return true;
}
return new WP_Error( 'invalid_user_permission_set_connection_owner', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
}
}

View File

@ -0,0 +1,281 @@
<?php
/**
* The Jetpack Connection Secrets class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Jetpack_Options;
use WP_Error;
/**
* The Jetpack Connection Secrets class that is used to manage secrets.
*/
class Secrets {
const SECRETS_MISSING = 'secrets_missing';
const SECRETS_EXPIRED = 'secrets_expired';
const LEGACY_SECRETS_OPTION_NAME = 'jetpack_secrets';
/**
* Deletes all connection secrets from the local Jetpack site.
*/
public function delete_all() {
Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
}
/**
* Runs the wp_generate_password function with the required parameters. This is the
* default implementation of the secret callable, can be overridden using the
* jetpack_connection_secret_generator filter.
*
* @return String $secret value.
*/
private function secret_callable_method() {
$secret = wp_generate_password( 32, false );
// Some sites may hook into the random_password filter and make the password shorter, let's make sure our secret has the required length.
$attempts = 1;
$secret_length = strlen( $secret );
while ( $secret_length < 32 && $attempts < 32 ) {
$attempts++;
$secret .= wp_generate_password( 32, false );
$secret_length = strlen( $secret );
}
return (string) substr( $secret, 0, 32 );
}
/**
* Generates two secret tokens and the end of life timestamp for them.
*
* @param String $action The action name.
* @param Integer|bool $user_id The user identifier. Defaults to `false`.
* @param Integer $exp Expiration time in seconds.
*/
public function generate( $action, $user_id = false, $exp = 600 ) {
if ( false === $user_id ) {
$user_id = get_current_user_id();
}
$callable = apply_filters( 'jetpack_connection_secret_generator', array( get_called_class(), 'secret_callable_method' ) );
$secrets = Jetpack_Options::get_raw_option(
self::LEGACY_SECRETS_OPTION_NAME,
array()
);
$secret_name = 'jetpack_' . $action . '_' . $user_id;
if (
isset( $secrets[ $secret_name ] ) &&
$secrets[ $secret_name ]['exp'] > time()
) {
return $secrets[ $secret_name ];
}
$secret_value = array(
'secret_1' => call_user_func( $callable ),
'secret_2' => call_user_func( $callable ),
'exp' => time() + $exp,
);
$secrets[ $secret_name ] = $secret_value;
$res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
return $res ? $secrets[ $secret_name ] : false;
}
/**
* Returns two secret tokens and the end of life timestamp for them.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
* @return string|array an array of secrets or an error string.
*/
public function get( $action, $user_id ) {
$secret_name = 'jetpack_' . $action . '_' . $user_id;
$secrets = Jetpack_Options::get_raw_option(
self::LEGACY_SECRETS_OPTION_NAME,
array()
);
if ( ! isset( $secrets[ $secret_name ] ) ) {
return self::SECRETS_MISSING;
}
if ( $secrets[ $secret_name ]['exp'] < time() ) {
$this->delete( $action, $user_id );
return self::SECRETS_EXPIRED;
}
return $secrets[ $secret_name ];
}
/**
* Deletes secret tokens in case they, for example, have expired.
*
* @param String $action The action name.
* @param Integer $user_id The user identifier.
*/
public function delete( $action, $user_id ) {
$secret_name = 'jetpack_' . $action . '_' . $user_id;
$secrets = Jetpack_Options::get_raw_option(
self::LEGACY_SECRETS_OPTION_NAME,
array()
);
if ( isset( $secrets[ $secret_name ] ) ) {
unset( $secrets[ $secret_name ] );
Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
}
}
/**
* Verify a Previously Generated Secret.
*
* @param string $action The type of secret to verify.
* @param string $secret_1 The secret string to compare to what is stored.
* @param int $user_id The user ID of the owner of the secret.
* @return WP_Error|string WP_Error on failure, secret_2 on success.
*/
public function verify( $action, $secret_1, $user_id ) {
$allowed_actions = array( 'register', 'authorize', 'publicize' );
if ( ! in_array( $action, $allowed_actions, true ) ) {
return new WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
}
$user = get_user_by( 'id', $user_id );
/**
* We've begun verifying the previously generated secret.
*
* @since 1.7.0
* @since-jetpack 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
*/
do_action( 'jetpack_verify_secrets_begin', $action, $user );
$return_error = function ( WP_Error $error ) use ( $action, $user ) {
/**
* Verifying of the previously generated secret has failed.
*
* @since 1.7.0
* @since-jetpack 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
* @param WP_Error $error The error object.
*/
do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
return $error;
};
$stored_secrets = $this->get( $action, $user_id );
$this->delete( $action, $user_id );
$error = null;
if ( empty( $secret_1 ) ) {
$error = $return_error(
new WP_Error(
'verify_secret_1_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack-connection' ), 'secret_1' ),
400
)
);
} elseif ( ! is_string( $secret_1 ) ) {
$error = $return_error(
new WP_Error(
'verify_secret_1_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack-connection' ), 'secret_1' ),
400
)
);
} elseif ( empty( $user_id ) ) {
// $user_id is passed around during registration as "state".
$error = $return_error(
new WP_Error(
'state_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack-connection' ), 'state' ),
400
)
);
} elseif ( ! ctype_digit( (string) $user_id ) ) {
$error = $return_error(
new WP_Error(
'state_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack-connection' ), 'state' ),
400
)
);
} elseif ( self::SECRETS_MISSING === $stored_secrets ) {
$error = $return_error(
new WP_Error(
'verify_secrets_missing',
__( 'Verification secrets not found', 'jetpack-connection' ),
400
)
);
} elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
$error = $return_error(
new WP_Error(
'verify_secrets_expired',
__( 'Verification took too long', 'jetpack-connection' ),
400
)
);
} elseif ( ! $stored_secrets ) {
$error = $return_error(
new WP_Error(
'verify_secrets_empty',
__( 'Verification secrets are empty', 'jetpack-connection' ),
400
)
);
} elseif ( is_wp_error( $stored_secrets ) ) {
$stored_secrets->add_data( 400 );
$error = $return_error( $stored_secrets );
} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
$error = $return_error(
new WP_Error(
'verify_secrets_incomplete',
__( 'Verification secrets are incomplete', 'jetpack-connection' ),
400
)
);
} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
$error = $return_error(
new WP_Error(
'verify_secrets_mismatch',
__( 'Secret mismatch', 'jetpack-connection' ),
400
)
);
}
// Something went wrong during the checks, returning the error.
if ( ! empty( $error ) ) {
return $error;
}
/**
* We've succeeded at verifying the previously generated secret.
*
* @since 1.7.0
* @since-jetpack 7.5.0
*
* @param string $action The type of secret to verify.
* @param \WP_User $user The user object.
*/
do_action( 'jetpack_verify_secrets_success', $action, $user );
return $stored_secrets['secret_2'];
}
}

View File

@ -0,0 +1,244 @@
<?php
/**
* The Server_Sandbox class.
*
* This feature is only useful for Automattic developers.
* It configures Jetpack to talk to staging/sandbox servers
* on WordPress.com instead of production servers.
*
* @package automattic/jetpack-sandbox
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
/**
* The Server_Sandbox class.
*/
class Server_Sandbox {
/**
* Sets up the action hooks for the server sandbox.
*/
public function init() {
if ( did_action( 'jetpack_server_sandbox_init' ) ) {
return;
}
add_action( 'requests-requests.before_request', array( $this, 'server_sandbox' ), 10, 4 );
add_action( 'admin_bar_menu', array( $this, 'admin_bar_add_sandbox_item' ), 999 );
/**
* Fires when the server sandbox is initialized. This action is used to ensure that
* the server sandbox action hooks are set up only once.
*
* @since 1.30.7
*/
do_action( 'jetpack_server_sandbox_init' );
}
/**
* Returns the new url and host values.
*
* @param string $sandbox Sandbox domain.
* @param string $url URL of request about to be made.
* @param array $headers Headers of request about to be made.
* @param string $data The body of request about to be made.
* @param string $method The method of request about to be made.
*
* @return array [ 'url' => new URL, 'host' => new Host, 'new_signature => New signature if url was changed ]
*/
public function server_sandbox_request_parameters( $sandbox, $url, $headers, $data = null, $method = 'GET' ) {
$host = '';
$new_signature = '';
if ( ! is_string( $sandbox ) || ! is_string( $url ) ) {
return array(
'url' => $url,
'host' => $host,
'new_signature' => $new_signature,
);
}
$url_host = wp_parse_url( $url, PHP_URL_HOST );
switch ( $url_host ) {
case 'public-api.wordpress.com':
case 'jetpack.wordpress.com':
case 'jetpack.com':
case 'dashboard.wordpress.com':
$host = isset( $headers['Host'] ) ? $headers['Host'] : $url_host;
$original_url = $url;
$url = preg_replace(
'@^(https?://)' . preg_quote( $url_host, '@' ) . '(?=[/?#].*|$)@',
'${1}' . $sandbox,
$url,
1
);
/**
* Whether to add the X Debug query parameter to the request made to the Sandbox
*
* @since 1.36.0
*
* @param bool $add_parameter Whether to add the parameter to the request or not. Default is to false.
* @param string $url The URL of the request being made.
* @param string $host The host of the request being made.
*/
if ( apply_filters( 'jetpack_sandbox_add_profile_parameter', false, $url, $host ) ) {
$url = add_query_arg( 'XDEBUG_PROFILE', 1, $url );
// URL has been modified since the signature was created. We'll need a new one.
$original_url = add_query_arg( 'XDEBUG_PROFILE', 1, $original_url );
$new_signature = $this->get_new_signature( $original_url, $headers, $data, $method );
}
}
return compact( 'url', 'host', 'new_signature' );
}
/**
* Gets a new signature for the request
*
* @param string $url The new URL to be signed.
* @param array $headers The headers of the request about to be made.
* @param string $data The body of request about to be made.
* @param string $method The method of the request about to be made.
* @return string|null
*/
private function get_new_signature( $url, $headers, $data, $method ) {
if ( ! empty( $headers['Authorization'] ) ) {
$a_headers = $this->extract_authorization_headers( $headers );
if ( ! empty( $a_headers ) ) {
$token_details = explode( ':', $a_headers['token'] );
if ( count( $token_details ) === 3 ) {
$user_id = $token_details[2];
$token = ( new Tokens() )->get_access_token( $user_id );
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
$signature = $jetpack_signature->sign_request(
$a_headers['token'],
$a_headers['timestamp'],
$a_headers['nonce'],
$a_headers['body-hash'],
$method,
$url,
$data,
false
);
if ( $signature && ! is_wp_error( $signature ) ) {
return $signature;
} elseif ( is_wp_error( $signature ) ) {
$this->log_new_signature_error( $signature->get_error_message() );
}
} else {
$this->log_new_signature_error( 'Malformed token on Authorization Header' );
}
} else {
$this->log_new_signature_error( 'Error extracting Authorization Header' );
}
} else {
$this->log_new_signature_error( 'Empty Authorization Header' );
}
}
/**
* Logs error if the attempt to create a new signature fails
*
* @param string $message The error message.
* @return void
*/
private function log_new_signature_error( $message ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf( "SANDBOXING: Error re-signing the request. '%s'", $message ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
}
}
/**
* Extract the values in the Authorization header into an array
*
* @param array $headers The headers of the request about to be made.
* @return array|null
*/
public function extract_authorization_headers( $headers ) {
if ( ! empty( $headers['Authorization'] ) && is_string( $headers['Authorization'] ) ) {
$header = str_replace( 'X_JETPACK ', '', $headers['Authorization'] );
$vars = explode( ' ', $header );
$result = array();
foreach ( $vars as $var ) {
$elements = explode( '"', $var );
if ( count( $elements ) === 3 ) {
$result[ substr( $elements[0], 0, -1 ) ] = $elements[1];
}
}
return $result;
}
}
/**
* Modifies parameters of request in order to send the request to the
* server specified by `JETPACK__SANDBOX_DOMAIN`.
*
* Attached to the `requests-requests.before_request` filter.
*
* @param string $url URL of request about to be made.
* @param array $headers Headers of request about to be made.
* @param array|string $data Data of request about to be made.
* @param string $type Type of request about to be made.
* @return void
*/
public function server_sandbox( &$url, &$headers, &$data = null, &$type = null ) {
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
return;
}
$original_url = $url;
$request_parameters = $this->server_sandbox_request_parameters( Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $url, $headers, $data, $type );
$url = $request_parameters['url'];
if ( $request_parameters['host'] ) {
$headers['Host'] = $request_parameters['host'];
if ( $request_parameters['new_signature'] ) {
$headers['Authorization'] = preg_replace( '/signature=\"[^\"]+\"/', 'signature="' . $request_parameters['new_signature'] . '"', $headers['Authorization'] );
}
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf( "SANDBOXING via '%s': '%s'", Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $original_url ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
}
}
}
/**
* Adds a "Jetpack API Sandboxed" item to the admin bar if the JETPACK__SANDBOX_DOMAIN
* constant is set.
*
* Attached to the `admin_bar_menu` action.
*
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
*/
public function admin_bar_add_sandbox_item( $wp_admin_bar ) {
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
return;
}
$node = array(
'id' => 'jetpack-connection-api-sandbox',
'title' => 'Jetpack API Sandboxed',
'meta' => array(
'title' => 'Sandboxing via ' . Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ),
),
);
$wp_admin_bar->add_menu( $node );
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* A Terms of Service class for Jetpack.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
/**
* Class Terms_Of_Service
*
* Helper class that is responsible for the state of agreement of the terms of service.
*/
class Terms_Of_Service {
/**
* Jetpack option name where the terms of service state is stored.
*
* @var string
*/
const OPTION_NAME = 'tos_agreed';
/**
* Allow the site to agree to the terms of service.
*/
public function agree() {
$this->set_agree();
/**
* Acton fired when the master user has agreed to the terms of service.
*
* @since 1.0.4
* @since-jetpack 7.9.0
*/
do_action( 'jetpack_agreed_to_terms_of_service' );
}
/**
* Allow the site to reject to the terms of service.
*/
public function reject() {
$this->set_reject();
/**
* Acton fired when the master user has revoked their agreement to the terms of service.
*
* @since 1.0.4
* @since-jetpack 7.9.1
*/
do_action( 'jetpack_reject_terms_of_service' );
}
/**
* Returns whether the master user has agreed to the terms of service.
*
* The following conditions have to be met in order to agree to the terms of service.
* 1. The master user has gone though the connect flow.
* 2. The site is not in dev mode.
* 3. The master user of the site is still connected (deprecated @since 1.4.0).
*
* @return bool
*/
public function has_agreed() {
if ( $this->is_offline_mode() ) {
return false;
}
/**
* Before 1.4.0 we used to also check if the master user of the site is connected
* by calling the Connection related `is_active` method.
* As of 1.4.0 we have removed this check in order to resolve the
* circular dependencies it was introducing to composer packages.
*
* @since 1.4.0
*/
return $this->get_raw_has_agreed();
}
/**
* Abstracted for testing purposes.
* Tells us if the site is in dev mode.
*
* @return bool
*/
protected function is_offline_mode() {
return ( new Status() )->is_offline_mode();
}
/**
* Gets just the Jetpack Option that contains the terms of service state.
* Abstracted for testing purposes.
*
* @return bool
*/
protected function get_raw_has_agreed() {
return \Jetpack_Options::get_option( self::OPTION_NAME, false );
}
/**
* Sets the correct Jetpack Option to mark the that the site has agreed to the terms of service.
* Abstracted for testing purposes.
*/
protected function set_agree() {
\Jetpack_Options::update_option( self::OPTION_NAME, true );
}
/**
* Sets the correct Jetpack Option to mark that the site has rejected the terms of service.
* Abstracted for testing purposes.
*/
protected function set_reject() {
\Jetpack_Options::update_option( self::OPTION_NAME, false );
}
}

View File

@ -0,0 +1,691 @@
<?php
/**
* The Jetpack Connection Tokens class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Roles;
use DateInterval;
use DateTime;
use Exception;
use Jetpack_Options;
use WP_Error;
/**
* The Jetpack Connection Tokens class that manages tokens.
*/
class Tokens {
const MAGIC_NORMAL_TOKEN_KEY = ';normal;';
/**
* Datetime format.
*/
const DATE_FORMAT_ATOM = 'Y-m-d\TH:i:sP';
/**
* Deletes all connection tokens and transients from the local Jetpack site.
*/
public function delete_all() {
Jetpack_Options::delete_option(
array(
'blog_token',
'user_token',
'user_tokens',
)
);
$this->remove_lock();
}
/**
* Perform the API request to validate the blog and user tokens.
*
* @param int|null $user_id ID of the user we need to validate token for. Current user's ID by default.
*
* @return array|false|WP_Error The API response: `array( 'blog_token_is_healthy' => true|false, 'user_token_is_healthy' => true|false )`.
*/
public function validate( $user_id = null ) {
$blog_id = Jetpack_Options::get_option( 'id' );
if ( ! $blog_id ) {
return new WP_Error( 'site_not_registered', 'Site not registered.' );
}
$url = sprintf(
'%s/%s/v%s/%s',
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
'wpcom',
'2',
'sites/' . $blog_id . '/jetpack-token-health'
);
$user_token = $this->get_access_token( $user_id ? $user_id : get_current_user_id() );
$blog_token = $this->get_access_token();
// Cannot validate non-existent tokens.
if ( false === $user_token || false === $blog_token ) {
return false;
}
$method = 'POST';
$body = array(
'user_token' => $this->get_signed_token( $user_token ),
'blog_token' => $this->get_signed_token( $blog_token ),
);
$response = Client::_wp_remote_request( $url, compact( 'body', 'method' ) );
if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return false;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return $body ? $body : false;
}
/**
* Perform the API request to validate only the blog.
*
* @return bool|WP_Error Boolean with the test result. WP_Error if test cannot be performed.
*/
public function validate_blog_token() {
$blog_id = Jetpack_Options::get_option( 'id' );
if ( ! $blog_id ) {
return new WP_Error( 'site_not_registered', 'Site not registered.' );
}
$url = sprintf(
'%s/%s/v%s/%s',
Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ),
'wpcom',
'2',
'sites/' . $blog_id . '/jetpack-token-health/blog'
);
$method = 'GET';
$response = Client::remote_request( compact( 'url', 'method' ) );
if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return false;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return is_array( $body ) && isset( $body['is_healthy'] ) && true === $body['is_healthy'];
}
/**
* Obtains the auth token.
*
* @param array $data The request data.
* @param string $token_api_url The URL of the Jetpack "token" API.
* @return object|WP_Error Returns the auth token on success.
* Returns a WP_Error on failure.
*/
public function get( $data, $token_api_url ) {
$roles = new Roles();
$role = $roles->translate_current_user_to_role();
if ( ! $role ) {
return new WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack-connection' ) );
}
$client_secret = $this->get_access_token();
if ( ! $client_secret ) {
return new WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack-connection' ) );
}
/**
* Filter the URL of the first time the user gets redirected back to your site for connection
* data processing.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param string $redirect_url Defaults to the site admin URL.
*/
$processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) );
$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
/**
* Filter the URL to redirect the user back to when the authentication process
* is complete.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param string $redirect_url Defaults to the site URL.
*/
$redirect = apply_filters( 'jetpack_token_redirect_url', $redirect );
$redirect_uri = ( 'calypso' === $data['auth_type'] )
? $data['redirect_uri']
: add_query_arg(
array(
'handler' => 'jetpack-connection-webhooks',
'action' => 'authorize',
'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
'redirect' => $redirect ? rawurlencode( $redirect ) : false,
),
esc_url( $processing_url )
);
/**
* Filters the token request data.
*
* @since 1.7.0
* @since-jetpack 8.0.0
*
* @param array $request_data request data.
*/
$body = apply_filters(
'jetpack_token_request_body',
array(
'client_id' => Jetpack_Options::get_option( 'id' ),
'client_secret' => $client_secret->secret,
'grant_type' => 'authorization_code',
'code' => $data['code'],
'redirect_uri' => $redirect_uri,
)
);
$args = array(
'method' => 'POST',
'body' => $body,
'headers' => array(
'Accept' => 'application/json',
),
);
add_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
$response = Client::_wp_remote_request( $token_api_url, $args );
remove_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 );
if ( is_wp_error( $response ) ) {
return new WP_Error( 'token_http_request_failed', $response->get_error_message() );
}
$code = wp_remote_retrieve_response_code( $response );
$entity = wp_remote_retrieve_body( $response );
if ( $entity ) {
$json = json_decode( $entity );
} else {
$json = false;
}
if ( 200 !== $code || ! empty( $json->error ) ) {
if ( empty( $json->error ) ) {
return new WP_Error( 'unknown', '', $code );
}
/* translators: Error description string. */
$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack-connection' ), (string) $json->error_description ) : '';
return new WP_Error( (string) $json->error, $error_description, $code );
}
if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
return new WP_Error( 'access_token', '', $code );
}
if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) {
return new WP_Error( 'token_type', '', $code );
}
if ( empty( $json->scope ) ) {
return new WP_Error( 'scope', 'No Scope', $code );
}
// TODO: get rid of the error silencer.
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
@list( $role, $hmac ) = explode( ':', $json->scope );
if ( empty( $role ) || empty( $hmac ) ) {
return new WP_Error( 'scope', 'Malformed Scope', $code );
}
if ( $this->sign_role( $role ) !== $json->scope ) {
return new WP_Error( 'scope', 'Invalid Scope', $code );
}
$cap = $roles->translate_role_to_cap( $role );
if ( ! $cap ) {
return new WP_Error( 'scope', 'No Cap', $code );
}
if ( ! current_user_can( $cap ) ) {
return new WP_Error( 'scope', 'current_user_cannot', $code );
}
return (string) $json->access_token;
}
/**
* Enters a user token into the user_tokens option
*
* @param int $user_id The user id.
* @param string $token The user token.
* @param bool $is_master_user Whether the user is the master user.
* @return bool
*/
public function update_user_token( $user_id, $token, $is_master_user ) {
// Not designed for concurrent updates.
$user_tokens = $this->get_user_tokens();
if ( ! is_array( $user_tokens ) ) {
$user_tokens = array();
}
$user_tokens[ $user_id ] = $token;
if ( $is_master_user ) {
$master_user = $user_id;
$options = compact( 'user_tokens', 'master_user' );
} else {
$options = compact( 'user_tokens' );
}
return Jetpack_Options::update_options( $options );
}
/**
* Sign a user role with the master access token.
* If not specified, will default to the current user.
*
* @access public
*
* @param string $role User role.
* @param int $user_id ID of the user.
* @return string Signed user role.
*/
public function sign_role( $role, $user_id = null ) {
if ( empty( $user_id ) ) {
$user_id = (int) get_current_user_id();
}
if ( ! $user_id ) {
return false;
}
$token = $this->get_access_token();
if ( ! $token || is_wp_error( $token ) ) {
return false;
}
return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
}
/**
* Increases the request timeout value to 30 seconds.
*
* @return int Returns 30.
*/
public function return_30() {
return 30;
}
/**
* Gets the requested token.
*
* Tokens are one of two types:
* 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
* though some sites can have multiple "Special" Blog Tokens (see below). These tokens
* are not associated with a user account. They represent the site's connection with
* the Jetpack servers.
* 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
*
* All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
* token, and $private is a secret that should never be displayed anywhere or sent
* over the network; it's used only for signing things.
*
* Blog Tokens can be "Normal" or "Special".
* * Normal: The result of a normal connection flow. They look like
* "{$random_string_1}.{$random_string_2}"
* That is, $token_key and $private are both random strings.
* Sites only have one Normal Blog Token. Normal Tokens are found in either
* Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
* constant (rare).
* * Special: A connection token for sites that have gone through an alternative
* connection flow. They look like:
* ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
* That is, $private is a random string and $token_key has a special structure with
* lots of semicolons.
* Most sites have zero Special Blog Tokens. Special tokens are only found in the
* JETPACK_BLOG_TOKEN constant.
*
* In particular, note that Normal Blog Tokens never start with ";" and that
* Special Blog Tokens always do.
*
* When searching for a matching Blog Tokens, Blog Tokens are examined in the following
* order:
* 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
* 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
* 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
*
* @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token.
* @param string|false $token_key If provided, check that the token matches the provided input.
* @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
*
* @return object|false|WP_Error
*/
public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
if ( $this->is_locked() ) {
$this->delete_all();
return false;
}
$possible_special_tokens = array();
$possible_normal_tokens = array();
$user_tokens = $this->get_user_tokens();
if ( $user_id ) {
if ( ! $user_tokens ) {
return $suppress_errors ? false : new WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack-connection' ) );
}
if ( true === $user_id ) { // connection owner.
$user_id = Jetpack_Options::get_option( 'master_user' );
if ( ! $user_id ) {
return $suppress_errors ? false : new WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack-connection' ) );
}
}
if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
// translators: %s is the user ID.
return $suppress_errors ? false : new WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack-connection' ), $user_id ) );
}
$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
// translators: %s is the user ID.
return $suppress_errors ? false : new WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack-connection' ), $user_id ) );
}
if ( $user_token_chunks[2] !== (string) $user_id ) {
// translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token.
return $suppress_errors ? false : new WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack-connection' ), $user_id, $user_token_chunks[2] ) );
}
$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
} else {
$stored_blog_token = Jetpack_Options::get_option( 'blog_token' );
if ( $stored_blog_token ) {
$possible_normal_tokens[] = $stored_blog_token;
}
$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
if ( $defined_tokens_string ) {
$defined_tokens = explode( ',', $defined_tokens_string );
foreach ( $defined_tokens as $defined_token ) {
if ( ';' === $defined_token[0] ) {
$possible_special_tokens[] = $defined_token;
} else {
$possible_normal_tokens[] = $defined_token;
}
}
}
}
if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
$possible_tokens = $possible_normal_tokens;
} else {
$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
}
if ( ! $possible_tokens ) {
// If no user tokens were found, it would have failed earlier, so this is about blog token.
return $suppress_errors ? false : new WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack-connection' ) );
}
$valid_token = false;
if ( false === $token_key ) {
// Use first token.
$valid_token = $possible_tokens[0];
} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
// Use first normal token.
$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
} else {
// Use the token matching $token_key or false if none.
// Ensure we check the full key.
$token_check = rtrim( $token_key, '.' ) . '.';
foreach ( $possible_tokens as $possible_token ) {
if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
$valid_token = $possible_token;
break;
}
}
}
if ( ! $valid_token ) {
if ( $user_id ) {
// translators: %d is the user ID.
return $suppress_errors ? false : new WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack-connection' ), $user_id ) );
} else {
return $suppress_errors ? false : new WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack-connection' ) );
}
}
return (object) array(
'secret' => $valid_token,
'external_user_id' => (int) $user_id,
);
}
/**
* Updates the blog token to a new value.
*
* @access public
*
* @param string $token the new blog token value.
* @return Boolean Whether updating the blog token was successful.
*/
public function update_blog_token( $token ) {
return Jetpack_Options::update_option( 'blog_token', $token );
}
/**
* Unlinks the current user from the linked WordPress.com user.
*
* @access public
* @static
*
* @todo Refactor to properly load the XMLRPC client independently.
*
* @param Integer $user_id the user identifier.
* @param bool $can_overwrite_primary_user Allow for the primary user to be disconnected.
* @return Boolean Whether the disconnection of the user was successful.
*/
public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) {
$tokens = $this->get_user_tokens();
if ( ! $tokens ) {
return false;
}
if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) {
return false;
}
if ( ! isset( $tokens[ $user_id ] ) ) {
return false;
}
unset( $tokens[ $user_id ] );
$this->update_user_tokens( $tokens );
return true;
}
/**
* Returns an array of user_id's that have user tokens for communicating with wpcom.
* Able to select by specific capability.
*
* @deprecated 1.30.0
* @see Manager::get_connected_users
*
* @param string $capability The capability of the user.
* @param int|null $limit How many connected users to get before returning.
* @return array Array of WP_User objects if found.
*/
public function get_connected_users( $capability = 'any', $limit = null ) {
_deprecated_function( __METHOD__, '1.30.0' );
return ( new Manager( 'jetpack' ) )->get_connected_users( $capability, $limit );
}
/**
* Fetches a signed token.
*
* @param object $token the token.
* @return WP_Error|string a signed token
*/
public function get_signed_token( $token ) {
if ( ! isset( $token->secret ) || empty( $token->secret ) ) {
return new WP_Error( 'invalid_token' );
}
list( $token_key, $token_secret ) = explode( '.', $token->secret );
$token_key = sprintf(
'%s:%d:%d',
$token_key,
Constants::get_constant( 'JETPACK__API_VERSION' ),
$token->external_user_id
);
$timestamp = time();
if ( function_exists( 'wp_generate_password' ) ) {
$nonce = wp_generate_password( 10, false );
} else {
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
}
$normalized_request_string = join(
"\n",
array(
$token_key,
$timestamp,
$nonce,
)
) . "\n";
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) );
$auth = array(
'token' => $token_key,
'timestamp' => $timestamp,
'nonce' => $nonce,
'signature' => $signature,
);
$header_pieces = array();
foreach ( $auth as $key => $value ) {
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
}
return join( ' ', $header_pieces );
}
/**
* Gets the list of user tokens
*
* @since 1.30.0
*
* @return bool|array An array of user tokens where keys are user IDs and values are the tokens. False if no user token is found.
*/
public function get_user_tokens() {
return Jetpack_Options::get_option( 'user_tokens' );
}
/**
* Updates the option that stores the user tokens
*
* @since 1.30.0
*
* @param array $tokens An array of user tokens where keys are user IDs and values are the tokens.
* @return bool Was the option successfully updated?
*
* @todo add validate the input.
*/
public function update_user_tokens( $tokens ) {
return Jetpack_Options::update_option( 'user_tokens', $tokens );
}
/**
* Lock the tokens to the current site URL.
*
* @param int $timespan How long the tokens should be locked, in seconds.
*
* @return bool
*/
public function set_lock( $timespan = HOUR_IN_SECONDS ) {
try {
$expires = ( new DateTime() )->add( DateInterval::createFromDateString( (int) $timespan . ' seconds' ) );
} catch ( Exception $e ) {
return false;
}
if ( false === $expires ) {
return false;
}
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return Jetpack_Options::update_option( 'token_lock', $expires->format( static::DATE_FORMAT_ATOM ) . '|||' . base64_encode( Urls::site_url() ) );
}
/**
* Remove the site lock from tokens.
*
* @return bool
*/
public function remove_lock() {
Jetpack_Options::delete_option( 'token_lock' );
return true;
}
/**
* Check if the domain is locked, remove the lock if needed.
* Possible scenarios:
* - lock expired, site URL matches the lock URL: remove the lock, return false.
* - lock not expired, site URL matches the lock URL: return false.
* - site URL does not match the lock URL (expiration date is ignored): return true, do not remove the lock.
*
* @return bool
*/
public function is_locked() {
$the_lock = Jetpack_Options::get_option( 'token_lock' );
if ( ! $the_lock ) {
// Not locked.
return false;
}
$the_lock = explode( '|||', $the_lock, 2 );
if ( count( $the_lock ) !== 2 ) {
// Something's wrong with the lock.
$this->remove_lock();
return false;
}
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$locked_site_url = base64_decode( $the_lock[1] );
$expires = $the_lock[0];
$expiration_date = DateTime::createFromFormat( static::DATE_FORMAT_ATOM, $expires );
if ( false === $expiration_date || ! $locked_site_url ) {
// Something's wrong with the lock.
$this->remove_lock();
return false;
}
if ( Urls::site_url() === $locked_site_url ) {
if ( new DateTime() > $expiration_date ) {
// Site lock expired.
// Site URL matches, removing the lock.
$this->remove_lock();
}
return false;
}
// Site URL doesn't match.
return true;
}
}

View File

@ -0,0 +1,321 @@
<?php
/**
* Nosara Tracks for Jetpack
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack;
/**
* The Tracking class, used to record events in wpcom
*/
class Tracking {
/**
* The assets version.
*
* @since 1.13.1
* @deprecated since 1.40.1
*
* @var string Assets version.
*/
const ASSETS_VERSION = '1.0.0';
/**
* Slug of the product that we are tracking.
*
* @var string
*/
private $product_name;
/**
* Connection manager object.
*
* @var Object
*/
private $connection;
/**
* Creates the Tracking object.
*
* @param String $product_name the slug of the product that we are tracking.
* @param Automattic\Jetpack\Connection\Manager $connection the connection manager object.
*/
public function __construct( $product_name = 'jetpack', $connection = null ) {
$this->product_name = $product_name;
$this->connection = $connection;
if ( $this->connection === null ) {
// TODO We should always pass a Connection.
$this->connection = new Connection\Manager();
}
if ( ! did_action( 'jetpack_set_tracks_ajax_hook' ) ) {
add_action( 'wp_ajax_jetpack_tracks', array( $this, 'ajax_tracks' ) );
/**
* Fires when the Tracking::ajax_tracks() callback has been hooked to the
* wp_ajax_jetpack_tracks action. This action is used to ensure that
* the callback is hooked only once.
*
* @since 1.13.11
*/
do_action( 'jetpack_set_tracks_ajax_hook' );
}
}
/**
* Universal method for for all tracking events triggered via the JavaScript client.
*
* @access public
*/
public function ajax_tracks() {
// Check for nonce.
if (
empty( $_REQUEST['tracksNonce'] )
|| ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either.
) {
wp_send_json_error(
__( 'You arent authorized to do that.', 'jetpack-connection' ),
403
);
}
if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] ) ) {
wp_send_json_error(
__( 'No valid event name or type.', 'jetpack-connection' ),
403
);
}
$tracks_data = array();
if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
if ( is_array( $_REQUEST['tracksEventProp'] ) ) {
$tracks_data = array_map( 'filter_var', wp_unslash( $_REQUEST['tracksEventProp'] ) );
} else {
$tracks_data = array( 'clicked' => filter_var( wp_unslash( $_REQUEST['tracksEventProp'] ) ) );
}
}
$this->record_user_event( filter_var( wp_unslash( $_REQUEST['tracksEventName'] ) ), $tracks_data, null, false );
wp_send_json_success();
}
/**
* Register script necessary for tracking.
*
* @param boolean $enqueue Also enqueue? defaults to false.
*/
public static function register_tracks_functions_scripts( $enqueue = false ) {
// Register jp-tracks as it is a dependency.
wp_register_script(
'jp-tracks',
'//stats.wp.com/w.js',
array(),
gmdate( 'YW' ),
true
);
Assets::register_script(
'jp-tracks-functions',
'../dist/tracks-callables.js',
__FILE__,
array(
'dependencies' => array( 'jp-tracks' ),
'enqueue' => $enqueue,
'in_footer' => true,
'nonmin_path' => 'js/tracks-callables.js',
)
);
}
/**
* Enqueue script necessary for tracking.
*/
public function enqueue_tracks_scripts() {
Assets::register_script(
'jptracks',
'../dist/tracks-ajax.js',
__FILE__,
array(
'dependencies' => array( 'jquery' ),
'enqueue' => true,
'in_footer' => true,
'nonmin_path' => 'js/tracks-ajax.js',
)
);
wp_localize_script(
'jptracks',
'jpTracksAJAX',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'jpTracksAJAX_nonce' => wp_create_nonce( 'jp-tracks-ajax-nonce' ),
)
);
}
/**
* Send an event in Tracks.
*
* @param string $event_type Type of the event.
* @param array $data Data to send with the event.
* @param mixed $user Username, user_id, or WP_user object.
* @param bool $use_product_prefix Whether to use the object's product name as a prefix to the event type. If
* set to false, the prefix will be 'jetpack_'.
*/
public function record_user_event( $event_type, $data = array(), $user = null, $use_product_prefix = true ) {
if ( ! $user ) {
$user = wp_get_current_user();
}
$site_url = get_option( 'siteurl' );
$data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? filter_var( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
$data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
$data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? filter_var( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '';
$data['blog_url'] = $site_url;
$data['blog_id'] = \Jetpack_Options::get_option( 'id' );
// Top level events should not be namespaced.
if ( '_aliasUser' !== $event_type ) {
$prefix = $use_product_prefix ? $this->product_name : 'jetpack';
$event_type = $prefix . '_' . $event_type;
}
$data['jetpack_version'] = defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '0';
return $this->tracks_record_event( $user, $event_type, $data );
}
/**
* Record an event in Tracks - this is the preferred way to record events from PHP.
*
* @param mixed $user username, user_id, or WP_user object.
* @param string $event_name The name of the event.
* @param array $properties Custom properties to send with the event.
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
*
* @return bool true for success | \WP_Error if the event pixel could not be fired
*/
public function tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
// We don't want to track user events during unit tests/CI runs.
if ( $user instanceof \WP_User && 'wptests_capabilities' === $user->cap_key ) {
return false;
}
$terms_of_service = new Terms_Of_Service();
$status = new Status();
// Don't track users who have not agreed to our TOS.
if ( ! $this->should_enable_tracking( $terms_of_service, $status ) ) {
return false;
}
$event_obj = $this->tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
if ( is_wp_error( $event_obj->error ) ) {
return $event_obj->error;
}
return $event_obj->record();
}
/**
* Determines whether tracking should be enabled.
*
* @param Automattic\Jetpack\Terms_Of_Service $terms_of_service A Terms_Of_Service object.
* @param Automattic\Jetpack\Status $status A Status object.
*
* @return boolean True if tracking should be enabled, else false.
*/
public function should_enable_tracking( $terms_of_service, $status ) {
if ( $status->is_offline_mode() ) {
return false;
}
return $terms_of_service->has_agreed() || $this->connection->is_user_connected();
}
/**
* Procedurally build a Tracks Event Object.
* NOTE: Use this only when the simpler Automattic\Jetpack\Tracking->jetpack_tracks_record_event() function won't work for you.
*
* @param WP_user $user WP_user object.
* @param string $event_name The name of the event.
* @param array $properties Custom properties to send with the event.
* @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
*
* @return \Jetpack_Tracks_Event|\WP_Error
*/
private function tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
$identity = $this->tracks_get_identity( $user->ID );
$properties['user_lang'] = $user->get( 'WPLANG' );
$blog_details = array(
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
);
$timestamp = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
$timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
return new \Jetpack_Tracks_Event(
array_merge(
$blog_details,
(array) $properties,
$identity,
array(
'_en' => $event_name,
'_ts' => $timestamp_string,
)
)
);
}
/**
* Get the identity to send to tracks.
*
* @param int $user_id The user id of the local user.
*
* @return array $identity
*/
public function tracks_get_identity( $user_id ) {
// Meta is set, and user is still connected. Use WPCOM ID.
$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
return array(
'_ut' => 'wpcom:user_id',
'_ui' => $wpcom_id,
);
}
// User is connected, but no meta is set yet. Use WPCOM ID and set meta.
if ( $this->connection->is_user_connected( $user_id ) ) {
$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
return array(
'_ut' => 'wpcom:user_id',
'_ui' => $wpcom_user_data['ID'],
);
}
// User isn't linked at all. Fall back to anonymous ID.
$anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
if ( ! $anon_id ) {
$anon_id = \Jetpack_Tracks_Client::get_anon_id();
add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
}
if ( ! isset( $_COOKIE['tk_ai'] ) && ! headers_sent() ) {
setcookie( 'tk_ai', $anon_id, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- This is a random string and should be fine.
}
return array(
'_ut' => 'anon',
'_ui' => $anon_id,
);
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* The Jetpack Connection package Urls class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Constants;
/**
* Provides Url methods for the Connection package.
*/
class Urls {
const HTTPS_CHECK_OPTION_PREFIX = 'jetpack_sync_https_history_';
const HTTPS_CHECK_HISTORY = 5;
/**
* Return URL from option or PHP constant.
*
* @param string $option_name (e.g. 'home').
*
* @return mixed|null URL.
*/
public static function get_raw_url( $option_name ) {
$value = null;
$constant = ( 'home' === $option_name )
? 'WP_HOME'
: 'WP_SITEURL';
// Since we disregard the constant for multisites in ms-default-filters.php,
// let's also use the db value if this is a multisite.
if ( ! is_multisite() && Constants::is_defined( $constant ) ) {
$value = Constants::get_constant( $constant );
} else {
// Let's get the option from the database so that we can bypass filters. This will help
// ensure that we get more uniform values.
$value = \Jetpack_Options::get_raw_option( $option_name );
}
return $value;
}
/**
* Normalize domains by removing www unless declared in the site's option.
*
* @param string $option Option value from the site.
* @param callable $url_function Function retrieving the URL to normalize.
* @return mixed|string URL.
*/
public static function normalize_www_in_url( $option, $url_function ) {
$url = wp_parse_url( call_user_func( $url_function ) );
$option_url = wp_parse_url( get_option( $option ) );
if ( ! $option_url || ! $url ) {
return $url;
}
if ( "www.{$option_url[ 'host' ]}" === $url['host'] ) {
// remove www if not present in option URL.
$url['host'] = $option_url['host'];
}
if ( "www.{$url[ 'host' ]}" === $option_url['host'] ) {
// add www if present in option URL.
$url['host'] = $option_url['host'];
}
$normalized_url = "{$url['scheme']}://{$url['host']}";
if ( isset( $url['path'] ) ) {
$normalized_url .= "{$url['path']}";
}
if ( isset( $url['query'] ) ) {
$normalized_url .= "?{$url['query']}";
}
return $normalized_url;
}
/**
* Return URL with a normalized protocol.
*
* @param callable $callable Function to retrieve URL option.
* @param string $new_value URL Protocol to set URLs to.
* @return string Normalized URL.
*/
public static function get_protocol_normalized_url( $callable, $new_value ) {
$option_key = self::HTTPS_CHECK_OPTION_PREFIX . $callable;
$parsed_url = wp_parse_url( $new_value );
if ( ! $parsed_url ) {
return $new_value;
}
if ( array_key_exists( 'scheme', $parsed_url ) ) {
$scheme = $parsed_url['scheme'];
} else {
$scheme = '';
}
$scheme_history = get_option( $option_key, array() );
$scheme_history[] = $scheme;
// Limit length to self::HTTPS_CHECK_HISTORY.
$scheme_history = array_slice( $scheme_history, ( self::HTTPS_CHECK_HISTORY * -1 ) );
update_option( $option_key, $scheme_history );
$forced_scheme = in_array( 'https', $scheme_history, true ) ? 'https' : 'http';
return set_url_scheme( $new_value, $forced_scheme );
}
/**
* Helper function that is used when getting home or siteurl values. Decides
* whether to get the raw or filtered value.
*
* @param string $url_type URL to get, home or siteurl.
* @return string
*/
public static function get_raw_or_filtered_url( $url_type ) {
$url_function = ( 'home' === $url_type )
? 'home_url'
: 'site_url';
if (
! Constants::is_defined( 'JETPACK_SYNC_USE_RAW_URL' ) ||
Constants::get_constant( 'JETPACK_SYNC_USE_RAW_URL' )
) {
$scheme = is_ssl() ? 'https' : 'http';
$url = self::get_raw_url( $url_type );
$url = set_url_scheme( $url, $scheme );
} else {
$url = self::normalize_www_in_url( $url_type, $url_function );
}
return self::get_protocol_normalized_url( $url_function, $url );
}
/**
* Return the escaped home_url.
*
* @return string
*/
public static function home_url() {
$url = self::get_raw_or_filtered_url( 'home' );
/**
* Allows overriding of the home_url value that is synced back to WordPress.com.
*
* @since 1.7.0
* @since-jetpack 5.2.0
*
* @param string $home_url
*/
return esc_url_raw( apply_filters( 'jetpack_sync_home_url', $url ) );
}
/**
* Return the escaped siteurl.
*
* @return string
*/
public static function site_url() {
$url = self::get_raw_or_filtered_url( 'siteurl' );
/**
* Allows overriding of the site_url value that is synced back to WordPress.com.
*
* @since 1.7.0
* @since-jetpack 5.2.0
*
* @param string $site_url
*/
return esc_url_raw( apply_filters( 'jetpack_sync_site_url', $url ) );
}
/**
* Return main site URL with a normalized protocol.
*
* @return string
*/
public static function main_network_site_url() {
return self::get_protocol_normalized_url( 'main_network_site_url', network_site_url() );
}
}

View File

@ -0,0 +1,87 @@
<?php
/**
* The Jetpack Connection package Utils class file.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\Tracking;
/**
* Provides utility methods for the Connection package.
*/
class Utils {
const DEFAULT_JETPACK__API_VERSION = 1;
const DEFAULT_JETPACK__API_BASE = 'https://jetpack.wordpress.com/jetpack.';
const DEFAULT_JETPACK__WPCOM_JSON_API_BASE = 'https://public-api.wordpress.com';
/**
* Enters a user token into the user_tokens option
*
* @deprecated 1.24.0 Use Automattic\Jetpack\Connection\Tokens->update_user_token() instead.
*
* @param int $user_id The user id.
* @param string $token The user token.
* @param bool $is_master_user Whether the user is the master user.
* @return bool
*/
public static function update_user_token( $user_id, $token, $is_master_user ) {
_deprecated_function( __METHOD__, '1.24.0', 'Automattic\\Jetpack\\Connection\\Tokens->update_user_token' );
return ( new Tokens() )->update_user_token( $user_id, $token, $is_master_user );
}
/**
* Filters the value of the api constant.
*
* @param String $constant_value The constant value.
* @param String $constant_name The constant name.
* @return mixed | null
*/
public static function jetpack_api_constant_filter( $constant_value, $constant_name ) {
if ( $constant_value !== null ) {
// If the constant value was already set elsewhere, use that value.
return $constant_value;
}
if ( defined( "self::DEFAULT_$constant_name" ) ) {
return constant( "self::DEFAULT_$constant_name" );
}
return null;
}
/**
* Add a filter to initialize default values of the constants.
*/
public static function init_default_constants() {
add_filter(
'jetpack_constant_default_value',
array( __CLASS__, 'jetpack_api_constant_filter' ),
10,
2
);
}
/**
* Filters the registration request body to include tracking properties.
*
* @param array $properties Already prepared tracking properties.
* @return array amended properties.
*/
public static function filter_register_request_body( $properties ) {
$tracking = new Tracking();
$tracks_identity = $tracking->tracks_get_identity( get_current_user_id() );
return array_merge(
$properties,
array(
'_ui' => $tracks_identity['_ui'],
'_ut' => $tracks_identity['_ut'],
)
);
}
}

View File

@ -0,0 +1,215 @@
<?php
/**
* Connection Webhooks class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Automattic\Jetpack\CookieState;
use Automattic\Jetpack\Roles;
use Automattic\Jetpack\Status\Host;
use Automattic\Jetpack\Tracking;
use Jetpack_Options;
/**
* Connection Webhooks class.
*/
class Webhooks {
/**
* The Connection Manager object.
*
* @var Manager
*/
private $connection;
/**
* Webhooks constructor.
*
* @param Manager $connection The Connection Manager object.
*/
public function __construct( $connection ) {
$this->connection = $connection;
}
/**
* Initialize the webhooks.
*
* @param Manager $connection The Connection Manager object.
*/
public static function init( $connection ) {
$webhooks = new static( $connection );
add_action( 'init', array( $webhooks, 'controller' ) );
add_action( 'load-toplevel_page_jetpack', array( $webhooks, 'fallback_jetpack_controller' ) );
}
/**
* Jetpack plugin used to trigger this webhooks in Jetpack::admin_page_load()
*
* The Jetpack toplevel menu is still accessible for stand-alone plugins, and while there's no content for that page, there are still
* actions from Calypso and WPCOM that reach that route regardless of the site having the Jetpack plugin or not. That's why we are still handling it here.
*/
public function fallback_jetpack_controller() {
$this->controller( true );
}
/**
* The "controller" decides which handler we need to run.
*
* @param bool $force Do not check if it's a webhook request and just run the controller.
*/
public function controller( $force = false ) {
if ( ! $force ) {
// The nonce is verified in specific handlers.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['handler'] ) || 'jetpack-connection-webhooks' !== $_GET['handler'] ) {
return;
}
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['connect_url_redirect'] ) ) {
$this->handle_connect_url_redirect();
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['action'] ) ) {
return;
}
// The nonce is verified in specific handlers.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
switch ( $_GET['action'] ) {
case 'authorize':
$this->handle_authorize();
$this->do_exit();
break;
case 'authorize_redirect':
$this->handle_authorize_redirect();
$this->do_exit();
break;
// Class Jetpack::admin_page_load() still handles other cases.
}
}
/**
* Perform the authorization action.
*/
public function handle_authorize() {
if ( $this->connection->is_connected() && $this->connection->is_user_connected() ) {
$redirect_url = apply_filters( 'jetpack_client_authorize_already_authorized_url', admin_url() );
wp_safe_redirect( $redirect_url );
return;
}
do_action( 'jetpack_client_authorize_processing' );
$data = stripslashes_deep( $_GET );
$data['auth_type'] = 'client';
$roles = new Roles();
$role = $roles->translate_current_user_to_role();
$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
$tracking = new Tracking();
$result = $this->connection->authorize( $data );
if ( is_wp_error( $result ) ) {
do_action( 'jetpack_client_authorize_error', $result );
$tracking->record_user_event(
'jpc_client_authorize_fail',
array(
'error_code' => $result->get_error_code(),
'error_message' => $result->get_error_message(),
)
);
} else {
/**
* Fires after the Jetpack client is authorized to communicate with WordPress.com.
*
* @param int Jetpack Blog ID.
*
* @since 1.7.0
* @since-jetpack 4.2.0
*/
do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) );
$tracking->record_user_event( 'jpc_client_authorize_success' );
}
$fallback_redirect = apply_filters( 'jetpack_client_authorize_fallback_url', admin_url() );
$redirect = wp_validate_redirect( $redirect ) ? $redirect : $fallback_redirect;
wp_safe_redirect( $redirect );
}
/**
* The authorhize_redirect webhook handler
*/
public function handle_authorize_redirect() {
$authorize_redirect_handler = new Webhooks\Authorize_Redirect( $this->connection );
$authorize_redirect_handler->handle();
}
/**
* The `exit` is wrapped into a method so we could mock it.
*/
protected function do_exit() {
exit;
}
/**
* Handle the `connect_url_redirect` action,
* which is usually called to repeat an attempt for user to authorize the connection.
*
* @return void
*/
public function handle_connect_url_redirect() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
$from = ! empty( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : 'iframe';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- no site changes, sanitization happens in get_authorization_url()
$redirect = ! empty( $_GET['redirect_after_auth'] ) ? wp_unslash( $_GET['redirect_after_auth'] ) : false;
add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_environments' ) );
if ( ! $this->connection->is_user_connected() ) {
if ( ! $this->connection->is_connected() ) {
$this->connection->register();
}
$connect_url = add_query_arg( 'from', $from, $this->connection->get_authorization_url( null, $redirect ) );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
if ( isset( $_GET['notes_iframe'] ) ) {
$connect_url .= '&notes_iframe';
}
wp_safe_redirect( $connect_url );
$this->do_exit();
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes.
if ( ! isset( $_GET['calypso_env'] ) ) {
( new CookieState() )->state( 'message', 'already_authorized' );
wp_safe_redirect( $redirect );
$this->do_exit();
} else {
$connect_url = add_query_arg(
array(
'from' => $from,
'already_authorized' => true,
),
$this->connection->get_authorization_url()
);
wp_safe_redirect( $connect_url );
$this->do_exit();
}
}
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* XMLRPC Async Call class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
use Jetpack_IXR_ClientMulticall;
/**
* Make XMLRPC async calls to WordPress.com
*
* This class allows you to enqueue XMLRPC calls that will be grouped and sent
* at once in a multi-call request at shutdown.
*
* Usage:
*
* XMLRPC_Async_Call::add_call( 'methodName', get_current_user_id(), $arg1, $arg2, etc... )
*
* See XMLRPC_Async_Call::add_call for details
*/
class XMLRPC_Async_Call {
/**
* Hold the IXR Clients that will be dispatched at shutdown
*
* Clients are stored in the following schema:
* [
* $blog_id => [
* $user_id => [
* arrat of Jetpack_IXR_ClientMulticall
* ]
* ]
* ]
*
* @var array
*/
public static $clients = array();
/**
* Adds a new XMLRPC call to the queue to be processed on shutdown
*
* @param string $method The XML-RPC method.
* @param integer $user_id The user ID used to make the request (will use this user's token); Use 0 for the blog token.
* @param mixed ...$args This function accepts any number of additional arguments, that will be passed to the call.
* @return void
*/
public static function add_call( $method, $user_id = 0, ...$args ) {
global $blog_id;
$client_blog_id = is_multisite() ? $blog_id : 0;
if ( ! isset( self::$clients[ $client_blog_id ] ) ) {
self::$clients[ $client_blog_id ] = array();
}
if ( ! isset( self::$clients[ $client_blog_id ][ $user_id ] ) ) {
self::$clients[ $client_blog_id ][ $user_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => $user_id ) );
}
if ( function_exists( 'ignore_user_abort' ) ) {
ignore_user_abort( true );
}
array_unshift( $args, $method );
call_user_func_array( array( self::$clients[ $client_blog_id ][ $user_id ], 'addCall' ), $args );
if ( false === has_action( 'shutdown', array( 'Automattic\Jetpack\Connection\XMLRPC_Async_Call', 'do_calls' ) ) ) {
add_action( 'shutdown', array( 'Automattic\Jetpack\Connection\XMLRPC_Async_Call', 'do_calls' ) );
}
}
/**
* Trigger the calls at shutdown
*
* @return void
*/
public static function do_calls() {
foreach ( self::$clients as $client_blog_id => $blog_clients ) {
if ( $client_blog_id > 0 ) {
$switch_success = switch_to_blog( $client_blog_id, true );
if ( ! $switch_success ) {
continue;
}
}
foreach ( $blog_clients as $client ) {
if ( empty( $client->calls ) ) {
continue;
}
flush();
$client->query();
}
if ( $client_blog_id > 0 ) {
restore_current_blog();
}
}
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* Sets up the Connection XML-RPC methods.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* Registers the XML-RPC methods for Connections.
*/
class XMLRPC_Connector {
/**
* The Connection Manager.
*
* @var Manager
*/
private $connection;
/**
* Constructor.
*
* @param Manager $connection The Connection Manager.
*/
public function __construct( Manager $connection ) {
$this->connection = $connection;
// Adding the filter late to avoid being overwritten by Jetpack's XMLRPC server.
add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ), 20 );
}
/**
* Attached to the `xmlrpc_methods` filter.
*
* @param array $methods The already registered XML-RPC methods.
* @return array
*/
public function xmlrpc_methods( $methods ) {
return array_merge(
$methods,
array(
'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
)
);
}
/**
* Handles verification that a site is registered.
*
* @param array $registration_data The data sent by the XML-RPC client:
* [ $secret_1, $user_id ].
*
* @return string|IXR_Error
*/
public function verify_registration( $registration_data ) {
return $this->output( $this->connection->handle_registration( $registration_data ) );
}
/**
* Normalizes output for XML-RPC.
*
* @param mixed $data The data to output.
*/
private function output( $data ) {
if ( is_wp_error( $data ) ) {
$code = $data->get_error_data();
if ( ! $code ) {
$code = -10520;
}
if ( ! class_exists( \IXR_Error::class ) ) {
require_once ABSPATH . WPINC . '/class-IXR.php';
}
return new \IXR_Error(
$code,
sprintf( 'Jetpack: [%s] %s', $data->get_error_code(), $data->get_error_message() )
);
}
return $data;
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* The Jetpack Connection Interface file.
* No longer used.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection;
/**
* This interface is no longer used and is now deprecated.
*
* @deprecated since jetpack 7.8
*/
interface Manager_Interface {
}

View File

@ -0,0 +1,63 @@
/* global jpTracksAJAX */
( function ( $, jpTracksAJAX ) {
window.jpTracksAJAX = window.jpTracksAJAX || {};
const debugSet = localStorage.getItem( 'debug' ) === 'dops:analytics';
window.jpTracksAJAX.record_ajax_event = function ( eventName, eventType, eventProp ) {
const data = {
tracksNonce: jpTracksAJAX.jpTracksAJAX_nonce,
action: 'jetpack_tracks',
tracksEventType: eventType,
tracksEventName: eventName,
tracksEventProp: eventProp || false,
};
return $.ajax( {
type: 'POST',
url: jpTracksAJAX.ajaxurl,
data: data,
success: function ( response ) {
if ( debugSet ) {
// eslint-disable-next-line
console.log( 'AJAX tracks event recorded: ', data, response );
}
},
} );
};
$( document ).ready( function () {
$( 'body' ).on( 'click', '.jptracks a, a.jptracks', function ( event ) {
const $this = $( event.target );
// We know that the jptracks element is either this, or its ancestor
const $jptracks = $this.closest( '.jptracks' );
// We need an event name at least
const eventName = $jptracks.attr( 'data-jptracks-name' );
if ( undefined === eventName ) {
return;
}
const eventProp = $jptracks.attr( 'data-jptracks-prop' ) || false;
const url = $this.attr( 'href' );
const target = $this.get( 0 ).target;
let newTabWindow = null;
if ( url && target && '_self' !== target ) {
newTabWindow = window.open( '', target );
newTabWindow.opener = null;
}
event.preventDefault();
window.jpTracksAJAX.record_ajax_event( eventName, 'click', eventProp ).always( function () {
// Continue on to whatever url they were trying to get to.
if ( url && ! $this.hasClass( 'thickbox' ) ) {
if ( newTabWindow ) {
newTabWindow.location = url;
return;
}
window.location = url;
}
} );
} );
} );
} )( jQuery, jpTracksAJAX );

View File

@ -0,0 +1,94 @@
/**
* This was abstracted from wp-calypso's analytics lib: https://github.com/Automattic/wp-calypso/blob/trunk/client/lib/analytics/README.md
* Some stuff was removed like GA tracking and other things not necessary for Jetpack tracking.
*
* This library should only be used and loaded if the Jetpack site is connected.
*/
// Load tracking scripts
window._tkq = window._tkq || [];
let _user;
const debug = console.error; // eslint-disable-line no-console
/**
* Build a query string.
*
* @param {string|object} group - Stat group, or object mapping groups to names.
* @param {string} [name] - Stat name, when `group` is a string.
* @returns {string} Query string fragment.
*/
function buildQuerystring( group, name ) {
let uriComponent = '';
if ( 'object' === typeof group ) {
for ( const key in group ) {
uriComponent += '&x_' + encodeURIComponent( key ) + '=' + encodeURIComponent( group[ key ] );
}
} else {
uriComponent = '&x_' + encodeURIComponent( group ) + '=' + encodeURIComponent( name );
}
return uriComponent;
}
const analytics = {
initialize: function ( userId, username ) {
analytics.setUser( userId, username );
analytics.identifyUser();
},
mc: {
bumpStat: function ( group, name ) {
const uriComponent = buildQuerystring( group, name ); // prints debug info
new Image().src =
document.location.protocol +
'//pixel.wp.com/g.gif?v=wpcom-no-pv' +
uriComponent +
'&t=' +
Math.random();
},
},
tracks: {
recordEvent: function ( eventName, eventProperties ) {
eventProperties = eventProperties || {};
if ( eventName.indexOf( 'jetpack_' ) !== 0 ) {
debug( '- Event name must be prefixed by "jetpack_"' );
return;
}
window._tkq.push( [ 'recordEvent', eventName, eventProperties ] );
},
recordPageView: function ( urlPath ) {
analytics.tracks.recordEvent( 'jetpack_page_view', {
path: urlPath,
} );
},
},
setUser: function ( userId, username ) {
_user = { ID: userId, username: username };
},
identifyUser: function () {
// Don't identify the user if we don't have one
if ( _user ) {
window._tkq.push( [ 'identifyUser', _user.ID, _user.username ] );
}
},
clearedIdentity: function () {
window._tkq.push( [ 'clearIdentity' ] );
},
};
if ( typeof module !== 'undefined' ) {
// Bundled by Webpack.
module.exports = analytics;
} else {
// Direct load.
window.analytics = analytics;
}

View File

@ -0,0 +1,197 @@
<?php
/**
* Authorize_Redirect Webhook handler class.
*
* @package automattic/jetpack-connection
*/
namespace Automattic\Jetpack\Connection\Webhooks;
use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Tracking;
use GP_Locales;
use Jetpack_Network;
/**
* Authorize_Redirect Webhook handler class.
*/
class Authorize_Redirect {
/**
* Constructs the object
*
* @param Automattic\Jetpack\Connection\Manager $connection The Connection Manager object.
*/
public function __construct( $connection ) {
$this->connection = $connection;
}
/**
* Handle the webhook
*
* This method implements what's in Jetpack::admin_page_load when the Jetpack plugin is not present
*/
public function handle() {
add_filter(
'allowed_redirect_hosts',
function ( $domains ) {
$domains[] = 'jetpack.com';
$domains[] = 'jetpack.wordpress.com';
$domains[] = 'wordpress.com';
// Calypso envs.
$domains[] = 'http://calypso.localhost:3000/';
$domains[] = 'https://wpcalypso.wordpress.com/';
$domains[] = 'https://horizon.wordpress.com/';
return array_unique( $domains );
}
);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$dest_url = empty( $_GET['dest_url'] ) ? null : esc_url_raw( wp_unslash( $_GET['dest_url'] ) );
if ( ! $dest_url || ( 0 === stripos( $dest_url, 'https://jetpack.com/' ) && 0 === stripos( $dest_url, 'https://wordpress.com/' ) ) ) {
// The destination URL is missing or invalid, nothing to do here.
exit;
}
if ( $this->connection->is_connected() && $this->connection->is_user_connected() ) {
// The user is either already connected, or finished the connection process.
wp_safe_redirect( $dest_url );
exit;
} elseif ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// The user decided not to proceed with setting up the connection.
wp_safe_redirect( Admin_Menu::get_top_level_menu_item_url() );
exit;
}
$redirect_args = array(
'page' => 'jetpack',
'action' => 'authorize_redirect',
'dest_url' => rawurlencode( $dest_url ),
'done' => '1',
);
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! empty( $_GET['from'] ) && 'jetpack_site_only_checkout' === $_GET['from'] ) {
$redirect_args['from'] = 'jetpack_site_only_checkout';
}
wp_safe_redirect( $this->build_authorize_url( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) ) );
exit;
}
/**
* Create the Jetpack authorization URL. Copied from Jetpack class.
*
* @param bool|string $redirect URL to redirect to.
*
* @todo Update default value for redirect since the called function expects a string.
*
* @return mixed|void
*/
public function build_authorize_url( $redirect = false ) {
add_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
add_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
$url = $this->connection->get_authorization_url( wp_get_current_user(), $redirect );
remove_filter( 'jetpack_connect_request_body', array( __CLASS__, 'filter_connect_request_body' ) );
remove_filter( 'jetpack_connect_redirect_url', array( __CLASS__, 'filter_connect_redirect_url' ) );
/**
* This filter is documented in plugins/jetpack/class-jetpack.php
*/
return apply_filters( 'jetpack_build_authorize_url', $url );
}
/**
* Filters the redirection URL that is used for connect requests. The redirect
* URL should return the user back to the Jetpack console.
* Copied from Jetpack class.
*
* @param String $redirect the default redirect URL used by the package.
* @return String the modified URL.
*/
public static function filter_connect_redirect_url( $redirect ) {
$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=jetpack' ) );
$redirect = $redirect
? wp_validate_redirect( esc_url_raw( $redirect ), $jetpack_admin_page )
: $jetpack_admin_page;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['is_multisite'] ) ) {
$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
}
return $redirect;
}
/**
* Filters the connection URL parameter array.
* Copied from Jetpack class.
*
* @param array $args default URL parameters used by the package.
* @return array the modified URL arguments array.
*/
public static function filter_connect_request_body( $args ) {
if (
Constants::is_defined( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
&& include_once Constants::get_constant( 'JETPACK__GLOTPRESS_LOCALES_PATH' )
) {
$gp_locale = GP_Locales::by_field( 'wp_locale', get_locale() );
$args['locale'] = isset( $gp_locale ) && isset( $gp_locale->slug )
? $gp_locale->slug
: '';
}
$tracking = new Tracking();
$tracks_identity = $tracking->tracks_get_identity( $args['state'] );
$args = array_merge(
$args,
array(
'_ui' => $tracks_identity['_ui'],
'_ut' => $tracks_identity['_ut'],
)
);
$calypso_env = self::get_calypso_env();
if ( ! empty( $calypso_env ) ) {
$args['calypso_env'] = $calypso_env;
}
return $args;
}
/**
* Return Calypso environment value; used for developing Jetpack and pairing
* it with different Calypso enrionments, such as localhost.
* Copied from Jetpack class.
*
* @since 1.37.1
*
* @return string Calypso environment
*/
public static function get_calypso_env() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['calypso_env'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return sanitize_key( $_GET['calypso_env'] );
}
if ( getenv( 'CALYPSO_ENV' ) ) {
return sanitize_key( getenv( 'CALYPSO_ENV' ) );
}
if ( defined( 'CALYPSO_ENV' ) && CALYPSO_ENV ) {
return sanitize_key( CALYPSO_ENV );
}
return '';
}
}

View File

@ -0,0 +1,164 @@
# 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).
## [1.6.18] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [1.6.17] - 2022-06-21
### Changed
- Renaming master to trunk.
## [1.6.16] - 2022-04-26
### Changed
- Updated package dependencies.
## [1.6.15] - 2022-01-25
### Changed
- Updated package dependencies.
## [1.6.14] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies
## [1.6.13] - 2021-12-14
### Changed
- Updated package dependencies.
## [1.6.12] - 2021-11-22
### Changed
- Updated package dependencies
## [1.6.11] - 2021-11-02
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [1.6.10] - 2021-10-26
### Fixed
- Updated is_true docblock to be more accurate.
## [1.6.9] - 2021-10-13
### Changed
- Updated package dependencies.
## [1.6.8] - 2021-10-06
### Changed
- Updated package dependencies
## [1.6.7] - 2021-09-28
### Changed
- Updated package dependencies.
## [1.6.6] - 2021-08-30
### Changed
- Run composer update on test-php command instead of phpunit
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- Updated versions in annotations
## [1.6.5] - 2021-05-25
### Changed
- Updated package dependencies.
## [1.6.4] - 2021-04-08
### Changed
- Packaging and build changes, no change to the package itself.
## [1.6.3] - 2021-03-30
### Added
- Composer alias for dev-master, to improve dependencies
### Changed
- Update package dependencies.
### Fixed
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
## [1.6.2] - 2021-02-05
- CI: Make tests more generic
## [1.6.1] - 2021-01-19
- Add mirror-repo information to all current composer packages
- Monorepo: Reorganize all projects
## [1.6.0] - 2020-12-14
- Update dependency brain/monkey to v2.6.0
- Pin dependencies
- Packages: Update for PHP 8 testing
## [1.5.1] - 2020-10-28
- Updated PHPCS: Packages and Debugger
## [1.5.0] - 2020-08-13
- CI: Try collect js coverage
- Docker: Add package testing shortcut
## [1.4.0] - 2020-07-01
- Package Unit tests: update test file names to make sure they runs in Travis
## [1.3.0] - 2020-06-22
- PHPCS: Clean up the packages
## [1.2.0] - 2020-04-15
- Use jp.com redirect in all links
- Connection: add a filter for setting Jetpack api constants
## [1.1.3] - 2019-11-08
- Packages: Use classmap instead of PSR-4
## [1.1.2] - 2019-10-28
- Packages: Add gitattributes files to all packages that need th…
## [1.1.1] - 2019-09-20
- Docs: Unify usage of @package phpdoc tags
## [1.1.0] - 2019-09-14
## 1.0.0 - 2019-07-09
- Packages: Finish the constants package
[1.6.18]: https://github.com/Automattic/jetpack-constants/compare/v1.6.17...v1.6.18
[1.6.17]: https://github.com/Automattic/jetpack-constants/compare/v1.6.16...v1.6.17
[1.6.16]: https://github.com/Automattic/jetpack-constants/compare/v1.6.15...v1.6.16
[1.6.15]: https://github.com/Automattic/jetpack-constants/compare/v1.6.14...v1.6.15
[1.6.14]: https://github.com/Automattic/jetpack-constants/compare/v1.6.13...v1.6.14
[1.6.13]: https://github.com/Automattic/jetpack-constants/compare/v1.6.12...v1.6.13
[1.6.12]: https://github.com/Automattic/jetpack-constants/compare/v1.6.11...v1.6.12
[1.6.11]: https://github.com/Automattic/jetpack-constants/compare/v1.6.10...v1.6.11
[1.6.10]: https://github.com/Automattic/jetpack-constants/compare/v1.6.9...v1.6.10
[1.6.9]: https://github.com/Automattic/jetpack-constants/compare/v1.6.8...v1.6.9
[1.6.8]: https://github.com/Automattic/jetpack-constants/compare/v1.6.7...v1.6.8
[1.6.7]: https://github.com/Automattic/jetpack-constants/compare/v1.6.6...v1.6.7
[1.6.6]: https://github.com/Automattic/jetpack-constants/compare/v1.6.5...v1.6.6
[1.6.5]: https://github.com/Automattic/jetpack-constants/compare/v1.6.4...v1.6.5
[1.6.4]: https://github.com/Automattic/jetpack-constants/compare/v1.6.3...v1.6.4
[1.6.3]: https://github.com/Automattic/jetpack-constants/compare/v1.6.2...v1.6.3
[1.6.2]: https://github.com/Automattic/jetpack-constants/compare/v1.6.1...v1.6.2
[1.6.1]: https://github.com/Automattic/jetpack-constants/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/Automattic/jetpack-constants/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/Automattic/jetpack-constants/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/Automattic/jetpack-constants/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/Automattic/jetpack-constants/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/Automattic/jetpack-constants/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Automattic/jetpack-constants/compare/v1.1.3...v1.2.0
[1.1.3]: https://github.com/Automattic/jetpack-constants/compare/v1.1.2...v1.1.3
[1.1.2]: https://github.com/Automattic/jetpack-constants/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/Automattic/jetpack-constants/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/Automattic/jetpack-constants/compare/v1.0.0...v1.1.0

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,40 @@
{
"name": "automattic/jetpack-constants",
"description": "A wrapper for defining constants in a more testable way.",
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {},
"require-dev": {
"brain/monkey": "2.6.1",
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.2"
},
"autoload": {
"classmap": [
"src/"
]
},
"scripts": {
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
],
"test-php": [
"@composer phpunit"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-constants",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}"
},
"branch-alias": {
"dev-trunk": "1.6.x-dev"
}
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* A constants manager for Jetpack.
*
* @package automattic/jetpack-constants
*/
namespace Automattic\Jetpack;
/**
* Class Automattic\Jetpack\Constants
*
* Testing constants is hard. Once you define a constant, it's defined. Constants Manager is an
* abstraction layer so that unit tests can set "constants" for tests.
*
* To test your code, you'll need to swap out `defined( 'CONSTANT' )` with `Automattic\Jetpack\Constants::is_defined( 'CONSTANT' )`
* and replace `CONSTANT` with `Automattic\Jetpack\Constants::get_constant( 'CONSTANT' )`. Then in the unit test, you can set the
* constant with `Automattic\Jetpack\Constants::set_constant( 'CONSTANT', $value )` and then clean up after each test with something like
* this:
*
* function tearDown() {
* Automattic\Jetpack\Constants::clear_constants();
* }
*/
class Constants {
/**
* A container for all defined constants.
*
* @access public
* @static
*
* @var array.
*/
public static $set_constants = array();
/**
* Checks if a "constant" has been set in constants Manager
* and has a truthy value (e.g. not null, not false, not 0, any string).
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_true( $name ) {
return self::is_defined( $name ) && self::get_constant( $name );
}
/**
* Checks if a "constant" has been set in constants Manager, and if not,
* checks if the constant was defined with define( 'name', 'value ).
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_defined( $name ) {
return array_key_exists( $name, self::$set_constants )
? true
: defined( $name );
}
/**
* Attempts to retrieve the "constant" from constants Manager, and if it hasn't been set,
* then attempts to get the constant with the constant() function. If that also hasn't
* been set, attempts to get a value from filters.
*
* @param string $name The name of the constant.
*
* @return mixed null if the constant does not exist or the value of the constant.
*/
public static function get_constant( $name ) {
if ( array_key_exists( $name, self::$set_constants ) ) {
return self::$set_constants[ $name ];
}
if ( defined( $name ) ) {
return constant( $name );
}
/**
* Filters the value of the constant.
*
* @since 1.2.0
*
* @param null The constant value to be filtered. The default is null.
* @param String $name The constant name.
*/
return apply_filters( 'jetpack_constant_default_value', null, $name );
}
/**
* Sets the value of the "constant" within constants Manager.
*
* @param string $name The name of the constant.
* @param string $value The value of the constant.
*/
public static function set_constant( $name, $value ) {
self::$set_constants[ $name ] = $value;
}
/**
* Will unset a "constant" from constants Manager if the constant exists.
*
* @param string $name The name of the constant.
*
* @return bool Whether the constant was removed.
*/
public static function clear_single_constant( $name ) {
if ( ! array_key_exists( $name, self::$set_constants ) ) {
return false;
}
unset( self::$set_constants[ $name ] );
return true;
}
/**
* Resets all of the constants within constants Manager.
*/
public static function clear_constants() {
self::$set_constants = array();
}
}

View File

@ -0,0 +1,296 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.18] - 2022-07-26
### Changed
- Updated package dependencies. [#25158]
## [0.8.17] - 2022-07-12
### Changed
- Updated package dependencies.
## [0.8.16] - 2022-07-06
### Changed
- Updated package dependencies. [#24923]
## [0.8.15] - 2022-06-21
### Changed
- Renaming master to trunk. [#24661]
## [0.8.14] - 2022-06-14
### Changed
- Updated package dependencies. [#24529]
## [0.8.13] - 2022-06-08
### Changed
- Reorder JS imports for `import/order` eslint rule. [#24601]
## [0.8.12] - 2022-05-30
### Changed
- Updated package dependencies
## [0.8.11] - 2022-05-18
### Changed
- Updated package dependencies [#24372]
## [0.8.10] - 2022-05-10
### Changed
- Updated package dependencies. [#24302]
## [0.8.9] - 2022-05-04
### Added
- Add missing JavaScript dependencies. [#24096]
### Changed
- Updated package dependencies. [#24095] [#24198]
### Deprecated
- Moved the options class into Connection. [#24095]
## [0.8.8] - 2022-04-26
### Changed
- Updated package dependencies.
- Update package.json metadata.
## [0.8.7] - 2022-04-19
### Changed
- PHPCS: Fix `WordPress.Security.ValidatedSanitizedInput`
## [0.8.6] - 2022-04-12
### Changed
- Updated package dependencies.
## [0.8.5] - 2022-04-06
### Changed
- Updated package dependencies
### Removed
- Removed tracking dependency.
## [0.8.4] - 2022-03-29
### Changed
- Microperformance: Use === null instead of is_null
- Updated package dependencies.
## [0.8.3] - 2022-03-23
### Changed
- Updated package dependencies
## [0.8.2] - 2022-03-15
### Changed
- Updated package dependencies.
## [0.8.1] - 2022-03-08
### Changed
- Updated package dependencies.
## [0.8.0] - 2022-03-02
### Added
- IDC: add detection for possibly dynamic HTTP_HOST being used in WP_SITEURL or WP_HOME.
### Changed
- Bring back the IDC screen in Staging mode.
- Updated package dependencies.
## [0.7.4] - 2022-02-22
### Added
- Add blog ID to event tracking.
## [0.7.3] - 2022-02-16
### Changed
- Updated package dependencies.
## [0.7.2] - 2022-02-09
### Changed
- Updated package dependencies
## [0.7.1] - 2022-02-02
### Changed
- Updated package dependencies.
## [0.7.0] - 2022-01-27
### Changed
- IDC "Safe Mode" admin bar button redesign.
## [0.6.4] - 2022-01-25
### Changed
- Add class notice to the IDC container div.
- Add missing JS peer dependency.
- Updated package dependencies.
## [0.6.3] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
## [0.6.2] - 2022-01-11
### Changed
- Updated package dependencies.
## [0.6.1] - 2022-01-11
### Fixed
- Do not add IDC query args to authenticated request when in offline or staging mode.
## [0.6.0] - 2022-01-04
### Added
- Build and display the new RNA IDC banner.
### Changed
- Switch to pcov for code coverage.
- Updated package dependencies.
- Updated package textdomain from `jetpack` to `jetpack-idc`.
- Updated references to '.jp-recommendations__app-badge' to its new name '.apps-badge'
## [0.5.0] - 2021-12-14
### Added
- Add a method to determine the safe mode status.
### Changed
- Updated package dependencies.
## [0.4.4] - 2021-11-30
### Changed
- Colors: update Jetpack Primary color to match latest brand book.
- Remove now-redundant `output.filename` from Webpack config.
## [0.4.3] - 2021-11-23
### Changed
- Updated package dependencies.
## [0.4.2] - 2021-11-17
## [0.4.1] - 2021-11-16
### Added
- Use monorepo `validate-es` script to validate Webpack builds.
### Changed
- Updated package dependencies
## [0.4.0] - 2021-11-09
### Added
- Add a method to unambiguously determine whether the site is experiencing identity crisis.
### Changed
- Updated package dependencies.
- Update webpack build config. Removes IE 11 support in the JavaScript.
## [0.3.1] - 2021-11-02
### Changed
- Add the idc url query args to remote requests
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
- Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't.
## [0.3.0] - 2021-10-26
### Added
- Add redirect_uri parameter for IDC Start Fresh endpoint.
- Delete the migrate_for_idc option when a remote request returns migrated_for_idc
### Changed
- Updated package dependencies
## [0.2.8] - 2021-10-13
### Changed
- Updated package dependencies.
## [0.2.7] - 2021-10-12
### Added
- Add the new check_response_for_idc method to the Identity_Crisis class
### Changed
- Updated package dependencies
## [0.2.6] - 2021-09-28
### Changed
- Allow Node ^14.17.6 to be used in this project. This shouldn't change the behavior of the code itself.
- IDC: Rename the Identity_Crisis::sync_idc_optin method to Identity_Crisis:should_handle_idc. Add a new filter and constant that use the new name.
- Updated package dependencies.
## [0.2.5] - 2021-08-31
### Changed
- Updated package dependencies.
## [0.2.4] - 2021-08-30
### Changed
- Bump changelogger version
- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
- update annotations versions
- Update to latest webpack, webpack-cli and calypso-build
- Use Node 16.7.0 in tooling. This shouldn't change the behavior of the code itself.
## [0.2.3] - 2021-08-12
### Changed
- Updated package dependencies
## [0.2.2] - 2021-07-27
### Added
- Add jetpack_connection_disconnect_site_wpcom filter.
## [0.2.1] - 2021-07-13
### Changed
- Updated package dependencies.
## [0.2.0] - 2021-06-29
### Added
- Add jetpack_idc_disconnect hook to properly disconnect based on IDC settings and clear IDC options.
### Changed
- Migrate jetpack/v4/identity-crisis endpoints into package.
- Update node version requirement to 14.16.1
## 0.1.0 - 2021-06-15
### Added
- Sync: Adding the Identity_Crisis package.
### Changed
- Updated package dependencies.
- Use Connection/Urls for home_url and site_url functions migrated from Sync.
[0.8.18]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.17...v0.8.18
[0.8.17]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.16...v0.8.17
[0.8.16]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.15...v0.8.16
[0.8.15]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.14...v0.8.15
[0.8.14]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.13...v0.8.14
[0.8.13]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.12...v0.8.13
[0.8.12]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.11...v0.8.12
[0.8.11]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.10...v0.8.11
[0.8.10]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.9...v0.8.10
[0.8.9]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.8...v0.8.9
[0.8.8]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.7...v0.8.8
[0.8.7]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.6...v0.8.7
[0.8.6]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.5...v0.8.6
[0.8.5]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.4...v0.8.5
[0.8.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.3...v0.8.4
[0.8.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.1...v0.8.2
[0.8.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.4...v0.8.0
[0.7.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.3...v0.7.4
[0.7.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.2...v0.7.3
[0.7.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.1...v0.7.2
[0.7.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.4...v0.7.0
[0.6.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.3...v0.6.4
[0.6.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.4.4...v0.5.0
[0.4.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.4.3...v0.4.4
[0.4.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.4.2...v0.4.3
[0.4.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.4.1...v0.4.2
[0.4.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.8...v0.3.0
[0.2.8]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.7...v0.2.8
[0.2.7]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.6...v0.2.7
[0.2.6]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.5...v0.2.6
[0.2.5]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.1.0...v0.2.0

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 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url'), 'version' => 'b0c1a72da90ab2ac7196');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/

Some files were not shown because too many files have changed in this diff Show More