plugin updates
This commit is contained in:
Vendored
+8
-7
@@ -13,7 +13,7 @@ namespace Automattic\Jetpack\Admin_UI;
|
||||
*/
|
||||
class Admin_Menu {
|
||||
|
||||
const PACKAGE_VERSION = '0.2.25';
|
||||
const PACKAGE_VERSION = '0.4.2';
|
||||
|
||||
/**
|
||||
* Whether this class has been initialized
|
||||
@@ -39,6 +39,7 @@ class Admin_Menu {
|
||||
self::$initialized = true;
|
||||
self::handle_akismet_menu();
|
||||
add_action( 'admin_menu', array( __CLASS__, 'admin_menu_hook_callback' ), 1000 ); // Jetpack uses 998.
|
||||
add_action( 'network_admin_menu', array( __CLASS__, 'admin_menu_hook_callback' ), 1000 ); // Jetpack uses 998.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,23 +51,23 @@ class Admin_Menu {
|
||||
*/
|
||||
private static function handle_akismet_menu() {
|
||||
if ( class_exists( 'Akismet_Admin' ) ) {
|
||||
// Prevent Akismet from adding a menu item.
|
||||
add_action(
|
||||
'admin_menu',
|
||||
function () {
|
||||
// Prevent Akismet from adding a menu item.
|
||||
remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
|
||||
|
||||
// Add an Anti-spam menu item for Jetpack.
|
||||
self::add_menu( __( 'Akismet Anti-spam', 'jetpack-admin-ui' ), __( 'Akismet Anti-spam', 'jetpack-admin-ui' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
|
||||
},
|
||||
4
|
||||
);
|
||||
|
||||
// Add an Anti-spam menu item for Jetpack.
|
||||
self::add_menu( __( 'Akismet Anti-spam', 'jetpack-admin-ui' ), __( 'Akismet Anti-spam', 'jetpack-admin-ui' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to the admin_menu hook that will register the enqueued menu items
|
||||
* Callback to the admin_menu and network_admin_menu hooks that will register the enqueued menu items
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -104,7 +105,7 @@ class Admin_Menu {
|
||||
function ( $a, $b ) {
|
||||
$position_a = empty( $a['position'] ) ? 0 : $a['position'];
|
||||
$position_b = empty( $b['position'] ) ? 0 : $b['position'];
|
||||
$result = $position_a - $position_b;
|
||||
$result = $position_a <=> $position_b;
|
||||
|
||||
if ( 0 === $result ) {
|
||||
$result = strcmp( $a['menu_title'], $b['menu_title'] );
|
||||
|
||||
+357
@@ -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.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?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' ) );
|
||||
add_action( 'plugins_loaded', array( Automattic\Jetpack\Script_Data::class, 'configure' ), 1 );
|
||||
} 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' ),
|
||||
);
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$wp_filter['plugins_loaded'][1][] = array(
|
||||
'accepted_args' => 0,
|
||||
'function' => array( Automattic\Jetpack\Script_Data::class, 'configure' ),
|
||||
);
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('wp-i18n'), 'version' => 'b5d2a25bb8ad1698db1c');
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '3efc8c9f2b724b4a0bcb');
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.JetpackScriptDataModule=t():e.JetpackScriptDataModule=t()}(globalThis,(()=>(()=>{var e={537:(e,t,r)=>{"use strict";r.r(t),r.d(t,{getActiveFeatures:()=>a.$l,getAdminUrl:()=>a.A7,getJetpackAdminPageUrl:()=>a.Z$,getMyJetpackUrl:()=>a.n3,getScriptData:()=>a.I4,getSiteData:()=>a.zf});var n=r(352),o={};for(const e in n)"default"!==e&&(o[e]=()=>n[e]);r.d(t,o);var a=r(618)},352:()=>{},618:(e,t,r)=>{"use strict";function n(){return window.JetpackScriptData}function o(){return n().site}function a(e=""){return`${n().site.admin_url}${e}`}function i(e=""){return a(`admin.php?page=jetpack${e}`)}function u(e=""){return a(`admin.php?page=my-jetpack${e}`)}function p(){return n().site.plan?.features?.active??[]}r.d(t,{$l:()=>p,A7:()=>a,I4:()=>n,Z$:()=>i,n3:()=>u,zf:()=>o})}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var a=t[n]={exports:{}};return e[n](a,a.exports,r),a.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};return(()=>{"use strict";r.r(n);var e=r(537),t={};for(const r in e)"default"!==r&&(t[r]=()=>e[r]);r.d(n,t)})(),n})()));
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/*! For license information please see react-jsx-runtime.js.LICENSE.txt */
|
||||
(()=>{"use strict";var r={574:(r,e,t)=>{var o=t(196),n=Symbol.for("react.element"),s=Symbol.for("react.fragment"),a=Object.prototype.hasOwnProperty,f=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0};function _(r,e,t){var o,s={},_=null,i=null;for(o in void 0!==t&&(_=""+t),void 0!==e.key&&(_=""+e.key),void 0!==e.ref&&(i=e.ref),e)a.call(e,o)&&!p.hasOwnProperty(o)&&(s[o]=e[o]);if(r&&r.defaultProps)for(o in e=r.defaultProps)void 0===s[o]&&(s[o]=e[o]);return{$$typeof:n,type:r,key:_,ref:i,props:s,_owner:f.current}}e.Fragment=s,e.jsx=_,e.jsxs=_},93:(r,e,t)=>{r.exports=t(574)},196:r=>{r.exports=window.React}},e={},t=function t(o){var n=e[o];if(void 0!==n)return n.exports;var s=e[o]={exports:{}};return r[o](s,s.exports,t),s.exports}(93);window.ReactJSXRuntime=t})();
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
+761
@@ -0,0 +1,761 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* A public method for adding the async script.
|
||||
*
|
||||
* @deprecated Since 2.1.0, the `strategy` feature should be used instead, with the "defer" setting.
|
||||
*
|
||||
* @param string $script_handle Script handle.
|
||||
*/
|
||||
public static function add_async_script( $script_handle ) {
|
||||
_deprecated_function( __METHOD__, '2.1.0' );
|
||||
|
||||
wp_script_add_data( $script_handle, 'strategy', 'defer' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an async attribute to scripts that can be loaded deferred.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
|
||||
*
|
||||
* @deprecated Since 2.1.0, the `strategy` feature should be used instead.
|
||||
*
|
||||
* @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 ) {
|
||||
_deprecated_function( __METHOD__, '2.1.0' );
|
||||
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.
|
||||
*
|
||||
* @deprecated Since 2.1.0 - use the strategy feature instead.
|
||||
*
|
||||
* @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 ) {
|
||||
_deprecated_function( __METHOD__, '2.1.0' );
|
||||
wp_enqueue_script( $handle, self::get_file_url_for_environment( $min_path, $non_min_path ), $deps, $ver, $in_footer );
|
||||
wp_script_add_data( $handle, 'strategy', 'defer' );
|
||||
}
|
||||
|
||||
// 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 .= $parts['user'] ?? '';
|
||||
$ret .= isset( $parts['pass'] ) ? ':' . $parts['pass'] : '';
|
||||
$ret .= '@';
|
||||
}
|
||||
$ret .= $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 .= implode( '/', $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
|
||||
* @since 2.1.0 Add a new `strategy` option to leverage WP >= 6.3 script strategy feature. The `async` option is deprecated.
|
||||
* @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 deferred, like `Assets::enqueue_async_script()`. Deprecated in favor of `strategy`.
|
||||
* - `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.
|
||||
* - `strategy`: (string) Specify a script strategy to use, eg. `defer` or `async`. Default is `""`.
|
||||
* - `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.
|
||||
* @phan-param array{asset_path?:?string,async?:bool,css_dependencies?:string[],css_path?:?string,dependencies?:string[],enqueue?:bool,in_footer?:bool,media?:string,minify?:?bool,nonmin_path?:string,strategy?:string,textdomain?:string,version?:string} $options
|
||||
* @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"' );
|
||||
}
|
||||
|
||||
if ( isset( $options['async'] ) ) {
|
||||
_deprecated_argument( __METHOD__, '2.1.0', 'The `async` option is deprecated in favor of `strategy`' );
|
||||
}
|
||||
|
||||
$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,
|
||||
'strategy' => '',
|
||||
'textdomain' => null,
|
||||
);
|
||||
'@phan-var array{asset_path:?string,async:bool,css_dependencies:string[],css_path:?string,dependencies:string[],enqueue:bool,in_footer:bool,media:string,minify:?bool,nonmin_path?:string,strategy:string,textdomain:string,version?:string} $options'; // Phan gets confused by the array addition.
|
||||
|
||||
if ( is_string( $options['css_path'] ) && $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 = $options['version'] ?? $asset['version'];
|
||||
} else {
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
$ver = $options['version'] ?? @filemtime( "$dir/$path" );
|
||||
}
|
||||
|
||||
if ( $options['async'] && '' === $options['strategy'] ) { // Handle the deprecated `async` option
|
||||
$options['strategy'] = 'defer';
|
||||
}
|
||||
wp_register_script(
|
||||
$handle,
|
||||
$url,
|
||||
$options['dependencies'],
|
||||
$ver,
|
||||
array(
|
||||
'in_footer' => $options['in_footer'],
|
||||
'strategy' => $options['strategy'],
|
||||
)
|
||||
);
|
||||
|
||||
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 ( is_string( $options['css_path'] ) && $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' );
|
||||
|
||||
// Note: str_starts_with() is not used here, as wp-includes/compat.php may not be loaded at this point.
|
||||
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', false, 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;' );
|
||||
|
||||
// Register the React JSX runtime script - used as a polyfill until we can update JSX transforms. See https://github.com/Automattic/jetpack/issues/38424.
|
||||
// @todo Remove this when we drop support for WordPress 6.5, as well as the script inclusion in test_wp_default_scripts_hook.
|
||||
$jsx_url = self::normalize_path( plugins_url( '../build/react-jsx-runtime.js', __FILE__ ) );
|
||||
$wp_scripts->add( 'react-jsx-runtime', $jsx_url, array( 'react' ), '18.3.1', true );
|
||||
}
|
||||
|
||||
// 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 -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
|
||||
$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 int $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 -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
|
||||
$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 -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
|
||||
$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 int $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 -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
|
||||
$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
|
||||
// .
|
||||
Vendored
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack script data.
|
||||
*
|
||||
* @package automattic/jetpack-assets
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* Class script data
|
||||
*/
|
||||
class Script_Data {
|
||||
|
||||
const SCRIPT_HANDLE = 'jetpack-script-data';
|
||||
|
||||
/**
|
||||
* Configure.
|
||||
*/
|
||||
public static function configure() {
|
||||
/**
|
||||
* Ensure that assets are registered on wp_loaded,
|
||||
* which is fired before *_enqueue_scripts actions.
|
||||
* It means that when the dependent scripts are registered,
|
||||
* the scripts here are already registered.
|
||||
*/
|
||||
add_action( 'wp_loaded', array( self::class, 'register_assets' ) );
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* 1. wp_print_scripts action is fired on both admin and public pages.
|
||||
* On admin pages, it's fired before admin_enqueue_scripts action,
|
||||
* which can be a problem if the consumer package uses admin_enqueue_scripts
|
||||
* to hook into the script data. Thus, we prefer to use admin_print_scripts on admin pages.
|
||||
* 2. We want to render the script data on print, instead of init or enqueue actions,
|
||||
* so that the hook callbacks have enough time and information
|
||||
* to decide whether to update the script data or not.
|
||||
*/
|
||||
$hook = is_admin() ? 'admin_print_scripts' : 'wp_print_scripts';
|
||||
add_action( $hook, array( self::class, 'render_script_data' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register assets.
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
public static function register_assets() {
|
||||
|
||||
Assets::register_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'../build/jetpack-script-data.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-assets',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the script data using an inline script.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render_script_data() {
|
||||
|
||||
$script_data = is_admin() ? self::get_admin_script_data() : self::get_public_script_data();
|
||||
|
||||
$script_data = wp_json_encode(
|
||||
$script_data,
|
||||
JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
sprintf( 'window.JetpackScriptData = %s;', $script_data ),
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin script data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_admin_script_data() {
|
||||
|
||||
global $wp_version;
|
||||
|
||||
$state = array(
|
||||
'site' => array(
|
||||
'admin_url' => esc_url_raw( admin_url() ),
|
||||
'date_format' => get_option( 'date_format' ),
|
||||
'icon' => self::get_site_icon(),
|
||||
'is_multisite' => is_multisite(),
|
||||
'plan' => array(
|
||||
// The properties here should be updated by the consumer package/plugin.
|
||||
// It includes properties like 'product_slug', 'features', etc.
|
||||
'product_slug' => '',
|
||||
),
|
||||
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'rest_root' => esc_url_raw( rest_url() ),
|
||||
'title' => self::get_site_title(),
|
||||
'wp_version' => $wp_version,
|
||||
'wpcom' => array(
|
||||
// This should contain the connected site details like blog_id, is_atomic etc.
|
||||
'blog_id' => 0,
|
||||
),
|
||||
),
|
||||
'user' => array(
|
||||
'current_user' => self::get_current_user_data(),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the admin script data.
|
||||
*
|
||||
* When using this filter, ensure that the data is added only if it is used by some script.
|
||||
* This filter may be called on almost every admin page load. So, one should check if the data is needed/used on that page.
|
||||
* For example, the social (publicize) data is used only on Social admin page, Jetpack settings page and the post editor.
|
||||
* So, the social data should be added only on those pages.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
* @param array $state The script data.
|
||||
*/
|
||||
return apply_filters( 'jetpack_admin_js_script_data', $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin script data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_public_script_data() {
|
||||
|
||||
$state = array(
|
||||
'site' => array(
|
||||
'icon' => self::get_site_icon(),
|
||||
'title' => self::get_site_title(),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the public script data.
|
||||
*
|
||||
* See the docs for `jetpack_admin_js_script_data` filter for more information.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
* @param array $state The script data.
|
||||
*/
|
||||
return apply_filters( 'jetpack_public_js_script_data', $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the site title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_site_title() {
|
||||
$title = get_bloginfo( 'name' );
|
||||
|
||||
return $title ? $title : esc_url_raw( ( get_site_url() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the site icon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_site_icon() {
|
||||
if ( ! has_site_icon() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the site icon using Photon.
|
||||
*
|
||||
* @see https://developer.wordpress.com/docs/photon/
|
||||
*
|
||||
* @param string $url The URL of the site icon.
|
||||
* @param array|string $args An array of arguments, e.g. array( 'w' => '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456).
|
||||
*/
|
||||
return apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_current_user_data() {
|
||||
$current_user = wp_get_current_user();
|
||||
|
||||
return array(
|
||||
'display_name' => $current_user->display_name,
|
||||
'id' => $current_user->ID,
|
||||
);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?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 (int) $a - (int) $b;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} elseif ( ctype_digit( $b ) ) {
|
||||
return 1;
|
||||
} else {
|
||||
$tmp = strcmp( $a, $b );
|
||||
if ( 0 !== $tmp ) {
|
||||
return $tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $al - $bl;
|
||||
}
|
||||
}
|
||||
+76
@@ -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 );
|
||||
},
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from '@automattic/jetpack-script-data';
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '490b076190173a65d8da');
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'd0b88193b5b4008c3108');
|
||||
+1
@@ -0,0 +1 @@
|
||||
.jetpack-sso-admin-create-user-invite-message{width:550px}.jetpack-sso-admin-create-user-invite-message-link-sso{text-decoration:none}#createuser .form-field textarea{width:25em}#createuser .form-field [type=checkbox]{width:1rem}#custom_email_message_description{color:#646970;font-size:12px;max-width:25rem}
|
||||
+1
@@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(function(){const e=document.getElementById("send_user_notification"),d=document.getElementById("user_external_contractor"),t=document.getElementById("invite_user_wpcom"),n=document.getElementById("custom_email_message_block");t&&e&&n&&(t.addEventListener("change",(function(){e.disabled=t.checked,t.checked?(e.checked=!1,d&&(d.disabled=!1),n.style.display="table"):(d&&(d.disabled=!0,d.checked=!1),n.style.display="none")})),t.checked&&(e.disabled=!0,e.checked=!1,n.style.display="table"),t.checked||(d&&(d.disabled=!0),n.style.display="none"))}));
|
||||
+1
@@ -0,0 +1 @@
|
||||
.jetpack-sso-admin-create-user-invite-message{width:550px}.jetpack-sso-admin-create-user-invite-message-link-sso{text-decoration:none}#createuser .form-field textarea{width:25em}#createuser .form-field [type=checkbox]{width:1rem}#custom_email_message_description{color:#646970;font-size:12px;max-width:25rem}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '049775f3b0a647a127f9');
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
#loginform{padding-bottom:92px;position:relative!important}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{bottom:20px;margin-left:-24px;margin-right:-24px;padding:0 24px;position:absolute;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{bottom:auto;margin-left:0;margin-right:0;margin-top:16px;padding:0;position:relative}.jetpack-sso-form-display #jetpack-sso-wrap{bottom:auto;margin-left:0;margin-right:0;margin-top:0;padding:0;position:relative}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;text-align:center;text-decoration:none;width:100%}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#dcdcde;content:"";height:1px;left:0;position:absolute;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;padding:0 8px;position:relative;text-transform:uppercase}#jetpack-sso-wrap .button{align-items:center;display:flex;height:36px;justify-content:center;margin-bottom:16px;width:100%}#jetpack-sso-wrap .button .genericon-wordpress{font-size:24px;margin-right:4px}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0}.jetpack-sso-clear:after{clear:both;content:"";display:table}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(()=>{const e=document.querySelector("body"),t=document.querySelector(".jetpack-sso-toggle"),d=document.getElementById("user_login"),o=document.getElementById("user_pass"),s=document.getElementById("jetpack-sso-wrap"),n=document.getElementById("loginform"),c=document.createElement("div");c.className="jetpack-sso-clear",n.appendChild(c),c.appendChild(document.querySelector("p.forgetmenot")),c.appendChild(document.querySelector("p.submit")),n.appendChild(s),e.classList.add("jetpack-sso-repositioned"),t.addEventListener("click",(t=>{t.preventDefault(),e.classList.toggle("jetpack-sso-form-display"),e.classList.contains("jetpack-sso-form-display")||(d.focus(),o.disabled=!1)}))}));
|
||||
+1
@@ -0,0 +1 @@
|
||||
#loginform{padding-bottom:92px;position:relative!important}.jetpack-sso-repositioned #loginform{padding-bottom:26px}#loginform #jetpack-sso-wrap,#loginform #jetpack-sso-wrap *{box-sizing:border-box}#jetpack-sso-wrap__action,#jetpack-sso-wrap__user{display:none}.jetpack-sso-form-display #jetpack-sso-wrap__action,.jetpack-sso-form-display #jetpack-sso-wrap__user{display:block}#jetpack-sso-wrap{bottom:20px;margin-left:-24px;margin-right:-24px;padding:0 24px;position:absolute;width:100%}.jetpack-sso-repositioned #jetpack-sso-wrap{bottom:auto;margin-left:0;margin-right:0;margin-top:16px;padding:0;position:relative}.jetpack-sso-form-display #jetpack-sso-wrap{bottom:auto;margin-left:0;margin-right:0;margin-top:0;padding:0;position:relative}#loginform #jetpack-sso-wrap p{color:#777;margin-bottom:16px}#jetpack-sso-wrap a{display:block;text-align:center;text-decoration:none;width:100%}#jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:none}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom{display:block}.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default,.jetpack-sso-form-display #loginform>div,.jetpack-sso-form-display #loginform>p{display:none}.jetpack-sso-form-display #loginform #jetpack-sso-wrap{display:block}.jetpack-sso-form-display #loginform{padding:26px 24px}.jetpack-sso-or{margin-bottom:16px;position:relative;text-align:center}.jetpack-sso-or:before{background:#dcdcde;content:"";height:1px;position:absolute;right:0;top:50%;width:100%}.jetpack-sso-or span{background:#fff;color:#777;padding:0 8px;position:relative;text-transform:uppercase}#jetpack-sso-wrap .button{align-items:center;display:flex;height:36px;justify-content:center;margin-bottom:16px;width:100%}#jetpack-sso-wrap .button .genericon-wordpress{font-size:24px;margin-left:4px}#jetpack-sso-wrap__user img{border-radius:50%;display:block;margin:0 auto 16px}#jetpack-sso-wrap__user h2{font-size:21px;font-weight:300;margin-bottom:16px;text-align:center}#jetpack-sso-wrap__user h2 span{font-weight:700}.jetpack-sso-wrap__reauth{margin-bottom:16px}.jetpack-sso-form-display #nav{display:none}.jetpack-sso-form-display #backtoblog{margin:24px 0 0}.jetpack-sso-clear:after{clear:both;content:"";display:table}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '04d208524c748ec232f3');
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded",(function(){function t(){this.querySelector(".jetpack-sso-invitation-tooltip").style.display="block"}function e(t){document.activeElement!==t.target&&(this.querySelector(".jetpack-sso-invitation-tooltip").style.display="none")}document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.sso-disconnected-user)").forEach((function(t){t.innerHTML+=" [?]";const e=document.createElement("span");e.classList.add("jetpack-sso-invitation-tooltip","jetpack-sso-th-tooltip");const n=window.Jetpack_SSOTooltip.tooltipString;function o(){t.appendChild(e),e.style.display="block"}function i(){document.activeElement!==t&&t.removeChild(e)}e.innerHTML+=n,t.addEventListener("mouseenter",o),t.addEventListener("focus",o),t.addEventListener("mouseleave",i),t.addEventListener("blur",i)})),document.querySelectorAll(".jetpack-sso-invitation-tooltip-icon:not(.jetpack-sso-status-column)").forEach((function(n){n.addEventListener("mouseenter",t),n.addEventListener("focus",t),n.addEventListener("mouseleave",e),n.addEventListener("blur",e)}))}));
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'd9dbf909a3d10fb26f39');
|
||||
<?php return array('dependencies' => array(), 'version' => '37afc9296c403dfe5f38');
|
||||
|
||||
Vendored
+1
-1
@@ -1 +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})();
|
||||
(()=>{var e={7775: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}(7775);window.analytics=t})();
|
||||
+10
-3
@@ -12,6 +12,13 @@
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
|
||||
/**
|
||||
* Disable direct access.
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( IXR_Client::class ) ) {
|
||||
require_once ABSPATH . WPINC . '/class-IXR.php';
|
||||
}
|
||||
@@ -70,7 +77,7 @@ class Jetpack_IXR_Client extends IXR_Client {
|
||||
/**
|
||||
* Perform the IXR request.
|
||||
*
|
||||
* @param string[] ...$args IXR args.
|
||||
* @param mixed ...$args IXR method and args.
|
||||
*
|
||||
* @return bool True if request succeeded, false otherwise.
|
||||
*/
|
||||
@@ -146,10 +153,10 @@ class Jetpack_IXR_Client extends IXR_Client {
|
||||
$code = $match[1];
|
||||
$message = $match[2];
|
||||
$status = $fault_code;
|
||||
return new \WP_Error( $code, $message, $status );
|
||||
return new WP_Error( $code, $message, $status );
|
||||
}
|
||||
|
||||
return new \WP_Error( "IXR_{$fault_code}", $fault_string );
|
||||
return new WP_Error( "IXR_{$fault_code}", $fault_string );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-4
@@ -65,7 +65,6 @@ class Jetpack_Options {
|
||||
'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.
|
||||
@@ -117,8 +116,6 @@ class Jetpack_Options {
|
||||
'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.
|
||||
@@ -129,6 +126,11 @@ class Jetpack_Options {
|
||||
'dismissed_backup_review_restore', // (bool) Determines if the component review request is dismissed for successful restore requests.
|
||||
'dismissed_backup_review_backups', // (bool) Determines if the component review request is dismissed for successful backup requests.
|
||||
'identity_crisis_url_secret', // (array) The IDC URL secret and its expiration date.
|
||||
'identity_crisis_ip_requester', // (array) The IDC IP address and its expiration date.
|
||||
'dismissed_welcome_banner', // (bool) Determines if the welcome banner has been dismissed or not.
|
||||
'recommendations_evaluation', // (object) Catalog of recommended modules with corresponding score following successful site evaluation in Welcome Banner.
|
||||
'dismissed_recommendations', // (bool) Determines if the recommendations have been dismissed or not.
|
||||
'historically_active_modules', // (array) List of installed plugins/enabled modules that have at one point in time been active and working
|
||||
);
|
||||
}
|
||||
|
||||
@@ -630,7 +632,6 @@ class Jetpack_Options {
|
||||
'jetpack_protect_key',
|
||||
'jetpack_protect_blocked_attempts',
|
||||
'jetpack_protect_activating',
|
||||
'jetpack_connection_banner_ab',
|
||||
'jetpack_active_plan',
|
||||
'jetpack_activation_source',
|
||||
'jetpack_site_products',
|
||||
|
||||
+3
-3
@@ -93,9 +93,9 @@ class Jetpack_Signature {
|
||||
// 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
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Used to generate a cryptographic signature of the post data. Not actually using any of it here.
|
||||
if ( empty( $body ) && is_array( $_POST ) && $_POST !== array() ) {
|
||||
$body = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$body = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- We need all of $_POST in order to generate a cryptographic signature of the post data.
|
||||
}
|
||||
}
|
||||
} elseif ( isset( $_SERVER['REQUEST_METHOD'] ) && 'PUT' === strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
|
||||
@@ -160,7 +160,7 @@ class Jetpack_Signature {
|
||||
|
||||
$signature_details = compact( 'token', 'timestamp', 'nonce', 'body_hash', 'method', 'url' );
|
||||
|
||||
if ( 0 !== strpos( $token, "$this->token:" ) ) {
|
||||
if ( ! str_starts_with( $token, "$this->token:" ) ) {
|
||||
return new WP_Error( 'token_mismatch', 'Incorrect token', compact( 'signature_details' ) );
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -269,7 +269,7 @@ class Jetpack_XMLRPC_Server {
|
||||
* 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.
|
||||
* @param array|ArrayAccess $request An array containing at minimum nonce and local_user keys.
|
||||
*
|
||||
* @return \WP_Error|array
|
||||
*/
|
||||
@@ -373,7 +373,7 @@ class Jetpack_XMLRPC_Server {
|
||||
* 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.
|
||||
* @param array|ArrayAccess $request An array containing at minimum a nonce key and a local_username key.
|
||||
*
|
||||
* @return \WP_Error|array
|
||||
*/
|
||||
@@ -440,8 +440,8 @@ class Jetpack_XMLRPC_Server {
|
||||
* 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.
|
||||
* @param array|ArrayAccess $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 ) {
|
||||
@@ -521,6 +521,7 @@ class Jetpack_XMLRPC_Server {
|
||||
* Getter for the local user to act as.
|
||||
*
|
||||
* @param array $request the current request data.
|
||||
* @return WP_User|IXR_Error|false IXR_Error if the request is missing a local_user field, WP_User object on success, or false on failure to find a user.
|
||||
*/
|
||||
private function fetch_and_verify_local_user( $request ) {
|
||||
if ( empty( $request['local_user'] ) ) {
|
||||
@@ -544,6 +545,7 @@ class Jetpack_XMLRPC_Server {
|
||||
* Gets the user object by its data.
|
||||
*
|
||||
* @param string $user_id can be any identifying user data.
|
||||
* @return WP_User|false WP_User object on success, false on failure.
|
||||
*/
|
||||
private function get_user_by_anything( $user_id ) {
|
||||
$user = get_user_by( 'login', $user_id );
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
/**
|
||||
* Authorize_Json_Api handler class.
|
||||
* Used to handle connections via JSON API.
|
||||
* Ported from the Jetpack class.
|
||||
*
|
||||
* @since 2.7.6 Ported from the Jetpack class.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* Authorize_Json_Api handler class.
|
||||
*/
|
||||
class Authorize_Json_Api {
|
||||
/**
|
||||
* Verified data for JSON authorization request
|
||||
*
|
||||
* @since 2.7.6
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $json_api_authorization_request = array();
|
||||
|
||||
/**
|
||||
* Verifies the request by checking the signature
|
||||
*
|
||||
* @since jetpack-4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
|
||||
* passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
|
||||
* @since 2.7.6 Ported from Jetpack to the Connection package.
|
||||
*
|
||||
* @param null|array $environment Value to override $_REQUEST.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function verify_json_api_authorization_request( $environment = null ) {
|
||||
$environment = $environment === null
|
||||
? $_REQUEST // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verification handled later in function and request data are 1) used to verify a cryptographic signature of the request data and 2) sanitized later in function.
|
||||
: $environment;
|
||||
|
||||
if ( ! isset( $environment['token'] ) ) {
|
||||
wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
|
||||
$token = ( new Tokens() )->get_access_token( (int) $env_user_id, $env_token );
|
||||
if ( ! $token || empty( $token->secret ) ) {
|
||||
wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack-connection' );
|
||||
|
||||
// Host has encoded the request URL, probably as a result of a bad http => https redirect.
|
||||
if (
|
||||
preg_match( '/https?%3A%2F%2F/i', esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) > 0 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- no site changes, we're erroring out.
|
||||
) {
|
||||
/**
|
||||
* Jetpack authorisation request Error.
|
||||
*
|
||||
* @since jetpack-7.5.0
|
||||
*/
|
||||
do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
|
||||
$die_error = sprintf(
|
||||
/* translators: %s is a URL */
|
||||
__( 'Your site is incorrectly double-encoding redirects from http to https. This is preventing Jetpack from authenticating your connection. Please visit our <a href="%s">support page</a> for details about how to resolve this.', 'jetpack-connection' ),
|
||||
esc_url( Redirect::get_url( 'jetpack-support-double-encoding' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$jetpack_signature = new \Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
|
||||
|
||||
if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
|
||||
$signature = $jetpack_signature->sign_request(
|
||||
$environment['token'],
|
||||
$environment['timestamp'],
|
||||
$environment['nonce'],
|
||||
'',
|
||||
'GET',
|
||||
$environment['jetpack_json_api_original_query'],
|
||||
null,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
$signature = $jetpack_signature->sign_current_request(
|
||||
array(
|
||||
'body' => null,
|
||||
'method' => 'GET',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $signature ) {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif ( is_wp_error( $signature ) ) {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
|
||||
if ( is_ssl() ) {
|
||||
// If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well.
|
||||
$signature = $jetpack_signature->sign_current_request(
|
||||
array(
|
||||
'scheme' => 'http',
|
||||
'body' => null,
|
||||
'method' => 'GET',
|
||||
)
|
||||
);
|
||||
if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = (int) $environment['timestamp'];
|
||||
$nonce = stripslashes( (string) $environment['nonce'] );
|
||||
|
||||
if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
|
||||
// De-nonce the nonce, at least for 5 minutes.
|
||||
// We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed).
|
||||
$old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
|
||||
if ( $old_nonce_time < time() - 300 ) {
|
||||
wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack-connection' ) );
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_decode(
|
||||
base64_decode( stripslashes( $environment['data'] ) ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
);
|
||||
$data_filters = array(
|
||||
'state' => 'opaque',
|
||||
'client_id' => 'int',
|
||||
'client_title' => 'string',
|
||||
'client_image' => 'url',
|
||||
);
|
||||
|
||||
foreach ( $data_filters as $key => $sanitation ) {
|
||||
if ( ! isset( $data->$key ) ) {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
switch ( $sanitation ) {
|
||||
case 'int':
|
||||
$this->json_api_authorization_request[ $key ] = (int) $data->$key;
|
||||
break;
|
||||
case 'opaque':
|
||||
$this->json_api_authorization_request[ $key ] = (string) $data->$key;
|
||||
break;
|
||||
case 'string':
|
||||
$this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
|
||||
break;
|
||||
case 'url':
|
||||
$this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
|
||||
wp_die(
|
||||
wp_kses(
|
||||
$die_error,
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Access Code details to the public-api.wordpress.com redirect.
|
||||
*
|
||||
* @since 2.7.6 Ported from Jetpack to the Connection package.
|
||||
*
|
||||
* @param string $redirect_to URL.
|
||||
* @param string $original_redirect_to URL.
|
||||
* @param \WP_User $user WP_User for the redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
return add_query_arg(
|
||||
urlencode_deep(
|
||||
array(
|
||||
'jetpack-code' => get_user_meta(
|
||||
$user->ID,
|
||||
'jetpack_json_api_' . $this->json_api_authorization_request['client_id'],
|
||||
true
|
||||
),
|
||||
'jetpack-user-id' => (int) $user->ID,
|
||||
'jetpack-state' => $this->json_api_authorization_request['state'],
|
||||
)
|
||||
),
|
||||
$redirect_to
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If someone logs in to approve API access, store the Access Code in usermeta.
|
||||
*
|
||||
* @since 2.7.6 Ported from Jetpack to the Connection package.
|
||||
*
|
||||
* @param string $user_login Unused.
|
||||
* @param \WP_User $user User logged in.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function store_json_api_authorization_token( $user_login, $user ) {
|
||||
add_filter( 'login_redirect', array( $this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
|
||||
add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_public_api_domain' ) );
|
||||
$token = wp_generate_password( 32, false );
|
||||
update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML for the JSON API authorization notice.
|
||||
*
|
||||
* @since 2.7.6 Ported from Jetpack to the Connection package.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function login_message_json_api_authorization() {
|
||||
return '<p class="message">' . sprintf(
|
||||
/* translators: Name/image of the client requesting authorization */
|
||||
esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack-connection' ),
|
||||
'<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
|
||||
) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
|
||||
}
|
||||
}
|
||||
Vendored
+46
-33
@@ -8,6 +8,10 @@
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use WP_Error;
|
||||
|
||||
// `wp_remote_request` returns an array with a particular format.
|
||||
'@phan-type _WP_Remote_Response_Array = array{headers:\WpOrg\Requests\Utility\CaseInsensitiveDictionary,body:string,response:array{code:int,message:string},cookies:\WP_HTTP_Cookie[],filename:?string,http_response:WP_HTTP_Requests_Response}';
|
||||
|
||||
/**
|
||||
* The Client class that is used to connect to WordPress.com Jetpack API.
|
||||
@@ -18,9 +22,10 @@ class Client {
|
||||
/**
|
||||
* Makes an authorized remote request using Jetpack_Signature
|
||||
*
|
||||
* @param array $args the arguments for the remote request.
|
||||
* @param array|String $body the request body.
|
||||
* @param array $args the arguments for the remote request.
|
||||
* @param array|string|null $body the request body.
|
||||
* @return array|WP_Error WP HTTP response on success
|
||||
* @phan-return _WP_Remote_Response_Array|WP_Error
|
||||
*/
|
||||
public static function remote_request( $args, $body = null ) {
|
||||
if ( isset( $args['url'] ) ) {
|
||||
@@ -35,7 +40,7 @@ class Client {
|
||||
}
|
||||
|
||||
$result = self::build_signed_request( $args, $body );
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -64,12 +69,12 @@ class Client {
|
||||
/**
|
||||
* 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 {
|
||||
* @param array $args the arguments for the remote request.
|
||||
* @param array|string|null $body the request body.
|
||||
* @return WP_Error|array{url:string,request:array,auth:array} {
|
||||
* An array containing URL and request items.
|
||||
*
|
||||
* @type String $url The request URL.
|
||||
* @type string $url The request URL.
|
||||
* @type array $request Request arguments.
|
||||
* @type array $auth Authorization data.
|
||||
* }
|
||||
@@ -88,6 +93,7 @@ class Client {
|
||||
'blog_id' => 0,
|
||||
'auth_location' => Constants::get_constant( 'JETPACK_CLIENT__AUTH_LOCATION' ),
|
||||
'method' => 'POST',
|
||||
'format' => 'json',
|
||||
'timeout' => 10,
|
||||
'redirection' => 0,
|
||||
'headers' => array(),
|
||||
@@ -106,7 +112,7 @@ class Client {
|
||||
|
||||
$token = ( new Tokens() )->get_access_token( $args['user_id'] );
|
||||
if ( ! $token ) {
|
||||
return new \WP_Error( 'missing_token' );
|
||||
return new WP_Error( 'missing_token' );
|
||||
}
|
||||
|
||||
$method = strtoupper( $args['method'] );
|
||||
@@ -121,8 +127,8 @@ class Client {
|
||||
$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' );
|
||||
if ( ! $secret ) {
|
||||
return new WP_Error( 'malformed_token' );
|
||||
}
|
||||
|
||||
$token_key = sprintf(
|
||||
@@ -140,7 +146,7 @@ class Client {
|
||||
if ( function_exists( 'wp_generate_password' ) ) {
|
||||
$nonce = wp_generate_password( 10, false );
|
||||
} else {
|
||||
$nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 );
|
||||
$nonce = substr( sha1( (string) wp_rand( 0, 1000000 ) ), 0, 10 );
|
||||
}
|
||||
|
||||
// Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing.
|
||||
@@ -151,20 +157,22 @@ class Client {
|
||||
// Allow arrays to be used in passing data.
|
||||
$body_to_hash = $body;
|
||||
|
||||
if ( is_array( $body ) ) {
|
||||
if ( $args['format'] === 'jsonl' ) {
|
||||
parse_str( $body, $body_to_hash );
|
||||
}
|
||||
if ( is_array( $body_to_hash ) ) {
|
||||
// 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 ( array() !== $body ) {
|
||||
$body_to_hash = wp_json_encode( self::_stringify_data( $body ) );
|
||||
if ( array() !== $body_to_hash ) {
|
||||
$body_to_hash = wp_json_encode( self::_stringify_data( $body_to_hash ) );
|
||||
} else {
|
||||
$body_to_hash = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_string( $body_to_hash ) ) {
|
||||
return new \WP_Error( 'invalid_body', 'Body is malformed.' );
|
||||
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
|
||||
}
|
||||
|
||||
@@ -192,7 +200,7 @@ class Client {
|
||||
|
||||
$signature = $jetpack_signature->sign_request( $token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false );
|
||||
|
||||
if ( ! $signature || is_wp_error( $signature ) ) {
|
||||
if ( is_wp_error( $signature ) ) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
@@ -229,10 +237,11 @@ class Client {
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param String $url the request URL.
|
||||
* @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.
|
||||
* @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
|
||||
* @phan-return _WP_Remote_Response_Array|WP_Error
|
||||
*/
|
||||
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' );
|
||||
@@ -312,8 +321,9 @@ class Client {
|
||||
/**
|
||||
* 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.
|
||||
* @param array|WP_Error $response Response array from `wp_remote_request`, or WP_Error on error.
|
||||
* @param bool $force_set whether to force setting the time difference.
|
||||
* @phan-param _WP_Remote_Response_Array|WP_Error $response
|
||||
*/
|
||||
public static function set_time_diff( &$response, $force_set = false ) {
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
@@ -353,7 +363,7 @@ class Client {
|
||||
* @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.
|
||||
* @return array Validated arguments.
|
||||
*/
|
||||
public static function validate_args_for_wpcom_json_api_request(
|
||||
$path,
|
||||
@@ -370,6 +380,7 @@ class Client {
|
||||
array(
|
||||
'headers' => 'array',
|
||||
'method' => 'string',
|
||||
'format' => 'string',
|
||||
'timeout' => 'int',
|
||||
'redirection' => 'int',
|
||||
'stream' => 'boolean',
|
||||
@@ -403,13 +414,14 @@ class Client {
|
||||
/**
|
||||
* 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`.
|
||||
* @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 null|string|array $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.
|
||||
* @phan-return _WP_Remote_Response_Array|WP_Error
|
||||
*/
|
||||
public static function wpcom_json_api_request_as_user(
|
||||
$path,
|
||||
@@ -435,12 +447,13 @@ class Client {
|
||||
/**
|
||||
* 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'.
|
||||
* @param string $path The API endpoint relative path.
|
||||
* @param string $version The API version.
|
||||
* @param array $args Request arguments.
|
||||
* @param array|string|null $body Request body.
|
||||
* @param string $base_api_path (optional) the API base path override, defaults to 'rest'.
|
||||
* @return array|WP_Error $response Data.
|
||||
* @phan-return _WP_Remote_Response_Array|WP_Error
|
||||
*/
|
||||
public static function wpcom_json_api_request_as_blog(
|
||||
$path,
|
||||
@@ -467,7 +480,7 @@ class Client {
|
||||
* 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.
|
||||
* @param mixed $data the data that needs to be stringified.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
|
||||
Vendored
+9
-5
@@ -702,11 +702,15 @@ class Error_Handler {
|
||||
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
|
||||
wp_admin_notice(
|
||||
esc_html( $message ),
|
||||
array(
|
||||
'type' => 'error',
|
||||
'dismissible' => true,
|
||||
'additional_classes' => array( 'jetpack-message', 'jp-connect' ),
|
||||
'attributes' => array( 'style' => 'display:block !important;' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+58
@@ -7,8 +7,13 @@
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack\Connection\REST_Connector;
|
||||
use Jetpack_Options;
|
||||
use WP_CLI;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Heartbeat sends a batch of stats to wp.com once a day
|
||||
@@ -73,6 +78,8 @@ class Heartbeat {
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
WP_CLI::add_command( 'jetpack-heartbeat', array( $this, 'cli_callback' ) );
|
||||
}
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'initialize_rest_api' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,4 +256,55 @@ class Heartbeat {
|
||||
WP_CLI::line( sprintf( __( 'Last heartbeat sent at: %s', 'jetpack-connection' ), $last_date ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the heartbeat REST API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize_rest_api() {
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/heartbeat/data',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'rest_heartbeat_data' ),
|
||||
'permission_callback' => array( $this, 'rest_heartbeat_data_permission_check' ),
|
||||
'args' => array(
|
||||
'prefix' => array(
|
||||
'description' => __( 'Prefix to add before the stats identifiers.', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to retrieve the heartbeat data.
|
||||
*
|
||||
* @param WP_REST_Request $request The request data.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rest_heartbeat_data( WP_REST_Request $request ) {
|
||||
return static::generate_stats_array( $request->get_param( 'prefix' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permissions for the `get_heartbeat_data` endpoint.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function rest_heartbeat_data_permission_check() {
|
||||
if ( current_user_can( 'jetpack_connect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_heartbeat_data', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+118
-35
@@ -10,13 +10,16 @@ namespace Automattic\Jetpack\Connection;
|
||||
use Automattic\Jetpack\A8c_Mc_Stats;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Heartbeat;
|
||||
use Automattic\Jetpack\Partner;
|
||||
use Automattic\Jetpack\Roles;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Terms_Of_Service;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use IXR_Error;
|
||||
use Jetpack_IXR_Client;
|
||||
use Jetpack_Options;
|
||||
use Jetpack_XMLRPC_Server;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
@@ -70,6 +73,13 @@ class Manager {
|
||||
*/
|
||||
private static $extra_register_params = array();
|
||||
|
||||
/**
|
||||
* We store ID's of users already disconnected to prevent multiple disconnect requests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $disconnected_users = array();
|
||||
|
||||
/**
|
||||
* Initialize the object.
|
||||
* Make sure to call the "Configure" first.
|
||||
@@ -101,7 +111,7 @@ class Manager {
|
||||
);
|
||||
|
||||
$manager->setup_xmlrpc_handlers(
|
||||
$_GET, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
null,
|
||||
$manager->has_connected_owner(),
|
||||
$manager->verify_xml_rpc_signature()
|
||||
);
|
||||
@@ -126,6 +136,10 @@ class Manager {
|
||||
|
||||
Webhooks::init( $manager );
|
||||
|
||||
// Unlink user before deleting the user from WP.com.
|
||||
add_action( 'deleted_user', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
add_action( 'remove_user_from_blog', array( $manager, 'disconnect_user_force' ), 9, 1 );
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
@@ -138,38 +152,40 @@ class Manager {
|
||||
|
||||
// Initialize token locks.
|
||||
new Tokens_Locks();
|
||||
|
||||
// Initial Partner management.
|
||||
Partner::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the XMLRPC request handlers.
|
||||
*
|
||||
* @since 1.25.0 Deprecate $is_active param.
|
||||
* @since 2.8.4 Deprecate $request_params param.
|
||||
*
|
||||
* @param array $request_params incoming request parameters.
|
||||
* @param bool $has_connected_owner Whether the site has a connected owner.
|
||||
* @param bool $is_signed whether the signature check has been successful.
|
||||
* @param \Jetpack_XMLRPC_Server $xmlrpc_server (optional) an instance of the server to use instead of instantiating a new one.
|
||||
* @param array|null $deprecated Deprecated. Not used.
|
||||
* @param bool $has_connected_owner Whether the site has a connected owner.
|
||||
* @param bool $is_signed whether the signature check has been successful.
|
||||
* @param Jetpack_XMLRPC_Server $xmlrpc_server (optional) an instance of the server to use instead of instantiating a new one.
|
||||
*/
|
||||
public function setup_xmlrpc_handlers(
|
||||
$request_params,
|
||||
$deprecated,
|
||||
$has_connected_owner,
|
||||
$is_signed,
|
||||
\Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
Jetpack_XMLRPC_Server $xmlrpc_server = null
|
||||
) {
|
||||
add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ), 1000, 2 );
|
||||
|
||||
if (
|
||||
! isset( $request_params['for'] )
|
||||
|| 'jetpack' !== $request_params['for']
|
||||
) {
|
||||
if ( $deprecated !== null ) {
|
||||
_deprecated_argument( __METHOD__, '2.8.4' );
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We are using the 'for' request param to early return unless it's 'jetpack'.
|
||||
if ( ! isset( $_GET['for'] ) || 'jetpack' !== $_GET['for'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Alternate XML-RPC, via ?for=jetpack&jetpack=comms.
|
||||
if (
|
||||
isset( $request_params['jetpack'] )
|
||||
&& 'comms' === $request_params['jetpack']
|
||||
) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This just determines whether to handle the request as an XML-RPC request. The actual XML-RPC endpoints do the appropriate nonce checking where applicable. Plus we make sure to clear all cookies via require_jetpack_authentication called later in method.
|
||||
if ( isset( $_GET['jetpack'] ) && 'comms' === $_GET['jetpack'] ) {
|
||||
if ( ! Constants::is_defined( 'XMLRPC_REQUEST' ) ) {
|
||||
// Use the real constant here for WordPress' sake.
|
||||
define( 'XMLRPC_REQUEST', true );
|
||||
@@ -183,13 +199,16 @@ class Manager {
|
||||
if ( ! Constants::get_constant( 'XMLRPC_REQUEST' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Display errors can cause the XML to be not well formed.
|
||||
// This only affects Jetpack XML-RPC endpoints received from WordPress.com servers.
|
||||
// All other XML-RPC requests are unaffected.
|
||||
@ini_set( 'display_errors', false ); // phpcs:ignore
|
||||
|
||||
if ( $xmlrpc_server ) {
|
||||
$this->xmlrpc_server = $xmlrpc_server;
|
||||
} else {
|
||||
$this->xmlrpc_server = new \Jetpack_XMLRPC_Server();
|
||||
$this->xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
}
|
||||
|
||||
$this->require_jetpack_authentication();
|
||||
@@ -235,6 +254,8 @@ class Manager {
|
||||
* from /xmlrpc.php so that we're replicating it as closely as possible.
|
||||
*
|
||||
* @todo Tighten $wp_xmlrpc_server_class a bit to make sure it doesn't do bad things.
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function alternate_xmlrpc() {
|
||||
// Some browser-embedded clients send cookies. We don't want them.
|
||||
@@ -274,7 +295,7 @@ class Manager {
|
||||
$jetpack_methods = array();
|
||||
|
||||
foreach ( $methods as $method => $callback ) {
|
||||
if ( 0 === strpos( $method, 'jetpack.' ) ) {
|
||||
if ( str_starts_with( $method, 'jetpack.' ) ) {
|
||||
$jetpack_methods[ $method ] = $callback;
|
||||
}
|
||||
}
|
||||
@@ -436,12 +457,12 @@ class Manager {
|
||||
}
|
||||
|
||||
$jetpack_signature = new \Jetpack_Signature( $token->secret, (int) \Jetpack_Options::get_option( 'time_diff' ) );
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Used to verify a cryptographic signature of the post data. Also a nonce is verified later in the function.
|
||||
if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
|
||||
$post_data = $_POST;
|
||||
$post_data = $_POST; // We need all of $_POST in order to verify a cryptographic signature of the post data.
|
||||
$file_hashes = array();
|
||||
foreach ( $post_data as $post_data_key => $post_data_value ) {
|
||||
if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
|
||||
if ( ! str_starts_with( $post_data_key, '_jetpack_file_hmac_' ) ) {
|
||||
continue;
|
||||
}
|
||||
$post_data_key = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
|
||||
@@ -857,6 +878,22 @@ class Manager {
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force user disconnect.
|
||||
*
|
||||
* @param int $user_id Local (external) user ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnect_user_force( $user_id ) {
|
||||
if ( ! (int) $user_id ) {
|
||||
// Missing user ID.
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->disconnect_user( $user_id, true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks the current user from the linked WordPress.com user.
|
||||
*
|
||||
@@ -878,6 +915,11 @@ class Manager {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( in_array( $user_id, self::$disconnected_users, true ) ) {
|
||||
// The user is already disconnected.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to disconnect the user from WordPress.com.
|
||||
$is_disconnected_from_wpcom = $this->unlink_user_from_wpcom( $user_id );
|
||||
|
||||
@@ -907,6 +949,8 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
self::$disconnected_users[] = $user_id;
|
||||
|
||||
return $is_disconnected_from_wpcom && $is_disconnected_locally;
|
||||
}
|
||||
|
||||
@@ -1188,7 +1232,7 @@ class Manager {
|
||||
$jetpack_public = false;
|
||||
}
|
||||
|
||||
\Jetpack_Options::update_options(
|
||||
Jetpack_Options::update_options(
|
||||
array(
|
||||
'id' => (int) $registration_details->jetpack_id,
|
||||
'public' => $jetpack_public,
|
||||
@@ -1199,6 +1243,13 @@ class Manager {
|
||||
|
||||
$this->get_tokens()->update_blog_token( (string) $registration_details->jetpack_secret );
|
||||
|
||||
if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->get_tokens()->get_access_token() ) {
|
||||
return new WP_Error(
|
||||
'connection_data_save_failed',
|
||||
'Failed to save connection data in the database'
|
||||
);
|
||||
}
|
||||
|
||||
$alternate_authorization_url = isset( $registration_details->alternate_authorization_url ) ? $registration_details->alternate_authorization_url : '';
|
||||
|
||||
add_filter(
|
||||
@@ -1829,11 +1880,16 @@ class Manager {
|
||||
/**
|
||||
* Builds a URL to the Jetpack connection auth page.
|
||||
*
|
||||
* @param WP_User $user (optional) defaults to the current logged in user.
|
||||
* @param String $redirect (optional) a redirect URL to use instead of the default.
|
||||
* @since 2.7.6 Added optional $from and $raw parameters.
|
||||
*
|
||||
* @param WP_User|null $user (optional) defaults to the current logged in user.
|
||||
* @param string|null $redirect (optional) a redirect URL to use instead of the default.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
*
|
||||
* @return string Connect URL.
|
||||
*/
|
||||
public function get_authorization_url( $user = null, $redirect = null ) {
|
||||
public function get_authorization_url( $user = null, $redirect = null, $from = false, $raw = false ) {
|
||||
if ( empty( $user ) ) {
|
||||
$user = wp_get_current_user();
|
||||
}
|
||||
@@ -1924,7 +1980,30 @@ class Manager {
|
||||
|
||||
$api_url = $this->api_url( 'authorize' );
|
||||
|
||||
return add_query_arg( $body, $api_url );
|
||||
$url = add_query_arg( $body, $api_url );
|
||||
|
||||
if ( is_network_admin() ) {
|
||||
$url = add_query_arg( 'is_multisite', network_admin_url( 'admin.php?page=jetpack-settings' ), $url );
|
||||
}
|
||||
|
||||
if ( $from ) {
|
||||
$url = add_query_arg( 'from', $from, $url );
|
||||
}
|
||||
|
||||
if ( $raw ) {
|
||||
$url = esc_url_raw( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the URL used when connecting a user to a WordPress.com account.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @since 2.7.6 Added $raw parameter.
|
||||
*
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
*/
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2047,7 +2126,7 @@ class Manager {
|
||||
( new Nonce_Handler() )->clean_all();
|
||||
|
||||
/**
|
||||
* Fires when a site is disconnected.
|
||||
* Fires before a site is disconnected.
|
||||
*
|
||||
* @since 1.36.3
|
||||
*/
|
||||
@@ -2271,7 +2350,7 @@ class Manager {
|
||||
* Handles a getOptions XMLRPC method call.
|
||||
*
|
||||
* @param array $args method call arguments.
|
||||
* @return an amended XMLRPC server options array.
|
||||
* @return array|IXR_Error An amended XMLRPC server options array.
|
||||
*/
|
||||
public function jetpack_get_options( $args ) {
|
||||
global $wp_xmlrpc_server;
|
||||
@@ -2545,17 +2624,21 @@ class Manager {
|
||||
/**
|
||||
* Get the WPCOM or self-hosted site ID.
|
||||
*
|
||||
* @return int|WP_Error
|
||||
* @param bool $quiet Return null instead of an error.
|
||||
*
|
||||
* @return int|WP_Error|null
|
||||
*/
|
||||
public static function get_site_id() {
|
||||
public static function get_site_id( $quiet = false ) {
|
||||
$is_wpcom = ( defined( 'IS_WPCOM' ) && IS_WPCOM );
|
||||
$site_id = $is_wpcom ? get_current_blog_id() : \Jetpack_Options::get_option( 'id' );
|
||||
if ( ! $site_id ) {
|
||||
return new \WP_Error(
|
||||
'unavailable_site_id',
|
||||
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
|
||||
403
|
||||
);
|
||||
return $quiet
|
||||
? null
|
||||
: new \WP_Error(
|
||||
'unavailable_site_id',
|
||||
__( 'Sorry, something is wrong with your Jetpack connection.', 'jetpack-connection' ),
|
||||
403
|
||||
);
|
||||
}
|
||||
return (int) $site_id;
|
||||
}
|
||||
|
||||
+67
-2
@@ -30,11 +30,31 @@ class Package_Version_Tracker {
|
||||
*/
|
||||
const CACHED_FAILED_REQUEST_EXPIRATION = 1 * HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Transient key for rate limiting the package version requests;
|
||||
*/
|
||||
const RATE_LIMITER_KEY = 'jetpack_update_remote_package_last_query';
|
||||
|
||||
/**
|
||||
* Only allow one versions check (and request) per minute.
|
||||
*/
|
||||
const RATE_LIMITER_TIMEOUT = MINUTE_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() {
|
||||
// Do not run too early or all the modules may not be loaded.
|
||||
if ( ! did_action( 'init' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The version check is being rate limited.
|
||||
if ( $this->is_rate_limiting() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the package versions.
|
||||
*
|
||||
@@ -65,13 +85,43 @@ class Package_Version_Tracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the package versions:
|
||||
* Updates the package versions option.
|
||||
*
|
||||
* @param array $package_versions The package versions.
|
||||
*/
|
||||
protected function update_package_versions_option( $package_versions ) {
|
||||
if ( ! $this->is_sync_enabled() ) {
|
||||
$this->update_package_versions_via_remote_request( $package_versions );
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( self::PACKAGE_VERSION_OPTION, $package_versions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether Jetpack Sync is enabled.
|
||||
*
|
||||
* @return boolean true if Sync is present and enabled, false otherwise
|
||||
*/
|
||||
protected function is_sync_enabled() {
|
||||
if ( class_exists( 'Automattic\Jetpack\Sync\Settings' ) && \Automattic\Jetpack\Sync\Settings::is_sync_enabled() ) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback for updating the package versions via a remote request when Sync is not present.
|
||||
*
|
||||
* Updates the package versions as follows:
|
||||
* - 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 ) {
|
||||
protected function update_package_versions_via_remote_request( $package_versions ) {
|
||||
$connection = new Manager();
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return;
|
||||
@@ -108,4 +158,19 @@ class Package_Version_Tracker {
|
||||
set_transient( self::CACHED_FAILED_REQUEST_KEY, time(), self::CACHED_FAILED_REQUEST_EXPIRATION );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if version check is being rate limited, and update the rate limiting transient if needed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_rate_limiting() {
|
||||
if ( get_transient( static::RATE_LIMITER_KEY ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
set_transient( static::RATE_LIMITER_KEY, time(), static::RATE_LIMITER_TIMEOUT );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
wp/wp-content/plugins/woocommerce/vendor/automattic/jetpack-connection/src/class-package-version.php
Vendored
+1
-1
@@ -12,7 +12,7 @@ namespace Automattic\Jetpack\Connection;
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '1.60.1';
|
||||
const PACKAGE_VERSION = '2.11.3';
|
||||
|
||||
const PACKAGE_SLUG = 'connection';
|
||||
|
||||
|
||||
Vendored
+466
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for the Jetpack partner coupon logic.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client as Connection_Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* Disable direct access.
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Jetpack_Partner_Coupon
|
||||
*
|
||||
* @since partner-1.6.0
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Partner_Coupon {
|
||||
|
||||
/**
|
||||
* Name of the Jetpack_Option coupon option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $coupon_option = 'partner_coupon';
|
||||
|
||||
/**
|
||||
* Name of the Jetpack_Option added option.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $added_option = 'partner_coupon_added';
|
||||
|
||||
/**
|
||||
* Name of "last availability check" transient.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $last_check_transient = 'jetpack_partner_coupon_last_check';
|
||||
|
||||
/**
|
||||
* Callable that executes a blog-authenticated request.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $request_as_blog;
|
||||
|
||||
/**
|
||||
* Jetpack_Partner_Coupon
|
||||
*
|
||||
* @var Partner_Coupon|null
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* A list of supported partners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $supported_partners = array(
|
||||
'IONOS' => array(
|
||||
'name' => 'IONOS',
|
||||
'logo' => array(
|
||||
'src' => '/images/ionos-logo.jpg',
|
||||
'width' => 119,
|
||||
'height' => 32,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* A list of supported presets.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $supported_presets = array(
|
||||
'IONA' => 'jetpack_backup_daily',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get singleton instance of class.
|
||||
*
|
||||
* @return Partner_Coupon
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new Partner_Coupon( array( Connection_Client::class, 'wpcom_json_api_request_as_blog' ) );
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param callable $request_as_blog Callable that executes a blog-authenticated request.
|
||||
*/
|
||||
public function __construct( $request_as_blog ) {
|
||||
$this->request_as_blog = $request_as_blog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks to catch and purge coupon.
|
||||
*
|
||||
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
|
||||
* @param string $redirect_location The location we should redirect to after catching the coupon.
|
||||
*/
|
||||
public static function register_coupon_admin_hooks( $plugin_slug, $redirect_location ) {
|
||||
$instance = self::get_instance();
|
||||
|
||||
// We have to use an anonymous function, so we can pass along relevant information
|
||||
// and not have to hardcode values for a single plugin.
|
||||
// This open up the opportunity for e.g. the "all-in-one" and backup plugins
|
||||
// to both implement partner coupon logic.
|
||||
add_action(
|
||||
'admin_init',
|
||||
function () use ( $plugin_slug, $redirect_location, $instance ) {
|
||||
$instance->catch_coupon( $plugin_slug, $redirect_location );
|
||||
$instance->maybe_purge_coupon( $plugin_slug );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch partner coupon and redirect to claim component.
|
||||
*
|
||||
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
|
||||
* @param string $redirect_location The location we should redirect to after catching the coupon.
|
||||
*/
|
||||
public function catch_coupon( $plugin_slug, $redirect_location ) {
|
||||
// Accept and store a partner coupon if present, and redirect to Jetpack connection screen.
|
||||
$partner_coupon = isset( $_GET['jetpack-partner-coupon'] ) ? sanitize_text_field( wp_unslash( $_GET['jetpack-partner-coupon'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( $partner_coupon ) {
|
||||
Jetpack_Options::update_options(
|
||||
array(
|
||||
self::$coupon_option => $partner_coupon,
|
||||
self::$added_option => time(),
|
||||
)
|
||||
);
|
||||
|
||||
$connection = new Connection_Manager( $plugin_slug );
|
||||
if ( $connection->is_connected() ) {
|
||||
$redirect_location = add_query_arg( array( 'showCouponRedemption' => 1 ), $redirect_location );
|
||||
wp_safe_redirect( $redirect_location );
|
||||
} else {
|
||||
wp_safe_redirect( $redirect_location );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge partner coupon.
|
||||
*
|
||||
* We try to remotely check if a coupon looks valid. We also automatically purge
|
||||
* partner coupons after a certain amount of time to prevent unnecessary look-ups
|
||||
* and/or promoting a product for months or years in the future due to unknown
|
||||
* errors.
|
||||
*
|
||||
* @param string $plugin_slug The plugin slug to differentiate between Jetpack connections.
|
||||
*/
|
||||
public function maybe_purge_coupon( $plugin_slug ) {
|
||||
// Only run coupon checks on Jetpack admin pages.
|
||||
// The "admin-ui" package is responsible for registering the Jetpack admin
|
||||
// page for all Jetpack plugins and has hardcoded the settings page to be
|
||||
// "jetpack", so we shouldn't need to allow for dynamic/custom values.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( new Status() )->is_offline_mode() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = new Connection_Manager( $plugin_slug );
|
||||
if ( ! $connection->is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->maybe_purge_coupon_by_added_date() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit checks to happen once a minute at most.
|
||||
if ( get_transient( self::$last_check_transient ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_transient( self::$last_check_transient, true, MINUTE_IN_SECONDS );
|
||||
|
||||
$this->maybe_purge_coupon_by_availability_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge coupon based on local added date.
|
||||
*
|
||||
* We automatically remove the coupon after a month to "self-heal" if
|
||||
* something in the claim process has broken with the site.
|
||||
*
|
||||
* @return bool Return whether we should skip further purge checks.
|
||||
*/
|
||||
protected function maybe_purge_coupon_by_added_date() {
|
||||
$date = Jetpack_Options::get_option( self::$added_option, '' );
|
||||
|
||||
if ( empty( $date ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$expire_date = strtotime( '+30 days', $date );
|
||||
$today = time();
|
||||
|
||||
if ( $today >= $expire_date ) {
|
||||
$this->delete_coupon_data();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge coupon based on availability check.
|
||||
*
|
||||
* @return bool Return whether we deleted coupon data.
|
||||
*/
|
||||
protected function maybe_purge_coupon_by_availability_check() {
|
||||
$blog_id = Jetpack_Options::get_option( 'id', false );
|
||||
|
||||
if ( ! $blog_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$coupon = self::get_coupon();
|
||||
|
||||
if ( ! $coupon ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = call_user_func_array(
|
||||
$this->request_as_blog,
|
||||
array(
|
||||
add_query_arg(
|
||||
array( 'coupon_code' => $coupon['coupon_code'] ),
|
||||
sprintf(
|
||||
'/sites/%d/jetpack-partner/coupon/v1/site/coupon',
|
||||
$blog_id
|
||||
)
|
||||
),
|
||||
2,
|
||||
array( 'method' => 'GET' ),
|
||||
null,
|
||||
'wpcom',
|
||||
)
|
||||
);
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if (
|
||||
200 === wp_remote_retrieve_response_code( $response ) &&
|
||||
is_array( $body ) &&
|
||||
isset( $body['available'] ) &&
|
||||
false === $body['available']
|
||||
) {
|
||||
$this->delete_coupon_data();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all coupon data.
|
||||
*/
|
||||
protected function delete_coupon_data() {
|
||||
Jetpack_Options::delete_option(
|
||||
array(
|
||||
self::$coupon_option,
|
||||
self::$added_option,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get partner coupon data.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function get_coupon() {
|
||||
$coupon_code = Jetpack_Options::get_option( self::$coupon_option, '' );
|
||||
|
||||
if ( ! is_string( $coupon_code ) || empty( $coupon_code ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instance = self::get_instance();
|
||||
$partner = $instance->get_coupon_partner( $coupon_code );
|
||||
|
||||
if ( ! $partner ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$preset = $instance->get_coupon_preset( $coupon_code );
|
||||
|
||||
if ( ! $preset ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$product = $instance->get_coupon_product( $preset );
|
||||
|
||||
if ( ! $product ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array(
|
||||
'coupon_code' => $coupon_code,
|
||||
'partner' => $partner,
|
||||
'preset' => $preset,
|
||||
'product' => $product,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon partner.
|
||||
*
|
||||
* @param string $coupon_code Coupon code to go through.
|
||||
* @return array|bool
|
||||
*/
|
||||
private function get_coupon_partner( $coupon_code ) {
|
||||
if ( ! is_string( $coupon_code ) || false === strpos( $coupon_code, '_' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prefix = strtok( $coupon_code, '_' );
|
||||
$supported_partners = $this->get_supported_partners();
|
||||
|
||||
if ( ! isset( $supported_partners[ $prefix ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array(
|
||||
'name' => $supported_partners[ $prefix ]['name'],
|
||||
'prefix' => $prefix,
|
||||
'logo' => isset( $supported_partners[ $prefix ]['logo'] ) ? $supported_partners[ $prefix ]['logo'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon product.
|
||||
*
|
||||
* @param string $coupon_preset The preset we wish to find a product for.
|
||||
* @return array|bool
|
||||
*/
|
||||
private function get_coupon_product( $coupon_preset ) {
|
||||
if ( ! is_string( $coupon_preset ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for plugins to register supported products.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*
|
||||
* @param array A list of product details.
|
||||
* @return array
|
||||
*/
|
||||
$product_details = apply_filters( 'jetpack_partner_coupon_products', array() );
|
||||
$product_slug = $this->get_supported_presets()[ $coupon_preset ];
|
||||
|
||||
foreach ( $product_details as $product ) {
|
||||
if ( ! $this->array_keys_exist( array( 'title', 'slug', 'description', 'features' ), $product ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $product_slug === $product['slug'] ) {
|
||||
return $product;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if multiple keys are present in an array.
|
||||
*
|
||||
* @param array $needles The keys we wish to check for.
|
||||
* @param array $haystack The array we want to compare keys against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function array_keys_exist( $needles, $haystack ) {
|
||||
foreach ( $needles as $needle ) {
|
||||
if ( ! isset( $haystack[ $needle ] ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon preset.
|
||||
*
|
||||
* @param string $coupon_code Coupon code to go through.
|
||||
* @return string|bool
|
||||
*/
|
||||
private function get_coupon_preset( $coupon_code ) {
|
||||
if ( ! is_string( $coupon_code ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$regex = '/^.*?_(?P<slug>.*?)_.+$/';
|
||||
$matches = array();
|
||||
|
||||
if ( ! preg_match( $regex, $coupon_code, $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset( $this->get_supported_presets()[ $matches['slug'] ] ) ? $matches['slug'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported partners.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_supported_partners() {
|
||||
/**
|
||||
* Allow external code to add additional supported partners.
|
||||
*
|
||||
* @since partner-1.6.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $supported_partners A list of supported partners.
|
||||
* @return array
|
||||
*/
|
||||
return apply_filters( 'jetpack_partner_coupon_supported_partners', self::$supported_partners );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported presets.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_supported_presets() {
|
||||
/**
|
||||
* Allow external code to add additional supported presets.
|
||||
*
|
||||
* @since partner-1.6.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $supported_presets A list of supported presets.
|
||||
* @return array
|
||||
*/
|
||||
return apply_filters( 'jetpack_partner_coupon_supported_presets', self::$supported_presets );
|
||||
}
|
||||
}
|
||||
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Partner utilities.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* This class introduces functionality used by Jetpack hosting partners.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Partner {
|
||||
|
||||
/**
|
||||
* Affiliate code.
|
||||
*/
|
||||
const AFFILIATE_CODE = 'affiliate';
|
||||
|
||||
/**
|
||||
* Subsidiary id code.
|
||||
*/
|
||||
const SUBSIDIARY_CODE = 'subsidiary';
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @var Partner This class instance.
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Partner constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the class or returns the singleton.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @return Partner | false
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new Partner();
|
||||
add_filter( 'jetpack_build_authorize_url', array( self::$instance, 'add_subsidiary_id_as_query_arg' ) );
|
||||
add_filter( 'jetpack_build_authorize_url', array( self::$instance, 'add_affiliate_code_as_query_arg' ) );
|
||||
add_filter( 'jetpack_build_connection_url', array( self::$instance, 'add_subsidiary_id_as_query_arg' ) );
|
||||
add_filter( 'jetpack_build_connection_url', array( self::$instance, 'add_affiliate_code_as_query_arg' ) );
|
||||
|
||||
add_filter( 'jetpack_register_request_body', array( self::$instance, 'add_subsidiary_id_to_params_array' ) );
|
||||
add_filter( 'jetpack_register_request_body', array( self::$instance, 'add_affiliate_code_to_params_array' ) );
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the partner subsidiary code to the passed URL.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function add_subsidiary_id_as_query_arg( $url ) {
|
||||
return $this->add_code_as_query_arg( self::SUBSIDIARY_CODE, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the affiliate code to the passed URL.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function add_affiliate_code_as_query_arg( $url ) {
|
||||
return $this->add_code_as_query_arg( self::AFFILIATE_CODE, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the partner subsidiary code to the passed array.
|
||||
*
|
||||
* @since partner-1.5.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $params The parameters array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_subsidiary_id_to_params_array( $params ) {
|
||||
if ( ! is_array( $params ) ) {
|
||||
return $params;
|
||||
}
|
||||
return array_merge( $params, $this->get_code_as_array( self::SUBSIDIARY_CODE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the affiliate code to the passed array.
|
||||
*
|
||||
* @since partner-1.5.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $params The parameters array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_affiliate_code_to_params_array( $params ) {
|
||||
if ( ! is_array( $params ) ) {
|
||||
return $params;
|
||||
}
|
||||
return array_merge( $params, $this->get_code_as_array( self::AFFILIATE_CODE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passed URL with the partner code added as a URL query arg.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $type The partner code.
|
||||
* @param string $url The URL where the partner subsidiary id will be added.
|
||||
*
|
||||
* @return string The passed URL with the partner code added.
|
||||
*/
|
||||
public function add_code_as_query_arg( $type, $url ) {
|
||||
return add_query_arg( $this->get_code_as_array( $type ), $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the partner code in an associative array format
|
||||
*
|
||||
* @since partner-1.5.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $type The partner code.
|
||||
* @return array
|
||||
*/
|
||||
private function get_code_as_array( $type ) {
|
||||
switch ( $type ) {
|
||||
case self::AFFILIATE_CODE:
|
||||
$query_arg_name = 'aff';
|
||||
break;
|
||||
case self::SUBSIDIARY_CODE:
|
||||
$query_arg_name = 'subsidiaryId';
|
||||
break;
|
||||
default:
|
||||
return array();
|
||||
}
|
||||
|
||||
$code = $this->get_partner_code( $type );
|
||||
|
||||
if ( '' === $code ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array( $query_arg_name => $code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a partner code.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $type This can be either 'affiliate' or 'subsidiary'. Returns empty string when code is unknown.
|
||||
*
|
||||
* @return string The partner code.
|
||||
*/
|
||||
public function get_partner_code( $type ) {
|
||||
switch ( $type ) {
|
||||
case self::AFFILIATE_CODE:
|
||||
/**
|
||||
* Allow to filter the affiliate code.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since-jetpack 6.9.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $affiliate_code The affiliate code, blank by default.
|
||||
*/
|
||||
return apply_filters( 'jetpack_affiliate_code', get_option( 'jetpack_affiliate_code', '' ) );
|
||||
case self::SUBSIDIARY_CODE:
|
||||
/**
|
||||
* Allow to filter the partner subsidiary id.
|
||||
*
|
||||
* @since partner-1.0.0
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $subsidiary_id The partner subsidiary id, blank by default.
|
||||
*/
|
||||
return apply_filters(
|
||||
'jetpack_partner_subsidiary_id',
|
||||
get_option( 'jetpack_partner_subsidiary_id', '' )
|
||||
);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the singleton for testing purposes.
|
||||
*/
|
||||
public static function reset() {
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
Vendored
+49
-17
@@ -24,6 +24,11 @@ class Plugin_Storage {
|
||||
*/
|
||||
const PLUGINS_DISABLED_OPTION_NAME = 'jetpack_connection_disabled_plugins';
|
||||
|
||||
/**
|
||||
* Transient name used as flag to indicate that the active connected plugins list needs refreshing.
|
||||
*/
|
||||
const ACTIVE_PLUGINS_REFRESH_FLAG = 'jetpack_connection_active_plugins_refresh';
|
||||
|
||||
/**
|
||||
* Whether this class was configured for the first time or not.
|
||||
*
|
||||
@@ -31,13 +36,6 @@ class Plugin_Storage {
|
||||
*/
|
||||
private static $configured = false;
|
||||
|
||||
/**
|
||||
* Refresh list of connected plugins upon intialization.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $refresh_connected_plugins = false;
|
||||
|
||||
/**
|
||||
* Connected plugins.
|
||||
*
|
||||
@@ -65,11 +63,6 @@ class Plugin_Storage {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -167,19 +160,58 @@ class Plugin_Storage {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$configured = true;
|
||||
|
||||
add_action( 'update_option_active_plugins', array( __CLASS__, 'set_flag_to_refresh_active_connected_plugins' ) );
|
||||
|
||||
self::maybe_update_active_connected_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a flag to indicate that the active connected plugins list needs to be updated.
|
||||
* This will happen when the `active_plugins` option is updated.
|
||||
*
|
||||
* @see configure
|
||||
*/
|
||||
public static function set_flag_to_refresh_active_connected_plugins() {
|
||||
set_transient( self::ACTIVE_PLUGINS_REFRESH_FLAG, time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we need to update the active connected plugins list.
|
||||
*/
|
||||
public static function maybe_update_active_connected_plugins() {
|
||||
$maybe_error = self::ensure_configured();
|
||||
|
||||
if ( $maybe_error instanceof WP_Error ) {
|
||||
return;
|
||||
}
|
||||
// Only attempt to update the option if the corresponding flag is set.
|
||||
if ( ! get_transient( self::ACTIVE_PLUGINS_REFRESH_FLAG ) ) {
|
||||
return;
|
||||
}
|
||||
// Only attempt to update the option on POST requests.
|
||||
// This will prevent the option from being updated multiple times due to concurrent requests.
|
||||
if ( ! ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_transient( self::ACTIVE_PLUGINS_REFRESH_FLAG );
|
||||
|
||||
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() ) );
|
||||
$configured_plugin_keys = array_keys( self::$plugins );
|
||||
$stored_plugin_keys = array_keys( (array) get_option( self::ACTIVE_PLUGINS_OPTION_NAME, array() ) );
|
||||
sort( $configured_plugin_keys );
|
||||
sort( $stored_plugin_keys );
|
||||
|
||||
if ( $number_of_plugins_differ || true === self::$refresh_connected_plugins ) {
|
||||
if ( $configured_plugin_keys !== $stored_plugin_keys ) {
|
||||
self::update_active_plugins_option();
|
||||
}
|
||||
|
||||
self::$configured = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +220,7 @@ class Plugin_Storage {
|
||||
* @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.
|
||||
// Note: Since this option 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() ) {
|
||||
|
||||
+6
-4
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* The Jetpack Connection Rest Authentication class.
|
||||
*/
|
||||
@@ -118,7 +120,7 @@ class Rest_Authentication {
|
||||
}
|
||||
|
||||
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
$this->rest_authentication_status = new WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'The request method is missing.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
@@ -131,7 +133,7 @@ class Rest_Authentication {
|
||||
// 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(
|
||||
$this->rest_authentication_status = new WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'This request method is not supported.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
@@ -139,7 +141,7 @@ class Rest_Authentication {
|
||||
return null;
|
||||
}
|
||||
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! empty( file_get_contents( 'php://input' ) ) ) {
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
$this->rest_authentication_status = new WP_Error(
|
||||
'rest_invalid_request',
|
||||
__( 'This request method does not support body parameters.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
@@ -173,7 +175,7 @@ class Rest_Authentication {
|
||||
}
|
||||
|
||||
// Something else went wrong. Probably a signature error.
|
||||
$this->rest_authentication_status = new \WP_Error(
|
||||
$this->rest_authentication_status = new WP_Error(
|
||||
'rest_invalid_signature',
|
||||
__( 'The request is not signed correctly.', 'jetpack-connection' ),
|
||||
array( 'status' => 400 )
|
||||
|
||||
Vendored
+182
-8
@@ -7,6 +7,7 @@
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use Automattic\Jetpack\Connection\Webhooks\Authorize_Redirect;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status;
|
||||
@@ -84,6 +85,49 @@ class REST_Connector {
|
||||
)
|
||||
);
|
||||
|
||||
// Authorize a remote user.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/remote_provision',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'remote_provision' ),
|
||||
'permission_callback' => array( $this, 'remote_provision_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/remote_register',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'remote_register' ),
|
||||
'permission_callback' => array( $this, 'remote_register_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Connect a remote user.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/remote_connect',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'remote_connect' ),
|
||||
'permission_callback' => array( $this, 'remote_connect_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// The endpoint verifies blog connection and blog token validity.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/connection/check',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'connection_check' ),
|
||||
'permission_callback' => array( $this, 'connection_check_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Get current connection status of Jetpack.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
@@ -274,7 +318,7 @@ class REST_Connector {
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array|wp-error
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function remote_authorize( $request ) {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
@@ -287,6 +331,103 @@ class REST_Connector {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the site provisioning process.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
public static function remote_provision( WP_REST_Request $request ) {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_provision( $request );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a remote user.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
public static function remote_connect( WP_REST_Request $request ) {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_connect( $request );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the site so that a plan can be provisioned.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_Error|array
|
||||
*/
|
||||
public function remote_register( WP_REST_Request $request ) {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->remote_register( $request );
|
||||
|
||||
if ( is_a( $result, 'IXR_Error' ) ) {
|
||||
$result = new WP_Error( $result->code, $result->message );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote provision endpoint permission check.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function remote_provision_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_remote_provision', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote connect endpoint permission check.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function remote_connect_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_remote_connect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote register endpoint permission check.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function remote_register_permission_check() {
|
||||
if ( $this->connection->has_connected_owner() ) {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'already_registered', __( 'Blog is already registered', 'jetpack-connection' ), 400 );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection status for this Jetpack site.
|
||||
*
|
||||
@@ -303,7 +444,7 @@ class REST_Connector {
|
||||
|
||||
$connection_status = array(
|
||||
'isActive' => $connection->has_connected_owner(), // TODO deprecate this.
|
||||
'isStaging' => $status->is_staging_site(),
|
||||
'isStaging' => $status->in_safe_mode(), // TODO deprecate this.
|
||||
'isRegistered' => $connection->is_connected(),
|
||||
'isUserConnected' => $connection->is_user_connected(),
|
||||
'hasConnectedOwner' => $connection->has_connected_owner(),
|
||||
@@ -674,11 +815,7 @@ class REST_Connector {
|
||||
|
||||
$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 );
|
||||
}
|
||||
$authorize_url = ( new Authorize_Redirect( $this->connection ) )->build_authorize_url( $redirect_uri );
|
||||
|
||||
/**
|
||||
* Filters the response of jetpack/v4/connection/register endpoint
|
||||
@@ -798,7 +935,7 @@ class REST_Connector {
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @return bool|WP_Error.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function update_user_token_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
@@ -847,4 +984,41 @@ class REST_Connector {
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_set_connection_owner', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint verifies blog connection and blog token validity.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function connection_check() {
|
||||
/**
|
||||
* Filters the successful response of the REST API test_connection method
|
||||
*
|
||||
* @param string $response The response string.
|
||||
*/
|
||||
$status = apply_filters( 'jetpack_rest_connection_check_response', 'success' );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'status' => $status,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote connect endpoint permission check.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function connection_check_permission_check() {
|
||||
if ( current_user_can( 'jetpack_connect' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error( 'invalid_permission_connection_check', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -158,6 +158,7 @@ class Secrets {
|
||||
*/
|
||||
do_action( 'jetpack_verify_secrets_begin', $action, $user );
|
||||
|
||||
/** Closure to run the 'fail' action and return an error. */
|
||||
$return_error = function ( WP_Error $error ) use ( $action, $user ) {
|
||||
/**
|
||||
* Verifying of the previously generated secret has failed.
|
||||
|
||||
Vendored
+1
-1
@@ -223,7 +223,7 @@ class Server_Sandbox {
|
||||
*
|
||||
* Attached to the `admin_bar_menu` action.
|
||||
*
|
||||
* @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
|
||||
* @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' ) ) {
|
||||
|
||||
Vendored
+11
-10
@@ -38,8 +38,8 @@ class Tracking {
|
||||
/**
|
||||
* 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.
|
||||
* @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;
|
||||
@@ -159,7 +159,7 @@ class Tracking {
|
||||
*
|
||||
* @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 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_'.
|
||||
*/
|
||||
@@ -189,7 +189,7 @@ class Tracking {
|
||||
/**
|
||||
* 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 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.
|
||||
@@ -221,8 +221,8 @@ class Tracking {
|
||||
/**
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
@@ -238,10 +238,10 @@ class Tracking {
|
||||
* 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.
|
||||
* @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
|
||||
*/
|
||||
@@ -252,6 +252,7 @@ class Tracking {
|
||||
|
||||
$blog_details = array(
|
||||
'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
|
||||
'blog_id' => \Jetpack_Options::get_option( 'id' ),
|
||||
);
|
||||
|
||||
$timestamp = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
|
||||
|
||||
+2
-2
@@ -82,8 +82,8 @@ class Urls {
|
||||
/**
|
||||
* Return URL with a normalized protocol.
|
||||
*
|
||||
* @param callable $callable Function to retrieve URL option.
|
||||
* @param string $new_value URL Protocol to set URLs to.
|
||||
* @param string $callable Function name that was used 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 ) {
|
||||
|
||||
+49
@@ -83,4 +83,53 @@ class Utils {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new user from a SSO attempt.
|
||||
*
|
||||
* @param object $user_data WordPress.com user information.
|
||||
*/
|
||||
public static function generate_user( $user_data ) {
|
||||
$username = $user_data->login;
|
||||
/**
|
||||
* Determines how many times the SSO module can attempt to randomly generate a user.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-4.3.2
|
||||
*
|
||||
* @param int 5 By default, SSO will attempt to random generate a user up to 5 times.
|
||||
*/
|
||||
$num_tries = (int) apply_filters( 'jetpack_sso_allowed_username_generate_retries', 5 );
|
||||
|
||||
$exists = username_exists( $username );
|
||||
$tries = 0;
|
||||
while ( $exists && $tries++ < $num_tries ) {
|
||||
$username = $user_data->login . '_' . $user_data->ID . '_' . wp_rand();
|
||||
$exists = username_exists( $username );
|
||||
}
|
||||
|
||||
if ( $exists ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = (object) array();
|
||||
$user->user_pass = wp_generate_password( 20 );
|
||||
$user->user_login = wp_slash( $username );
|
||||
$user->user_email = wp_slash( $user_data->email );
|
||||
$user->display_name = $user_data->display_name;
|
||||
$user->first_name = $user_data->first_name;
|
||||
$user->last_name = $user_data->last_name;
|
||||
$user->url = $user_data->url;
|
||||
$user->description = $user_data->description;
|
||||
|
||||
if ( isset( $user_data->role ) && $user_data->role ) {
|
||||
$user->role = $user_data->role;
|
||||
}
|
||||
|
||||
$created_user_id = wp_insert_user( $user );
|
||||
|
||||
update_user_meta( $created_user_id, 'wpcom_user_id', $user_data->ID );
|
||||
return get_userdata( $created_user_id );
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-3
@@ -86,11 +86,11 @@ class Webhooks {
|
||||
case 'authorize':
|
||||
$this->handle_authorize();
|
||||
$this->do_exit();
|
||||
break;
|
||||
break; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though do_exit never returns.
|
||||
case 'authorize_redirect':
|
||||
$this->handle_authorize_redirect();
|
||||
$this->do_exit();
|
||||
break;
|
||||
break; // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though do_exit never returns.
|
||||
// Class Jetpack::admin_page_load() still handles other cases.
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ class Webhooks {
|
||||
}
|
||||
do_action( 'jetpack_client_authorize_processing' );
|
||||
|
||||
$data = stripslashes_deep( $_GET );
|
||||
$data = stripslashes_deep( $_GET ); // We need all request data under the context of an authorization request.
|
||||
$data['auth_type'] = 'client';
|
||||
$roles = new Roles();
|
||||
$role = $roles->translate_current_user_to_role();
|
||||
@@ -159,6 +159,8 @@ class Webhooks {
|
||||
|
||||
/**
|
||||
* The `exit` is wrapped into a method so we could mock it.
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
protected function do_exit() {
|
||||
exit;
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ class XMLRPC_Async_Call {
|
||||
self::$clients[ $client_blog_id ][ $user_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => $user_id ) );
|
||||
}
|
||||
|
||||
// https://plugins.trac.wordpress.org/ticket/2041
|
||||
if ( function_exists( 'ignore_user_abort' ) ) {
|
||||
ignore_user_abort( true );
|
||||
}
|
||||
|
||||
+4
-2
@@ -7,6 +7,8 @@
|
||||
|
||||
namespace Automattic\Jetpack\Connection;
|
||||
|
||||
use IXR_Error;
|
||||
|
||||
/**
|
||||
* Registers the XML-RPC methods for Connections.
|
||||
*/
|
||||
@@ -69,10 +71,10 @@ class XMLRPC_Connector {
|
||||
$code = -10520;
|
||||
}
|
||||
|
||||
if ( ! class_exists( \IXR_Error::class ) ) {
|
||||
if ( ! class_exists( IXR_Error::class ) ) {
|
||||
require_once ABSPATH . WPINC . '/class-IXR.php';
|
||||
}
|
||||
return new \IXR_Error(
|
||||
return new IXR_Error(
|
||||
$code,
|
||||
sprintf( 'Jetpack: [%s] %s', $data->get_error_code(), $data->get_error_message() )
|
||||
);
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
#wpadminbar #wp-admin-bar-jetpack-idc {
|
||||
margin-right: 5px;
|
||||
|
||||
.jp-idc-admin-bar {
|
||||
border-radius: 2px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #EFEFF0;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dashicons {
|
||||
font-family: 'dashicons';
|
||||
margin-top: -6px;
|
||||
|
||||
&:before {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ab-item {
|
||||
padding: 0;
|
||||
background: #E68B28;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { IDCScreen } from '@automattic/jetpack-idc';
|
||||
import * as WPElement from '@wordpress/element';
|
||||
import React from 'react';
|
||||
|
||||
import './admin-bar.scss';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* The initial renderer function.
|
||||
*/
|
||||
function render() {
|
||||
if ( ! window.hasOwnProperty( 'JP_IDENTITY_CRISIS__INITIAL_STATE' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById(
|
||||
window.JP_IDENTITY_CRISIS__INITIAL_STATE.containerID || 'jp-identity-crisis-container'
|
||||
);
|
||||
|
||||
if ( null === container ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
WP_API_root,
|
||||
WP_API_nonce,
|
||||
wpcomHomeUrl,
|
||||
currentUrl,
|
||||
redirectUri,
|
||||
tracksUserData,
|
||||
tracksEventData,
|
||||
isSafeModeConfirmed,
|
||||
consumerData,
|
||||
isAdmin,
|
||||
possibleDynamicSiteUrlDetected,
|
||||
isDevelopmentSite,
|
||||
} = window.JP_IDENTITY_CRISIS__INITIAL_STATE;
|
||||
|
||||
if ( ! isSafeModeConfirmed ) {
|
||||
const component = (
|
||||
<IDCScreen
|
||||
wpcomHomeUrl={ wpcomHomeUrl }
|
||||
currentUrl={ currentUrl }
|
||||
apiRoot={ WP_API_root }
|
||||
apiNonce={ WP_API_nonce }
|
||||
redirectUri={ redirectUri }
|
||||
tracksUserData={ tracksUserData || {} }
|
||||
tracksEventData={ tracksEventData }
|
||||
customContent={
|
||||
consumerData.hasOwnProperty( 'customContent' ) ? consumerData.customContent : {}
|
||||
}
|
||||
isAdmin={ isAdmin }
|
||||
logo={ consumerData.hasOwnProperty( 'logo' ) ? consumerData.logo : undefined }
|
||||
possibleDynamicSiteUrlDetected={ possibleDynamicSiteUrlDetected }
|
||||
isDevelopmentSite={ isDevelopmentSite }
|
||||
/>
|
||||
);
|
||||
WPElement.createRoot( container ).render( component );
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener( 'load', () => render() );
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#jp-identity-crisis-container .jp-idc__idc-screen {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
#jp-identity-crisis-container.notice {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception class for the Identity Crisis component.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
/**
|
||||
* Exception class for the Identity Crisis component.
|
||||
*/
|
||||
class Exception extends \Exception {}
|
||||
+833
@@ -0,0 +1,833 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis class of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\IdentityCrisis\Exception;
|
||||
use Automattic\Jetpack\IdentityCrisis\UI;
|
||||
use Automattic\Jetpack\IdentityCrisis\URL_Secret;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* This class will handle everything involved with fixing an Identity Crisis.
|
||||
*
|
||||
* @since automattic/jetpack-identity-crisis:0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
* @since 2.9.0
|
||||
*/
|
||||
class Identity_Crisis {
|
||||
/**
|
||||
* Persistent WPCOM blog ID that stays in the options after disconnect.
|
||||
*/
|
||||
const PERSISTENT_BLOG_ID_OPTION_NAME = 'jetpack_persistent_blog_id';
|
||||
|
||||
/**
|
||||
* Instance of the object.
|
||||
*
|
||||
* @var Identity_Crisis
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* The wpcom value of the home URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $wpcom_home_url;
|
||||
|
||||
/**
|
||||
* Has safe mode been confirmed?
|
||||
* Beware, it never contains `true` for non-admins, so doesn't always reflect the actual value.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $is_safe_mode_confirmed;
|
||||
|
||||
/**
|
||||
* The current screen, which is set if the current user is a non-admin and this is an admin page.
|
||||
*
|
||||
* @var \WP_Screen
|
||||
*/
|
||||
public static $current_screen;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new Identity_Crisis();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'jetpack_sync_processed_actions', array( $this, 'maybe_clear_migrate_option' ) );
|
||||
add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\IdentityCrisis\\REST_Endpoints', 'initialize_rest_api' ) );
|
||||
add_action( 'jetpack_idc_disconnect', array( __CLASS__, 'do_jetpack_idc_disconnect' ) );
|
||||
add_action( 'jetpack_received_remote_request_response', array( $this, 'check_http_response_for_idc_detected' ) );
|
||||
|
||||
add_filter( 'jetpack_connection_disconnect_site_wpcom', array( __CLASS__, 'jetpack_connection_disconnect_site_wpcom_filter' ) );
|
||||
|
||||
add_filter( 'jetpack_remote_request_url', array( $this, 'add_idc_query_args_to_url' ) );
|
||||
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_secret_to_url_validation_response' ) );
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_ip_requester_to_url_validation_response' ) );
|
||||
|
||||
add_filter( 'jetpack_options', array( static::class, 'reverse_wpcom_urls_for_idc' ) );
|
||||
|
||||
add_filter( 'jetpack_register_request_body', array( static::class, 'register_request_body' ) );
|
||||
add_action( 'jetpack_site_registered', array( static::class, 'site_registered' ) );
|
||||
|
||||
$urls_in_crisis = self::check_identity_crisis();
|
||||
if ( false === $urls_in_crisis ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$wpcom_home_url = $urls_in_crisis['wpcom_home'];
|
||||
add_action( 'init', array( $this, 'wordpress_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect current connection and clear IDC options.
|
||||
*/
|
||||
public static function do_jetpack_idc_disconnect() {
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
// If the site is in an IDC because sync is not allowed,
|
||||
// let's make sure to not disconnect the production site.
|
||||
if ( ! self::validate_sync_error_idc_option() ) {
|
||||
$connection->disconnect_site( true );
|
||||
} else {
|
||||
$connection->disconnect_site( false );
|
||||
}
|
||||
|
||||
delete_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
|
||||
// Clear IDC options.
|
||||
self::clear_all_idc_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to prevent site from disconnecting from WPCOM if it's in an IDC.
|
||||
*
|
||||
* @see jetpack_connection_disconnect_site_wpcom filter.
|
||||
*
|
||||
* @return bool False if the site is in IDC, true otherwise.
|
||||
*/
|
||||
public static function jetpack_connection_disconnect_site_wpcom_filter() {
|
||||
return ! self::validate_sync_error_idc_option();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops through the array of processed items from sync and checks if one of the items was the
|
||||
* home_url or site_url callable. If so, then we delete the jetpack_migrate_for_idc option.
|
||||
*
|
||||
* @param array $processed_items Array of processed items that were synced to WordPress.com.
|
||||
*/
|
||||
public function maybe_clear_migrate_option( $processed_items ) {
|
||||
foreach ( (array) $processed_items as $item ) {
|
||||
|
||||
// First, is this item a jetpack_sync_callable action? If so, then proceed.
|
||||
$callable_args = ( is_array( $item ) && isset( $item[0] ) && isset( $item[1] ) && 'jetpack_sync_callable' === $item[0] )
|
||||
? $item[1]
|
||||
: null;
|
||||
|
||||
// Second, if $callable_args is set, check if the callable was home_url or site_url. If so,
|
||||
// clear the migrate option.
|
||||
if (
|
||||
isset( $callable_args[0] )
|
||||
&& ( 'home_url' === $callable_args[0] || 'site_url' === $callable_args[1] )
|
||||
) {
|
||||
Jetpack_Options::delete_option( 'migrate_for_idc' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WordPress init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wordpress_init() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
if (
|
||||
isset( $_GET['jetpack_idc_clear_confirmation'] ) && isset( $_GET['_wpnonce'] ) &&
|
||||
wp_verify_nonce( $_GET['_wpnonce'], 'jetpack_idc_clear_confirmation' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WordPress core doesn't unslash or verify nonces either.
|
||||
) {
|
||||
Jetpack_Options::delete_option( 'safe_mode_confirmed' );
|
||||
self::$is_safe_mode_confirmed = false;
|
||||
} else {
|
||||
self::$is_safe_mode_confirmed = (bool) Jetpack_Options::get_option( 'safe_mode_confirmed' );
|
||||
}
|
||||
}
|
||||
|
||||
// 121 Priority so that it's the most inner Jetpack item in the admin bar.
|
||||
add_action( 'admin_bar_menu', array( $this, 'display_admin_bar_button' ), 121 );
|
||||
|
||||
UI::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the idc query arguments to the url.
|
||||
*
|
||||
* @param string $url The remote request url.
|
||||
*/
|
||||
public function add_idc_query_args_to_url( $url ) {
|
||||
$status = new Status();
|
||||
if ( ! is_string( $url )
|
||||
|| $status->is_offline_mode()
|
||||
|| self::validate_sync_error_idc_option() ) {
|
||||
return $url;
|
||||
}
|
||||
$home_url = Urls::home_url();
|
||||
$site_url = Urls::site_url();
|
||||
$hostname = wp_parse_url( $site_url, PHP_URL_HOST );
|
||||
|
||||
// If request is from an IP, make sure ip_requester option is set
|
||||
if ( self::url_is_ip( $hostname ) ) {
|
||||
self::maybe_update_ip_requester( $hostname );
|
||||
}
|
||||
|
||||
$query_args = array(
|
||||
'home' => $home_url,
|
||||
'siteurl' => $site_url,
|
||||
);
|
||||
|
||||
if ( self::should_handle_idc() ) {
|
||||
$query_args['idc'] = true;
|
||||
}
|
||||
|
||||
if ( \Jetpack_Options::get_option( 'migrate_for_idc', false ) ) {
|
||||
$query_args['migrate_for_idc'] = true;
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$query_args['multisite'] = true;
|
||||
}
|
||||
|
||||
return add_query_arg( $query_args, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the admin bar button.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_admin_bar_button() {
|
||||
global $wp_admin_bar;
|
||||
|
||||
$href = is_admin()
|
||||
? add_query_arg( 'jetpack_idc_clear_confirmation', '1' )
|
||||
: add_query_arg( 'jetpack_idc_clear_confirmation', '1', admin_url() );
|
||||
|
||||
$href = wp_nonce_url( $href, 'jetpack_idc_clear_confirmation' );
|
||||
|
||||
$consumer_data = UI::get_consumer_data();
|
||||
$label = isset( $consumer_data['customContent']['adminBarSafeModeLabel'] )
|
||||
? esc_html( $consumer_data['customContent']['adminBarSafeModeLabel'] )
|
||||
: esc_html__( 'Jetpack Safe Mode', 'jetpack-connection' );
|
||||
|
||||
$title = sprintf(
|
||||
'<span class="jp-idc-admin-bar">%s %s</span>',
|
||||
'<span class="dashicons dashicons-info-outline"></span>',
|
||||
$label
|
||||
);
|
||||
|
||||
$menu = array(
|
||||
'id' => 'jetpack-idc',
|
||||
'title' => $title,
|
||||
'href' => esc_url( $href ),
|
||||
'parent' => 'top-secondary',
|
||||
);
|
||||
|
||||
if ( ! self::$is_safe_mode_confirmed ) {
|
||||
$menu['meta'] = array(
|
||||
'class' => 'hide',
|
||||
);
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_node( $menu );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site is currently in an identity crisis.
|
||||
*
|
||||
* @return array|bool Array of options that are in a crisis, or false if everything is OK.
|
||||
*/
|
||||
public static function check_identity_crisis() {
|
||||
$connection = new Connection_Manager( 'jetpack' );
|
||||
|
||||
if ( ! $connection->is_connected() || ( new Status() )->is_offline_mode() || ! self::validate_sync_error_idc_option() ) {
|
||||
return false;
|
||||
}
|
||||
return Jetpack_Options::get_option( 'sync_error_idc' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the HTTP response body for the 'idc_detected' key. If the key exists,
|
||||
* checks the idc_detected value for a valid idc error.
|
||||
*
|
||||
* @param array|WP_Error $http_response The HTTP response.
|
||||
*
|
||||
* @return bool Whether the site is in an identity crisis.
|
||||
*/
|
||||
public function check_http_response_for_idc_detected( $http_response ) {
|
||||
if ( ! is_array( $http_response ) ) {
|
||||
return false;
|
||||
}
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $http_response ), true );
|
||||
|
||||
if ( isset( $response_body['idc_detected'] ) ) {
|
||||
return $this->check_response_for_idc( $response_body['idc_detected'] );
|
||||
}
|
||||
|
||||
if ( isset( $response_body['migrated_for_idc'] ) ) {
|
||||
Jetpack_Options::delete_option( 'migrate_for_idc' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the WPCOM response to determine if the site is in an identity crisis. Updates the
|
||||
* sync_error_idc option if it is.
|
||||
*
|
||||
* @param array $response The response data.
|
||||
*
|
||||
* @return bool Whether the site is in an identity crisis.
|
||||
*/
|
||||
public function check_response_for_idc( $response ) {
|
||||
if ( is_array( $response ) && isset( $response['error_code'] ) ) {
|
||||
$error_code = $response['error_code'];
|
||||
$allowed_idc_error_codes = array(
|
||||
'jetpack_url_mismatch',
|
||||
'jetpack_home_url_mismatch',
|
||||
'jetpack_site_url_mismatch',
|
||||
);
|
||||
|
||||
if ( in_array( $error_code, $allowed_idc_error_codes, true ) ) {
|
||||
Jetpack_Options::update_option(
|
||||
'sync_error_idc',
|
||||
self::get_sync_error_idc_option( $response )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all IDC specific options. This method is used on disconnect and reconnect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_all_idc_options() {
|
||||
// If the site is currently in IDC, let's also clear the VaultPress connection options.
|
||||
// We have to check if the site is in IDC, otherwise we'd be clearing the VaultPress
|
||||
// connection any time the Jetpack connection is cycled.
|
||||
if ( self::validate_sync_error_idc_option() ) {
|
||||
delete_option( 'vaultpress' );
|
||||
delete_option( 'vaultpress_auto_register' );
|
||||
}
|
||||
|
||||
Jetpack_Options::delete_option(
|
||||
array(
|
||||
'sync_error_idc',
|
||||
'safe_mode_confirmed',
|
||||
'migrate_for_idc',
|
||||
)
|
||||
);
|
||||
|
||||
delete_transient( 'jetpack_idc_possible_dynamic_site_url_detected' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the sync_error_idc option is valid or not, and if not, will do cleanup.
|
||||
*
|
||||
* @return bool
|
||||
* @since-jetpack 5.4.0 Do not call get_sync_error_idc_option() unless site is in IDC
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
public static function validate_sync_error_idc_option() {
|
||||
$is_valid = false;
|
||||
|
||||
// Is the site opted in and does the stored sync_error_idc option match what we now generate?
|
||||
$sync_error = Jetpack_Options::get_option( 'sync_error_idc' );
|
||||
if ( $sync_error && self::should_handle_idc() ) {
|
||||
$local_options = self::get_sync_error_idc_option();
|
||||
|
||||
// Ensure all values are set.
|
||||
if ( isset( $sync_error['home'] ) && isset( $local_options['home'] ) && isset( $sync_error['siteurl'] ) && isset( $local_options['siteurl'] ) ) {
|
||||
// If the WP.com expected home and siteurl match local home and siteurl it is not valid IDC.
|
||||
if (
|
||||
isset( $sync_error['wpcom_home'] ) &&
|
||||
isset( $sync_error['wpcom_siteurl'] ) &&
|
||||
$sync_error['wpcom_home'] === $local_options['home'] &&
|
||||
$sync_error['wpcom_siteurl'] === $local_options['siteurl']
|
||||
) {
|
||||
// Enable migrate_for_idc so that sync actions are accepted.
|
||||
Jetpack_Options::update_option( 'migrate_for_idc', true );
|
||||
} elseif ( $sync_error['home'] === $local_options['home'] && $sync_error['siteurl'] === $local_options['siteurl'] ) {
|
||||
$is_valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether the sync_error_idc option is valid.
|
||||
*
|
||||
* @param bool $is_valid If the sync_error_idc is valid or not.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
$is_valid = (bool) apply_filters( 'jetpack_sync_error_idc_validation', $is_valid );
|
||||
|
||||
if ( ! $is_valid && $sync_error ) {
|
||||
// Since the option exists, and did not validate, delete it.
|
||||
Jetpack_Options::delete_option( 'sync_error_idc' );
|
||||
}
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses WP.com URLs stored in sync_error_idc option.
|
||||
*
|
||||
* @param array $sync_error error option containing reversed URLs.
|
||||
* @return array
|
||||
*/
|
||||
public static function reverse_wpcom_urls_for_idc( $sync_error ) {
|
||||
if ( isset( $sync_error['reversed_url'] ) ) {
|
||||
if ( array_key_exists( 'wpcom_siteurl', $sync_error ) ) {
|
||||
$sync_error['wpcom_siteurl'] = strrev( $sync_error['wpcom_siteurl'] );
|
||||
}
|
||||
if ( array_key_exists( 'wpcom_home', $sync_error ) ) {
|
||||
$sync_error['wpcom_home'] = strrev( $sync_error['wpcom_home'] );
|
||||
}
|
||||
}
|
||||
return $sync_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a url by doing three things:
|
||||
* - Strips protocol
|
||||
* - Strips www
|
||||
* - Adds a trailing slash
|
||||
*
|
||||
* @param string $url URL to parse.
|
||||
*
|
||||
* @return WP_Error|string
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*/
|
||||
public static function normalize_url_protocol_agnostic( $url ) {
|
||||
$parsed_url = wp_parse_url( trailingslashit( esc_url_raw( $url ) ) );
|
||||
if ( ! $parsed_url || empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) ) {
|
||||
return new WP_Error(
|
||||
'cannot_parse_url',
|
||||
sprintf(
|
||||
/* translators: %s: URL to parse. */
|
||||
esc_html__( 'Cannot parse URL %s', 'jetpack-connection' ),
|
||||
$url
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Strip www and protocols.
|
||||
$url = preg_replace( '/^www\./i', '', $parsed_url['host'] . $parsed_url['path'] );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value that is to be saved in the jetpack_sync_error_idc option.
|
||||
*
|
||||
* @param array $response HTTP response.
|
||||
*
|
||||
* @return array Array of the local urls, wpcom urls, and error code.
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
* @since-jetpack 5.4.0 Add transient since home/siteurl retrieved directly from DB.
|
||||
*/
|
||||
public static function get_sync_error_idc_option( $response = array() ) {
|
||||
// Since the local options will hit the database directly, store the values
|
||||
// in a transient to allow for autoloading and caching on subsequent views.
|
||||
$local_options = get_transient( 'jetpack_idc_local' );
|
||||
if ( false === $local_options ) {
|
||||
$local_options = array(
|
||||
'home' => Urls::home_url(),
|
||||
'siteurl' => Urls::site_url(),
|
||||
);
|
||||
set_transient( 'jetpack_idc_local', $local_options, MINUTE_IN_SECONDS );
|
||||
}
|
||||
|
||||
$options = array_merge( $local_options, $response );
|
||||
|
||||
$returned_values = array();
|
||||
foreach ( $options as $key => $option ) {
|
||||
if ( 'error_code' === $key ) {
|
||||
$returned_values[ $key ] = $option;
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized_url = self::normalize_url_protocol_agnostic( $option );
|
||||
if ( is_wp_error( $normalized_url ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$returned_values[ $key ] = $normalized_url;
|
||||
}
|
||||
// We need to protect WPCOM URLs from search & replace by reversing them. See https://wp.me/pf5801-3R
|
||||
// Add 'reversed_url' key for backward compatibility
|
||||
if ( array_key_exists( 'wpcom_home', $returned_values ) && array_key_exists( 'wpcom_siteurl', $returned_values ) ) {
|
||||
$returned_values['reversed_url'] = true;
|
||||
$returned_values = self::reverse_wpcom_urls_for_idc( $returned_values );
|
||||
}
|
||||
|
||||
return $returned_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the jetpack_should_handle_idc filter or constant.
|
||||
* If set to true, the site will be put into staging mode.
|
||||
*
|
||||
* This method uses both the current jetpack_should_handle_idc filter
|
||||
* and constant to determine whether an IDC should be handled.
|
||||
*
|
||||
* @return bool
|
||||
* @since 0.2.6
|
||||
*/
|
||||
public static function should_handle_idc() {
|
||||
if ( Constants::is_defined( 'JETPACK_SHOULD_HANDLE_IDC' ) ) {
|
||||
$default = Constants::get_constant( 'JETPACK_SHOULD_HANDLE_IDC' );
|
||||
} else {
|
||||
$default = ! Constants::is_defined( 'SUNRISE' ) && ! is_multisite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows sites to opt in for IDC mitigation which blocks the site from syncing to WordPress.com when the home
|
||||
* URL or site URL do not match what WordPress.com expects. The default value is either true, or the value of
|
||||
* JETPACK_SHOULD_HANDLE_IDC constant if set.
|
||||
*
|
||||
* @param bool $default Whether the site is opted in to IDC mitigation.
|
||||
*
|
||||
* @since 0.2.6
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_should_handle_idc', $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site is undergoing identity crisis.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_identity_crisis() {
|
||||
return false !== static::check_identity_crisis() && ! static::$is_safe_mode_confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an admin has confirmed safe mode.
|
||||
* Unlike `static::$is_safe_mode_confirmed` this function always returns the actual flag value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function safe_mode_is_confirmed() {
|
||||
return Jetpack_Options::get_option( 'safe_mode_confirmed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mismatched URLs.
|
||||
*
|
||||
* @return array|bool The mismatched urls, or false if the site is not connected, offline, in safe mode, or the IDC error is not valid.
|
||||
*/
|
||||
public static function get_mismatched_urls() {
|
||||
if ( ! static::has_identity_crisis() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = static::check_identity_crisis();
|
||||
|
||||
if ( ! $data ||
|
||||
! isset( $data['error_code'] ) ||
|
||||
! isset( $data['wpcom_home'] ) ||
|
||||
! isset( $data['home'] ) ||
|
||||
! isset( $data['wpcom_siteurl'] ) ||
|
||||
! isset( $data['siteurl'] )
|
||||
) {
|
||||
// The jetpack_sync_error_idc option is missing a key.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'jetpack_site_url_mismatch' === $data['error_code'] ) {
|
||||
return array(
|
||||
'wpcom_url' => $data['wpcom_siteurl'],
|
||||
'current_url' => $data['siteurl'],
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'wpcom_url' => $data['wpcom_home'],
|
||||
'current_url' => $data['home'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect $_SERVER['HTTP_HOST'] being used within WP_SITEURL or WP_HOME definitions inside of wp-config.
|
||||
*
|
||||
* If `HTTP_HOST` usage is found, it's possbile (though not certain) that site URLs are dynamic.
|
||||
*
|
||||
* When a site URL is dynamic, it can lead to a Jetpack IDC. If potentially dynamic usage is detected,
|
||||
* helpful support info will be shown on the IDC UI about setting a static site/home URL.
|
||||
*
|
||||
* @return bool True if potentially dynamic site urls were detected in wp-config, false otherwise.
|
||||
*/
|
||||
public static function detect_possible_dynamic_site_url() {
|
||||
$transient_key = 'jetpack_idc_possible_dynamic_site_url_detected';
|
||||
$transient_val = get_transient( $transient_key );
|
||||
|
||||
if ( false !== $transient_val ) {
|
||||
return (bool) $transient_val;
|
||||
}
|
||||
|
||||
$path = self::locate_wp_config();
|
||||
$wp_config = $path ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
if ( $wp_config ) {
|
||||
$matched = preg_match(
|
||||
'/define ?\( ?[\'"](?:WP_SITEURL|WP_HOME).+(?:HTTP_HOST).+\);/',
|
||||
$wp_config
|
||||
);
|
||||
|
||||
if ( $matched ) {
|
||||
set_transient( $transient_key, 1, HOUR_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( $transient_key, 0, HOUR_IN_SECONDS );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path to WordPress configuration.
|
||||
* Source: https://github.com/wp-cli/wp-cli/blob/master/php/utils.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function locate_wp_config() {
|
||||
static $path;
|
||||
|
||||
if ( null === $path ) {
|
||||
$path = false;
|
||||
|
||||
if ( getenv( 'WP_CONFIG_PATH' ) && file_exists( getenv( 'WP_CONFIG_PATH' ) ) ) {
|
||||
$path = getenv( 'WP_CONFIG_PATH' );
|
||||
} elseif ( file_exists( ABSPATH . 'wp-config.php' ) ) {
|
||||
$path = ABSPATH . 'wp-config.php';
|
||||
} elseif ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
|
||||
$path = dirname( ABSPATH ) . '/wp-config.php';
|
||||
}
|
||||
|
||||
if ( $path ) {
|
||||
$path = realpath( $path );
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `url_secret` to the `jetpack.idcUrlValidation` URL validation endpoint.
|
||||
* Adds `url_secret_error` in case of an error.
|
||||
*
|
||||
* @param array $response The endpoint response that we're modifying.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag -- The exception is being caught, false positive.
|
||||
*/
|
||||
public static function add_secret_to_url_validation_response( array $response ) {
|
||||
try {
|
||||
$secret = new URL_Secret();
|
||||
|
||||
$secret->create();
|
||||
|
||||
if ( $secret->exists() ) {
|
||||
$response['url_secret'] = $secret->get_secret();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$response['url_secret_error'] = new WP_Error( 'unable_to_create_url_secret', $e->getMessage() );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL is an IP.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @return bool
|
||||
*/
|
||||
public static function url_is_ip( $hostname = null ) {
|
||||
|
||||
if ( ! $hostname ) {
|
||||
$hostname = wp_parse_url( Urls::site_url(), PHP_URL_HOST );
|
||||
}
|
||||
|
||||
$is_ip = filter_var( $hostname, FILTER_VALIDATE_IP ) !== false ? $hostname : false;
|
||||
return $is_ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add IDC-related data to the registration query.
|
||||
*
|
||||
* @param array $params The existing query params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function register_request_body( array $params ) {
|
||||
$persistent_blog_id = get_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
if ( $persistent_blog_id ) {
|
||||
$params['persistent_blog_id'] = $persistent_blog_id;
|
||||
$params['url_secret'] = URL_Secret::create_secret( 'registration_request_url_secret_failed' );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the necessary options when site gets registered.
|
||||
*
|
||||
* @param int $blog_id The blog ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function site_registered( $blog_id ) {
|
||||
update_option( static::PERSISTENT_BLOG_ID_OPTION_NAME, (int) $blog_id, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to update the ip_requester option.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_update_ip_requester( $hostname ) {
|
||||
// Check if transient exists
|
||||
$transient_key = ip2long( $hostname );
|
||||
if ( $transient_key && ! get_transient( 'jetpack_idc_ip_requester_' . $transient_key ) ) {
|
||||
self::set_ip_requester_for_idc( $hostname, $transient_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If URL is an IP, add the IP value to the ip_requester option with its expiry value.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @param int $transient_key The transient key.
|
||||
*/
|
||||
public static function set_ip_requester_for_idc( $hostname, $transient_key ) {
|
||||
// Check if option exists
|
||||
$data = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
|
||||
$ip_requester = array(
|
||||
'ip' => $hostname,
|
||||
'expires_at' => time() + 360,
|
||||
);
|
||||
|
||||
// If not set, initialize it
|
||||
if ( empty( $data ) ) {
|
||||
$data = array( $ip_requester );
|
||||
} else {
|
||||
$updated_data = array();
|
||||
$updated_value = false;
|
||||
|
||||
// Remove expired values and update existing IP
|
||||
foreach ( $data as $item ) {
|
||||
if ( time() > $item['expires_at'] ) {
|
||||
continue; // Skip expired IP
|
||||
}
|
||||
|
||||
if ( $item['ip'] === $hostname ) {
|
||||
$item['expires_at'] = time() + 360;
|
||||
$updated_value = true;
|
||||
}
|
||||
|
||||
$updated_data[] = $item;
|
||||
}
|
||||
|
||||
if ( ! $updated_value || empty( $updated_data ) ) {
|
||||
$updated_data[] = $ip_requester;
|
||||
}
|
||||
|
||||
$data = $updated_data;
|
||||
}
|
||||
|
||||
self::update_ip_requester( $data, $transient_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ip_requester option and set a transient to expire in 5 minutes.
|
||||
*
|
||||
* @param array $data The data to be updated.
|
||||
* @param int $transient_key The transient key.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_ip_requester( $data, $transient_key ) {
|
||||
// Update the option
|
||||
$updated = Jetpack_Options::update_option( 'identity_crisis_ip_requester', $data );
|
||||
// Set a transient to expire in 5 minutes
|
||||
if ( $updated ) {
|
||||
$transient_name = 'jetpack_idc_ip_requester_' . $transient_key;
|
||||
set_transient( $transient_name, $data, 300 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `ip_requester` to the `jetpack.idcUrlValidation` URL validation endpoint.
|
||||
*
|
||||
* @param array $response The enpoint response that we're modifying.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_ip_requester_to_url_validation_response( array $response ) {
|
||||
$requesters = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
if ( $requesters ) {
|
||||
// Loop through the requesters and add the IP to the response if it's not expired
|
||||
$i = 0;
|
||||
foreach ( $requesters as $ip ) {
|
||||
if ( $ip['expires_at'] > time() ) {
|
||||
$response['ip_requester'][] = $ip['ip'];
|
||||
}
|
||||
// Limit the response to five IPs
|
||||
$i = ++$i;
|
||||
if ( $i === 5 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis REST endpoints of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Jetpack_Options;
|
||||
use Jetpack_XMLRPC_Server;
|
||||
use WP_Error;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* This class will handle Identity Crisis Endpoints
|
||||
*
|
||||
* @since automattic/jetpack-identity-crisis:0.2.0
|
||||
* @since 2.9.0
|
||||
*/
|
||||
class REST_Endpoints {
|
||||
|
||||
/**
|
||||
* Initialize REST routes.
|
||||
*/
|
||||
public static function initialize_rest_api() {
|
||||
|
||||
// Confirm that a site in identity crisis should be in staging mode.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/confirm-safe-mode',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::confirm_safe_mode',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// Handles the request to migrate stats and subscribers during an identity crisis.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'identity-crisis/migrate',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
|
||||
)
|
||||
);
|
||||
|
||||
// IDC resolve: create an entirely new shadow site for this URL.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/start-fresh',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::start_fresh_connection',
|
||||
'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_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',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL and secret for IDC check.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/idc-url-validation',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( static::class, 'validate_urls_and_set_secret' ),
|
||||
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL verification secret.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/url-secret',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( static::class, 'fetch_url_secret' ),
|
||||
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL verification secret.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/compare-url-secret',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'compare_url_secret' ),
|
||||
'permission_callback' => array( static::class, 'compare_url_secret_permission_check' ),
|
||||
'args' => array(
|
||||
'secret' => array(
|
||||
'description' => __( 'URL secret to compare to the ones stored in the database.', 'jetpack-connection' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles identity crisis mitigation, confirming safe mode for this site.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return bool | WP_Error True if option is properly set.
|
||||
*/
|
||||
public static function confirm_safe_mode() {
|
||||
$updated = Jetpack_Options::update_option( 'safe_mode_confirmed', true );
|
||||
if ( $updated ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'error_setting_jetpack_safe_mode',
|
||||
esc_html__( 'Could not confirm safe mode.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return bool | WP_Error True if option is properly set.
|
||||
*/
|
||||
public static function migrate_stats_and_subscribers() {
|
||||
if ( Jetpack_Options::get_option( 'sync_error_idc' ) && ! Jetpack_Options::delete_option( 'sync_error_idc' ) ) {
|
||||
return new WP_Error(
|
||||
'error_deleting_sync_error_idc',
|
||||
esc_html__( 'Could not delete sync error option.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
)
|
||||
);
|
||||
}
|
||||
return new WP_Error(
|
||||
'error_setting_jetpack_migrate',
|
||||
esc_html__( 'Could not confirm migration.', 'jetpack-connection' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This IDC resolution will disconnect the site and re-connect to a completely new
|
||||
* and separate shadow site than the original.
|
||||
*
|
||||
* It will first will disconnect the site without phoning home as to not disturb the production site.
|
||||
* It then builds a fresh connection URL and sends it back along with the response.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return \WP_REST_Response|WP_Error
|
||||
*/
|
||||
public static function start_fresh_connection( $request ) {
|
||||
/**
|
||||
* Fires when Users have requested through Identity Crisis for the connection to be reset.
|
||||
* Should be used to disconnect any connections and reset options.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
do_action( 'jetpack_idc_disconnect' );
|
||||
|
||||
$connection = new Connection_Manager();
|
||||
$result = $connection->try_registration( true );
|
||||
|
||||
// early return if site registration fails.
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
|
||||
$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
|
||||
|
||||
/**
|
||||
* Filters the connection url that users should be redirected to for re-establishing their connection.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param \WP_REST_Response|WP_Error $connection_url Connection URL user should be redirected to.
|
||||
*/
|
||||
return apply_filters( 'jetpack_idc_authorization_url', rest_ensure_response( $connection->get_authorization_url( null, $redirect_uri ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user can mitigate an identity crisis.
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @since-jetpack 4.4.0
|
||||
*
|
||||
* @return true|WP_Error True if the user has capability 'jetpack_disconnect', an error object otherwise.
|
||||
*/
|
||||
public static function identity_crisis_mitigation_permission_check() {
|
||||
if ( current_user_can( 'jetpack_disconnect' ) ) {
|
||||
return true;
|
||||
}
|
||||
$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'
|
||||
);
|
||||
|
||||
return new WP_Error( 'invalid_user_permission_identity_crisis', $error_msg, array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for URL validation and creating a secret.
|
||||
*
|
||||
* @since 0.18.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function validate_urls_and_set_secret() {
|
||||
$xmlrpc_server = new Jetpack_XMLRPC_Server();
|
||||
$result = $xmlrpc_server->validate_urls_for_idc_mitigation();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for fetching the existing secret.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function fetch_url_secret() {
|
||||
$secret = new URL_Secret();
|
||||
|
||||
if ( ! $secret->exists() ) {
|
||||
return new WP_Error( 'missing_url_secret', esc_html__( 'URL secret does not exist.', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'data' => array(
|
||||
'secret' => $secret->get_secret(),
|
||||
'expires_at' => $secret->get_expires_at(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for comparing the existing secret.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function compare_url_secret( $request ) {
|
||||
$match = false;
|
||||
|
||||
$storage = new URL_Secret();
|
||||
|
||||
if ( $storage->exists() ) {
|
||||
$remote_secret = $request->get_param( 'secret' );
|
||||
$match = $remote_secret && hash_equals( $storage->get_secret(), $remote_secret );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'match' => $match,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify url_secret create/fetch permissions (valid blog token authentication).
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function url_secret_permission_check() {
|
||||
return Rest_Authentication::is_signed_with_blog_token()
|
||||
? true
|
||||
: new WP_Error(
|
||||
'invalid_user_permission_identity_crisis',
|
||||
esc_html__( 'You do not have the correct user permissions to perform this action.', 'jetpack-connection' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint is only available on non-connected sites.
|
||||
* use `/identity-crisis/url-secret` for connected sites.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function compare_url_secret_permission_check() {
|
||||
return ( new Connection_Manager() )->is_connected()
|
||||
? new WP_Error(
|
||||
'invalid_connection_status',
|
||||
esc_html__( 'The endpoint is not available on connected sites.', 'jetpack-connection' ),
|
||||
array( 'status' => 403 )
|
||||
)
|
||||
: true;
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* Identity_Crisis UI class of the Connection package.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
use Automattic\Jetpack\Identity_Crisis;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack_Options;
|
||||
use Jetpack_Tracks_Client;
|
||||
|
||||
/**
|
||||
* The Identity Crisis UI handling.
|
||||
*/
|
||||
class UI {
|
||||
|
||||
/**
|
||||
* Temporary storage for consumer data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $consumers;
|
||||
|
||||
/**
|
||||
* Initialization.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( did_action( 'jetpack_identity_crisis_ui_init' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action called after initializing Identity Crisis UI.
|
||||
*
|
||||
* @since 0.6.0
|
||||
*/
|
||||
do_action( 'jetpack_identity_crisis_ui_init' );
|
||||
|
||||
$idc_data = Identity_Crisis::check_identity_crisis();
|
||||
|
||||
if ( false === $idc_data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( static::class, 'enqueue_scripts' ) );
|
||||
|
||||
Tracking::register_tracks_functions_scripts( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts!
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
if ( is_admin() ) {
|
||||
Assets::register_script(
|
||||
'jp_identity_crisis_banner',
|
||||
'../../dist/identity-crisis.js',
|
||||
__FILE__,
|
||||
array(
|
||||
'in_footer' => true,
|
||||
'textdomain' => 'jetpack-connection',
|
||||
)
|
||||
);
|
||||
Assets::enqueue_script( 'jp_identity_crisis_banner' );
|
||||
wp_add_inline_script( 'jp_identity_crisis_banner', static::get_initial_state(), 'before' );
|
||||
|
||||
add_action( 'admin_notices', array( static::class, 'render_container' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the container element for the IDC banner.
|
||||
*/
|
||||
public static function render_container() {
|
||||
?>
|
||||
<div id="jp-identity-crisis-container" class="notice"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rendered initial state JavaScript code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_initial_state() {
|
||||
return 'var JP_IDENTITY_CRISIS__INITIAL_STATE=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( static::get_initial_state_data() ) ) . '"));';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial state data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_initial_state_data() {
|
||||
$idc_urls = Identity_Crisis::get_mismatched_urls();
|
||||
$current_screen = get_current_screen();
|
||||
$is_admin = current_user_can( 'jetpack_disconnect' );
|
||||
$possible_dynamic_site_url_detected = (bool) Identity_Crisis::detect_possible_dynamic_site_url();
|
||||
$is_development_site = (bool) Status::is_development_site();
|
||||
|
||||
return array(
|
||||
'WP_API_root' => esc_url_raw( rest_url() ),
|
||||
'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'wpcomHomeUrl' => ( is_array( $idc_urls ) && array_key_exists( 'wpcom_url', $idc_urls ) ) ? $idc_urls['wpcom_url'] : null,
|
||||
'currentUrl' => ( is_array( $idc_urls ) && array_key_exists( 'current_url', $idc_urls ) ) ? $idc_urls['current_url'] : null,
|
||||
'redirectUri' => isset( $_SERVER['REQUEST_URI'] ) ? str_replace( '/wp-admin/', '/', filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : '',
|
||||
'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
|
||||
'tracksEventData' => array(
|
||||
'isAdmin' => $is_admin,
|
||||
'currentScreen' => $current_screen ? $current_screen->id : false,
|
||||
'blogID' => Jetpack_Options::get_option( 'id' ),
|
||||
'platform' => static::get_platform(),
|
||||
),
|
||||
'isSafeModeConfirmed' => Identity_Crisis::$is_safe_mode_confirmed,
|
||||
'consumerData' => static::get_consumer_data(),
|
||||
'isAdmin' => $is_admin,
|
||||
'possibleDynamicSiteUrlDetected' => $possible_dynamic_site_url_detected,
|
||||
'isDevelopmentSite' => $is_development_site,
|
||||
|
||||
/**
|
||||
* Use the filter to provide custom HTML elecontainer ID.
|
||||
*
|
||||
* @since 0.10.0
|
||||
*
|
||||
* @param string|null $containerID The container ID.
|
||||
*/
|
||||
'containerID' => apply_filters( 'identity_crisis_container_id', null ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package consumer data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_consumer_data() {
|
||||
if ( null !== static::$consumers ) {
|
||||
return static::$consumers;
|
||||
}
|
||||
|
||||
$consumers = apply_filters( 'jetpack_idc_consumers', array() );
|
||||
|
||||
if ( ! $consumers ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
usort(
|
||||
$consumers,
|
||||
function ( $c1, $c2 ) {
|
||||
$priority1 = ( array_key_exists( 'priority', $c1 ) && (int) $c1['priority'] ) ? (int) $c1['priority'] : 10;
|
||||
$priority2 = ( array_key_exists( 'priority', $c2 ) && (int) $c2['priority'] ) ? (int) $c2['priority'] : 10;
|
||||
|
||||
return $priority1 <=> $priority2;
|
||||
}
|
||||
);
|
||||
|
||||
$consumer_chosen = null;
|
||||
$consumer_url_length = 0;
|
||||
|
||||
foreach ( $consumers as $consumer ) {
|
||||
if ( empty( $consumer['admin_page'] ) || ! is_string( $consumer['admin_page'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
$consumer_chosen = $consumer;
|
||||
$consumer_url_length = strlen( $consumer['admin_page'] );
|
||||
}
|
||||
}
|
||||
|
||||
static::$consumers = $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
|
||||
|
||||
return static::$consumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the site platform.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_platform() {
|
||||
$host = new Host();
|
||||
|
||||
if ( $host->is_woa_site() ) {
|
||||
return 'woa';
|
||||
}
|
||||
|
||||
if ( $host->is_vip_site() ) {
|
||||
return 'vip';
|
||||
}
|
||||
|
||||
if ( $host->is_newspack_site() ) {
|
||||
return 'newspack';
|
||||
}
|
||||
|
||||
return 'self-hosted';
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* IDC URL secret functionality.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\IdentityCrisis;
|
||||
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* IDC URL secret functionality.
|
||||
* A short-lived secret used to verify whether an IDC is coming from the same vs a different Jetpack site.
|
||||
*/
|
||||
class URL_Secret {
|
||||
|
||||
/**
|
||||
* The options key used to store the secret.
|
||||
*/
|
||||
const OPTION_KEY = 'identity_crisis_url_secret';
|
||||
|
||||
/**
|
||||
* Secret lifespan (5 minutes)
|
||||
*/
|
||||
const LIFESPAN = 300;
|
||||
|
||||
/**
|
||||
* The URL secret string.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $secret = null;
|
||||
|
||||
/**
|
||||
* The URL secret expiration date in unix timestamp.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $expires_at = null;
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$secret_data = $this->fetch();
|
||||
|
||||
if ( $secret_data !== null ) {
|
||||
$this->secret = $secret_data['secret'];
|
||||
$this->expires_at = $secret_data['expires_at'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URL secret from the database.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function fetch() {
|
||||
$data = Jetpack_Options::get_option( static::OPTION_KEY );
|
||||
|
||||
if ( $data === false || empty( $data['secret'] ) || empty( $data['expires_at'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( time() > $data['expires_at'] ) {
|
||||
Jetpack_Options::delete_option( static::OPTION_KEY );
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new secret and save it in the options.
|
||||
*
|
||||
* @throws Exception Thrown if unable to save the new secret.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function create() {
|
||||
$secret_data = array(
|
||||
'secret' => $this->generate_secret(),
|
||||
'expires_at' => strval( time() + static::LIFESPAN ),
|
||||
);
|
||||
|
||||
$result = Jetpack_Options::update_option( static::OPTION_KEY, $secret_data );
|
||||
|
||||
if ( ! $result ) {
|
||||
throw new Exception( esc_html__( 'Unable to save new URL secret', 'jetpack-connection' ) );
|
||||
}
|
||||
|
||||
$this->secret = $secret_data['secret'];
|
||||
$this->expires_at = $secret_data['expires_at'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL secret.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_secret() {
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL secret expiration date.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_expires_at() {
|
||||
return $this->expires_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the secret exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() {
|
||||
return $this->secret && $this->expires_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the secret string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generate_secret() {
|
||||
return wp_generate_password( 12, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret for response.
|
||||
*
|
||||
* @param string $flow used to tell which flow generated the exception.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function create_secret( $flow = 'generating_secret_failed' ) {
|
||||
$secret_value = null;
|
||||
try {
|
||||
|
||||
$secret = new self();
|
||||
$secret->create();
|
||||
|
||||
if ( $secret->exists() ) {
|
||||
$secret_value = $secret->get_secret();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// Track the error and proceed.
|
||||
( new Tracking() )->record_user_event( $flow, array( 'current_url' => Urls::site_url() ) );
|
||||
}
|
||||
return $secret_value;
|
||||
}
|
||||
}
|
||||
Vendored
+184
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* Force Jetpack 2FA Functionality
|
||||
*
|
||||
* Ported from original repo at https://github.com/automattic/jetpack-force-2fa
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\SSO;
|
||||
|
||||
use Automattic\Jetpack\Connection\SSO;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Force users to use two factor authentication.
|
||||
*/
|
||||
class Force_2FA {
|
||||
/**
|
||||
* The role to force 2FA for.
|
||||
*
|
||||
* Defaults to manage_options via the plugins_loaded function.
|
||||
* Can be modified with the jetpack_force_2fa_cap filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $role;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'after_setup_theme', array( $this, 'plugins_loaded' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the plugin via the plugins_loaded hook.
|
||||
*/
|
||||
public function plugins_loaded() {
|
||||
/**
|
||||
* Filter the role to force 2FA for.
|
||||
* Defaults to manage_options.
|
||||
*
|
||||
* @param string $role The role to force 2FA for.
|
||||
* @return string
|
||||
* @since jetpack-12.7
|
||||
* @module SSO
|
||||
*/
|
||||
$this->role = apply_filters( 'jetpack_force_2fa_cap', 'manage_options' );
|
||||
|
||||
// Bail if Jetpack SSO is not active
|
||||
if ( ! ( new Modules() )->is_active( 'sso' ) ) {
|
||||
add_action( 'admin_notices', array( $this, 'admin_notice' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->force_2fa();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an admin notice if Jetpack SSO is not active.
|
||||
*/
|
||||
public function admin_notice() {
|
||||
/**
|
||||
* Filter if an admin notice is deplayed when Force 2FA is required, but SSO is not enabled.
|
||||
* Defaults to true.
|
||||
*
|
||||
* @param bool $display_notice Whether to display the notice.
|
||||
* @return bool
|
||||
* @since jetpack-12.7
|
||||
* @module SSO
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_force_2fa_dependency_notice', true ) && current_user_can( $this->role ) ) {
|
||||
wp_admin_notice(
|
||||
esc_html__( 'Jetpack Force 2FA requires Jetpack’s SSO feature.', 'jetpack-connection' ),
|
||||
array(
|
||||
'type' => 'warning',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force 2FA when using Jetpack SSO and force Jetpack SSO.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function force_2fa() {
|
||||
// Allows WP.com login to a local account if it matches the local account.
|
||||
add_filter( 'jetpack_sso_match_by_email', '__return_true', 9999 );
|
||||
|
||||
// multisite
|
||||
if ( is_multisite() ) {
|
||||
|
||||
// Hide the login form
|
||||
add_filter( 'jetpack_remove_login_form', '__return_true', 9999 );
|
||||
add_filter( 'jetpack_sso_bypass_login_forward_wpcom', '__return_true', 9999 );
|
||||
add_filter( 'jetpack_sso_display_disclaimer', '__return_false', 9999 );
|
||||
|
||||
add_filter(
|
||||
'wp_authenticate_user',
|
||||
function () {
|
||||
return new WP_Error( 'wpcom-required', $this->get_login_error_message() ); },
|
||||
9999
|
||||
);
|
||||
|
||||
add_filter( 'jetpack_sso_require_two_step', '__return_true' );
|
||||
|
||||
add_filter( 'allow_password_reset', '__return_false' );
|
||||
} else {
|
||||
// Not multisite.
|
||||
|
||||
// Completely disable the standard login form for admins.
|
||||
add_filter(
|
||||
'wp_authenticate_user',
|
||||
function ( $user ) {
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
if ( $user->has_cap( $this->role ) ) {
|
||||
return new WP_Error( 'wpcom-required', $this->get_login_error_message(), $user->user_login );
|
||||
}
|
||||
return $user;
|
||||
},
|
||||
9999
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'allow_password_reset',
|
||||
function ( $allow, $user_id ) {
|
||||
if ( user_can( $user_id, $this->role ) ) {
|
||||
return false;
|
||||
}
|
||||
return $allow; },
|
||||
9999,
|
||||
2
|
||||
);
|
||||
|
||||
add_action( 'jetpack_sso_pre_handle_login', array( $this, 'jetpack_set_two_step' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically set the two step filter for Jetpack SSO.
|
||||
*
|
||||
* @param Object $user_data The user data from WordPress.com.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function jetpack_set_two_step( $user_data ) {
|
||||
$user = SSO::get_user_by_wpcom_id( $user_data->ID );
|
||||
|
||||
// Borrowed from Jetpack. Ignores the match_by_email setting.
|
||||
if ( empty( $user ) ) {
|
||||
$user = get_user_by( 'email', $user_data->email );
|
||||
}
|
||||
|
||||
if ( $user && $user->has_cap( $this->role ) ) {
|
||||
add_filter( 'jetpack_sso_require_two_step', '__return_true' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_login_error_message() {
|
||||
/**
|
||||
* Filter the login error message.
|
||||
* Defaults to a message that explains the user must use a WordPress.com account with 2FA enabled.
|
||||
*
|
||||
* @param string $message The login error message.
|
||||
* @return string
|
||||
* @since jetpack-12.7
|
||||
* @module SSO
|
||||
*/
|
||||
return apply_filters(
|
||||
'jetpack_force_2fa_login_error_message',
|
||||
sprintf( 'For added security, please log in using your WordPress.com account.<br /><br />Note: Your account must have <a href="%1$s" target="_blank">Two Step Authentication</a> enabled, which can be configured from <a href="%2$s" target="_blank">Security Settings</a>.', 'https://support.wordpress.com/security/two-step-authentication/', 'https://wordpress.com/me/security/two-step' )
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+387
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
/**
|
||||
* A collection of helper functions used in the SSO module.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\SSO;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Jetpack_IXR_Client;
|
||||
|
||||
/**
|
||||
* A collection of helper functions used in the SSO module.
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*/
|
||||
class Helpers {
|
||||
/**
|
||||
* Determine if the login form should be hidden or not
|
||||
*
|
||||
* @return bool
|
||||
**/
|
||||
public static function should_hide_login_form() {
|
||||
/**
|
||||
* Remove the default log in form, only leave the WordPress.com log in button.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-3.1.0
|
||||
*
|
||||
* @param bool get_option( 'jetpack_sso_remove_login_form', false ) Should the default log in form be removed. Default to false.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value for whether logging in by matching the WordPress.com user email to a
|
||||
* Jetpack site user's email is allowed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function match_by_email() {
|
||||
$match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? \WPCC_MATCH_BY_EMAIL : (bool) get_option( 'jetpack_sso_match_by_email', true );
|
||||
|
||||
/**
|
||||
* Link the local account to an account on WordPress.com using the same email address.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-2.6.0
|
||||
*
|
||||
* @param bool $match_by_email Should we link the local account to an account on WordPress.com using the same email address. Default to false.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean for whether users are allowed to register on the Jetpack site with SSO,
|
||||
* even though the site disallows normal registrations.
|
||||
*
|
||||
* @param object|null $user_data WordPress.com user information.
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function new_user_override( $user_data = null ) {
|
||||
$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? \WPCC_NEW_USER_OVERRIDE : false;
|
||||
|
||||
/**
|
||||
* Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
|
||||
* If you return a string that corresponds to a user role, the user will be given that role.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-2.6.0
|
||||
* @since jetpack-4.6 $user_data object is now passed to the jetpack_sso_new_user_override filter
|
||||
*
|
||||
* @param bool|string $new_user_override Allow users to register on your site with a WordPress.com account. Default to false.
|
||||
* @param object|null $user_data An object containing the user data returned from WordPress.com.
|
||||
*/
|
||||
$role = apply_filters( 'jetpack_sso_new_user_override', $new_user_override, $user_data );
|
||||
|
||||
if ( $role ) {
|
||||
if ( is_string( $role ) && get_role( $role ) ) {
|
||||
return $role;
|
||||
} else {
|
||||
return get_option( 'default_role' );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean value for whether two-step authentication is required for SSO.
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_two_step_required() {
|
||||
/**
|
||||
* Is it required to have 2-step authentication enabled on WordPress.com to use SSO?
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-2.8.0
|
||||
*
|
||||
* @param bool get_option( 'jetpack_sso_require_two_step' ) Does SSO require 2-step authentication?
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step', false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean for whether a user that is attempting to log in will be automatically
|
||||
* redirected to WordPress.com to begin the SSO flow.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function bypass_login_forward_wpcom() {
|
||||
/**
|
||||
* Redirect the site's log in form to WordPress.com's log in form.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-3.1.0
|
||||
*
|
||||
* @param bool false Should the site's log in form be automatically forwarded to WordPress.com's log in form.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean for whether the SSO login form should be displayed as the default
|
||||
* when both the default and SSO login form allowed.
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function show_sso_login() {
|
||||
if ( self::should_hide_login_form() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the SSO login form as the default when both the default and SSO login forms are enabled.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*
|
||||
* @param bool true Should the SSO login form be displayed by default when the default login form is also enabled?
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_sso_default_to_sso_login', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean for whether the two step required checkbox, displayed on the Jetpack admin page, should be disabled.
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_require_two_step_checkbox_disabled() {
|
||||
return (bool) has_filter( 'jetpack_sso_require_two_step' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean for whether the match by email checkbox, displayed on the Jetpack admin page, should be disabled.
|
||||
*
|
||||
* @since jetpack-4.1.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_match_by_email_checkbox_disabled() {
|
||||
return defined( 'WPCC_MATCH_BY_EMAIL' ) || has_filter( 'jetpack_sso_match_by_email' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of hosts that SSO will redirect to.
|
||||
*
|
||||
* Instead of accessing JETPACK__API_BASE within the method directly, we set it as the
|
||||
* default for $api_base due to restrictions with testing constants in our tests.
|
||||
*
|
||||
* @since jetpack-4.3.0
|
||||
* @since jetpack-4.6.0 Added public-api.wordpress.com as an allowed redirect
|
||||
*
|
||||
* @param array $hosts Allowed redirect hosts.
|
||||
* @param string $api_base Base API URL.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function allowed_redirect_hosts( $hosts, $api_base = '' ) {
|
||||
if ( empty( $api_base ) ) {
|
||||
$api_base = Constants::get_constant( 'JETPACK__API_BASE' );
|
||||
}
|
||||
|
||||
if ( empty( $hosts ) ) {
|
||||
$hosts = array();
|
||||
}
|
||||
|
||||
$hosts[] = 'wordpress.com';
|
||||
$hosts[] = 'jetpack.wordpress.com';
|
||||
$hosts[] = 'public-api.wordpress.com';
|
||||
$hosts[] = 'jetpack.com';
|
||||
|
||||
if ( ! str_contains( $api_base, 'jetpack.wordpress.com/jetpack' ) ) {
|
||||
$base_url_parts = wp_parse_url( esc_url_raw( $api_base ) );
|
||||
if ( $base_url_parts && ! empty( $base_url_parts['host'] ) ) {
|
||||
$hosts[] = $base_url_parts['host'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique( $hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how long the auth cookie is valid for when a user logs in with SSO.
|
||||
*
|
||||
* @return int result of the jetpack_sso_auth_cookie_expiration filter.
|
||||
*/
|
||||
public static function extend_auth_cookie_expiration_for_sso() {
|
||||
/**
|
||||
* Determines how long the auth cookie is valid for when a user logs in with SSO.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-4.4.0
|
||||
* @since jetpack-6.1.0 Fixed a typo. Filter was previously jetpack_sso_auth_cookie_expirtation.
|
||||
*
|
||||
* @param int YEAR_IN_SECONDS
|
||||
*/
|
||||
return (int) apply_filters( 'jetpack_sso_auth_cookie_expiration', YEAR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the SSO form should be displayed for the current action.
|
||||
*
|
||||
* @since jetpack-4.6.0
|
||||
*
|
||||
* @param string $action SSO action being performed.
|
||||
*
|
||||
* @return bool Is SSO allowed for the current action?
|
||||
*/
|
||||
public static function display_sso_form_for_action( $action ) {
|
||||
/**
|
||||
* Allows plugins the ability to overwrite actions where the SSO form is allowed to be used.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-4.6.0
|
||||
*
|
||||
* @param array $allowed_actions_for_sso
|
||||
*/
|
||||
$allowed_actions_for_sso = (array) apply_filters(
|
||||
'jetpack_sso_allowed_actions',
|
||||
array(
|
||||
'login',
|
||||
'jetpack-sso',
|
||||
'jetpack_json_api_authorization',
|
||||
)
|
||||
);
|
||||
return in_array( $action, $allowed_actions_for_sso, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns an environment array that is meant to simulate `$_REQUEST` when the initial
|
||||
* JSON API auth request was made.
|
||||
*
|
||||
* @since jetpack-4.6.0
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function get_json_api_auth_environment() {
|
||||
if ( empty( $_COOKIE['jetpack_sso_original_request'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$original_request = esc_url_raw( wp_unslash( $_COOKIE['jetpack_sso_original_request'] ) );
|
||||
|
||||
$parsed_url = wp_parse_url( $original_request );
|
||||
if ( empty( $parsed_url ) || empty( $parsed_url['query'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$args = array();
|
||||
wp_parse_str( $parsed_url['query'], $args );
|
||||
|
||||
if ( empty( $args ) || empty( $args['action'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'jetpack_json_api_authorization' !== $args['action'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$args,
|
||||
array( 'jetpack_json_api_original_query' => $original_request )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the site has a custom login page URL, and return it.
|
||||
* If default login page URL is used (`wp-login.php`), `null` will be returned.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_custom_login_url() {
|
||||
$login_url = wp_login_url();
|
||||
|
||||
if ( str_ends_with( $login_url, 'wp-login.php' ) ) {
|
||||
// No custom URL found.
|
||||
return null;
|
||||
}
|
||||
|
||||
$site_url = trailingslashit( site_url() );
|
||||
|
||||
if ( ! str_starts_with( $login_url, $site_url ) ) {
|
||||
// Something went wrong, we can't properly extract the custom URL.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extracting the "path" part of the URL, because we don't need the `site_url` part.
|
||||
return str_ireplace( $site_url, '', $login_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cookies that store the profile information for the last
|
||||
* WPCOM user to connect.
|
||||
*/
|
||||
public static function clear_wpcom_profile_cookies() {
|
||||
if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
|
||||
setcookie(
|
||||
'jetpack_sso_wpcom_name_' . COOKIEHASH,
|
||||
' ',
|
||||
time() - YEAR_IN_SECONDS,
|
||||
COOKIEPATH,
|
||||
COOKIE_DOMAIN,
|
||||
is_ssl(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
|
||||
setcookie(
|
||||
'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
|
||||
' ',
|
||||
time() - YEAR_IN_SECONDS,
|
||||
COOKIEPATH,
|
||||
COOKIE_DOMAIN,
|
||||
is_ssl(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an SSO connection for a user.
|
||||
*
|
||||
* @param int $user_id The local user id.
|
||||
*/
|
||||
public static function delete_connection_for_user( $user_id ) {
|
||||
$wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true );
|
||||
if ( ! $wpcom_user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = new Jetpack_IXR_Client(
|
||||
array(
|
||||
'wpcom_user_id' => $user_id,
|
||||
)
|
||||
);
|
||||
$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
|
||||
|
||||
if ( $xml->isError() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up local data stored for SSO.
|
||||
delete_user_meta( $user_id, 'wpcom_user_id' );
|
||||
delete_user_meta( $user_id, 'wpcom_user_data' );
|
||||
self::clear_wpcom_profile_cookies();
|
||||
|
||||
return $xml->getResponse();
|
||||
}
|
||||
}
|
||||
Vendored
+272
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* A collection of helper functions used in the SSO module.
|
||||
*
|
||||
* @package automattic/jetpack-connection
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Connection\SSO;
|
||||
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
/**
|
||||
* A collection of helper functions used in the SSO module.
|
||||
*
|
||||
* @since jetpack-4.4.0
|
||||
*/
|
||||
class Notices {
|
||||
/**
|
||||
* Error message displayed on the login form when two step is required and
|
||||
* the user's account on WordPress.com does not have two step enabled.
|
||||
*
|
||||
* @since jetpack-2.7
|
||||
* @param string $message Error message.
|
||||
* @return string
|
||||
**/
|
||||
public static function error_msg_enable_two_step( $message ) {
|
||||
$error = sprintf(
|
||||
wp_kses(
|
||||
/* translators: URL to settings page */
|
||||
__(
|
||||
'Two-Step Authentication is required to access this site. Please visit your <a href="%1$s" rel="noopener noreferrer" target="_blank">Security Settings</a> to configure <a href="%2$s" rel="noopener noreferrer" target="_blank">Two-step Authentication</a> for your account.',
|
||||
'jetpack-connection'
|
||||
),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
Redirect::get_url( 'calypso-me-security-two-step' ),
|
||||
Redirect::get_url( 'wpcom-support-security-two-step-authentication' )
|
||||
);
|
||||
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message displayed when the user tries to SSO, but match by email
|
||||
* is off and they already have an account with their email address on
|
||||
* this site.
|
||||
*
|
||||
* @param string $message Error message.
|
||||
* @return string
|
||||
*/
|
||||
public static function error_msg_email_already_exists( $message ) {
|
||||
$error = sprintf(
|
||||
wp_kses(
|
||||
/* translators: login URL */
|
||||
__(
|
||||
'You already have an account on this site. Please <a href="%1$s">sign in</a> with your username and password and then connect to WordPress.com.',
|
||||
'jetpack-connection'
|
||||
),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
esc_url_raw( add_query_arg( 'jetpack-sso-show-default-form', '1', wp_login_url() ) )
|
||||
);
|
||||
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
|
||||
*
|
||||
* @since jetpack-4.3.2
|
||||
*
|
||||
* @param string $message Error Message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function error_msg_identity_crisis( $message ) {
|
||||
$error = esc_html__( 'Logging in with WordPress.com is not currently available because this site is experiencing connection problems.', 'jetpack-connection' );
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when we are not able to verify the SSO nonce due to an XML error or
|
||||
* failed validation. In either case, we prompt the user to try again or log in with username and password.
|
||||
*
|
||||
* @since jetpack-4.3.2
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function error_invalid_response_data( $message ) {
|
||||
$error = esc_html__(
|
||||
'There was an error logging you in via WordPress.com, please try again or try logging in with your username and password.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when we were not able to automatically create an account for a user
|
||||
* after a user has logged in via SSO. By default, this message is triggered after trying to create an account 5 times.
|
||||
*
|
||||
* @since jetpack-4.3.2
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function error_unable_to_create_user( $message ) {
|
||||
$error = esc_html__(
|
||||
'There was an error creating a user for you. Please contact the administrator of your site.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the default login form is hidden, this method is called on the 'authenticate' filter with a priority of 30.
|
||||
* This method disables the ability to submit the default login form.
|
||||
*
|
||||
* @param WP_User|WP_Error $user Either the user attempting to login or an existing authentication failure.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
public static function disable_default_login_form( $user ) {
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we're returning an error that will be shown as a red notice, let's remove the
|
||||
* informational "blue" notice.
|
||||
*/
|
||||
remove_filter( 'login_message', array( static::class, 'msg_login_by_jetpack' ) );
|
||||
return new WP_Error( 'jetpack_sso_required', self::get_sso_required_message() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message displayed when the site admin has disabled the default WordPress
|
||||
* login form in Settings > General > Secure Sign On
|
||||
*
|
||||
* @since jetpack-2.7
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
**/
|
||||
public static function msg_login_by_jetpack( $message ) {
|
||||
$message .= sprintf( '<p class="message">%s</p>', self::get_sso_required_message() );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message for SSO required.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_sso_required_message() {
|
||||
$msg = esc_html__(
|
||||
'A WordPress.com account is required to access this site. Click the button below to sign in or create a free WordPress.com account.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the message displayed when the default WordPress login form is disabled.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-2.8.0
|
||||
*
|
||||
* @param string $msg Disclaimer when default WordPress login form is disabled.
|
||||
*/
|
||||
return apply_filters( 'jetpack_sso_disclaimer_message', $msg );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message displayed when the user can not be found after approving the SSO process on WordPress.com
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function cant_find_user( $message ) {
|
||||
$error = __(
|
||||
"We couldn't find your account. If you already have an account, make sure you have connected to WordPress.com.",
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the "couldn't find your account" notice after an attempted SSO.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-10.5.0
|
||||
*
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
$error = apply_filters( 'jetpack_sso_unknown_user_notice', $error );
|
||||
|
||||
$message .= sprintf( '<p class="message" id="login_error">%s</p>', esc_html( $error ) );
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
|
||||
*
|
||||
* @since jetpack-4.4.0
|
||||
* @deprecated since 2.10.0
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sso_not_allowed_in_staging( $message ) {
|
||||
_deprecated_function( __FUNCTION__, '2.10.0', 'sso_not_allowed_in_safe_mode' );
|
||||
$error = __(
|
||||
'Logging in with WordPress.com is disabled for sites that are in staging mode.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the disallowed notice for staging sites attempting SSO.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since jetpack-10.5.0
|
||||
*
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
$error = apply_filters_deprecated( 'jetpack_sso_disallowed_staging_notice', array( $error ), '2.9.1', 'jetpack_sso_disallowed_safe_mode_notice' );
|
||||
$message .= sprintf( '<p class="message">%s</p>', esc_html( $error ) );
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
|
||||
*
|
||||
* @since 2.10.0
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sso_not_allowed_in_safe_mode( $message ) {
|
||||
$error = __(
|
||||
'Logging in with WordPress.com is disabled for sites that are in safe mode.',
|
||||
'jetpack-connection'
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the disallowed notice for sites in safe mode attempting SSO.
|
||||
*
|
||||
* @module sso
|
||||
*
|
||||
* @since 2.10.0
|
||||
*
|
||||
* @param string $error Error text.
|
||||
*/
|
||||
$error = apply_filters( 'jetpack_sso_disallowed_safe_mode_notice', $error );
|
||||
$message .= sprintf( '<p class="message">%s</p>', esc_html( $error ) );
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
Vendored
+1283
File diff suppressed because it is too large
Load Diff
Vendored
+1322
File diff suppressed because it is too large
Load Diff
+21
@@ -0,0 +1,21 @@
|
||||
.jetpack-sso-admin-create-user-invite-message {
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.jetpack-sso-admin-create-user-invite-message-link-sso {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#createuser .form-field textarea {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
#createuser .form-field [type=checkbox] {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
#custom_email_message_description {
|
||||
max-width: 25rem;
|
||||
color: #646970;
|
||||
font-size: 12px;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
document.addEventListener( 'DOMContentLoaded', function () {
|
||||
const sendUserNotificationCheckbox = document.getElementById( 'send_user_notification' );
|
||||
const userExternalContractorCheckbox = document.getElementById( 'user_external_contractor' );
|
||||
const inviteUserWpcomCheckbox = document.getElementById( 'invite_user_wpcom' );
|
||||
const customEmailMessageBlock = document.getElementById( 'custom_email_message_block' );
|
||||
|
||||
if ( inviteUserWpcomCheckbox && sendUserNotificationCheckbox && customEmailMessageBlock ) {
|
||||
// Toggle Send User Notification checkbox enabled/disabled based on Invite User checkbox
|
||||
// Enable External Contractor checkbox if Invite User checkbox is checked
|
||||
// Show/hide the external email message field.
|
||||
inviteUserWpcomCheckbox.addEventListener( 'change', function () {
|
||||
sendUserNotificationCheckbox.disabled = inviteUserWpcomCheckbox.checked;
|
||||
if ( inviteUserWpcomCheckbox.checked ) {
|
||||
sendUserNotificationCheckbox.checked = false;
|
||||
if ( userExternalContractorCheckbox ) {
|
||||
userExternalContractorCheckbox.disabled = false;
|
||||
}
|
||||
customEmailMessageBlock.style.display = 'table';
|
||||
} else {
|
||||
if ( userExternalContractorCheckbox ) {
|
||||
userExternalContractorCheckbox.disabled = true;
|
||||
userExternalContractorCheckbox.checked = false;
|
||||
}
|
||||
customEmailMessageBlock.style.display = 'none';
|
||||
}
|
||||
} );
|
||||
|
||||
// On load, disable Send User Notification checkbox
|
||||
// and show the custom email message if Invite User checkbox is checked
|
||||
if ( inviteUserWpcomCheckbox.checked ) {
|
||||
sendUserNotificationCheckbox.disabled = true;
|
||||
sendUserNotificationCheckbox.checked = false;
|
||||
customEmailMessageBlock.style.display = 'table';
|
||||
}
|
||||
|
||||
// On load, disable External Contractor checkbox
|
||||
// and hide the custom email message if Invite User checkbox is unchecked
|
||||
if ( ! inviteUserWpcomCheckbox.checked ) {
|
||||
if ( userExternalContractorCheckbox ) {
|
||||
userExternalContractorCheckbox.disabled = true;
|
||||
}
|
||||
customEmailMessageBlock.style.display = 'none';
|
||||
}
|
||||
}
|
||||
} );
|
||||
wp/wp-content/plugins/woocommerce/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
#loginform {
|
||||
/* We set !important because sometimes static is added inline */
|
||||
position: relative !important;
|
||||
padding-bottom: 92px;
|
||||
}
|
||||
|
||||
.jetpack-sso-repositioned #loginform {
|
||||
padding-bottom: 26px;
|
||||
}
|
||||
|
||||
#loginform #jetpack-sso-wrap,
|
||||
#loginform #jetpack-sso-wrap * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
#jetpack-sso-wrap__action,
|
||||
#jetpack-sso-wrap__user {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #jetpack-sso-wrap__action,
|
||||
.jetpack-sso-form-display #jetpack-sso-wrap__user {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
padding: 0 24px;
|
||||
margin-left: -24px;
|
||||
margin-right: -24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jetpack-sso-repositioned #jetpack-sso-wrap {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
padding: 0;
|
||||
margin-top: 16px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #jetpack-sso-wrap {
|
||||
position: relative;
|
||||
bottom: auto;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#loginform #jetpack-sso-wrap p {
|
||||
color: #777777;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap .jetpack-sso-toggle.wpcom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.wpcom {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.jetpack-sso-form-display #jetpack-sso-wrap .jetpack-sso-toggle.default {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.jetpack-sso-form-display #loginform>p,
|
||||
.jetpack-sso-form-display #loginform>div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #loginform #jetpack-sso-wrap {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #loginform {
|
||||
padding: 26px 24px;
|
||||
}
|
||||
|
||||
.jetpack-sso-or {
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jetpack-sso-or:before {
|
||||
background: #dcdcde;
|
||||
content: '';
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jetpack-sso-or span {
|
||||
background: #fff;
|
||||
color: #777;
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
text-transform: uppercase
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap .button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap .button .genericon-wordpress {
|
||||
font-size: 24px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap__user img {
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap__user h2 {
|
||||
font-size: 21px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#jetpack-sso-wrap__user h2 span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jetpack-sso-wrap__reauth {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jetpack-sso-form-display #backtoblog {
|
||||
margin: 24px 0 0;
|
||||
}
|
||||
|
||||
.jetpack-sso-clear:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
const body = document.querySelector( 'body' ),
|
||||
toggleSSO = document.querySelector( '.jetpack-sso-toggle' ),
|
||||
userLogin = document.getElementById( 'user_login' ),
|
||||
userPassword = document.getElementById( 'user_pass' ),
|
||||
ssoWrap = document.getElementById( 'jetpack-sso-wrap' ),
|
||||
loginForm = document.getElementById( 'loginform' ),
|
||||
overflow = document.createElement( 'div' );
|
||||
|
||||
overflow.className = 'jetpack-sso-clear';
|
||||
|
||||
loginForm.appendChild( overflow );
|
||||
overflow.appendChild( document.querySelector( 'p.forgetmenot' ) );
|
||||
overflow.appendChild( document.querySelector( 'p.submit' ) );
|
||||
|
||||
loginForm.appendChild( ssoWrap );
|
||||
body.classList.add( 'jetpack-sso-repositioned' );
|
||||
|
||||
toggleSSO.addEventListener( 'click', e => {
|
||||
e.preventDefault();
|
||||
body.classList.toggle( 'jetpack-sso-form-display' );
|
||||
if ( ! body.classList.contains( 'jetpack-sso-form-display' ) ) {
|
||||
userLogin.focus();
|
||||
userPassword.disabled = false;
|
||||
}
|
||||
} );
|
||||
} );
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
document.addEventListener( 'DOMContentLoaded', function () {
|
||||
document
|
||||
.querySelectorAll( '.jetpack-sso-invitation-tooltip-icon:not(.sso-disconnected-user)' )
|
||||
.forEach( function ( tooltip ) {
|
||||
tooltip.innerHTML += ' [?]';
|
||||
|
||||
const tooltipTextbox = document.createElement( 'span' );
|
||||
tooltipTextbox.classList.add( 'jetpack-sso-invitation-tooltip', 'jetpack-sso-th-tooltip' );
|
||||
|
||||
const tooltipString = window.Jetpack_SSOTooltip.tooltipString;
|
||||
tooltipTextbox.innerHTML += tooltipString;
|
||||
|
||||
tooltip.addEventListener( 'mouseenter', appendTooltip );
|
||||
tooltip.addEventListener( 'focus', appendTooltip );
|
||||
tooltip.addEventListener( 'mouseleave', removeTooltip );
|
||||
tooltip.addEventListener( 'blur', removeTooltip );
|
||||
|
||||
/**
|
||||
* Display the tooltip textbox.
|
||||
*/
|
||||
function appendTooltip() {
|
||||
tooltip.appendChild( tooltipTextbox );
|
||||
tooltipTextbox.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the tooltip textbox.
|
||||
*/
|
||||
function removeTooltip() {
|
||||
// Only remove tooltip if the element isn't currently active.
|
||||
if ( document.activeElement === tooltip ) {
|
||||
return;
|
||||
}
|
||||
tooltip.removeChild( tooltipTextbox );
|
||||
}
|
||||
} );
|
||||
document
|
||||
.querySelectorAll( '.jetpack-sso-invitation-tooltip-icon:not(.jetpack-sso-status-column)' )
|
||||
.forEach( function ( tooltip ) {
|
||||
tooltip.addEventListener( 'mouseenter', appendSSOInvitationTooltip );
|
||||
tooltip.addEventListener( 'focus', appendSSOInvitationTooltip );
|
||||
tooltip.addEventListener( 'mouseleave', removeSSOInvitationTooltip );
|
||||
tooltip.addEventListener( 'blur', removeSSOInvitationTooltip );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Display the SSO invitation tooltip textbox.
|
||||
*/
|
||||
function appendSSOInvitationTooltip() {
|
||||
this.querySelector( '.jetpack-sso-invitation-tooltip' ).style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the SSO invitation tooltip textbox.
|
||||
*
|
||||
* @param {Event} event - Triggering event.
|
||||
*/
|
||||
function removeSSOInvitationTooltip( event ) {
|
||||
if ( document.activeElement === event.target ) {
|
||||
return;
|
||||
}
|
||||
this.querySelector( '.jetpack-sso-invitation-tooltip' ).style.display = 'none';
|
||||
}
|
||||
} );
|
||||
+38
-29
@@ -10,6 +10,7 @@ namespace Automattic\Jetpack\Connection\Webhooks;
|
||||
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Licensing;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Automattic\Jetpack\Tracking;
|
||||
use GP_Locales;
|
||||
use Jetpack_Network;
|
||||
@@ -18,11 +19,17 @@ use Jetpack_Network;
|
||||
* Authorize_Redirect Webhook handler class.
|
||||
*/
|
||||
class Authorize_Redirect {
|
||||
/**
|
||||
* The Connection Manager object.
|
||||
*
|
||||
* @var \Automattic\Jetpack\Connection\Manager
|
||||
*/
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Constructs the object
|
||||
*
|
||||
* @param Automattic\Jetpack\Connection\Manager $connection The Connection Manager object.
|
||||
* @param \Automattic\Jetpack\Connection\Manager $connection The Connection Manager object.
|
||||
*/
|
||||
public function __construct( $connection ) {
|
||||
$this->connection = $connection;
|
||||
@@ -32,6 +39,8 @@ class Authorize_Redirect {
|
||||
* Handle the webhook
|
||||
*
|
||||
* This method implements what's in Jetpack::admin_page_load when the Jetpack plugin is not present
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function handle() {
|
||||
|
||||
@@ -89,46 +98,57 @@ class Authorize_Redirect {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Jetpack authorization URL. Copied from Jetpack class.
|
||||
* Create the Jetpack authorization URL.
|
||||
*
|
||||
* @since 2.7.6 Added optional $from and $raw parameters.
|
||||
*
|
||||
* @param bool|string $redirect URL to redirect to.
|
||||
* @param bool|string $from If not false, adds 'from=$from' param to the connect URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
*
|
||||
* @todo Update default value for redirect since the called function expects a string.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function build_authorize_url( $redirect = false ) {
|
||||
public function build_authorize_url( $redirect = false, $from = false, $raw = 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 );
|
||||
$url = $this->connection->get_authorization_url( wp_get_current_user(), $redirect, $from, $raw );
|
||||
|
||||
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
|
||||
* Filter the URL used when authorizing a user to a WordPress.com account.
|
||||
*
|
||||
* @since jetpack-8.9.0
|
||||
* @since 2.7.6 Added $raw parameter.
|
||||
*
|
||||
* @param string $url Connection URL.
|
||||
* @param bool $raw If true, URL will not be escaped.
|
||||
*/
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url );
|
||||
return apply_filters( 'jetpack_build_authorize_url', $url, $raw );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* URL should return the user back to the My Jetpack page.
|
||||
*
|
||||
* @param String $redirect the default redirect URL used by the package.
|
||||
* @return String the modified URL.
|
||||
* @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' ) );
|
||||
$jetpack_admin_page = esc_url_raw( admin_url( 'admin.php?page=my-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'] ) ) {
|
||||
if (
|
||||
class_exists( 'Jetpack_Network' )
|
||||
&& isset( $_REQUEST['is_multisite'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
) {
|
||||
$redirect = Jetpack_Network::init()->get_url( 'network_admin_page' );
|
||||
}
|
||||
|
||||
@@ -137,7 +157,6 @@ class Authorize_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.
|
||||
@@ -164,7 +183,7 @@ class Authorize_Redirect {
|
||||
)
|
||||
);
|
||||
|
||||
$calypso_env = self::get_calypso_env();
|
||||
$calypso_env = ( new Host() )->get_calypso_env();
|
||||
|
||||
if ( ! empty( $calypso_env ) ) {
|
||||
$args['calypso_env'] = $calypso_env;
|
||||
@@ -178,25 +197,15 @@ class Authorize_Redirect {
|
||||
* it with different Calypso enrionments, such as localhost.
|
||||
* Copied from Jetpack class.
|
||||
*
|
||||
* @deprecated 2.7.6
|
||||
*
|
||||
* @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'] );
|
||||
}
|
||||
_deprecated_function( __METHOD__, '2.7.6', 'Automattic\\Jetpack\\Status\\Host::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 '';
|
||||
return ( new Host() )->get_calypso_env();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -29,7 +29,7 @@ class Constants {
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @var array.
|
||||
* @var array
|
||||
*/
|
||||
public static $set_constants = array();
|
||||
|
||||
@@ -91,8 +91,8 @@ class Constants {
|
||||
/**
|
||||
* 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.
|
||||
* @param string $name The name of the constant.
|
||||
* @param int|float|string|bool|array|null $value The value of the constant.
|
||||
*/
|
||||
public static function set_constant( $name, $value ) {
|
||||
self::$set_constants[ $name ] = $value;
|
||||
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@ class Redirect {
|
||||
|
||||
$source_key = 'source';
|
||||
|
||||
if ( 0 === strpos( $source, 'https://' ) ) {
|
||||
if ( \str_starts_with( $source, 'https://' ) ) {
|
||||
$source_key = 'url';
|
||||
$source_url = \wp_parse_url( $source );
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ class Cache {
|
||||
*
|
||||
* @param string $key Key to fetch.
|
||||
* @param mixed $default Default value to return if the key is not set.
|
||||
* @returns mixed Data.
|
||||
* @return mixed Data.
|
||||
*/
|
||||
public static function get( $key, $default = null ) {
|
||||
$blog_id = get_current_blog_id();
|
||||
|
||||
+6
@@ -14,25 +14,31 @@ namespace Automattic\Jetpack;
|
||||
|
||||
/**
|
||||
* Erros class.
|
||||
*
|
||||
* @deprecated since 3.2.0
|
||||
*/
|
||||
class Errors {
|
||||
/**
|
||||
* Catches PHP errors. Must be used in conjunction with output buffering.
|
||||
*
|
||||
* @deprecated since 3.2.0
|
||||
* @param bool $catch True to start catching, False to stop.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
public function catch_errors( $catch ) {
|
||||
_deprecated_function( __METHOD__, '3.2.0' );
|
||||
static $display_errors, $error_reporting;
|
||||
|
||||
if ( $catch ) {
|
||||
// Force error reporting and output, store original values.
|
||||
$display_errors = @ini_set( 'display_errors', 1 );
|
||||
$error_reporting = @error_reporting( E_ALL );
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
add_action( 'shutdown', array( 'Jetpack', 'catch_errors_on_shutdown' ), 0 );
|
||||
}
|
||||
} else {
|
||||
// Restore the original values for error reporting and output.
|
||||
@ini_set( 'display_errors', $display_errors );
|
||||
@error_reporting( $error_reporting );
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ class Files {
|
||||
|
||||
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
|
||||
while ( false !== $file = readdir( $dir ) ) {
|
||||
if ( '.' === substr( $file, 0, 1 ) || '.php' !== substr( $file, -4 ) ) {
|
||||
if ( str_starts_with( $file, '.' ) || ! str_ends_with( $file, '.php' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+160
-2
@@ -34,7 +34,7 @@ class Host {
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @return bool;
|
||||
* @return bool
|
||||
*/
|
||||
public function is_atomic_platform() {
|
||||
return Constants::is_true( 'ATOMIC_SITE_ID' ) && Constants::is_true( 'ATOMIC_CLIENT_ID' );
|
||||
@@ -127,11 +127,169 @@ class Host {
|
||||
*/
|
||||
public function get_source_query() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$allowed_sources = array( 'jetpack-manage' );
|
||||
$allowed_sources = array( 'jetpack-manage', 'a8c-for-agencies' );
|
||||
if ( isset( $_GET['source'] ) && in_array( $_GET['source'], $allowed_sources, true ) ) {
|
||||
return sanitize_key( $_GET['source'] );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of nameservers for the current site.
|
||||
*
|
||||
* @param string $domain The domain of the site to check.
|
||||
* @return array
|
||||
*/
|
||||
public function get_nameserver_dns_records( $domain ) {
|
||||
if ( ! function_exists( 'dns_get_record' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$dns_records = dns_get_record( $domain, DNS_NS ); // Fetches the DNS records of type NS (Name Server)
|
||||
$nameservers = array();
|
||||
foreach ( $dns_records as $record ) {
|
||||
if ( isset( $record['target'] ) ) {
|
||||
$nameservers[] = $record['target']; // Adds the nameserver to the array
|
||||
}
|
||||
}
|
||||
|
||||
return $nameservers; // Returns an array of nameserver names
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a DNS entry, will return a hosting provider if one can be determined. Otherwise, will return 'unknown'.
|
||||
* Sourced from: fbhepr%2Skers%2Sjcpbz%2Sjc%2Qpbagrag%2Syvo%2Subfgvat%2Qcebivqre%2Sanzrfreiref.cuc-og
|
||||
*
|
||||
* @param string $domain The domain of the site to check.
|
||||
* @return string The hosting provider of 'unknown'.
|
||||
*/
|
||||
public function get_hosting_provider_by_nameserver( $domain ) {
|
||||
$known_nameservers = array(
|
||||
'bluehost' => array(
|
||||
'.bluehost.com',
|
||||
),
|
||||
'dreamhost' => array(
|
||||
'.dreamhost.com',
|
||||
),
|
||||
'mediatemple' => array(
|
||||
'.mediatemple.net',
|
||||
),
|
||||
'xserver' => array(
|
||||
'.xserver.jp',
|
||||
),
|
||||
'namecheap' => array(
|
||||
'.namecheaphosting.com',
|
||||
),
|
||||
'hostmonster' => array(
|
||||
'.hostmonster.com',
|
||||
),
|
||||
'justhost' => array(
|
||||
'.justhost.com',
|
||||
),
|
||||
'digitalocean' => array(
|
||||
'.digitalocean.com',
|
||||
),
|
||||
'one' => array(
|
||||
'.one.com',
|
||||
),
|
||||
'hostpapa' => array(
|
||||
'.hostpapa.com',
|
||||
),
|
||||
'siteground' => array(
|
||||
'.sgcloud.net',
|
||||
'.sgedu.site',
|
||||
'.sgsrv1.com',
|
||||
'.sgvps.net',
|
||||
'.siteground.biz',
|
||||
'.siteground.net',
|
||||
'.siteground.eu',
|
||||
),
|
||||
'inmotion' => array(
|
||||
'.inmotionhosting.com',
|
||||
),
|
||||
'ionos' => array(
|
||||
'.ui-dns.org',
|
||||
'.ui-dns.de',
|
||||
'.ui-dns.biz',
|
||||
'.ui-dns.com',
|
||||
),
|
||||
);
|
||||
|
||||
$dns_records = $this->get_nameserver_dns_records( $domain );
|
||||
$dns_records = array_map( 'strtolower', $dns_records );
|
||||
|
||||
foreach ( $known_nameservers as $host => $ns_patterns ) {
|
||||
foreach ( $ns_patterns as $ns_pattern ) {
|
||||
foreach ( $dns_records as $record ) {
|
||||
if ( false !== strpos( $record, $ns_pattern ) ) {
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a guess of the hosting provider for the current site based on various checks.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_known_host_guess() {
|
||||
$host = Cache::get( 'host_guess' );
|
||||
|
||||
if ( null !== $host ) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
// First, let's check if we can recognize provider manually:
|
||||
switch ( true ) {
|
||||
case $this->is_woa_site():
|
||||
$provider = 'woa';
|
||||
break;
|
||||
case $this->is_atomic_platform():
|
||||
$provider = 'atomic';
|
||||
break;
|
||||
case $this->is_newspack_site():
|
||||
$provider = 'newspack';
|
||||
break;
|
||||
case $this->is_vip_site():
|
||||
$provider = 'vip';
|
||||
break;
|
||||
case $this->is_wpcom_simple():
|
||||
case $this->is_wpcom_platform():
|
||||
$provider = 'wpcom';
|
||||
break;
|
||||
default:
|
||||
$provider = 'unknown';
|
||||
break;
|
||||
}
|
||||
|
||||
// Second, let's check if we can recognize provider by nameservers:
|
||||
$domain = isset( $_SERVER['SERVER_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) : '';
|
||||
if ( $provider === 'unknown' && ! empty( $domain ) ) {
|
||||
$provider = $this->get_hosting_provider_by_nameserver( $domain );
|
||||
}
|
||||
|
||||
Cache::set( 'host_guess', $provider );
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add public-api.wordpress.com to the safe redirect allowed list - only added when someone allows API access.
|
||||
*
|
||||
* @since 3.0.2 Ported from Jetpack to the Status package.
|
||||
*
|
||||
* To be used with a filter of allowed domains for a redirect.
|
||||
*
|
||||
* @param array $domains Allowed WP.com Environments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function allow_wpcom_public_api_domain( $domains ) {
|
||||
$domains[] = 'public-api.wordpress.com';
|
||||
return $domains;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-11
@@ -22,10 +22,16 @@ class Modules {
|
||||
* Check whether or not a Jetpack module is active.
|
||||
*
|
||||
* @param string $module The slug of a Jetpack module.
|
||||
* @param bool $available_only Whether to only check among available modules.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active( $module ) {
|
||||
return in_array( $module, self::get_active(), true );
|
||||
public function is_active( $module, $available_only = true ) {
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array( $module, self::get_active( $available_only ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +168,7 @@ class Modules {
|
||||
}
|
||||
|
||||
$key = md5( $file_name . maybe_serialize( $headers ) );
|
||||
$refresh_cache = is_admin() && isset( $_GET['page'] ) && 'jetpack' === substr( $_GET['page'], 0, 7 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
|
||||
$refresh_cache = is_admin() && isset( $_GET['page'] ) && is_string( $_GET['page'] ) && str_starts_with( $_GET['page'], 'jetpack' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
|
||||
|
||||
// If we don't need to refresh the cache, and already have the value, short-circuit!
|
||||
if ( ! $refresh_cache && isset( $file_data_option[ $key ] ) ) {
|
||||
@@ -180,8 +186,12 @@ class Modules {
|
||||
|
||||
/**
|
||||
* Get a list of activated modules as an array of module slugs.
|
||||
*
|
||||
* @param bool $available_only Filter out the unavailable (deleted) modules.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_active() {
|
||||
public function get_active( $available_only = true ) {
|
||||
$active = \Jetpack_Options::get_option( 'active_modules' );
|
||||
|
||||
if ( ! is_array( $active ) ) {
|
||||
@@ -202,9 +212,11 @@ class Modules {
|
||||
$active[] = 'protect';
|
||||
}
|
||||
|
||||
// If it's not available, it shouldn't be active.
|
||||
// We don't delete it from the options though, as it will be active again when a plugin gets reactivated.
|
||||
$active = array_intersect( $active, $this->get_available() );
|
||||
if ( $available_only ) {
|
||||
// If it's not available, it shouldn't be active.
|
||||
// We don't delete it from the options though, as it will be active again when a plugin gets reactivated.
|
||||
$active = array_intersect( $active, $this->get_available() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow filtering of the active modules.
|
||||
@@ -450,10 +462,8 @@ class Modules {
|
||||
}
|
||||
|
||||
// Check the file for fatal errors, a la wp-admin/plugins.php::activate.
|
||||
$errors = new Errors();
|
||||
$state->state( 'module', $module );
|
||||
$state->state( 'error', 'module_activation_failed' ); // we'll override this later if the plugin can be included without fatal error.
|
||||
$errors->catch_errors( true );
|
||||
|
||||
ob_start();
|
||||
$module_path = $this->get_path( $module );
|
||||
@@ -466,7 +476,6 @@ class Modules {
|
||||
|
||||
$state->state( 'error', false ); // the override.
|
||||
ob_end_clean();
|
||||
$errors->catch_errors( false );
|
||||
} else { // Not a Jetpack plugin.
|
||||
$active[] = $module;
|
||||
$this->update_active( $active );
|
||||
@@ -530,7 +539,7 @@ class Modules {
|
||||
*
|
||||
* @param array $modules Array of active modules to be saved in options.
|
||||
*
|
||||
* @return $success bool true for success, false for failure.
|
||||
* @return bool $success true for success, false for failure.
|
||||
*/
|
||||
public function update_active( $modules ) {
|
||||
$current_modules = \Jetpack_Options::get_option( 'active_modules', array() );
|
||||
|
||||
+53
@@ -25,4 +25,57 @@ class Paths {
|
||||
$url = add_query_arg( $args, admin_url( 'admin.php' ) );
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current request is activating a plugin from the plugins page.
|
||||
*
|
||||
* @param string $plugin Plugin file path to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_current_request_activating_plugin_from_plugins_screen( $plugin ) {
|
||||
// Filter out common async request contexts
|
||||
if (
|
||||
wp_doing_ajax() ||
|
||||
( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
|
||||
( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) ||
|
||||
( defined( 'WP_CLI' ) && WP_CLI )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
|
||||
$request_file = esc_url_raw( wp_unslash( $_SERVER['SCRIPT_NAME'] ) );
|
||||
} elseif ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
list( $request_file ) = explode( '?', esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not the plugins page
|
||||
if ( strpos( $request_file, 'wp-admin/plugins.php' ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same method to get the action as used by plugins.php
|
||||
$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
|
||||
$action = $wp_list_table->current_action();
|
||||
|
||||
// Not a singular activation
|
||||
// This also means that if the plugin is activated as part of a group ( bulk activation ), this function will return false here.
|
||||
if ( 'activate' !== $action ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the nonce associated with the plugin activation
|
||||
// We are not changing any data here, so this is not super necessary, it's just a best practice before using the form data from $_REQUEST.
|
||||
check_admin_referer( 'activate-plugin_' . $plugin );
|
||||
|
||||
// Not the right plugin
|
||||
$requested_plugin = isset( $_REQUEST['plugin'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) : null;
|
||||
if ( $requested_plugin !== $plugin ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-42
@@ -17,18 +17,6 @@ use WPCOM_Masterbar;
|
||||
* Used to retrieve information about the current status of Jetpack and the site overall.
|
||||
*/
|
||||
class Status {
|
||||
/**
|
||||
* Is Jetpack in development (offline) mode?
|
||||
*
|
||||
* @deprecated 1.3.0 Use Status->is_offline_mode().
|
||||
*
|
||||
* @return bool Whether Jetpack's offline mode is active.
|
||||
*/
|
||||
public function is_development_mode() {
|
||||
_deprecated_function( __FUNCTION__, '1.3.0', 'Automattic\Jetpack\Status->is_offline_mode' );
|
||||
return $this->is_offline_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Jetpack in offline mode?
|
||||
*
|
||||
@@ -54,20 +42,6 @@ class Status {
|
||||
$offline_mode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters Jetpack's offline mode.
|
||||
*
|
||||
* @see https://jetpack.com/support/development-mode/
|
||||
* @todo Update documentation ^^.
|
||||
*
|
||||
* @since 1.1.1
|
||||
* @since-jetpack 2.2.1
|
||||
* @deprecated 1.3.0
|
||||
*
|
||||
* @param bool $offline_mode Is Jetpack's offline mode active.
|
||||
*/
|
||||
$offline_mode = (bool) apply_filters_deprecated( 'jetpack_development_mode', array( $offline_mode ), '1.3.0', 'jetpack_offline_mode' );
|
||||
|
||||
/**
|
||||
* Filters Jetpack's offline mode.
|
||||
*
|
||||
@@ -84,21 +58,6 @@ class Status {
|
||||
return $offline_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Jetpack in "No User test mode"?
|
||||
*
|
||||
* This will make Jetpack act as if there were no connected users, but only a site connection (aka blog token)
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @deprecated 1.7.5 Since this version, Jetpack connection is considered active after registration, making no_user_testing_mode obsolete.
|
||||
*
|
||||
* @return bool Whether Jetpack's No User Testing Mode is active.
|
||||
*/
|
||||
public function is_no_user_testing_mode() {
|
||||
_deprecated_function( __METHOD__, '1.7.5' );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a system with a multiple networks.
|
||||
* Implemented since there is no core is_multi_network function.
|
||||
@@ -167,6 +126,7 @@ class Status {
|
||||
$site_url = site_url();
|
||||
|
||||
// Check for localhost and sites using an IP only first.
|
||||
// Note: str_contains() is not used here, as wp-includes/compat.php is not loaded in this file.
|
||||
$is_local = $site_url && false === strpos( $site_url, '.' );
|
||||
|
||||
// Use Core's environment check, if available.
|
||||
@@ -211,11 +171,12 @@ class Status {
|
||||
/**
|
||||
* If is a staging site.
|
||||
*
|
||||
* @todo Add IDC detection to a package.
|
||||
* @deprecated since 3.3.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_staging_site() {
|
||||
_deprecated_function( __FUNCTION__, '3.3.0', 'in_safe_mode' );
|
||||
$cached = Cache::get( 'is_staging_site' );
|
||||
if ( null !== $cached ) {
|
||||
return $cached;
|
||||
@@ -301,6 +262,64 @@ class Status {
|
||||
return $is_staging;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the site is in safe mode.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function in_safe_mode() {
|
||||
$cached = Cache::get( 'in_safe_mode' );
|
||||
if ( null !== $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
$in_safe_mode = false;
|
||||
if ( method_exists( 'Automattic\\Jetpack\\Identity_Crisis', 'validate_sync_error_idc_option' ) && \Automattic\Jetpack\Identity_Crisis::validate_sync_error_idc_option() ) {
|
||||
$in_safe_mode = true;
|
||||
}
|
||||
/**
|
||||
* Filters in_safe_mode check.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @param bool $in_safe_mode If the current site is in safe mode.
|
||||
*/
|
||||
$in_safe_mode = apply_filters( 'jetpack_is_in_safe_mode', $in_safe_mode );
|
||||
|
||||
Cache::set( 'in_safe_mode', $in_safe_mode );
|
||||
return $in_safe_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the site is a development/staging site.
|
||||
* This is a new version of is_staging_site added to separate safe mode from the legacy staging mode.
|
||||
* This method checks for core WP_ENVIRONMENT_TYPE setting
|
||||
* Using the jetpack_is_development_site filter.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_development_site() {
|
||||
$cached = Cache::get( 'is_development_site' );
|
||||
if ( null !== $cached ) {
|
||||
return $cached;
|
||||
}
|
||||
$is_dev_site = ! in_array( wp_get_environment_type(), array( 'production', 'local' ), true );
|
||||
/**
|
||||
* Filters is_development_site check.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @param bool $is_dev_site If the current site is a staging or dev site.
|
||||
*/
|
||||
$is_dev_site = apply_filters( 'jetpack_is_development_site', $is_dev_site );
|
||||
|
||||
Cache::set( 'is_development_site', $is_dev_site );
|
||||
return $is_dev_site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site is currently onboarding or not.
|
||||
* A site is considered as being onboarded if it currently has an onboarding token.
|
||||
|
||||
+13
@@ -40,4 +40,17 @@ class Visitor {
|
||||
|
||||
return ! empty( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple gate check for a11n feature testing purposes using AT_PROXIED_REQUEST constant.
|
||||
* IMPORTANT: Only use it for internal feature test purposes, not authorization.
|
||||
*
|
||||
* The goal of this function is to help us gate features by using a similar function name
|
||||
* we find on simple sites: is_automattician().
|
||||
*
|
||||
* @return bool True if the current request is PROXIED, false otherwise.
|
||||
*/
|
||||
public function is_automattician_feature_flags_only() {
|
||||
return ( defined( 'AT_PROXIED_REQUEST' ) && AT_PROXIED_REQUEST );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user