wip
|
Before Width: | Height: | Size: 642 KiB After Width: | Height: | Size: 642 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 929 KiB After Width: | Height: | Size: 929 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 973 KiB After Width: | Height: | Size: 973 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
@@ -5,7 +5,9 @@ body {
|
||||
--akismet-color-dark-grey: #646970;
|
||||
--akismet-color-grey-80: #2c3338;
|
||||
--akismet-color-grey-100: #101517;
|
||||
--akismet-color-grey-border: #dcdcde;
|
||||
--akismet-color-white: #fff;
|
||||
--akismet-color-dark-green: #2d6a40;
|
||||
--akismet-color-mid-green: #357b49;
|
||||
--akismet-color-light-green: #4eb26a;
|
||||
--akismet-color-mid-red: #e82c3f;
|
||||
@@ -31,6 +33,7 @@ body {
|
||||
#akismet-plugin-container {
|
||||
background-color: var(--akismet-color-light-grey);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen-Sans', 'Ubuntu', 'Cantarell', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
#akismet-plugin-container a {
|
||||
@@ -69,10 +72,26 @@ body {
|
||||
padding-left: 0.2em;
|
||||
}
|
||||
|
||||
.akismet-button, .akismet-button:hover {
|
||||
background-color: var(--akismet-color-mid-green);
|
||||
.akismet-button,
|
||||
.akismet-button:hover {
|
||||
border: 0;
|
||||
color: #fff;
|
||||
color: var(--akismet-color-white);
|
||||
}
|
||||
|
||||
.akismet-button {
|
||||
background-color: var(--akismet-color-mid-green);
|
||||
}
|
||||
|
||||
.akismet-button:hover {
|
||||
background-color: var(--akismet-color-dark-green);
|
||||
}
|
||||
|
||||
.akismet-external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-left: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
/* Need this specificity to override the existing header rule */
|
||||
@@ -84,9 +103,13 @@ body {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot .akismet-new-snapshot__number {
|
||||
.akismet-new-snapshot__number {
|
||||
color: var(--akismet-color-charcoal);
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -1px;
|
||||
line-height: 1.5em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -152,6 +175,10 @@ body {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.akismet-box:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.akismet-box,
|
||||
.akismet-card {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06), 0 0 2px rgba(0, 0, 0, 0.16);
|
||||
@@ -340,6 +367,133 @@ body {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
/* Setup */
|
||||
.akismet-setup-instructions__heading {
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
h3.akismet-setup-instructions__subheading {
|
||||
color: var(--akismet-color-dark-grey);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 1.25rem;
|
||||
padding-block-start: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__feature-list {
|
||||
list-style: none;
|
||||
margin: 1rem 0.5rem 1.5rem;
|
||||
max-width: 640px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__feature {
|
||||
align-items: start;
|
||||
display: flex;
|
||||
margin-block-end: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__body {
|
||||
flex: 1;
|
||||
padding-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__title {
|
||||
color: #1d2327;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p.akismet-setup-instructions__text {
|
||||
color: var(--akismet-color-grey-80);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin: 0.25rem 0 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__button,
|
||||
.akismet-setup-instructions__button:hover,
|
||||
.akismet-setup-instructions__button:visited {
|
||||
font-size: 1rem;
|
||||
margin-inline-start: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection {
|
||||
background: var(--akismet-color-light-grey);
|
||||
border: 1px solid var(--akismet-color-grey-border);
|
||||
border-radius: 8px;
|
||||
margin: 1rem 1rem 2rem 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-action:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-user {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-avatar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-avatar-image {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-account-name {
|
||||
color: var(--akismet-color-charcoal);
|
||||
font-size: 0.9rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-account-email {
|
||||
margin-top: 0.1rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-action {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-button {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-intro,
|
||||
p.akismet-setup__connection-action-description {
|
||||
color: var(--akismet-color-dark-grey);
|
||||
font-size: 0.875rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-intro {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-description {
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
|
||||
/* Setup - API key input */
|
||||
.akismet-enter-api-key-box {
|
||||
margin: 1.5rem 0;
|
||||
@@ -467,22 +621,6 @@ h3.akismet-enter-api-key-box__header {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.akismet-settings__external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-left: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.akismet-compatible-plugins__external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-left: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.akismet-compatible-plugins__show-more {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -91,13 +91,6 @@ table.comments td.comment p a:after {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.akismet-alert-text {
|
||||
color: #dd3d36;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot {
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
@@ -118,12 +111,6 @@ table.comments td.comment p a:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot__number {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
font-weight: lighter;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.akismet-settings th:first-child {
|
||||
vertical-align: top;
|
||||
@@ -295,18 +282,6 @@ table.comments td.comment p a:after {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.akismet-jetpack-email {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.akismet-jetpack-gravatar {
|
||||
padding: 0 0 0 1.5rem;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
.akismet-box p:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
@@ -333,31 +308,10 @@ table.comments td.comment p a:after {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-box-header {
|
||||
max-width: 700px;
|
||||
margin: 0 auto 40px auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.akismet-box-header h2 {
|
||||
margin: 1.5rem 10% 0;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.akismet-box .centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.akismet-box .akismet-toggles {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
.akismet-box .akismet-ak-connect, .akismet-box .toggle-jp-connect {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.akismet-button, .akismet-button:hover, .akismet-button:visited {
|
||||
background: white;
|
||||
border-color: #c8d7e1;
|
||||
@@ -432,14 +386,15 @@ table.comments td.comment p a:after {
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions form {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions > a.akismet-button {
|
||||
display: inline-block;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
div.error.akismet-usage-limit-alert {
|
||||
padding: 25px 45px 25px 15px;
|
||||
display: flex;
|
||||
@@ -477,6 +432,7 @@ div.error.akismet-usage-limit-alert {
|
||||
}
|
||||
|
||||
.akismet-usage-limit-alert h3 {
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -394,28 +394,4 @@ jQuery( function ( $ ) {
|
||||
|
||||
$( this ).hide();
|
||||
} );
|
||||
|
||||
/**
|
||||
* Hides the Connect with Jetpack form | Shows the Activate Akismet Account form
|
||||
*/
|
||||
$( 'a.toggle-ak-connect' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
$( '.akismet-ak-connect' ).slideToggle('slow');
|
||||
$( 'a.toggle-ak-connect' ).hide();
|
||||
$( '.akismet-jp-connect' ).hide();
|
||||
$( 'a.toggle-jp-connect' ).show();
|
||||
} );
|
||||
|
||||
/**
|
||||
* Shows the Connect with Jetpack form | Hides the Activate Akismet Account form
|
||||
*/
|
||||
$( 'a.toggle-jp-connect' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
$( '.akismet-jp-connect' ).slideToggle('slow');
|
||||
$( 'a.toggle-jp-connect' ).hide();
|
||||
$( '.akismet-ak-connect' ).hide();
|
||||
$( 'a.toggle-ak-connect' ).show();
|
||||
} );
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* This file was automatically generated on Jun 19 2025 22:31:12 */
|
||||
/* This file was automatically generated on Oct 23 2025 00:26:05 */
|
||||
|
||||
body {
|
||||
--akismet-color-charcoal: #272635;
|
||||
@@ -7,7 +7,9 @@ body {
|
||||
--akismet-color-dark-grey: #646970;
|
||||
--akismet-color-grey-80: #2c3338;
|
||||
--akismet-color-grey-100: #101517;
|
||||
--akismet-color-grey-border: #dcdcde;
|
||||
--akismet-color-white: #fff;
|
||||
--akismet-color-dark-green: #2d6a40;
|
||||
--akismet-color-mid-green: #357b49;
|
||||
--akismet-color-light-green: #4eb26a;
|
||||
--akismet-color-mid-red: #e82c3f;
|
||||
@@ -33,6 +35,7 @@ body {
|
||||
#akismet-plugin-container {
|
||||
background-color: var(--akismet-color-light-grey);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen-Sans', 'Ubuntu', 'Cantarell', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
#akismet-plugin-container a {
|
||||
@@ -71,10 +74,26 @@ body {
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
|
||||
.akismet-button, .akismet-button:hover {
|
||||
background-color: var(--akismet-color-mid-green);
|
||||
.akismet-button,
|
||||
.akismet-button:hover {
|
||||
border: 0;
|
||||
color: #fff;
|
||||
color: var(--akismet-color-white);
|
||||
}
|
||||
|
||||
.akismet-button {
|
||||
background-color: var(--akismet-color-mid-green);
|
||||
}
|
||||
|
||||
.akismet-button:hover {
|
||||
background-color: var(--akismet-color-dark-green);
|
||||
}
|
||||
|
||||
.akismet-external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
/* Need this specificity to override the existing header rule */
|
||||
@@ -86,9 +105,13 @@ body {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot .akismet-new-snapshot__number {
|
||||
.akismet-new-snapshot__number {
|
||||
color: var(--akismet-color-charcoal);
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -1px;
|
||||
line-height: 1.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -154,6 +177,10 @@ body {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.akismet-box:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.akismet-box,
|
||||
.akismet-card {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06), 0 0 2px rgba(0, 0, 0, 0.16);
|
||||
@@ -342,6 +369,133 @@ body {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
/* Setup */
|
||||
.akismet-setup-instructions__heading {
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
h3.akismet-setup-instructions__subheading {
|
||||
color: var(--akismet-color-dark-grey);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 1.25rem;
|
||||
padding-block-start: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__feature-list {
|
||||
list-style: none;
|
||||
margin: 1rem 0.5rem 1.5rem;
|
||||
max-width: 640px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__feature {
|
||||
align-items: start;
|
||||
display: flex;
|
||||
margin-block-end: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__body {
|
||||
flex: 1;
|
||||
padding-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__title {
|
||||
color: #1d2327;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p.akismet-setup-instructions__text {
|
||||
color: var(--akismet-color-grey-80);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin: 0.25rem 0 0;
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions__button,
|
||||
.akismet-setup-instructions__button:hover,
|
||||
.akismet-setup-instructions__button:visited {
|
||||
font-size: 1rem;
|
||||
margin-inline-start: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection {
|
||||
background: var(--akismet-color-light-grey);
|
||||
border: 1px solid var(--akismet-color-grey-border);
|
||||
border-radius: 8px;
|
||||
margin: 1rem 1rem 2rem 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-action:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-user {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-avatar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-avatar-image {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-account-name {
|
||||
color: var(--akismet-color-charcoal);
|
||||
font-size: 0.9rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-account-email {
|
||||
margin-top: 0.1rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-action {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.akismet-setup__connection-button {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-intro,
|
||||
p.akismet-setup__connection-action-description {
|
||||
color: var(--akismet-color-dark-grey);
|
||||
font-size: 0.875rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-intro {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
p.akismet-setup__connection-action-description {
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
|
||||
/* Setup - API key input */
|
||||
.akismet-enter-api-key-box {
|
||||
margin: 1.5rem 0;
|
||||
@@ -469,22 +623,6 @@ h3.akismet-enter-api-key-box__header {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.akismet-settings__external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.akismet-compatible-plugins__external-link::after {
|
||||
content: "↗";
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
text-decoration: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.akismet-compatible-plugins__show-more {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* This file was automatically generated on Jun 19 2025 22:00:12 */
|
||||
/* This file was automatically generated on Oct 30 2025 21:26:42 */
|
||||
|
||||
.wp-admin.jetpack_page_akismet-key-config, .wp-admin.settings_page_akismet-key-config {
|
||||
background-color:#f3f6f8;
|
||||
@@ -93,13 +93,6 @@ table.comments td.comment p a:after {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.akismet-alert-text {
|
||||
color: #dd3d36;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot {
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
@@ -120,12 +113,6 @@ table.comments td.comment p a:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.akismet-new-snapshot__number {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
font-weight: lighter;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.akismet-settings th:first-child {
|
||||
vertical-align: top;
|
||||
@@ -297,18 +284,6 @@ table.comments td.comment p a:after {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.akismet-jetpack-email {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.akismet-jetpack-gravatar {
|
||||
padding: 0 1.5rem 0 0;
|
||||
float: right;
|
||||
margin-left: 1rem;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
.akismet-box p:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
@@ -335,31 +310,10 @@ table.comments td.comment p a:after {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-box-header {
|
||||
max-width: 700px;
|
||||
margin: 0 auto 40px auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.akismet-box-header h2 {
|
||||
margin: 1.5rem 10% 0;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.akismet-box .centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.akismet-box .akismet-toggles {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
.akismet-box .akismet-ak-connect, .akismet-box .toggle-jp-connect {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.akismet-button, .akismet-button:hover, .akismet-button:visited {
|
||||
background: white;
|
||||
border-color: #c8d7e1;
|
||||
@@ -434,14 +388,15 @@ table.comments td.comment p a:after {
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions form {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.akismet-setup-instructions > a.akismet-button {
|
||||
display: inline-block;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
div.error.akismet-usage-limit-alert {
|
||||
padding: 25px 15px 25px 45px;
|
||||
display: flex;
|
||||
@@ -479,6 +434,7 @@ div.error.akismet-usage-limit-alert {
|
||||
}
|
||||
|
||||
.akismet-usage-limit-alert h3 {
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Plugin Name: Akismet Anti-spam: Spam Protection
|
||||
Plugin URI: https://akismet.com/
|
||||
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
|
||||
Version: 5.5
|
||||
Version: 5.6
|
||||
Requires at least: 5.8
|
||||
Requires PHP: 7.2
|
||||
Author: Automattic - Anti-spam Team
|
||||
@@ -39,7 +39,7 @@ if ( ! function_exists( 'add_action' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define( 'AKISMET_VERSION', '5.5' );
|
||||
define( 'AKISMET_VERSION', '5.6' );
|
||||
define( 'AKISMET__MINIMUM_WP_VERSION', '5.8' );
|
||||
define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'AKISMET_DELETE_LIMIT', 10000 );
|
||||
|
||||
@@ -52,12 +52,6 @@ class Akismet_Compatible_Plugins {
|
||||
*/
|
||||
protected const CACHE_KEY = 'akismet_compatible_plugin_list';
|
||||
|
||||
/**
|
||||
* The cache group for things cached in this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const CACHE_GROUP = 'akismet_compatible_plugins';
|
||||
|
||||
/**
|
||||
* How many plugins should be visible by default?
|
||||
@@ -69,6 +63,7 @@ class Akismet_Compatible_Plugins {
|
||||
/**
|
||||
* Get the list of active, installed compatible plugins.
|
||||
*
|
||||
* @param bool $bypass_cache Whether to bypass the cache and fetch fresh data.
|
||||
* @return WP_Error|array {
|
||||
* Array of active, installed compatible plugins with their metadata.
|
||||
* @type string $name The display name of the plugin
|
||||
@@ -76,9 +71,9 @@ class Akismet_Compatible_Plugins {
|
||||
* @type string $logo URL or path to the plugin's logo
|
||||
* }
|
||||
*/
|
||||
public static function get_installed_compatible_plugins() {
|
||||
public static function get_installed_compatible_plugins( bool $bypass_cache = false ) {
|
||||
// Retrieve and validate the full compatible plugins list.
|
||||
$compatible_plugins = static::get_compatible_plugins();
|
||||
$compatible_plugins = static::get_compatible_plugins( $bypass_cache );
|
||||
|
||||
if ( empty( $compatible_plugins ) ) {
|
||||
return new WP_Error(
|
||||
@@ -149,13 +144,14 @@ class Akismet_Compatible_Plugins {
|
||||
/**
|
||||
* Gets plugins that are compatible with Akismet from the Akismet API.
|
||||
*
|
||||
* @param bool $bypass_cache Whether to bypass the cache and fetch fresh data.
|
||||
* @return array
|
||||
*/
|
||||
private static function get_compatible_plugins(): array {
|
||||
private static function get_compatible_plugins( bool $bypass_cache = false ): array {
|
||||
// Return cached result if present (false => cache miss; empty array is valid).
|
||||
$cached_plugins = static::get_cached_plugins();
|
||||
|
||||
if ( $cached_plugins ) {
|
||||
if ( false !== $cached_plugins && ! $bypass_cache ) {
|
||||
return $cached_plugins;
|
||||
}
|
||||
|
||||
@@ -278,10 +274,9 @@ class Akismet_Compatible_Plugins {
|
||||
private static function set_cached_plugins( array $plugins ): bool {
|
||||
$_blog_id = (int) get_current_blog_id();
|
||||
|
||||
return wp_cache_set(
|
||||
return set_transient(
|
||||
static::CACHE_KEY . "_$_blog_id",
|
||||
$plugins,
|
||||
static::CACHE_GROUP . "_$_blog_id",
|
||||
DAY_IN_SECONDS
|
||||
);
|
||||
}
|
||||
@@ -294,9 +289,8 @@ class Akismet_Compatible_Plugins {
|
||||
private static function get_cached_plugins() {
|
||||
$_blog_id = (int) get_current_blog_id();
|
||||
|
||||
return wp_cache_get(
|
||||
static::CACHE_KEY . "_$_blog_id",
|
||||
static::CACHE_GROUP . "_$_blog_id"
|
||||
return get_transient(
|
||||
static::CACHE_KEY . "_$_blog_id"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,9 +302,8 @@ class Akismet_Compatible_Plugins {
|
||||
private static function purge_cache(): bool {
|
||||
$_blog_id = (int) get_current_blog_id();
|
||||
|
||||
return wp_cache_delete(
|
||||
static::CACHE_KEY . "_$_blog_id",
|
||||
static::CACHE_GROUP . "_$_blog_id"
|
||||
return delete_transient(
|
||||
static::CACHE_KEY . "_$_blog_id"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +111,22 @@ class Akismet_Admin {
|
||||
}
|
||||
|
||||
public static function admin_menu() {
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
if ( self::is_jetpack_active() ) {
|
||||
add_action( 'jetpack_admin_menu', array( 'Akismet_Admin', 'load_menu' ) );
|
||||
} else {
|
||||
self::load_menu();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Jetpack is active.
|
||||
*
|
||||
* @return bool True if Jetpack class exists, false otherwise.
|
||||
*/
|
||||
public static function is_jetpack_active(): bool {
|
||||
return class_exists( 'Jetpack' );
|
||||
}
|
||||
|
||||
public static function admin_head() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
@@ -131,7 +140,7 @@ class Akismet_Admin {
|
||||
}
|
||||
|
||||
public static function load_menu() {
|
||||
if ( class_exists( 'Jetpack' ) ) {
|
||||
if ( self::is_jetpack_active() ) {
|
||||
$hook = add_submenu_page( 'jetpack', __( 'Akismet Anti-spam', 'akismet' ), __( 'Akismet Anti-spam', 'akismet' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
|
||||
} else {
|
||||
$hook = add_options_page( __( 'Akismet Anti-spam', 'akismet' ), __( 'Akismet Anti-spam', 'akismet' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
|
||||
@@ -234,7 +243,7 @@ class Akismet_Admin {
|
||||
'<p><strong>' . esc_html__( 'Akismet Setup', 'akismet' ) . '</strong></p>' .
|
||||
'<p>' . esc_html__( 'You need to enter an API key to activate the Akismet service on your site.', 'akismet' ) . '</p>' .
|
||||
/* translators: %s: a link to the signup page with the text 'Akismet.com'. */
|
||||
'<p>' . sprintf( __( 'Sign up for an account on %s to get an API Key.', 'akismet' ), '<a href="https://akismet.com/plugin-signup/" target="_blank">Akismet.com</a>' ) . '</p>',
|
||||
'<p>' . sprintf( __( 'Sign up for an account on %s to get an API Key.', 'akismet' ), '<a href="https://akismet.com/pricing/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=help_signup" target="_blank">Akismet.com</a>' ) . '</p>',
|
||||
)
|
||||
);
|
||||
|
||||
@@ -306,8 +315,9 @@ class Akismet_Admin {
|
||||
// Help Sidebar
|
||||
$current_screen->set_help_sidebar(
|
||||
'<p><strong>' . esc_html__( 'For more information:', 'akismet' ) . '</strong></p>' .
|
||||
'<p><a href="https://akismet.com/faq/" target="_blank">' . esc_html__( 'Akismet FAQ', 'akismet' ) . '</a></p>' .
|
||||
'<p><a href="https://akismet.com/support/" target="_blank">' . esc_html__( 'Akismet Support', 'akismet' ) . '</a></p>'
|
||||
|
||||
'<p><a href="https://akismet.com/resources/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=help_faq" target="_blank">' . esc_html__( 'Akismet FAQ', 'akismet' ) . '</a></p>' .
|
||||
'<p><a href="https://akismet.com/support/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=help_support" target="_blank">' . esc_html__( 'Akismet Support', 'akismet' ) . '</a></p>'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -356,14 +366,14 @@ class Akismet_Admin {
|
||||
$akismet_user = self::get_akismet_user( $api_key );
|
||||
|
||||
if ( $akismet_user ) {
|
||||
if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) ) ) {
|
||||
if ( in_array( $akismet_user->status, array( Akismet::USER_STATUS_ACTIVE, 'active-dunning' ) ) ) {
|
||||
update_option( 'wordpress_api_key', $api_key );
|
||||
}
|
||||
|
||||
if ( $akismet_user->status == 'active' ) {
|
||||
if ( $akismet_user->status == Akismet::USER_STATUS_ACTIVE ) {
|
||||
self::$notices['status'] = 'new-key-valid';
|
||||
} elseif ( $akismet_user->status == 'notice' ) {
|
||||
self::$notices['status'] = $akismet_user;
|
||||
} elseif ( $akismet_user->status == Akismet::USER_STATUS_NO_SUB ) {
|
||||
self::$notices['status'] = 'no-sub';
|
||||
} else {
|
||||
self::$notices['status'] = $akismet_user->status;
|
||||
}
|
||||
@@ -371,7 +381,15 @@ class Akismet_Admin {
|
||||
self::$notices['status'] = 'new-key-invalid';
|
||||
}
|
||||
} elseif ( in_array( $key_status, array( 'invalid', 'failed' ) ) ) {
|
||||
self::$notices['status'] = 'new-key-' . $key_status;
|
||||
// When verify-key returns 'invalid', it could be truly invalid OR suspended.
|
||||
// Check get-subscription to distinguish between these cases.
|
||||
$akismet_user = self::get_akismet_user( $api_key );
|
||||
|
||||
if ( $akismet_user && isset( $akismet_user->status ) && $akismet_user->status === Akismet::USER_STATUS_SUSPENDED ) {
|
||||
self::$notices['status'] = Akismet::USER_STATUS_SUSPENDED;
|
||||
} else {
|
||||
self::$notices['status'] = 'new-key-' . $key_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +414,7 @@ class Akismet_Admin {
|
||||
$count,
|
||||
'akismet'
|
||||
),
|
||||
'https://akismet.com/wordpress/',
|
||||
'https://akismet.com/wordpress/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=dashboard_stats',
|
||||
esc_url( add_query_arg( array( 'page' => 'akismet-admin' ), admin_url( isset( $submenu['edit-comments.php'] ) ? 'edit-comments.php' : 'edit.php' ) ) ),
|
||||
number_format_i18n( $count )
|
||||
) . '</p>';
|
||||
@@ -413,12 +431,12 @@ class Akismet_Admin {
|
||||
$count,
|
||||
'akismet'
|
||||
),
|
||||
'https://akismet.com/wordpress/',
|
||||
'https://akismet.com/wordpress/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=dashboard_stats',
|
||||
number_format_i18n( $count )
|
||||
);
|
||||
} else {
|
||||
/* translators: %s: Akismet website URL. */
|
||||
$intro = sprintf( __( '<a href="%s">Akismet</a> blocks spam from getting to your blog. ', 'akismet' ), 'https://akismet.com/wordpress/' );
|
||||
$intro = sprintf( __( '<a href="%s">Akismet</a> blocks spam from getting to your blog. ', 'akismet' ), 'https://akismet.com/wordpress/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=dashboard_stats' );
|
||||
}
|
||||
|
||||
$link = add_query_arg( array( 'comment_status' => 'spam' ), admin_url( 'edit-comments.php' ) );
|
||||
@@ -508,7 +526,7 @@ class Akismet_Admin {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $_POST['nonce'], 'akismet_check_for_spam' ) ) {
|
||||
if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'akismet_check_for_spam' ) ) {
|
||||
wp_send_json(
|
||||
array(
|
||||
'error' => __( 'You don’t have permission to do that.', 'akismet' ),
|
||||
@@ -959,9 +977,21 @@ class Akismet_Admin {
|
||||
return add_query_arg( $args, menu_page_url( 'akismet-key-config', false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Akismet user subscription information.
|
||||
*
|
||||
* @param string $api_key The Akismet API key.
|
||||
* @return object|false Object with subscription info, or false if key is invalid or has no subscription.
|
||||
*
|
||||
* The returned object contains these properties:
|
||||
* - account_id (int|false): WordPress.com user ID, or false if unavailable.
|
||||
* - status (string): Account status - 'active', 'active-dunning', 'no-sub', 'cancelled', 'suspended', 'missing', or 'notice'.
|
||||
* - account_name (string): Subscription plan display name.
|
||||
* - account_type (string): Account type slug.
|
||||
* - next_billing_date (int|false): Unix timestamp of next billing date, or false if none.
|
||||
* - limit_reached (bool): Whether the usage limit has been reached.
|
||||
*/
|
||||
public static function get_akismet_user( $api_key ) {
|
||||
$akismet_user = false;
|
||||
|
||||
$request_args = array(
|
||||
'key' => $api_key,
|
||||
'blog' => get_option( 'home' ),
|
||||
@@ -971,9 +1001,14 @@ class Akismet_Admin {
|
||||
|
||||
$subscription_verification = Akismet::http_post( Akismet::build_query( $request_args ), 'get-subscription' );
|
||||
|
||||
$akismet_user = false;
|
||||
|
||||
if ( ! empty( $subscription_verification[1] ) ) {
|
||||
if ( 'invalid' !== $subscription_verification[1] ) {
|
||||
$akismet_user = json_decode( $subscription_verification[1] );
|
||||
$decoded = json_decode( $subscription_verification[1] );
|
||||
if ( is_object( $decoded ) ) {
|
||||
$akismet_user = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,7 +1075,7 @@ class Akismet_Admin {
|
||||
|
||||
if ( is_object( $akismet_user ) ) {
|
||||
self::save_key( $akismet_user->api_key );
|
||||
return in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) );
|
||||
return in_array( $akismet_user->status, array( Akismet::USER_STATUS_ACTIVE, 'active-dunning', Akismet::USER_STATUS_NO_SUB ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1061,15 +1096,16 @@ class Akismet_Admin {
|
||||
|
||||
public static function get_usage_limit_alert_data() {
|
||||
return array(
|
||||
'type' => 'usage-limit',
|
||||
'code' => (int) get_option( 'akismet_alert_code' ),
|
||||
'msg' => get_option( 'akismet_alert_msg' ),
|
||||
'api_calls' => get_option( 'akismet_alert_api_calls' ),
|
||||
'usage_limit' => get_option( 'akismet_alert_usage_limit' ),
|
||||
'upgrade_plan' => get_option( 'akismet_alert_upgrade_plan' ),
|
||||
'upgrade_url' => get_option( 'akismet_alert_upgrade_url' ),
|
||||
'upgrade_type' => get_option( 'akismet_alert_upgrade_type' ),
|
||||
'upgrade_via_support' => get_option( 'akismet_alert_upgrade_via_support' ) === 'true',
|
||||
'type' => 'usage-limit',
|
||||
'code' => (int) get_option( 'akismet_alert_code' ),
|
||||
'msg' => get_option( 'akismet_alert_msg' ),
|
||||
'api_calls' => get_option( 'akismet_alert_api_calls' ),
|
||||
'usage_limit' => get_option( 'akismet_alert_usage_limit' ),
|
||||
'upgrade_plan' => get_option( 'akismet_alert_upgrade_plan' ),
|
||||
'upgrade_url' => get_option( 'akismet_alert_upgrade_url' ),
|
||||
'upgrade_type' => get_option( 'akismet_alert_upgrade_type' ),
|
||||
'upgrade_via_support' => get_option( 'akismet_alert_upgrade_via_support' ) === 'true',
|
||||
'recommended_plan_name' => get_option( 'akismet_alert_recommended_plan_name' ),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1165,11 +1201,11 @@ class Akismet_Admin {
|
||||
|
||||
/*
|
||||
// To see all variants when testing.
|
||||
$akismet_user->status = 'no-sub';
|
||||
$akismet_user->status = Akismet::USER_STATUS_NO_SUB;
|
||||
Akismet::view( 'start', compact( 'akismet_user' ) );
|
||||
$akismet_user->status = 'cancelled';
|
||||
$akismet_user->status = Akismet::USER_STATUS_CANCELLED;
|
||||
Akismet::view( 'start', compact( 'akismet_user' ) );
|
||||
$akismet_user->status = 'suspended';
|
||||
$akismet_user->status = Akismet::USER_STATUS_SUSPENDED;
|
||||
Akismet::view( 'start', compact( 'akismet_user' ) );
|
||||
$akismet_user->status = 'other';
|
||||
Akismet::view( 'start', compact( 'akismet_user' ) );
|
||||
@@ -1208,7 +1244,7 @@ class Akismet_Admin {
|
||||
$notices = array();
|
||||
|
||||
if ( empty( self::$notices ) ) {
|
||||
if ( ! empty( $stat_totals['all'] ) && isset( $stat_totals['all']->time_saved ) && $akismet_user->status == 'active' && $akismet_user->account_type == 'free-api-key' ) {
|
||||
if ( ! empty( $stat_totals['all'] ) && isset( $stat_totals['all']->time_saved ) && $akismet_user->status == Akismet::USER_STATUS_ACTIVE && $akismet_user->account_type == 'free-api-key' ) {
|
||||
|
||||
$time_saved = false;
|
||||
|
||||
@@ -1237,7 +1273,7 @@ class Akismet_Admin {
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! Akismet::predefined_api_key() && ! isset( self::$notices['status'] ) && in_array( $akismet_user->status, array( 'cancelled', 'suspended', 'missing', 'no-sub' ) ) ) {
|
||||
if ( ! Akismet::predefined_api_key() && ! isset( self::$notices['status'] ) && in_array( $akismet_user->status, array( Akismet::USER_STATUS_CANCELLED, Akismet::USER_STATUS_SUSPENDED, Akismet::USER_STATUS_MISSING, Akismet::USER_STATUS_NO_SUB ) ) ) {
|
||||
$notices[] = array( 'type' => $akismet_user->status );
|
||||
}
|
||||
|
||||
@@ -1264,15 +1300,17 @@ class Akismet_Admin {
|
||||
// $notices[] = array( 'type' => 'missing-functions' );
|
||||
// $notices[] = array( 'type' => 'servers-be-down' );
|
||||
// $notices[] = array( 'type' => 'active-dunning' );
|
||||
// $notices[] = array( 'type' => 'cancelled' );
|
||||
// $notices[] = array( 'type' => 'suspended' );
|
||||
// $notices[] = array( 'type' => 'missing' );
|
||||
// $notices[] = array( 'type' => 'no-sub' );
|
||||
// $notices[] = array( 'type' => Akismet::USER_STATUS_CANCELLED );
|
||||
// $notices[] = array( 'type' => Akismet::USER_STATUS_SUSPENDED );
|
||||
// $notices[] = array( 'type' => Akismet::USER_STATUS_MISSING );
|
||||
// $notices[] = array( 'type' => Akismet::USER_STATUS_NO_SUB );
|
||||
// $notices[] = array( 'type' => 'new-key-valid' );
|
||||
// $notices[] = array( 'type' => 'new-key-invalid' );
|
||||
// $notices[] = array( 'type' => 'existing-key-invalid' );
|
||||
// $notices[] = array( 'type' => 'new-key-failed' );
|
||||
// $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10502 );
|
||||
// $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_type' => 'qty', 'upgrade_plan' => 'Business', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10504, 'recommended_plan_name' => 'Akismet Pro (500)' );
|
||||
// $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_type' => 'qty', 'upgrade_plan' => 'Business', 'upgrade_url' => 'https://akismet.com/pricing/', 'code' => 10508 );
|
||||
// $notices[] = array( 'type' => 'spam-check', 'link_text' => 'Link text.' );
|
||||
// $notices[] = array( 'type' => 'spam-check-cron-disabled' );
|
||||
// $notices[] = array( 'type' => 'alert', 'code' => 123 );
|
||||
@@ -1294,6 +1332,9 @@ class Akismet_Admin {
|
||||
// Akismet::view( 'notice', array( 'type' => 'spam-check-cron-disabled' ) );
|
||||
// Akismet::view( 'notice', array( 'type' => 'spam-check' ) );
|
||||
// Akismet::view( 'notice', array( 'type' => 'alert', 'code' => 123, 'msg' => 'Message' ) );
|
||||
// Akismet::view( 'notice', array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10502 ) );
|
||||
// Akismet::view( 'notice', array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_type' => 'qty', 'upgrade_plan' => 'Business', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10504, 'recommended_plan_name' => 'Akismet Pro (500)' ) );
|
||||
// Akismet::view( 'notice', array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_type' => 'qty', 'upgrade_plan' => 'Business', 'upgrade_url' => 'https://akismet.com/pricing/', 'code' => 10508 ) );
|
||||
|
||||
if ( in_array( $hook_suffix, array( 'edit-comments.php' ) ) && (int) get_option( 'akismet_alert_code' ) > 0 ) {
|
||||
Akismet::verify_key( Akismet::get_api_key() ); // verify that the key is still in alert state
|
||||
@@ -1383,7 +1424,7 @@ class Akismet_Admin {
|
||||
* @return array|false
|
||||
*/
|
||||
private static function get_jetpack_user() {
|
||||
if ( ! class_exists( 'Jetpack' ) ) {
|
||||
if ( ! self::is_jetpack_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class Akismet_REST_API {
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'Akismet_REST_API', 'sanitize_key' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/get/', 'akismet' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/account', 'akismet' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -119,7 +119,7 @@ class Akismet_REST_API {
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'Akismet_REST_API', 'sanitize_key' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/get/', 'akismet' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/account', 'akismet' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -132,7 +132,7 @@ class Akismet_REST_API {
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'Akismet_REST_API', 'sanitize_key' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/get/', 'akismet' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/account', 'akismet' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -145,7 +145,7 @@ class Akismet_REST_API {
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'Akismet_REST_API', 'sanitize_key' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/get/', 'akismet' ),
|
||||
'description' => __( 'A 12-character Akismet API key. Available at akismet.com/account', 'akismet' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -388,7 +388,7 @@ class Akismet_REST_API {
|
||||
public static function remote_call_permission_callback( $request ) {
|
||||
$local_key = Akismet::get_api_key();
|
||||
|
||||
return $local_key && ( strtolower( $request->get_param( 'key' ) ) === strtolower( $local_key ) );
|
||||
return $local_key && ( strtolower( $request->get_param( 'key' ) ?? '' ) === strtolower( $local_key ) );
|
||||
}
|
||||
|
||||
public static function sanitize_interval( $interval, $request, $param ) {
|
||||
|
||||
@@ -137,7 +137,7 @@ class Akismet_Widget extends WP_Widget {
|
||||
</style>
|
||||
|
||||
<div class="a-stats">
|
||||
<a href="https://akismet.com" class="a-stats__link" target="_blank" rel="noopener" style="background-color: var(--akismet-color-mid-green); color: var(--akismet-color-white);">
|
||||
<a href="https://akismet.com?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=widget_stats" class="a-stats__link" target="_blank" rel="noopener" style="background-color: var(--akismet-color-mid-green); color: var(--akismet-color-white);">
|
||||
<?php
|
||||
|
||||
echo wp_kses(
|
||||
|
||||
@@ -12,6 +12,13 @@ class Akismet {
|
||||
const MAX_DELAY_BEFORE_MODERATION_EMAIL = 86400; // One day in seconds
|
||||
const ALERT_CODE_COMMERCIAL = 30001;
|
||||
|
||||
// User account status constants
|
||||
const USER_STATUS_ACTIVE = 'active';
|
||||
const USER_STATUS_NO_SUB = 'no-sub';
|
||||
const USER_STATUS_MISSING = 'missing';
|
||||
const USER_STATUS_CANCELLED = 'cancelled';
|
||||
const USER_STATUS_SUSPENDED = 'suspended';
|
||||
|
||||
public static $limit_notices = array(
|
||||
10501 => 'FIRST_MONTH_OVER_LIMIT',
|
||||
10502 => 'SECOND_MONTH_OVER_LIMIT',
|
||||
@@ -864,7 +871,7 @@ class Akismet {
|
||||
|
||||
/**
|
||||
* Get the full comment history for a given comment, as an array in reverse chronological order.
|
||||
* Each entry will have an 'event', a 'time', and possible a 'message' member (if the entry is old enough).
|
||||
* Each entry will have an 'event', a 'time', and possibly a 'message' member (if the entry is old enough).
|
||||
* Some entries will also have a 'user' or 'meta' member.
|
||||
*
|
||||
* @param int $comment_id The relevant comment ID.
|
||||
@@ -915,7 +922,17 @@ class Akismet {
|
||||
$history[] = array( 'time' => 445856427, 'event' => 'webhook-ham-noaction' );
|
||||
*/
|
||||
|
||||
usort( $history, array( 'Akismet', '_cmp_time' ) );
|
||||
// Validate history entries to guard against malformed data.
|
||||
// In one case, serialized data was returned in $entry instead of an array.
|
||||
$history = array_filter(
|
||||
$history,
|
||||
function ( $entry ) {
|
||||
return is_array( $entry ) && isset( $entry['time'] ) && is_numeric( $entry['time'] );
|
||||
}
|
||||
);
|
||||
|
||||
usort( $history, 'Akismet::_cmp_time' );
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
@@ -1712,6 +1729,7 @@ class Akismet {
|
||||
'upgrade-url',
|
||||
'upgrade-type',
|
||||
'upgrade-via-support',
|
||||
'recommended-plan-name',
|
||||
);
|
||||
|
||||
foreach ( $alert_header_names as $alert_header_name ) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau, kbrownkd, bluefuton, derekspringer, lschuyler, andyperdomo, akismetantispam
|
||||
Tags: comments, spam, antispam, anti-spam, contact form
|
||||
Requires at least: 5.8
|
||||
Tested up to: 6.8.1
|
||||
Stable tag: 5.5
|
||||
Tested up to: 6.9
|
||||
Stable tag: 5.6
|
||||
License: GPLv2 or later
|
||||
|
||||
The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce.
|
||||
@@ -32,6 +32,15 @@ Upload the Akismet plugin to your blog, activate it, and then enter your Akismet
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 5.6 =
|
||||
*Release Date - 12 November 2025*
|
||||
|
||||
* Improve caching of compatible plugins.
|
||||
* Explain the key features of Akismet more clearly on the setup page.
|
||||
* Improve the configuration process to better explain errors when they occur.
|
||||
* UI cleanup and refresh
|
||||
* Improve messaging related to usage limits.
|
||||
|
||||
= 5.5 =
|
||||
*Release Date - 15 July 2025*
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<div class="akismet-box">
|
||||
<?php Akismet::view( 'title' ); ?>
|
||||
<?php Akismet::view( 'setup' ); ?>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div class="akismet-box">
|
||||
<?php Akismet::view( 'enter' ); ?>
|
||||
</div>
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
/** @var array|WP_Error $compatible_plugins */
|
||||
$compatible_plugins = Akismet_Compatible_Plugins::get_installed_compatible_plugins();
|
||||
$bypass_cache = ! empty( $_GET['akismet_refresh_compatible_plugins'] );
|
||||
$compatible_plugins = Akismet_Compatible_Plugins::get_installed_compatible_plugins( $bypass_cache );
|
||||
if ( is_array( $compatible_plugins ) ) :
|
||||
|
||||
$compatible_plugin_count = count( $compatible_plugins );
|
||||
@@ -24,14 +25,14 @@ if ( is_array( $compatible_plugins ) ) :
|
||||
echo '<p>';
|
||||
|
||||
if ( 0 === $compatible_plugin_count ) {
|
||||
echo '<a class="akismet-settings__external-link" href="https://akismet.com/developers/plugins-and-libraries/">';
|
||||
echo '<a class="akismet-external-link" href="https://akismet.com/developers/plugins-and-libraries/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=compatible_plugins">';
|
||||
echo esc_html( __( 'See supported integrations', 'akismet' ) );
|
||||
echo '</a>';
|
||||
} else {
|
||||
echo esc_html(
|
||||
_n(
|
||||
"The plugin you've installed is compatible. Follow the documentation link to get started.",
|
||||
"The plugins you've installed are compatible. Follow the documentation links to get started.",
|
||||
"This plugin you've installed is compatible. Follow the documentation link to get started.",
|
||||
"These plugins you've installed are compatible. Follow the documentation links to get started.",
|
||||
$compatible_plugin_count,
|
||||
'akismet'
|
||||
)
|
||||
@@ -75,7 +76,7 @@ if ( is_array( $compatible_plugins ) ) :
|
||||
<h3 class="akismet-compatible-plugins__card-title"><?php echo esc_html( $compatible_plugin['name'] ); ?></h3>
|
||||
<div class="akismet-compatible-plugins__docs">
|
||||
<a
|
||||
class="akismet-settings__external-link"
|
||||
class="akismet-external-link"
|
||||
href="<?php echo esc_url( $compatible_plugin['help_url'] ); ?>"
|
||||
aria-label="
|
||||
<?php
|
||||
|
||||
@@ -168,7 +168,7 @@ $kses_allow_link_href = array(
|
||||
</div>
|
||||
<div>
|
||||
<label class="akismet-settings__row-input-label" for="akismet_strictness_0">
|
||||
<input type="radio" name="akismet_strictness" id="akismet_strictness_0" value="0" <?php checked( '0', get_option( 'akismet_strictness' ) ); ?> />
|
||||
<input type="radio" name="akismet_strictness" id="akismet_strictness_0" value="0" <?php checked( true, get_option( 'akismet_strictness' ) !== '1' ); ?> />
|
||||
<span class="akismet-settings__row-label-text">
|
||||
<?php esc_html_e( 'Always put spam in the Spam folder for review.', 'akismet' ); ?>
|
||||
</span>
|
||||
@@ -274,13 +274,13 @@ $kses_allow_link_href = array(
|
||||
<th scope="row"><?php esc_html_e( 'Status', 'akismet' ); ?></th>
|
||||
<td>
|
||||
<?php
|
||||
if ( 'cancelled' === $akismet_user->status ) :
|
||||
if ( Akismet::USER_STATUS_CANCELLED === $akismet_user->status ) :
|
||||
esc_html_e( 'Cancelled', 'akismet' );
|
||||
elseif ( 'suspended' === $akismet_user->status ) :
|
||||
elseif ( Akismet::USER_STATUS_SUSPENDED === $akismet_user->status ) :
|
||||
esc_html_e( 'Suspended', 'akismet' );
|
||||
elseif ( 'missing' === $akismet_user->status ) :
|
||||
elseif ( Akismet::USER_STATUS_MISSING === $akismet_user->status ) :
|
||||
esc_html_e( 'Missing', 'akismet' );
|
||||
elseif ( 'no-sub' === $akismet_user->status ) :
|
||||
elseif ( Akismet::USER_STATUS_NO_SUB === $akismet_user->status ) :
|
||||
esc_html_e( 'No subscription found', 'akismet' );
|
||||
else :
|
||||
esc_html_e( 'Active', 'akismet' );
|
||||
@@ -299,9 +299,9 @@ $kses_allow_link_href = array(
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="akismet-card-actions">
|
||||
<?php if ( $akismet_user->status === 'active' ) : ?>
|
||||
<?php if ( $akismet_user->status === Akismet::USER_STATUS_ACTIVE ) : ?>
|
||||
<div class="akismet-card-actions__secondary-action">
|
||||
<a href="https://akismet.com/account" class="akismet-settings__external-link" aria-label="Account overview on akismet.com"><?php esc_html_e( 'Account overview', 'akismet' ); ?></a>
|
||||
<a href="https://akismet.com/account?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=account_overview" class="akismet-external-link" aria-label="Account overview on akismet.com"><?php esc_html_e( 'Account overview', 'akismet' ); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div id="publishing-action">
|
||||
@@ -309,8 +309,9 @@ $kses_allow_link_href = array(
|
||||
Akismet::view(
|
||||
'get',
|
||||
array(
|
||||
'text' => ( $akismet_user->account_type === 'free-api-key' && $akismet_user->status === 'active' ? __( 'Upgrade', 'akismet' ) : __( 'Change', 'akismet' ) ),
|
||||
'redirect' => 'upgrade',
|
||||
'text' => ( $akismet_user->account_type === 'free-api-key' && $akismet_user->status === Akismet::USER_STATUS_ACTIVE ? __( 'Upgrade', 'akismet' ) : __( 'Change', 'akismet' ) ),
|
||||
'redirect' => 'upgrade',
|
||||
'utm_content' => ( $akismet_user->account_type === 'free-api-key' && $akismet_user->status === Akismet::USER_STATUS_ACTIVE ? 'config_upgrade' : 'config_change' ),
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
@@ -1,98 +1,14 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
//phpcs:disable VariableAnalysis
|
||||
// There are "undefined" variables here because they're defined in the code that includes this file as a template.
|
||||
|
||||
?>
|
||||
<div class="akismet-box">
|
||||
<?php Akismet::view( 'title' ); ?>
|
||||
<div class="akismet-jp-connect">
|
||||
<h3><?php esc_html_e( 'Connect with Jetpack', 'akismet' ); ?></h3>
|
||||
<?php if ( in_array( $akismet_user->status, array( 'no-sub', 'missing' ) ) ) { ?>
|
||||
<p><?php esc_html_e( 'Use your Jetpack connection to set up Akismet.', 'akismet' ); ?></p>
|
||||
<form name="akismet_activate" id="akismet_activate" action="https://akismet.com/get/" method="post" class="akismet-right" target="_blank">
|
||||
<input type="hidden" name="passback_url" value="<?php echo esc_url( Akismet_Admin::get_page_url() ); ?>"/>
|
||||
<input type="hidden" name="blog" value="<?php echo esc_url( get_option( 'home' ) ); ?>"/>
|
||||
<input type="hidden" name="auto-connect" value="<?php echo esc_attr( $akismet_user->ID ); ?>"/>
|
||||
<input type="hidden" name="redirect" value="plugin-signup"/>
|
||||
<input type="submit" class="akismet-button akismet-is-primary" value="<?php esc_attr_e( 'Connect with Jetpack', 'akismet' ); ?>"/>
|
||||
</form>
|
||||
<?php echo get_avatar( $akismet_user->user_email, null, null, null, array( 'class' => 'akismet-jetpack-gravatar' ) ); ?>
|
||||
<p>
|
||||
<?php
|
||||
|
||||
/* translators: %s is the WordPress.com username */
|
||||
printf( esc_html( __( 'You are connected as %s.', 'akismet' ) ), '<b>' . esc_html( $akismet_user->user_login ) . '</b>' );
|
||||
|
||||
?>
|
||||
<br />
|
||||
<span class="akismet-jetpack-email"><?php echo esc_html( $akismet_user->user_email ); ?></span>
|
||||
</p>
|
||||
<?php } elseif ( $akismet_user->status == 'cancelled' ) { ?>
|
||||
<p><?php esc_html_e( 'Use your Jetpack connection to set up Akismet.', 'akismet' ); ?></p>
|
||||
<form name="akismet_activate" id="akismet_activate" action="https://akismet.com/get/" method="post" class="akismet-right" target="_blank">
|
||||
<input type="hidden" name="passback_url" value="<?php echo esc_url( Akismet_Admin::get_page_url() ); ?>"/>
|
||||
<input type="hidden" name="blog" value="<?php echo esc_url( get_option( 'home' ) ); ?>"/>
|
||||
<input type="hidden" name="user_id" value="<?php echo esc_attr( $akismet_user->ID ); ?>"/>
|
||||
<input type="hidden" name="redirect" value="upgrade"/>
|
||||
<input type="submit" class="akismet-button akismet-is-primary" value="<?php esc_attr_e( 'Connect with Jetpack', 'akismet' ); ?>"/>
|
||||
</form>
|
||||
<?php echo get_avatar( $akismet_user->user_email, null, null, null, array( 'class' => 'akismet-jetpack-gravatar' ) ); ?>
|
||||
<p>
|
||||
<?php
|
||||
|
||||
/* translators: %s is the WordPress.com email address */
|
||||
echo esc_html( sprintf( __( 'Your subscription for %s is cancelled.', 'akismet' ), $akismet_user->user_email ) );
|
||||
|
||||
?>
|
||||
<br />
|
||||
<span class="akismet-jetpack-email"><?php echo esc_html( $akismet_user->user_email ); ?></span>
|
||||
</p>
|
||||
<?php } elseif ( $akismet_user->status == 'suspended' ) { ?>
|
||||
<div class="akismet-right">
|
||||
<p><a href="https://akismet.com/contact" class="akismet-button akismet-is-primary"><?php esc_html_e( 'Contact Akismet support', 'akismet' ); ?></a></p>
|
||||
</div>
|
||||
<p>
|
||||
<span class="akismet-alert-text">
|
||||
<?php
|
||||
|
||||
/* translators: %s is the WordPress.com email address */
|
||||
echo esc_html( sprintf( __( 'Your subscription for %s is suspended.', 'akismet' ), $akismet_user->user_email ) );
|
||||
|
||||
?>
|
||||
</span>
|
||||
<?php esc_html_e( 'No worries! Get in touch and we’ll sort this out.', 'akismet' ); ?>
|
||||
</p>
|
||||
<?php } else { // ask do they want to use akismet account found using jetpack wpcom connection ?>
|
||||
<p><?php esc_html_e( 'Use your Jetpack connection to set up Akismet.', 'akismet' ); ?></p>
|
||||
<form name="akismet_use_wpcom_key" action="<?php echo esc_url( Akismet_Admin::get_page_url() ); ?>" method="post" id="akismet-activate" class="akismet-right">
|
||||
<input type="hidden" name="key" value="<?php echo esc_attr( $akismet_user->api_key ); ?>"/>
|
||||
<input type="hidden" name="action" value="enter-key">
|
||||
<?php wp_nonce_field( Akismet_Admin::NONCE ); ?>
|
||||
<input type="submit" class="akismet-button akismet-is-primary" value="<?php esc_attr_e( 'Connect with Jetpack', 'akismet' ); ?>"/>
|
||||
</form>
|
||||
<?php echo get_avatar( $akismet_user->user_email, null, null, null, array( 'class' => 'akismet-jetpack-gravatar' ) ); ?>
|
||||
<p>
|
||||
<?php
|
||||
|
||||
/* translators: %s is the WordPress.com username */
|
||||
printf( esc_html( __( 'You are connected as %s.', 'akismet' ) ), '<b>' . esc_html( $akismet_user->user_login ) . '</b>' );
|
||||
|
||||
?>
|
||||
<br />
|
||||
<span class="akismet-jetpack-email"><?php echo esc_html( $akismet_user->user_email ); ?></span>
|
||||
</p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<div class="akismet-ak-connect">
|
||||
<?php Akismet::view( 'setup' ); ?>
|
||||
</div>
|
||||
<div class="centered akismet-toggles">
|
||||
<a href="#" class="toggle-jp-connect"><?php esc_html_e( 'Connect with Jetpack', 'akismet' ); ?></a>
|
||||
<a href="#" class="toggle-ak-connect"><?php esc_html_e( 'Set up a different account', 'akismet' ); ?></a>
|
||||
</div>
|
||||
<?php Akismet::view( 'setup', array( 'use_jetpack_connection' => true ) ); ?>
|
||||
<?php Akismet::view( 'setup-jetpack', array( 'akismet_user' => $akismet_user ) ); ?>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div class="akismet-box">
|
||||
<?php Akismet::view( 'enter' ); ?>
|
||||
</div>
|
||||
|
||||
@@ -8,11 +8,25 @@ $submit_classes_attr = 'akismet-button';
|
||||
if ( isset( $classes ) && ( is_countable( $classes ) ? count( $classes ) : 0 ) > 0 ) {
|
||||
$submit_classes_attr = implode( ' ', $classes );
|
||||
}
|
||||
?>
|
||||
|
||||
<form name="akismet_activate" action="https://akismet.com/get/" method="POST" target="_blank">
|
||||
<input type="hidden" name="passback_url" value="<?php echo esc_url( Akismet_Admin::get_page_url() ); ?>"/>
|
||||
<input type="hidden" name="blog" value="<?php echo esc_url( get_option( 'home' ) ); ?>"/>
|
||||
<input type="hidden" name="redirect" value="<?php echo isset( $redirect ) ? esc_attr( $redirect ) : 'plugin-signup'; ?>"/>
|
||||
<button type="submit" class="<?php echo esc_attr( $submit_classes_attr ); ?>" value="<?php echo esc_attr( $text ); ?>"><?php echo esc_attr( $text ) . '<span class="screen-reader-text">' . esc_html__( '(opens in a new tab)', 'akismet' ) . '</span>'; ?></button>
|
||||
</form>
|
||||
$query_args = array(
|
||||
'passback_url' => Akismet_Admin::get_page_url(),
|
||||
'redirect' => isset( $redirect ) ? $redirect : 'plugin-signup',
|
||||
);
|
||||
|
||||
// Set default UTM parameters, overriding with any provided values.
|
||||
$utm_args = array(
|
||||
'utm_source' => isset( $utm_source ) ? $utm_source : 'akismet_plugin',
|
||||
'utm_medium' => isset( $utm_medium ) ? $utm_medium : 'in_plugin',
|
||||
'utm_campaign' => isset( $utm_campaign ) ? $utm_campaign : 'plugin_static_link',
|
||||
'utm_content' => isset( $utm_content ) ? $utm_content : 'get_view_link',
|
||||
);
|
||||
|
||||
$query_args = array_merge( $query_args, $utm_args );
|
||||
|
||||
$url = add_query_arg( $query_args, 'https://akismet.com/get/' );
|
||||
?>
|
||||
<a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $submit_classes_attr ); ?>" target="_blank">
|
||||
<?php echo esc_html( is_string( $text ) ? $text : '' ); ?>
|
||||
<span class="screen-reader-text"><?php esc_html_e( '(opens in a new tab)', 'akismet' ); ?></span>
|
||||
</a>
|
||||
|
||||
@@ -5,6 +5,7 @@ $kses_allow_link = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'target' => true,
|
||||
'class' => true,
|
||||
),
|
||||
);
|
||||
$kses_allow_strong = array( 'strong' => true );
|
||||
@@ -29,7 +30,6 @@ if ( ! isset( $type ) ) {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php elseif ( $type === 'spam-check' ) : ?>
|
||||
<?php // This notice is only displayed on edit-comments.php. ?>
|
||||
<div class="notice notice-warning">
|
||||
@@ -47,7 +47,7 @@ if ( ! isset( $type ) ) {
|
||||
<p><?php esc_html_e( 'WP-Cron has been disabled using the DISABLE_WP_CRON constant. Comment rechecks may not work properly.', 'akismet' ); ?></p>
|
||||
</div>
|
||||
|
||||
<?php elseif ( $type === 'alert' && $code === Akismet::ALERT_CODE_COMMERCIAL && $parent_view === 'config' ) : ?>
|
||||
<?php elseif ( $type === 'alert' && $code === Akismet::ALERT_CODE_COMMERCIAL && isset( $parent_view ) && $parent_view === 'config' ) : ?>
|
||||
<?php // Display a different commercial warning alert on the config page ?>
|
||||
<div class="akismet-card akismet-alert is-commercial">
|
||||
<div>
|
||||
@@ -55,20 +55,28 @@ if ( ! isset( $type ) ) {
|
||||
<p class="akismet-alert-info">
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'Your current subscription is for <a href="%s">personal, non-commercial use</a>. Please upgrade your plan to continue using Akismet.', 'akismet' ), esc_url( 'https://akismet.com/support/getting-started/free-or-paid/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Your current subscription is for <a class="akismet-external-link" href="%s">personal, non-commercial use</a>. Please upgrade your plan to continue using Akismet.', 'akismet' ), esc_url( 'https://akismet.com/support/getting-started/free-or-paid/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=commercial_support' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
<p class="akismet-alert-info">
|
||||
<?php
|
||||
/* translators: The placeholder is a URL to the contact form. */
|
||||
echo wp_kses( sprintf( __( 'If you believe your site should not be classified as commercial, <a href="%s">please get in touch</a>.', 'akismet' ), esc_url( 'https://akismet.com/contact/?purpose=commercial' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'If you believe your site should not be classified as commercial, <a class="akismet-external-link" href="%s">please get in touch</a>', 'akismet' ), esc_url( 'https://akismet.com/contact/?purpose=commercial&utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=commercial_contact' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="akismet-alert-button-wrapper">
|
||||
<a href="https://akismet.com/pricing/?flow=upgrade&utm_source=akismet_plugin&utm_campaign=commercial_notice&utm_medium=banner" class="akismet-alert-button akismet-button">
|
||||
<?php esc_html_e( 'Upgrade plan', 'akismet' ); ?>
|
||||
</a>
|
||||
<?php
|
||||
Akismet::view(
|
||||
'get',
|
||||
array(
|
||||
'text' => __( 'Upgrade plan', 'akismet' ),
|
||||
'classes' => array( 'akismet-alert-button', 'akismet-button' ),
|
||||
'redirect' => 'upgrade',
|
||||
'utm_content' => 'commercial_upgrade',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,8 +87,14 @@ if ( ! isset( $type ) ) {
|
||||
<p><?php echo isset( $msg ) ? esc_html( $msg ) : ''; ?></p>
|
||||
<p>
|
||||
<?php
|
||||
/* translators: the placeholder is a clickable URL that leads to more information regarding an error code. */
|
||||
printf( esc_html__( 'For more information: %s', 'akismet' ), '<a href="https://akismet.com/errors/' . esc_attr( $code ) . '">https://akismet.com/errors/' . esc_attr( $code ) . '</a>' );
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
/* translators: the placeholder is a clickable URL that leads to more information regarding an error code. */
|
||||
__( 'For more information, see the <a class="akismet-external-link" href="%s">error documentation on akismet.com</a>', 'akismet' ),
|
||||
esc_url( 'https://akismet.com/developers/detailed-docs/errors/akismet-error-' . absint( $code ) . '?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=error_info' )
|
||||
),
|
||||
$kses_allow_link
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -96,10 +110,15 @@ if ( ! isset( $type ) ) {
|
||||
<?php elseif ( $type === 'missing-functions' ) : ?>
|
||||
<div class="akismet-alert is-bad">
|
||||
<h3 class="akismet-alert__heading"><?php esc_html_e( 'Network functions are disabled.', 'akismet' ); ?></h3>
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses( __( 'Your web host or server administrator has disabled PHP’s <code>gethostbynamel</code> function.', 'akismet' ), array_merge( $kses_allow_link, $kses_allow_strong, array( 'code' => true ) ) );
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'Your web host or server administrator has disabled PHP’s <code>gethostbynamel</code> function. <strong>Akismet cannot work correctly until this is fixed.</strong> Please contact your web host or firewall administrator and give them <a href="%s" target="_blank">this information about Akismet’s system requirements</a>.', 'akismet' ), esc_url( 'https://akismet.com/akismet-hosting-faq/' ) ), array_merge( $kses_allow_link, $kses_allow_strong, array( 'code' => true ) ) );
|
||||
echo wp_kses( sprintf( __( 'Please contact your web host or firewall administrator and give them <a class="akismet-external-link" href="%s" target="_blank">this information about Akismet’s system requirements</a>', 'akismet' ), esc_url( 'https://akismet.com/akismet-hosting-faq/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=hosting_faq_php' ) ), array_merge( $kses_allow_link, $kses_allow_strong, array( 'code' => true ) ) );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -110,7 +129,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'Your firewall may be blocking Akismet from connecting to its API. Please contact your host and refer to <a href="%s" target="_blank">our guide about firewalls</a>.', 'akismet' ), esc_url( 'https://akismet.com/akismet-hosting-faq/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Your firewall may be blocking Akismet from connecting to its API. Please contact your host and refer to <a class="akismet-external-link" href="%s" target="_blank">our guide about firewalls</a>', 'akismet' ), esc_url( 'https://akismet.com/akismet-hosting-faq/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=hosting_faq_firewall' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -121,7 +140,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'We cannot process your payment. Please <a href="%s" target="_blank">update your payment details</a>.', 'akismet' ), esc_url( 'https://akismet.com/account/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'We cannot process your payment. Please <a class="akismet-external-link" href="%s" target="_blank">update your payment details</a>', 'akismet' ), esc_url( 'https://wordpress.com/me/purchases/payment-methods?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=payment_update' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -132,7 +151,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'Please visit your <a href="%s" target="_blank">Akismet account page</a> to reactivate your subscription.', 'akismet' ), esc_url( 'https://akismet.com/account/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Please visit <a class="akismet-external-link" href="%s" target="_blank">Akismet.com</a> to purchase a new subscription.', 'akismet' ), esc_url( 'https://akismet.com/pricing/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=pricing_cancelled' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -143,7 +162,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL. */
|
||||
echo wp_kses( sprintf( __( 'Please contact <a href="%s" target="_blank">Akismet support</a> for assistance.', 'akismet' ), esc_url( 'https://akismet.com/contact/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Please contact <a class="akismet-external-link" href="%s" target="_blank">Akismet support</a> for assistance.', 'akismet' ), esc_url( 'https://akismet.com/contact/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=support_suspended' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -154,7 +173,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: the placeholder is a clickable URL to the Akismet account upgrade page. */
|
||||
echo wp_kses( sprintf( __( 'You can help us fight spam and upgrade your account by <a href="%s" target="_blank">contributing a token amount</a>.', 'akismet' ), esc_url( 'https://akismet.com/pricing' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'You can help us fight spam and upgrade your account by <a class="akismet-external-link" href="%s" target="_blank">contributing a token amount</a>', 'akismet' ), esc_url( 'https://akismet.com/pricing?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=upgrade_contribution' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -165,7 +184,7 @@ if ( ! isset( $type ) ) {
|
||||
<p>
|
||||
<?php
|
||||
/* translators: The placeholder is a URL to the Akismet contact form. */
|
||||
echo wp_kses( sprintf( __( 'Please contact <a href="%s" target="_blank">Akismet support</a> for assistance.', 'akismet' ), esc_url( 'https://akismet.com/contact/' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Please contact <a class="akismet-external-link" href="%s" target="_blank">Akismet support</a> for assistance.', 'akismet' ), esc_url( 'https://akismet.com/contact/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=support_missing' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -173,13 +192,13 @@ if ( ! isset( $type ) ) {
|
||||
<?php elseif ( $type === 'no-sub' ) : ?>
|
||||
<div class="akismet-alert is-bad">
|
||||
<h3 class="akismet-alert__heading"><?php esc_html_e( 'You don’t have an Akismet plan.', 'akismet' ); ?></h3>
|
||||
<p><?php echo esc_html__( 'Your API key must have an Akismet plan before it can protect your site from spam.', 'akismet' ); ?></p>
|
||||
<p>
|
||||
<?php
|
||||
/* translators: the placeholder is the URL to the Akismet pricing page. */
|
||||
echo wp_kses( sprintf( __( 'Please <a href="%s" target="_blank">choose a plan</a> to get started with Akismet.', 'akismet' ), esc_url( 'https://akismet.com/pricing' ) ), $kses_allow_link );
|
||||
echo wp_kses( sprintf( __( 'Please <a class="akismet-external-link" href="%s" target="_blank">choose a free or paid plan</a> so Akismet can protect your site from spam.', 'akismet' ), esc_url( 'https://akismet.com/pricing?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=choose_plan' ) ), $kses_allow_link );
|
||||
?>
|
||||
</p>
|
||||
<p><?php echo esc_html__( 'Once you\'ve chosen a plan, return here to complete your setup.', 'akismet' ); ?></p>
|
||||
</div>
|
||||
|
||||
<?php elseif ( $type === 'new-key-valid' ) : ?>
|
||||
@@ -225,8 +244,8 @@ if ( ! isset( $type ) ) {
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
/* translators: The placeholder is a URL to the Akismet contact form. */
|
||||
__( 'Please enter a new key or <a href="%s" target="_blank">contact Akismet support</a>.', 'akismet' ),
|
||||
'https://akismet.com/contact/'
|
||||
__( 'Please enter a new key or <a class="akismet-external-link" href="%s" target="_blank">contact Akismet support</a>', 'akismet' ),
|
||||
'https://akismet.com/contact/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=support_invalid_key'
|
||||
),
|
||||
$kses_allow_link
|
||||
);
|
||||
@@ -242,8 +261,8 @@ if ( ! isset( $type ) ) {
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
/* translators: The placeholder is a URL. */
|
||||
__( 'The connection to akismet.com could not be established. Please refer to <a href="%s" target="_blank">our guide about firewalls</a> and check your server configuration.', 'akismet' ),
|
||||
'https://blog.akismet.com/akismet-hosting-faq/'
|
||||
__( 'The connection to akismet.com could not be established. Please refer to <a class="akismet-external-link" href="%s" target="_blank">our guide about firewalls</a> and check your server configuration.', 'akismet' ),
|
||||
'https://akismet.com/akismet-hosting-faq/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=hosting_faq'
|
||||
),
|
||||
$kses_allow_link
|
||||
);
|
||||
@@ -289,33 +308,33 @@ if ( ! isset( $type ) ) {
|
||||
)
|
||||
);
|
||||
echo ' ';
|
||||
echo '<a href="https://docs.akismet.com/akismet-api-usage-limits/" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
|
||||
echo '<a class="akismet-external-link" href="https://akismet.com/support/general/akismet-api-usage-limits/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=usage_limit_docs" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits', 'akismet' ) );
|
||||
echo '</a>';
|
||||
|
||||
break;
|
||||
case 'SECOND_MONTH_OVER_LIMIT':
|
||||
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for two consecutive months. Next month, we will restrict your account after you reach the limit. Please consider upgrading your plan.', 'akismet' ) );
|
||||
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for two consecutive months. Next month, we will restrict your account after you reach the limit. Increase your limit to make sure your site stays protected from spam.', 'akismet' ) );
|
||||
echo ' ';
|
||||
echo '<a href="https://docs.akismet.com/akismet-api-usage-limits/" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
|
||||
echo '<a class="akismet-external-link" href="https://akismet.com/support/general/akismet-api-usage-limits/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=usage_limit_docs" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits', 'akismet' ) );
|
||||
echo '</a>';
|
||||
|
||||
break;
|
||||
case 'THIRD_MONTH_APPROACHING_LIMIT':
|
||||
echo esc_html( __( 'Your Akismet usage is nearing your plan’s limit for the third consecutive month. We will restrict your account after you reach the limit. Upgrade your plan so Akismet can continue blocking spam.', 'akismet' ) );
|
||||
echo esc_html( __( 'Your Akismet usage is nearing your plan’s limit for the third consecutive month. We will restrict your account after you reach the limit. Increase your limit to make sure your site stays protected from spam.', 'akismet' ) );
|
||||
echo ' ';
|
||||
echo '<a href="https://docs.akismet.com/akismet-api-usage-limits/" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
|
||||
echo '<a class="akismet-external-link" href="https://akismet.com/support/general/akismet-api-usage-limits/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=usage_limit_docs" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits', 'akismet' ) );
|
||||
echo '</a>';
|
||||
|
||||
break;
|
||||
case 'THIRD_MONTH_OVER_LIMIT':
|
||||
case 'FOUR_PLUS_MONTHS_OVER_LIMIT':
|
||||
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for three consecutive months. We have restricted your account for the rest of the month. Upgrade your plan so Akismet can continue blocking spam.', 'akismet' ) );
|
||||
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for three consecutive months. We have restricted your account for the rest of the month. Increase your limit to make sure your site stays protected from spam.', 'akismet' ) );
|
||||
echo ' ';
|
||||
echo '<a href="https://docs.akismet.com/akismet-api-usage-limits/" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
|
||||
echo '<a class="akismet-external-link" href="https://akismet.com/support/general/akismet-api-usage-limits/?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=usage_limit_docs" target="_blank">';
|
||||
echo esc_html( __( 'Learn more about usage limits', 'akismet' ) );
|
||||
echo '</a>';
|
||||
|
||||
break;
|
||||
@@ -326,18 +345,28 @@ if ( ! isset( $type ) ) {
|
||||
</p>
|
||||
</div>
|
||||
<div class="akismet-usage-limit-cta">
|
||||
<a href="<?php echo esc_attr( $upgrade_url ); ?>" class="button" target="_blank">
|
||||
<a href="<?php echo esc_attr( $upgrade_url . ( strpos( $upgrade_url, '?' ) !== false ? '&' : '?' ) . 'utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=usage_limit_upgrade' ); ?>" class="button" target="_blank">
|
||||
<?php
|
||||
if ( isset( $upgrade_via_support ) && $upgrade_via_support ) {
|
||||
// Direct user to contact support.
|
||||
esc_html_e( 'Contact Akismet support', 'akismet' );
|
||||
} elseif ( ! empty( $upgrade_type ) && 'qty' === $upgrade_type ) {
|
||||
// If only a qty upgrade is required, show a more generic message.
|
||||
esc_html_e( 'Upgrade your subscription level', 'akismet' );
|
||||
// If a qty upgrade is required, use recommended plan name if available.
|
||||
if ( ! empty( $recommended_plan_name ) && is_string( $recommended_plan_name ) ) {
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
/* translators: The placeholder is the name of an Akismet subscription plan, like "Akismet Pro" or "Akismet Business" . */
|
||||
__( 'Add an %s subscription', 'akismet' ),
|
||||
$recommended_plan_name
|
||||
)
|
||||
);
|
||||
} else {
|
||||
esc_html_e( 'Increase your limit', 'akismet' );
|
||||
}
|
||||
} else {
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
/* translators: The placeholder is the name of a subscription level, like "Plus" or "Enterprise" . */
|
||||
/* translators: The placeholder is the name of a subscription level, like "Akismet Business" or "Akismet Enterprise" . */
|
||||
__( 'Upgrade to %s', 'akismet' ),
|
||||
$upgrade_plan
|
||||
)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
//phpcs:disable VariableAnalysis
|
||||
// There are "undefined" variables here because they're defined in the code that includes this file as a template.
|
||||
|
||||
$user_status = $akismet_user->status ?? null;
|
||||
?>
|
||||
<div class="akismet-setup__connection">
|
||||
<?php if ( ! empty( $akismet_user->user_email ) && ! empty( $akismet_user->user_login ) ) : ?>
|
||||
<div class="akismet-setup__connection-user">
|
||||
<div class="akismet-setup__connection-avatar">
|
||||
<?php
|
||||
// Decorative avatar; empty alt for screen readers.
|
||||
echo get_avatar(
|
||||
$akismet_user->user_email,
|
||||
48,
|
||||
'',
|
||||
'',
|
||||
array(
|
||||
'class' => 'akismet-setup__connection-avatar-image',
|
||||
'alt' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
<div class="akismet-setup__connection-account">
|
||||
<div class="akismet-setup__connection-account-name">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s is the WordPress.com username */
|
||||
esc_html__( 'Signed in as %s', 'akismet' ),
|
||||
'<strong>' . esc_html( $akismet_user->user_login ) . '</strong>'
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<div class="akismet-setup__connection-account-email"><?php echo esc_html( $akismet_user->user_email ); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="akismet-setup__connection-action">
|
||||
<?php if ( in_array( $user_status, array( Akismet::USER_STATUS_CANCELLED, Akismet::USER_STATUS_MISSING, Akismet::USER_STATUS_NO_SUB ) ) ) : ?>
|
||||
|
||||
<p class="akismet-setup__connection-action-intro">
|
||||
<?php esc_html_e( "Your Jetpack account is connected, but it doesn't have an active Akismet subscription yet. To continue, please choose a plan on Akismet.com.", 'akismet' ); ?>
|
||||
</p>
|
||||
|
||||
<a href="https://akismet.com/get?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=jetpack_flow_<?php echo esc_attr( str_replace( '-', '_', $user_status ) ); ?>" class="akismet-setup__connection-button akismet-button">
|
||||
<?php esc_html_e( 'Choose a plan on Akismet.com', 'akismet' ); ?>
|
||||
</a>
|
||||
|
||||
<p class="akismet-setup__connection-action-description">
|
||||
<?php esc_html_e( "Once you've chosen a plan, return here to complete your setup.", 'akismet' ); ?>
|
||||
</p>
|
||||
|
||||
<?php elseif ( $user_status === Akismet::USER_STATUS_SUSPENDED ) : ?>
|
||||
<p class="akismet-setup__connection-action-intro">
|
||||
<?php esc_html_e( "Your Akismet account appears to be suspended. This sometimes happens if there's a billing or verification issue. Please contact our support team so we can help you get it sorted.", 'akismet' ); ?>
|
||||
</p>
|
||||
|
||||
<a href="https://akismet.com/contact?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=jetpack_flow_suspended" class="akismet-setup__connection-button akismet-button">
|
||||
<?php esc_html_e( 'Contact support', 'akismet' ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<form name="akismet_use_wpcom_key" action="<?php echo esc_url( Akismet_Admin::get_page_url() ); ?>" method="post" id="akismet-activate">
|
||||
<input type="hidden" name="key" value="<?php echo esc_attr( $akismet_user->api_key ); ?>"/>
|
||||
<input type="hidden" name="action" value="enter-key">
|
||||
<?php wp_nonce_field( Akismet_Admin::NONCE ); ?>
|
||||
<input type="submit" class="akismet-setup__connection-button akismet-button" value="<?php esc_attr_e( 'Connect with Jetpack', 'akismet' ); ?>"/>
|
||||
</form>
|
||||
|
||||
<p class="akismet-setup__connection-action-description">
|
||||
<?php esc_html_e( "By connecting, we'll use your Jetpack account to activate Akismet on this site.", 'akismet' ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! in_array( $user_status, array( Akismet::USER_STATUS_CANCELLED, Akismet::USER_STATUS_MISSING, Akismet::USER_STATUS_NO_SUB ) ) ) : ?>
|
||||
<p class="akismet-setup__connection-action-description">
|
||||
<?php
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
/* translators: The placeholder is a URL. */
|
||||
__( 'Want to use a different account? <a href="%s" class="akismet-external-link">Visit akismet.com</a> to set it up and get your API key.', 'akismet' ),
|
||||
esc_url( 'https://akismet.com/get?utm_source=akismet_plugin&utm_campaign=plugin_static_link&utm_medium=in_plugin&utm_content=jetpack_flow_different_account' )
|
||||
),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'class' => array(),
|
||||
),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +1,78 @@
|
||||
<div class="akismet-setup-instructions">
|
||||
<p><?php esc_html_e( 'Set up your Akismet account to enable spam filtering on this site.', 'akismet' ); ?></p>
|
||||
<?php
|
||||
declare( strict_types = 1 );
|
||||
|
||||
//phpcs:disable VariableAnalysis
|
||||
// There are "undefined" variables here because they're defined in the code that includes this file as a template.
|
||||
|
||||
$tick_icon = '<svg class="akismet-setup-instructions__icon" width="48" height="48" viewBox="0 0 48 48" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="24" r="22" fill="#2E7D32"/>
|
||||
<path d="M16 24l6 6 12-14" fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>';
|
||||
?>
|
||||
<section class="akismet-setup-instructions">
|
||||
<h2 class="akismet-setup-instructions__heading"><?php esc_html_e( 'Eliminate spam from your site', 'akismet' ); ?></h2>
|
||||
|
||||
<h3 class="akismet-setup-instructions__subheading">
|
||||
<?php echo esc_html__( 'Protect your site from comment spam and contact form spam — automatically.', 'akismet' ); ?>
|
||||
</h3>
|
||||
|
||||
<ul class="akismet-setup-instructions__feature-list">
|
||||
<li class="akismet-setup-instructions__feature">
|
||||
<?php echo $tick_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="akismet-setup-instructions__body">
|
||||
<h4 class="akismet-setup-instructions__title">
|
||||
<?php echo esc_html__( 'Machine learning accuracy', 'akismet' ); ?>
|
||||
</h4>
|
||||
<p class="akismet-setup-instructions__text">
|
||||
<?php echo esc_html__( 'Learns from billions of spam signals across the web to stop junk before it reaches you.', 'akismet' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="akismet-setup-instructions__feature">
|
||||
<?php echo $tick_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="akismet-setup-instructions__body">
|
||||
<h4 class="akismet-setup-instructions__title">
|
||||
<?php echo esc_html__( 'Zero effort', 'akismet' ); ?>
|
||||
</h4>
|
||||
<p class="akismet-setup-instructions__text">
|
||||
<?php echo esc_html__( 'Akismet runs quietly in the background, saving you hours of manual moderation.', 'akismet' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="akismet-setup-instructions__feature">
|
||||
<?php echo $tick_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="akismet-setup-instructions__body">
|
||||
<h4 class="akismet-setup-instructions__title">
|
||||
<?php echo esc_html__( 'Works with popular contact forms', 'akismet' ); ?>
|
||||
</h4>
|
||||
<p class="akismet-setup-instructions__text">
|
||||
<?php echo esc_html__( 'Seamlessly integrates with plugins like Elementor, Contact Form 7, Jetpack and WPForms.', 'akismet' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="akismet-setup-instructions__feature">
|
||||
<?php echo $tick_icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<div class="akismet-setup-instructions__body">
|
||||
<h4 class="akismet-setup-instructions__title">
|
||||
<?php echo esc_html__( 'Flexible pricing', 'akismet' ); ?>
|
||||
</h4>
|
||||
<p class="akismet-setup-instructions__text">
|
||||
<?php echo esc_html__( 'Name your own price for personal sites. Businesses start on a paid plan.', 'akismet' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<?php
|
||||
Akismet::view(
|
||||
'get',
|
||||
array(
|
||||
'text' => __( 'Choose an Akismet plan', 'akismet' ),
|
||||
'classes' => array( 'akismet-button', 'akismet-is-primary' ),
|
||||
)
|
||||
);
|
||||
if ( empty( $use_jetpack_connection ) ) :
|
||||
Akismet::view(
|
||||
'get',
|
||||
array(
|
||||
'text' => __( 'Get started', 'akismet' ),
|
||||
'classes' => array( 'akismet-button', 'akismet-is-primary', 'akismet-setup-instructions__button' ),
|
||||
'utm_content' => 'setup_instructions',
|
||||
)
|
||||
);
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
//phpcs:disable VariableAnalysis
|
||||
// There are "undefined" variables here because they're defined in the code that includes this file as a template.
|
||||
|
||||
?>
|
||||
<div id="akismet-plugin-container">
|
||||
<div class="akismet-masthead">
|
||||
@@ -14,15 +13,13 @@
|
||||
<?php Akismet_Admin::display_status(); ?>
|
||||
<div class="akismet-boxes">
|
||||
<?php
|
||||
|
||||
if ( Akismet::predefined_api_key() ) {
|
||||
Akismet::view( 'predefined' );
|
||||
} elseif ( $akismet_user && in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub', 'missing', 'cancelled', 'suspended' ) ) ) {
|
||||
} elseif ( $akismet_user && in_array( $akismet_user->status, array( Akismet::USER_STATUS_ACTIVE, 'active-dunning', Akismet::USER_STATUS_NO_SUB, Akismet::USER_STATUS_MISSING, Akismet::USER_STATUS_CANCELLED, Akismet::USER_STATUS_SUSPENDED ) ) ) {
|
||||
Akismet::view( 'connect-jp', compact( 'akismet_user' ) );
|
||||
} else {
|
||||
Akismet::view( 'activate' );
|
||||
}
|
||||
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<div class="centered akismet-box-header">
|
||||
<h2><?php esc_html_e( 'Eliminate spam from your site', 'akismet' ); ?></h2>
|
||||
</div>
|
||||
@@ -582,12 +582,9 @@ function wpcf7_admin_integration_page() {
|
||||
|
||||
$formatter->append_preformatted(
|
||||
sprintf(
|
||||
/* translators: %s: link labeled 'Integration with external APIs' */
|
||||
esc_html( __( 'You can expand the possibilities of your contact forms by integrating them with external services. For details, see %s.', 'contact-form-7' ) ),
|
||||
wpcf7_link(
|
||||
__( 'https://contactform7.com/integration-with-external-apis/', 'contact-form-7' ),
|
||||
__( 'Integration with external APIs', 'contact-form-7' )
|
||||
)
|
||||
/* translators: %s: URL to support page about integration with external APIs */
|
||||
__( 'You can expand the possibilities of your contact forms by integrating them with external services. For details, see <a href="%s">Integration with external APIs</a>.', 'contact-form-7' ),
|
||||
__( 'https://contactform7.com/integration-with-external-apis/', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -31,16 +31,11 @@ function wpcf7_add_form_tag_captcha() {
|
||||
|
||||
function wpcf7_captchac_form_tag_handler( $tag ) {
|
||||
if ( ! class_exists( 'ReallySimpleCaptcha' ) ) {
|
||||
$error = sprintf(
|
||||
/* translators: %s: link labeled 'Really Simple CAPTCHA' */
|
||||
esc_html( __( 'To use CAPTCHA, you need %s plugin installed.', 'contact-form-7' ) ),
|
||||
wpcf7_link(
|
||||
'https://wordpress.org/plugins/really-simple-captcha/',
|
||||
'Really Simple CAPTCHA'
|
||||
)
|
||||
);
|
||||
|
||||
return sprintf( '<em>%s</em>', $error );
|
||||
return wp_kses_data( sprintf(
|
||||
/* translators: %s: URL to the Really Simple CAPTCHA plugin page */
|
||||
__( '<strong>Warning:</strong> The <a href="%s">Really Simple CAPTCHA</a> plugin is not active.', 'contact-form-7' ),
|
||||
'https://wordpress.org/plugins/really-simple-captcha/'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( empty( $tag->name ) ) {
|
||||
@@ -418,8 +413,10 @@ function wpcf7_generate_captcha( $options = null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! is_dir( $captcha->tmp_dir )
|
||||
or ! wp_is_writable( $captcha->tmp_dir ) ) {
|
||||
if (
|
||||
! is_dir( $captcha->tmp_dir ) or
|
||||
! wp_is_writable( $captcha->tmp_dir )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
Contributors: rocklobsterinc, takayukister
|
||||
Donate link: https://contactform7.com/donate/
|
||||
Tags: contact form, schema-woven validation
|
||||
Tested up to: 6.8
|
||||
Tested up to: 6.9
|
||||
Requires at least: 6.7
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 6.1.3
|
||||
Stable tag: 6.1.4
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
@@ -68,6 +68,10 @@ Do you have questions or issues with Contact Form 7? Use these support channels
|
||||
|
||||
For more information, see [Releases](https://contactform7.com/category/releases/).
|
||||
|
||||
= 6.1.4 =
|
||||
|
||||
[https://contactform7.com/contact-form-7-614/](https://contactform7.com/contact-form-7-614/)
|
||||
|
||||
= 6.1.3 =
|
||||
|
||||
[https://contactform7.com/contact-form-7-613/](https://contactform7.com/contact-form-7-613/)
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
* Author URI: https://github.com/rocklobster-in/
|
||||
* License: GPL v2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Version: 6.1.3
|
||||
* Version: 6.1.4
|
||||
* Requires at least: 6.7
|
||||
* Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
define( 'WPCF7_VERSION', '6.1.3' );
|
||||
define( 'WPCF7_VERSION', '6.1.4' );
|
||||
|
||||
define( 'WPCF7_REQUIRED_WP_VERSION', '6.7' );
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Hello_Dolly
|
||||
* @version 1.7.2
|
||||
*/
|
||||
/*
|
||||
Plugin Name: Hello Dolly
|
||||
Plugin URI: http://wordpress.org/plugins/hello-dolly/
|
||||
Description: This is not just a plugin, it symbolizes the hope and enthusiasm of an entire generation summed up in two words sung most famously by Louis Armstrong: Hello, Dolly. When activated you will randomly see a lyric from <cite>Hello, Dolly</cite> in the upper right of your admin screen on every page.
|
||||
Author: Matt Mullenweg
|
||||
Version: 1.7.2
|
||||
Author URI: http://ma.tt/
|
||||
*/
|
||||
|
||||
// Do not load directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
die();
|
||||
}
|
||||
|
||||
function hello_dolly_get_lyric() {
|
||||
/** These are the lyrics to Hello Dolly */
|
||||
$lyrics = "Hello, Dolly
|
||||
Well, hello, Dolly
|
||||
It's so nice to have you back where you belong
|
||||
You're lookin' swell, Dolly
|
||||
I can tell, Dolly
|
||||
You're still glowin', you're still crowin'
|
||||
You're still goin' strong
|
||||
I feel the room swayin'
|
||||
While the band's playin'
|
||||
One of our old favorite songs from way back when
|
||||
So, take her wrap, fellas
|
||||
Dolly, never go away again
|
||||
Hello, Dolly
|
||||
Well, hello, Dolly
|
||||
It's so nice to have you back where you belong
|
||||
You're lookin' swell, Dolly
|
||||
I can tell, Dolly
|
||||
You're still glowin', you're still crowin'
|
||||
You're still goin' strong
|
||||
I feel the room swayin'
|
||||
While the band's playin'
|
||||
One of our old favorite songs from way back when
|
||||
So, golly, gee, fellas
|
||||
Have a little faith in me, fellas
|
||||
Dolly, never go away
|
||||
Promise, you'll never go away
|
||||
Dolly'll never go away again";
|
||||
|
||||
// Here we split it into lines.
|
||||
$lyrics = explode( "\n", $lyrics );
|
||||
|
||||
// And then randomly choose a line.
|
||||
return wptexturize( $lyrics[ mt_rand( 0, count( $lyrics ) - 1 ) ] );
|
||||
}
|
||||
|
||||
// This just echoes the chosen line, we'll position it later.
|
||||
function hello_dolly() {
|
||||
$chosen = hello_dolly_get_lyric();
|
||||
$lang = '';
|
||||
if ( 'en_' !== substr( get_user_locale(), 0, 3 ) ) {
|
||||
$lang = ' lang="en"';
|
||||
}
|
||||
|
||||
printf(
|
||||
'<p id="dolly"><span class="screen-reader-text">%s </span><span dir="ltr"%s>%s</span></p>',
|
||||
__( 'Quote from Hello Dolly song, by Jerry Herman:' ),
|
||||
$lang,
|
||||
$chosen
|
||||
);
|
||||
}
|
||||
|
||||
// Now we set that function up to execute when the admin_notices action is called.
|
||||
add_action( 'admin_notices', 'hello_dolly' );
|
||||
|
||||
// We need some CSS to position the paragraph.
|
||||
function dolly_css() {
|
||||
echo "
|
||||
<style type='text/css'>
|
||||
#dolly {
|
||||
float: right;
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.6666;
|
||||
}
|
||||
.rtl #dolly {
|
||||
float: left;
|
||||
}
|
||||
.block-editor-page #dolly {
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 782px) {
|
||||
#dolly,
|
||||
.rtl #dolly {
|
||||
float: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
";
|
||||
}
|
||||
|
||||
add_action( 'admin_head', 'dolly_css' );
|
||||
@@ -39,6 +39,7 @@ class MLS_CLI {
|
||||
WP_CLI::add_command('mls cache', array($instance, 'cache'));
|
||||
WP_CLI::add_command('mls recovery', array($instance, 'recovery'));
|
||||
WP_CLI::add_command('mls media', array($instance, 'media'));
|
||||
WP_CLI::add_command('mls geo', array($instance, 'geo'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1078,4 +1079,154 @@ class MLS_CLI {
|
||||
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Geographic coordinate validation commands.
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <action>
|
||||
* : Action to perform: validate, stats, or show-invalid
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* # Validate all property coordinates
|
||||
* wp mls geo validate
|
||||
*
|
||||
* # Show validation statistics
|
||||
* wp mls geo stats
|
||||
*
|
||||
* # Show properties with invalid coordinates
|
||||
* wp mls geo show-invalid
|
||||
*
|
||||
* @param array $args Positional arguments
|
||||
* @param array $assoc_args Associative arguments
|
||||
*/
|
||||
public function geo($args, $assoc_args) {
|
||||
if (empty($args[0])) {
|
||||
WP_CLI::error('Please specify an action: validate, stats, or show-invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $args[0];
|
||||
|
||||
switch ($action) {
|
||||
case 'validate':
|
||||
$this->geo_validate();
|
||||
break;
|
||||
|
||||
case 'stats':
|
||||
$this->geo_stats();
|
||||
break;
|
||||
|
||||
case 'show-invalid':
|
||||
$this->geo_show_invalid();
|
||||
break;
|
||||
|
||||
default:
|
||||
WP_CLI::error("Unknown action: {$action}. Use validate, stats, or show-invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all property coordinates
|
||||
*/
|
||||
private function geo_validate() {
|
||||
WP_CLI::log('Validating property coordinates against state boundaries...');
|
||||
WP_CLI::log('');
|
||||
|
||||
$progress = null;
|
||||
$progress_callback = function($processed, $total, $valid, $invalid) use (&$progress) {
|
||||
if ($progress === null) {
|
||||
$progress = WP_CLI\Utils\make_progress_bar('Validating', $total);
|
||||
}
|
||||
$progress->tick();
|
||||
};
|
||||
|
||||
$results = MLS_Geo_Validator::validate_all_properties($progress_callback);
|
||||
|
||||
if ($progress) {
|
||||
$progress->finish();
|
||||
}
|
||||
|
||||
WP_CLI::log('');
|
||||
WP_CLI::success("Validation complete!");
|
||||
WP_CLI::log(" Total properties: " . number_format($results['total']));
|
||||
WP_CLI::log(" Valid coordinates: " . number_format($results['valid']));
|
||||
WP_CLI::log(" Invalid coordinates: " . number_format($results['invalid']));
|
||||
|
||||
if ($results['invalid'] > 0) {
|
||||
WP_CLI::log('');
|
||||
WP_CLI::log("Properties with invalid coordinates will be excluded from map views.");
|
||||
WP_CLI::log("Run 'wp mls geo show-invalid' to see details.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show coordinate validation statistics
|
||||
*/
|
||||
private function geo_stats() {
|
||||
$stats = MLS_Geo_Validator::get_stats();
|
||||
|
||||
WP_CLI::log('Coordinate Validation Statistics:');
|
||||
WP_CLI::log('');
|
||||
WP_CLI::log(" Total properties: " . number_format($stats['total']));
|
||||
WP_CLI::log(" Valid coordinates: " . number_format($stats['valid']));
|
||||
WP_CLI::log(" Invalid coordinates: " . number_format($stats['invalid']));
|
||||
WP_CLI::log(" Null coordinates: " . number_format($stats['null_coords']));
|
||||
|
||||
if ($stats['total'] > 0) {
|
||||
$pct_valid = round(($stats['valid'] / $stats['total']) * 100, 1);
|
||||
$pct_invalid = round(($stats['invalid'] / $stats['total']) * 100, 1);
|
||||
WP_CLI::log('');
|
||||
WP_CLI::log(" Valid %: {$pct_valid}%");
|
||||
WP_CLI::log(" Invalid %: {$pct_invalid}%");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show properties with invalid coordinates
|
||||
*/
|
||||
private function geo_show_invalid() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->plugin->get_db()->properties_table();
|
||||
|
||||
$invalid = $wpdb->get_results(
|
||||
"SELECT listing_id, listing_key, street_number, street_name, city, state_or_province, latitude, longitude
|
||||
FROM {$table}
|
||||
WHERE coordinates_invalid = 1
|
||||
ORDER BY city, street_name
|
||||
LIMIT 100"
|
||||
);
|
||||
|
||||
if (empty($invalid)) {
|
||||
WP_CLI::success("No properties with invalid coordinates found.");
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::log("Properties with invalid coordinates (showing up to 100):");
|
||||
WP_CLI::log('');
|
||||
|
||||
$items = array();
|
||||
foreach ($invalid as $row) {
|
||||
$address = trim($row->street_number . ' ' . $row->street_name);
|
||||
$items[] = array(
|
||||
'MLS ID' => $row->listing_id,
|
||||
'Address' => $address ?: '(no address)',
|
||||
'City' => $row->city,
|
||||
'State' => $row->state_or_province,
|
||||
'Lat' => $row->latitude,
|
||||
'Lng' => $row->longitude,
|
||||
);
|
||||
}
|
||||
|
||||
WP_CLI\Utils\format_items('table', $items, array('MLS ID', 'Address', 'City', 'State', 'Lat', 'Lng'));
|
||||
|
||||
$total = $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 1");
|
||||
if ($total > 100) {
|
||||
WP_CLI::log('');
|
||||
WP_CLI::log("... and " . ($total - 100) . " more.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* US State Bounding Boxes
|
||||
*
|
||||
* Approximate rectangular bounds for each US state.
|
||||
* Used to validate that property coordinates fall within their claimed state.
|
||||
*
|
||||
* Format: state_code => [min_lat, max_lat, min_lng, max_lng]
|
||||
*
|
||||
* Data sourced from US Census Bureau state boundaries.
|
||||
* Bounds include a small buffer for edge cases.
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
return array(
|
||||
'AL' => array(30.14, 35.01, -88.47, -84.89), // Alabama
|
||||
'AK' => array(51.21, 71.39, -179.15, 179.77), // Alaska (crosses date line)
|
||||
'AZ' => array(31.33, 37.00, -114.82, -109.04), // Arizona
|
||||
'AR' => array(33.00, 36.50, -94.62, -89.64), // Arkansas
|
||||
'CA' => array(32.53, 42.01, -124.42, -114.13), // California
|
||||
'CO' => array(36.99, 41.00, -109.06, -102.04), // Colorado
|
||||
'CT' => array(40.95, 42.05, -73.73, -71.79), // Connecticut
|
||||
'DE' => array(38.45, 39.84, -75.79, -75.05), // Delaware
|
||||
'FL' => array(24.40, 31.00, -87.63, -80.03), // Florida
|
||||
'GA' => array(30.36, 35.00, -85.61, -80.84), // Georgia
|
||||
'HI' => array(18.91, 22.24, -160.25, -154.81), // Hawaii
|
||||
'ID' => array(41.99, 49.00, -117.24, -111.04), // Idaho
|
||||
'IL' => array(36.97, 42.51, -91.51, -87.02), // Illinois
|
||||
'IN' => array(37.77, 41.76, -88.10, -84.78), // Indiana
|
||||
'IA' => array(40.37, 43.50, -96.64, -90.14), // Iowa
|
||||
'KS' => array(36.99, 40.00, -102.05, -94.59), // Kansas
|
||||
'KY' => array(36.50, 39.15, -89.57, -81.96), // Kentucky
|
||||
'LA' => array(28.93, 33.02, -94.04, -88.82), // Louisiana
|
||||
'ME' => array(43.06, 47.46, -71.08, -66.95), // Maine
|
||||
'MD' => array(37.91, 39.72, -79.49, -75.05), // Maryland
|
||||
'MA' => array(41.24, 42.89, -73.51, -69.93), // Massachusetts
|
||||
'MI' => array(41.70, 48.19, -90.42, -82.41), // Michigan
|
||||
'MN' => array(43.50, 49.38, -97.24, -89.49), // Minnesota
|
||||
'MS' => array(30.17, 35.00, -91.66, -88.10), // Mississippi
|
||||
'MO' => array(35.99, 40.61, -95.77, -89.10), // Missouri
|
||||
'MT' => array(44.36, 49.00, -116.05, -104.04), // Montana
|
||||
'NE' => array(40.00, 43.00, -104.05, -95.31), // Nebraska
|
||||
'NV' => array(35.00, 42.00, -120.01, -114.04), // Nevada
|
||||
'NH' => array(42.70, 45.31, -72.56, -70.70), // New Hampshire
|
||||
'NJ' => array(38.93, 41.36, -75.56, -73.89), // New Jersey
|
||||
'NM' => array(31.33, 37.00, -109.05, -103.00), // New Mexico
|
||||
'NY' => array(40.50, 45.02, -79.76, -71.86), // New York
|
||||
'NC' => array(33.84, 36.59, -84.32, -75.46), // North Carolina
|
||||
'ND' => array(45.94, 49.00, -104.05, -96.55), // North Dakota
|
||||
'OH' => array(38.40, 42.33, -84.82, -80.52), // Ohio
|
||||
'OK' => array(33.62, 37.00, -103.00, -94.43), // Oklahoma
|
||||
'OR' => array(41.99, 46.29, -124.57, -116.46), // Oregon
|
||||
'PA' => array(39.72, 42.27, -80.52, -74.69), // Pennsylvania
|
||||
'RI' => array(41.15, 42.02, -71.86, -71.12), // Rhode Island
|
||||
'SC' => array(32.03, 35.22, -83.35, -78.54), // South Carolina
|
||||
'SD' => array(42.48, 45.95, -104.06, -96.44), // South Dakota
|
||||
'TN' => array(34.98, 36.68, -90.31, -81.65), // Tennessee
|
||||
'TX' => array(25.84, 36.50, -106.65, -93.51), // Texas
|
||||
'UT' => array(36.99, 42.00, -114.05, -109.04), // Utah
|
||||
'VT' => array(42.73, 45.02, -73.44, -71.46), // Vermont
|
||||
'VA' => array(36.54, 39.47, -83.68, -75.24), // Virginia
|
||||
'WA' => array(45.54, 49.00, -124.76, -116.92), // Washington
|
||||
'WV' => array(37.20, 40.64, -82.64, -77.72), // West Virginia
|
||||
'WI' => array(42.49, 47.08, -92.89, -86.25), // Wisconsin
|
||||
'WY' => array(40.99, 45.01, -111.06, -104.05), // Wyoming
|
||||
'DC' => array(38.79, 38.99, -77.12, -76.91), // District of Columbia
|
||||
'PR' => array(17.88, 18.52, -67.95, -65.22), // Puerto Rico
|
||||
'VI' => array(17.62, 18.42, -65.08, -64.56), // US Virgin Islands
|
||||
'GU' => array(13.23, 13.65, 144.62, 144.96), // Guam
|
||||
);
|
||||
|
Can't render this file because it is too large.
|
@@ -249,7 +249,8 @@ class MLS_Cluster {
|
||||
$table = $this->db->properties_table();
|
||||
|
||||
// Build WHERE clause
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
// Exclude properties with invalid coordinates from map display
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN, IA only)
|
||||
@@ -291,7 +292,7 @@ class MLS_Cluster {
|
||||
$values[] = (int) $args['min_beds'];
|
||||
}
|
||||
|
||||
// Add bounds filter if provided
|
||||
// Add bounds filter (BETWEEN is efficient for rectangular viewport queries)
|
||||
if ($args['bounds'] && is_array($args['bounds']) && count($args['bounds']) === 4) {
|
||||
list($sw_lat, $sw_lng, $ne_lat, $ne_lng) = $args['bounds'];
|
||||
$where[] = 'latitude BETWEEN %f AND %f';
|
||||
@@ -571,7 +572,8 @@ class MLS_Cluster {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->db->properties_table();
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
// Exclude properties with invalid coordinates
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN, IA only)
|
||||
|
||||
@@ -13,7 +13,7 @@ class MLS_DB {
|
||||
* Schema version for index migrations
|
||||
* Increment this when adding new indexes or columns
|
||||
*/
|
||||
const SCHEMA_VERSION = 3;
|
||||
const SCHEMA_VERSION = 5;
|
||||
|
||||
/**
|
||||
* Get table name with prefix
|
||||
@@ -415,8 +415,66 @@ class MLS_DB {
|
||||
update_option('mls_schema_version', 3);
|
||||
}
|
||||
|
||||
// Migration to schema version 4: Add spatial POINT column and index
|
||||
if ($current_schema < 4) {
|
||||
$table_properties = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
// Check if location column exists
|
||||
$column_exists = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'location'",
|
||||
DB_NAME,
|
||||
$table_properties
|
||||
));
|
||||
|
||||
if (!$column_exists) {
|
||||
// Add POINT column (nullable initially for population)
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD COLUMN location POINT SRID 4326 DEFAULT NULL AFTER longitude");
|
||||
|
||||
// Populate location from existing lat/lng
|
||||
// Note: SRID 4326 uses axis order (latitude, longitude) in MySQL 8.0+
|
||||
$wpdb->query("UPDATE {$table_properties} SET location = ST_PointFromText(CONCAT('POINT(', latitude, ' ', longitude, ')'), 4326) WHERE latitude IS NOT NULL AND longitude IS NOT NULL");
|
||||
|
||||
// Make column NOT NULL (required for spatial index)
|
||||
$wpdb->query("ALTER TABLE {$table_properties} MODIFY location POINT NOT NULL SRID 4326");
|
||||
|
||||
// Add spatial index
|
||||
$existing_indexes = self::get_existing_indexes($table_properties);
|
||||
if (!isset($existing_indexes['idx_location_spatial'])) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD SPATIAL INDEX idx_location_spatial (location)");
|
||||
}
|
||||
}
|
||||
|
||||
update_option('mls_schema_version', 4);
|
||||
}
|
||||
|
||||
// Migration to schema version 5: Add coordinates_invalid column for geo validation
|
||||
if ($current_schema < 5) {
|
||||
$table_properties = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
// Check if column exists
|
||||
$column_exists = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'coordinates_invalid'",
|
||||
DB_NAME,
|
||||
$table_properties
|
||||
));
|
||||
|
||||
if (!$column_exists) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD COLUMN coordinates_invalid TINYINT(1) NOT NULL DEFAULT 0 AFTER longitude");
|
||||
}
|
||||
|
||||
// Add index if not exists
|
||||
$existing_indexes = self::get_existing_indexes($table_properties);
|
||||
if (!isset($existing_indexes['idx_coordinates_invalid'])) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD INDEX idx_coordinates_invalid (coordinates_invalid)");
|
||||
}
|
||||
|
||||
update_option('mls_schema_version', 5);
|
||||
}
|
||||
|
||||
// Future migrations go here:
|
||||
// if ($current_schema < 4) { ... }
|
||||
// if ($current_schema < 6) { ... }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Geographic Coordinate Validator
|
||||
*
|
||||
* Validates that property coordinates fall within their claimed state boundaries.
|
||||
* Uses bounding box approximations for fast validation.
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MLS_Geo_Validator {
|
||||
|
||||
/**
|
||||
* State bounding boxes
|
||||
* Format: state_code => [min_lat, max_lat, min_lng, max_lng]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $state_bounds = null;
|
||||
|
||||
/**
|
||||
* Load state bounds data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_state_bounds() {
|
||||
if (self::$state_bounds === null) {
|
||||
$bounds_file = MLS_PLUGIN_DIR . 'data/state-bounds.php';
|
||||
if (file_exists($bounds_file)) {
|
||||
self::$state_bounds = include $bounds_file;
|
||||
} else {
|
||||
self::$state_bounds = array();
|
||||
}
|
||||
}
|
||||
return self::$state_bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid for a given state
|
||||
*
|
||||
* @param float $latitude Latitude
|
||||
* @param float $longitude Longitude
|
||||
* @param string $state_code Two-letter state code (e.g., 'MN', 'IA')
|
||||
* @return bool True if coordinates are within state bounds
|
||||
*/
|
||||
public static function validate_coordinates($latitude, $longitude, $state_code) {
|
||||
// Null or empty coordinates are invalid
|
||||
if ($latitude === null || $longitude === null || $latitude === '' || $longitude === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lat = (float) $latitude;
|
||||
$lng = (float) $longitude;
|
||||
|
||||
// Basic sanity check - valid lat/lng ranges
|
||||
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continental US rough bounds check (catches obviously wrong data like European coords)
|
||||
// This is a quick filter before state-specific validation
|
||||
if ($lat < 24 || $lat > 50 || $lng < -125 || $lng > -66) {
|
||||
// Allow Alaska, Hawaii, and territories which fall outside continental bounds
|
||||
if (!in_array(strtoupper($state_code), array('AK', 'HI', 'PR', 'VI', 'GU'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get state-specific bounds
|
||||
$bounds = self::get_state_bounds();
|
||||
$state_code = strtoupper($state_code);
|
||||
|
||||
if (!isset($bounds[$state_code])) {
|
||||
// Unknown state - can't validate, assume valid
|
||||
return true;
|
||||
}
|
||||
|
||||
list($min_lat, $max_lat, $min_lng, $max_lng) = $bounds[$state_code];
|
||||
|
||||
// Check if coordinates fall within state bounding box
|
||||
// Add small buffer (0.1 degrees ~= 7 miles) for edge cases
|
||||
$buffer = 0.1;
|
||||
|
||||
return $lat >= ($min_lat - $buffer) &&
|
||||
$lat <= ($max_lat + $buffer) &&
|
||||
$lng >= ($min_lng - $buffer) &&
|
||||
$lng <= ($max_lng + $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a property record and return whether coordinates are invalid
|
||||
*
|
||||
* @param object|array $property Property data with latitude, longitude, state_or_province
|
||||
* @return int 1 if coordinates are invalid, 0 if valid
|
||||
*/
|
||||
public static function check_property($property) {
|
||||
// Convert to array if object
|
||||
if (is_object($property)) {
|
||||
$lat = isset($property->latitude) ? $property->latitude : null;
|
||||
$lng = isset($property->longitude) ? $property->longitude : null;
|
||||
$state = isset($property->state_or_province) ? $property->state_or_province : null;
|
||||
} else {
|
||||
$lat = isset($property['latitude']) ? $property['latitude'] : null;
|
||||
$lng = isset($property['longitude']) ? $property['longitude'] : null;
|
||||
$state = isset($property['state_or_province']) ? $property['state_or_province'] : null;
|
||||
}
|
||||
|
||||
// If no coordinates, mark as invalid for map purposes
|
||||
if ($lat === null || $lng === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If no state, can't validate - assume valid
|
||||
if (empty($state)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Normalize state code (handle full names or codes)
|
||||
$state_code = self::normalize_state_code($state);
|
||||
|
||||
// Validate
|
||||
return self::validate_coordinates($lat, $lng, $state_code) ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize state name/code to two-letter code
|
||||
*
|
||||
* @param string $state State name or code
|
||||
* @return string Two-letter state code
|
||||
*/
|
||||
private static function normalize_state_code($state) {
|
||||
$state = trim($state);
|
||||
|
||||
// Already a two-letter code
|
||||
if (strlen($state) === 2) {
|
||||
return strtoupper($state);
|
||||
}
|
||||
|
||||
// Common state name to code mappings
|
||||
$name_to_code = array(
|
||||
'minnesota' => 'MN',
|
||||
'iowa' => 'IA',
|
||||
'wisconsin' => 'WI',
|
||||
'north dakota' => 'ND',
|
||||
'south dakota' => 'SD',
|
||||
'nebraska' => 'NE',
|
||||
'missouri' => 'MO',
|
||||
'illinois' => 'IL',
|
||||
// Add more as needed
|
||||
);
|
||||
|
||||
$lower = strtolower($state);
|
||||
if (isset($name_to_code[$lower])) {
|
||||
return $name_to_code[$lower];
|
||||
}
|
||||
|
||||
// Return first two characters as fallback
|
||||
return strtoupper(substr($state, 0, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all existing properties in the database
|
||||
* Updates coordinates_invalid column for each record
|
||||
*
|
||||
* @param callable $progress_callback Optional callback for progress updates
|
||||
* @return array Results with counts
|
||||
*/
|
||||
public static function validate_all_properties($progress_callback = null) {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'valid' => 0,
|
||||
'invalid' => 0,
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
// Get total count
|
||||
$results['total'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
|
||||
|
||||
if ($results['total'] === 0) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Process in batches
|
||||
$batch_size = 500;
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $results['total']) {
|
||||
$properties = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT id, latitude, longitude, state_or_province FROM {$table} LIMIT %d OFFSET %d",
|
||||
$batch_size,
|
||||
$offset
|
||||
));
|
||||
|
||||
if (empty($properties)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$valid_ids = array();
|
||||
$invalid_ids = array();
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$is_invalid = self::check_property($property);
|
||||
|
||||
if ($is_invalid) {
|
||||
$invalid_ids[] = $property->id;
|
||||
$results['invalid']++;
|
||||
} else {
|
||||
$valid_ids[] = $property->id;
|
||||
$results['valid']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Batch update valid records
|
||||
if (!empty($valid_ids)) {
|
||||
$ids_string = implode(',', array_map('intval', $valid_ids));
|
||||
$wpdb->query("UPDATE {$table} SET coordinates_invalid = 0 WHERE id IN ({$ids_string})");
|
||||
}
|
||||
|
||||
// Batch update invalid records
|
||||
if (!empty($invalid_ids)) {
|
||||
$ids_string = implode(',', array_map('intval', $invalid_ids));
|
||||
$wpdb->query("UPDATE {$table} SET coordinates_invalid = 1 WHERE id IN ({$ids_string})");
|
||||
}
|
||||
|
||||
$offset += $batch_size;
|
||||
|
||||
// Progress callback
|
||||
if ($progress_callback && is_callable($progress_callback)) {
|
||||
$progress_callback($offset, $results['total'], $results['valid'], $results['invalid']);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about coordinate validity
|
||||
*
|
||||
* @return array Statistics
|
||||
*/
|
||||
public static function get_stats() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
return array(
|
||||
'total' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"),
|
||||
'valid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 0"),
|
||||
'invalid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 1"),
|
||||
'null_coords' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE latitude IS NULL OR longitude IS NULL"),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -375,13 +375,15 @@ class MLS_Image_Endpoint {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Send headers
|
||||
// Send headers - remove any no-cache headers WordPress may have added
|
||||
header_remove('Pragma');
|
||||
header('Pragma: public');
|
||||
header('Content-Type: ' . $mime_type);
|
||||
header('Content-Length: ' . $file_size);
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $last_modified) . ' GMT');
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Cache-Control: public, max-age=31536000'); // 1 year
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
|
||||
header('Cache-Control: public, max-age=3600'); // 1 hour
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
|
||||
|
||||
// Stream file
|
||||
readfile($path);
|
||||
|
||||
@@ -109,8 +109,8 @@ class MLS_Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL for distance-based filtering using Haversine formula
|
||||
* Returns properties within specified miles of a center point
|
||||
* Build SQL for distance-based filtering using spatial index
|
||||
* Uses bounding box pre-filter + ST_Distance_Sphere for accuracy
|
||||
*
|
||||
* @param float $lat Center latitude
|
||||
* @param float $lng Center longitude
|
||||
@@ -118,14 +118,46 @@ class MLS_Query {
|
||||
* @return string SQL expression for distance filter
|
||||
*/
|
||||
private function get_distance_filter_sql($lat, $lng, $miles) {
|
||||
// Haversine formula: distance in miles
|
||||
// 3959 is Earth's radius in miles
|
||||
// Convert miles to meters for ST_Distance_Sphere (returns meters)
|
||||
$meters = $miles * 1609.344;
|
||||
|
||||
// Create center point (SRID 4326 uses lat, lng order in MySQL 8.0+)
|
||||
// Use ST_Distance_Sphere with the spatial indexed location column
|
||||
return sprintf(
|
||||
"(3959 * acos(cos(radians(%f)) * cos(radians(latitude)) * cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)))) <= %f",
|
||||
"ST_Distance_Sphere(location, ST_PointFromText('POINT(%f %f)', 4326)) <= %f",
|
||||
$lat,
|
||||
$lng,
|
||||
$lat,
|
||||
$miles
|
||||
$meters
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL for bounding box pre-filter
|
||||
* Uses simple BETWEEN for fast initial filtering before distance calc
|
||||
* This narrows down candidates significantly before the expensive ST_Distance_Sphere
|
||||
*
|
||||
* @param float $lat Center latitude
|
||||
* @param float $lng Center longitude
|
||||
* @param float $miles Radius in miles
|
||||
* @return string SQL expression for bounding box filter
|
||||
*/
|
||||
private function get_bounding_box_filter_sql($lat, $lng, $miles) {
|
||||
// Approximate degrees per mile (varies by latitude, using average)
|
||||
// 1 degree latitude ≈ 69 miles
|
||||
// 1 degree longitude ≈ 69 miles * cos(latitude)
|
||||
$lat_delta = $miles / 69.0;
|
||||
$lng_delta = $miles / (69.0 * cos(deg2rad($lat)));
|
||||
|
||||
$min_lat = $lat - $lat_delta;
|
||||
$max_lat = $lat + $lat_delta;
|
||||
$min_lng = $lng - $lng_delta;
|
||||
$max_lng = $lng + $lng_delta;
|
||||
|
||||
// Use BETWEEN for bounding box - efficient with indexes on lat/lng
|
||||
return sprintf(
|
||||
"(latitude BETWEEN %f AND %f AND longitude BETWEEN %f AND %f)",
|
||||
$min_lat, $max_lat,
|
||||
$min_lng, $max_lng
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,6 +193,7 @@ class MLS_Query {
|
||||
'search' => null, // Search in address/remarks
|
||||
'bounds' => null, // Map bounds: array(sw_lat, sw_lng, ne_lat, ne_lng)
|
||||
'center' => null, // Map center for distance sort: array(lat, lng)
|
||||
'featured_ids' => null, // Array of listing_id values to prioritize after HomeProz
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
'orderby' => 'modification_timestamp',
|
||||
@@ -218,12 +251,18 @@ class MLS_Query {
|
||||
$values[] = $args['postal_code'];
|
||||
} elseif ($args['center_lat'] && $args['center_lng']) {
|
||||
// Direct lat/lng radius search (from homepage location search)
|
||||
// Use bounding box pre-filter for spatial index, then exact distance
|
||||
$bbox_filter = $this->get_bounding_box_filter_sql(
|
||||
(float) $args['center_lat'],
|
||||
(float) $args['center_lng'],
|
||||
(int) $args['radius']
|
||||
);
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
(float) $args['center_lat'],
|
||||
(float) $args['center_lng'],
|
||||
(int) $args['radius']
|
||||
);
|
||||
$where[] = "({$distance_filter})";
|
||||
$where[] = "({$bbox_filter} AND {$distance_filter})";
|
||||
}
|
||||
|
||||
if ($args['county']) {
|
||||
@@ -311,6 +350,18 @@ class MLS_Query {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $where);
|
||||
|
||||
// ORDER BY
|
||||
// Always prioritize: 1) HomeProz listings, 2) Featured listings, 3) Regular listings
|
||||
// Build featured sort expression if featured_ids provided
|
||||
$featured_sort = '';
|
||||
if (!empty($args['featured_ids']) && is_array($args['featured_ids'])) {
|
||||
$featured_ids = array_map('sanitize_text_field', $args['featured_ids']);
|
||||
$featured_placeholders = implode(',', array_fill(0, count($featured_ids), '%s'));
|
||||
$featured_sort = $wpdb->prepare(
|
||||
", (CASE WHEN listing_id IN ({$featured_placeholders}) THEN 1 ELSE 0 END) DESC",
|
||||
...$featured_ids
|
||||
);
|
||||
}
|
||||
|
||||
// If center provided, sort by distance from center
|
||||
if ($args['center'] && is_array($args['center']) && count($args['center']) === 2) {
|
||||
list($center_lat, $center_lng) = $args['center'];
|
||||
@@ -318,7 +369,7 @@ class MLS_Query {
|
||||
// Using squared Euclidean distance with latitude adjustment for speed
|
||||
$lat_factor = cos(deg2rad((float) $center_lat));
|
||||
$sql .= $wpdb->prepare(
|
||||
" ORDER BY (POW(latitude - %f, 2) + POW((longitude - %f) * %f, 2)) ASC",
|
||||
" ORDER BY is_homeproz DESC{$featured_sort}, (POW(latitude - %f, 2) + POW((longitude - %f) * %f, 2)) ASC",
|
||||
(float) $center_lat,
|
||||
(float) $center_lng,
|
||||
$lat_factor
|
||||
@@ -338,7 +389,7 @@ class MLS_Query {
|
||||
|
||||
$orderby = in_array($args['orderby'], $allowed_orderby) ? $args['orderby'] : 'modification_timestamp';
|
||||
$order = strtoupper($args['order']) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$sql .= " ORDER BY {$orderby} {$order}";
|
||||
$sql .= " ORDER BY is_homeproz DESC{$featured_sort}, {$orderby} {$order}";
|
||||
}
|
||||
|
||||
// LIMIT/OFFSET
|
||||
@@ -530,13 +581,19 @@ class MLS_Query {
|
||||
$values[] = $args['postal_code'];
|
||||
} elseif (!empty($args['center_lat']) && !empty($args['center_lng'])) {
|
||||
// Direct lat/lng radius search (from homepage location search)
|
||||
// Use bounding box pre-filter for spatial index, then exact distance
|
||||
$radius = !empty($args['radius']) ? (int) $args['radius'] : 30;
|
||||
$bbox_filter = $this->get_bounding_box_filter_sql(
|
||||
(float) $args['center_lat'],
|
||||
(float) $args['center_lng'],
|
||||
$radius
|
||||
);
|
||||
$distance_filter = $this->get_distance_filter_sql(
|
||||
(float) $args['center_lat'],
|
||||
(float) $args['center_lng'],
|
||||
$radius
|
||||
);
|
||||
$where[] = "({$distance_filter})";
|
||||
$where[] = "({$bbox_filter} AND {$distance_filter})";
|
||||
}
|
||||
|
||||
if (!empty($args['county'])) {
|
||||
@@ -728,7 +785,8 @@ class MLS_Query {
|
||||
|
||||
$table = $this->db->properties_table();
|
||||
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
// Exclude properties with invalid coordinates from map bounds
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN and IA only)
|
||||
|
||||
@@ -627,6 +627,18 @@ class MLS_Sync_Engine {
|
||||
$this->emit_progress('property_created', array('listing_key' => $listing_key));
|
||||
}
|
||||
|
||||
// Update spatial location column (wpdb can't handle ST_PointFromText directly)
|
||||
$lat = $property['Latitude'] ?? null;
|
||||
$lng = $property['Longitude'] ?? null;
|
||||
if ($lat !== null && $lng !== null) {
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$this->db->properties_table()} SET location = ST_PointFromText(CONCAT('POINT(', %f, ' ', %f, ')'), 4326) WHERE listing_key = %s",
|
||||
(float) $lat,
|
||||
(float) $lng,
|
||||
$listing_key
|
||||
));
|
||||
}
|
||||
|
||||
// Process media if present
|
||||
if (isset($property['Media']) && is_array($property['Media'])) {
|
||||
$this->media_handler->sync_property_media($listing_key, $property['Media'], false, $this->progress_callback);
|
||||
@@ -652,6 +664,13 @@ class MLS_Sync_Engine {
|
||||
* @return array Mapped data for database
|
||||
*/
|
||||
private function map_property_data($property) {
|
||||
// Validate coordinates against state boundaries
|
||||
$coordinates_invalid = MLS_Geo_Validator::validate_coordinates(
|
||||
$property['Latitude'] ?? null,
|
||||
$property['Longitude'] ?? null,
|
||||
$property['StateOrProvince'] ?? 'MN'
|
||||
) ? 0 : 1;
|
||||
|
||||
return array(
|
||||
'listing_id' => $property['ListingId'] ?? null,
|
||||
'originating_system' => $property['OriginatingSystemName'] ?? 'northstar',
|
||||
@@ -673,6 +692,7 @@ class MLS_Sync_Engine {
|
||||
'county' => $property['CountyOrParish'] ?? null,
|
||||
'latitude' => $property['Latitude'] ?? null,
|
||||
'longitude' => $property['Longitude'] ?? null,
|
||||
'coordinates_invalid' => $coordinates_invalid,
|
||||
|
||||
'property_type' => $property['PropertyType'] ?? null,
|
||||
'property_sub_type' => $property['PropertySubType'] ?? null,
|
||||
|
||||
@@ -97,6 +97,7 @@ final class MLS_Plugin {
|
||||
require_once MLS_PLUGIN_DIR . 'includes/class-mls-image-endpoint.php';
|
||||
require_once MLS_PLUGIN_DIR . 'includes/class-mls-query.php';
|
||||
require_once MLS_PLUGIN_DIR . 'includes/class-mls-cluster.php';
|
||||
require_once MLS_PLUGIN_DIR . 'includes/class-mls-geo-validator.php';
|
||||
|
||||
// Activation/Deactivation
|
||||
require_once MLS_PLUGIN_DIR . 'includes/class-mls-activator.php';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script directory and filename
|
||||
SCRIPT_DIR="$(dirname "$0")"
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
# Check for the required argument
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 New_Namespace"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_NAMESPACE="$1"
|
||||
|
||||
# Use find to get all files recursively from the script's directory, excluding the script itself
|
||||
find "$SCRIPT_DIR" -type f \( -name "*.php" -o -name "*.json" \) ! -name "$SCRIPT_NAME" | while read -r file; do
|
||||
echo $file
|
||||
# Use perl for the replacement in each file
|
||||
perl -pi -e "s/WP_Compat_Validation_Tool/$NEW_NAMESPACE/g" "$file"
|
||||
done
|
||||
|
||||
cd 10up-lib/wp-compat-validation-tool && rm -rf .git .github .gitignore composer.json composer.lock CHANGELOG.md CONTRIBUTING.md README.md LICENSE.md CODE_OF_CONDUCT.md CREDITS.md
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
namespace Simple_Page_Ordering_Validator;
|
||||
|
||||
class Validator {
|
||||
/**
|
||||
* Array of checks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $checklist = array();
|
||||
|
||||
/**
|
||||
* Array of error messages
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $messages = array();
|
||||
|
||||
/**
|
||||
* Constructor function.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->checklist = array(
|
||||
'plugin_name' => array(
|
||||
'value' => '',
|
||||
'required' => true,
|
||||
),
|
||||
'php_min_required_version' => array(
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
),
|
||||
'php_max_required_version' => array(
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
),
|
||||
'wp_min_required_version' => array(
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
),
|
||||
'wp_max_required_version' => array(
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin name.
|
||||
*
|
||||
* @param string $value Plugin name.
|
||||
* @return Validator
|
||||
*/
|
||||
public function set_plugin_name( $value = '' ) {
|
||||
$this->checklist['plugin_name']['value'] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum PHP version supported by a plugin.
|
||||
*
|
||||
* @param string $value Minimum PHP version.
|
||||
* @return Validator
|
||||
*/
|
||||
public function set_php_min_required_version( $value = '' ) {
|
||||
$this->checklist['php_min_required_version']['value'] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum PHP version supported by a plugin.
|
||||
*
|
||||
* @param string $value Maximum PHP version.
|
||||
* @return Validator
|
||||
*/
|
||||
public function set_php_max_required_version( $value = '' ) {
|
||||
$this->checklist['php_max_required_version']['value'] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum WordPress version supported by a plugin.
|
||||
*
|
||||
* @param string $value Minimum WordPress version.
|
||||
* @return Validator
|
||||
*/
|
||||
public function set_wordpress_min_required_version( $value = '' ) {
|
||||
$this->checklist['wp_min_required_version']['value'] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum WordPress version supported by a plugin.
|
||||
*
|
||||
* @param string $value Maximum WordPress version.
|
||||
* @return Validator
|
||||
*/
|
||||
public function set_wordpress_max_required_version( $value = '' ) {
|
||||
$this->checklist['wp_max_required_version']['value'] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the plugin meets all compatibility checks, false otherwise.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_plugin_compatible() {
|
||||
foreach ( $this->checklist as $item_name => $item_details ) {
|
||||
if ( $item_details['required'] && empty( $item_details['value'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ( $item_name ) {
|
||||
case 'php_min_required_version':
|
||||
if ( ! empty( $item_details['value'] ) && version_compare( phpversion(), $item_details['value'], '<' ) ) {
|
||||
$this->messages[] = sprintf( esc_html__( 'The minimum PHP version required is %s' ), $item_details['value'] );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'php_max_required_version':
|
||||
if ( ! empty( $item_details['value'] ) && version_compare( phpversion(), $item_details['value'], '>' ) ) {
|
||||
$this->messages[] = sprintf( esc_html__( 'The maximum PHP version supported is %s' ), $item_details['value'] );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->messages ) ) {
|
||||
add_action( 'admin_notices', array( $this, 'render_php_compat_error' ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the error messages as notice.
|
||||
*/
|
||||
public function render_php_compat_error() {
|
||||
?>
|
||||
<div class="notice notice-error">
|
||||
<p>
|
||||
<strong>
|
||||
<?php printf( esc_html__( '%s error:' ), $this->checklist['plugin_name']['value'] ); ?>
|
||||
</strong>
|
||||
</p>
|
||||
<?php if ( count( $this->messages ) > 1 ) : ?>
|
||||
<ul>
|
||||
<?php foreach ( $this->messages as $message ) : ?>
|
||||
<li><?php echo esc_html( $message ); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php elseif ( 1 === count( $this->messages ) ): ?>
|
||||
<p>
|
||||
<?php echo esc_html( $this->messages[0] ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,900 @@
|
||||
<?php
|
||||
|
||||
namespace SimplePageOrdering;
|
||||
|
||||
use stdClass;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
use WP_REST_Response;
|
||||
use WP_Query;
|
||||
|
||||
// Useful global constants.
|
||||
define( 'SIMPLE_PAGE_ORDERING_VERSION', '2.7.4' );
|
||||
|
||||
if ( ! class_exists( 'Simple_Page_Ordering' ) ) :
|
||||
|
||||
/**
|
||||
* Simple_Page_Ordering class
|
||||
*/
|
||||
class Simple_Page_Ordering {
|
||||
|
||||
/**
|
||||
* Handles initializing this class and returning the singleton instance after it's been cached.
|
||||
*
|
||||
* @return null|Simple_Page_Ordering
|
||||
*/
|
||||
public static function get_instance() {
|
||||
// Store the instance locally to avoid private static replication
|
||||
static $instance = null;
|
||||
|
||||
if ( null === $instance ) {
|
||||
$instance = new self();
|
||||
self::add_actions();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty constructor
|
||||
*
|
||||
* Purposely do nothing here
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Handles registering hooks that initialize this plugin.
|
||||
*/
|
||||
public static function add_actions() {
|
||||
add_action( 'load-edit.php', array( __CLASS__, 'load_edit_screen' ) );
|
||||
add_action( 'wp_ajax_simple_page_ordering', array( __CLASS__, 'ajax_simple_page_ordering' ) );
|
||||
add_action( 'wp_ajax_reset_simple_page_ordering', array( __CLASS__, 'ajax_reset_simple_page_ordering' ) );
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
|
||||
add_action( 'rest_api_init', array( __CLASS__, 'rest_api_init' ) );
|
||||
|
||||
// Custom edit page actions.
|
||||
add_action( 'post_action_spo-move-under-grandparent', array( __CLASS__, 'handle_move_under_grandparent' ) );
|
||||
add_action( 'post_action_spo-move-under-sibling', array( __CLASS__, 'handle_move_under_sibling' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a post in/up the post parent tree.
|
||||
*
|
||||
* This is a custom action on the edit page to modify the post parent
|
||||
* to be the child it's current grandparent post. If no grandparent
|
||||
* exists, the post becomes a top level page.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
public static function handle_move_under_grandparent( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post ) {
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
check_admin_referer( "simple-page-ordering-nonce-move-{$post->ID}", 'spo_nonce' );
|
||||
|
||||
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
wp_die( esc_html__( 'You are not allowed to edit this item.', 'simple-page-ordering' ) );
|
||||
}
|
||||
|
||||
if ( 0 === $post->post_parent ) {
|
||||
// Top level. Politely continue without doing anything.
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
$ancestors = get_post_ancestors( $post );
|
||||
|
||||
// If only one ancestor, set to top level page.
|
||||
if ( 1 === count( $ancestors ) ) {
|
||||
$parent_id = 0;
|
||||
} else {
|
||||
$parent_id = $ancestors[1];
|
||||
}
|
||||
|
||||
// Update the post.
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $post->ID,
|
||||
'post_parent' => $parent_id,
|
||||
)
|
||||
);
|
||||
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a post out/down the post parent tree.
|
||||
*
|
||||
* This is a custom action on the edit page to modify the post parent
|
||||
* to be the child of it's previous sibling post on the current post
|
||||
* tree.
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
public static function handle_move_under_sibling( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post ) {
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
check_admin_referer( "simple-page-ordering-nonce-move-{$post->ID}", 'spo_nonce' );
|
||||
|
||||
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
wp_die( esc_html__( 'You are not allowed to edit this item.', 'simple-page-ordering' ) );
|
||||
}
|
||||
|
||||
list( 'top_level_pages' => $top_level_pages, 'children_pages' => $children_pages ) = self::get_walked_pages( $post->post_type );
|
||||
|
||||
// Get the relevant siblings.
|
||||
if ( 0 === $post->post_parent ) {
|
||||
$siblings = $top_level_pages;
|
||||
} else {
|
||||
$siblings = $children_pages[ $post->post_parent ];
|
||||
}
|
||||
|
||||
// Check if the post being moved is a top level page.
|
||||
$filtered_siblings = wp_list_filter( $siblings, array( 'ID' => $post->ID ) );
|
||||
if ( empty( $filtered_siblings ) ) {
|
||||
// Something went wrong. Do nothing.
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
// Find the previous page in the sibling tree
|
||||
$key = array_key_first( $filtered_siblings );
|
||||
if ( 0 === $key ) {
|
||||
// It's the first page. Do nothing.
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
$previous_page = $siblings[ $key - 1 ];
|
||||
$previous_page_id = $previous_page->ID;
|
||||
|
||||
// Update the post with the previous page as the parent.
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $post->ID,
|
||||
'post_parent' => $previous_page_id,
|
||||
)
|
||||
);
|
||||
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user after modifying the post parent.
|
||||
*/
|
||||
public static function redirect_to_referer() {
|
||||
global $post_type;
|
||||
|
||||
$send_back = wp_get_referer();
|
||||
if ( ! $send_back ||
|
||||
str_contains( $send_back, 'post.php' ) ||
|
||||
str_contains( $send_back, 'post-new.php' ) ) {
|
||||
if ( 'attachment' === $post_type ) {
|
||||
$send_back = admin_url( 'upload.php' );
|
||||
} else {
|
||||
$send_back = admin_url( 'edit.php' );
|
||||
if ( ! empty( $post_type ) ) {
|
||||
$send_back = add_query_arg( 'post_type', $post_type, $send_back );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$send_back = remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), $send_back );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $send_back );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the pages and return top level and children pages.
|
||||
*
|
||||
* @param string $post_type Post type to walk.
|
||||
*
|
||||
* @return array {
|
||||
* @type WP_Post[] $top_level_pages Top level pages.
|
||||
* @type WP_Post[] $children_pages Children pages.
|
||||
* }
|
||||
*/
|
||||
public static function get_walked_pages( $post_type = 'page' ) {
|
||||
global $wpdb;
|
||||
$pages = get_pages(
|
||||
array(
|
||||
'sort_column' => 'menu_order title',
|
||||
'post_type' => $post_type,
|
||||
)
|
||||
);
|
||||
|
||||
$top_level_pages = array();
|
||||
$children_pages = array();
|
||||
$bad_parents = array();
|
||||
|
||||
foreach ( $pages as $page ) {
|
||||
// Catch and repair bad pages.
|
||||
if ( $page->post_parent === $page->ID ) {
|
||||
$page->post_parent = 0;
|
||||
$wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'ID' => $page->ID ) );
|
||||
clean_post_cache( $page );
|
||||
$bad_parents[] = $page->ID;
|
||||
}
|
||||
|
||||
if ( $page->post_parent > 0 ) {
|
||||
$children_pages[ $page->post_parent ][] = $page;
|
||||
} else {
|
||||
$top_level_pages[] = $page;
|
||||
}
|
||||
}
|
||||
// Reprime post cache for bad parents.
|
||||
_prime_post_caches( $bad_parents, false, false );
|
||||
|
||||
return array(
|
||||
'top_level_pages' => $top_level_pages,
|
||||
'children_pages' => $children_pages,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the plugin textdomain
|
||||
*/
|
||||
public static function load_textdomain() {
|
||||
load_plugin_textdomain( 'simple-page-ordering', false, dirname( plugin_basename( __FILE__ ) ) . '/localization/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether given post type is sortable or not.
|
||||
*
|
||||
* @param string $post_type Post type to check.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function is_post_type_sortable( $post_type = 'post' ) {
|
||||
$sortable = ( post_type_supports( $post_type, 'page-attributes' ) || is_post_type_hierarchical( $post_type ) );
|
||||
|
||||
/**
|
||||
* Change default ordering support for a post type.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param boolean $sortable Whether this post type is sortable or not.
|
||||
* @param string $post_type The post type being checked.
|
||||
*/
|
||||
return apply_filters( 'simple_page_ordering_is_sortable', $sortable, $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load up page ordering scripts for the edit screen
|
||||
*/
|
||||
public static function load_edit_screen() {
|
||||
$screen = get_current_screen();
|
||||
$post_type = $screen->post_type;
|
||||
|
||||
// is post type sortable?
|
||||
$sortable = self::is_post_type_sortable( $post_type );
|
||||
if ( ! $sortable ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// does user have the right to manage these post objects?
|
||||
if ( ! self::check_edit_others_caps( $post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add view by menu order to views
|
||||
add_filter(
|
||||
'views_' . $screen->id,
|
||||
array(
|
||||
__CLASS__,
|
||||
'sort_by_order_link',
|
||||
)
|
||||
);
|
||||
add_action( 'pre_get_posts', array( __CLASS__, 'filter_query' ) );
|
||||
add_action( 'wp', array( __CLASS__, 'wp' ) );
|
||||
add_action( 'admin_head', array( __CLASS__, 'admin_head' ) );
|
||||
add_action( 'page_row_actions', array( __CLASS__, 'page_row_actions' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to enable pagination.
|
||||
*
|
||||
* @param WP_Query $query The WP_Query instance (passed by reference).
|
||||
*/
|
||||
public static function filter_query( $query ) {
|
||||
if ( ! $query->is_main_query() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_simple_page_ordering = isset( $_GET['id'] ) ? 'simple-page-ordering' === $_GET['id'] : false;
|
||||
|
||||
if ( ! $is_simple_page_ordering ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->set( 'posts_per_page', -1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* when we load up our posts query, if we're actually sorting by menu order, initialize sorting scripts
|
||||
*/
|
||||
public static function wp() {
|
||||
$orderby = get_query_var( 'orderby' );
|
||||
$screen = get_current_screen();
|
||||
$post_type = $screen->post_type ?? 'post';
|
||||
|
||||
if ( ( is_string( $orderby ) && 0 === strpos( $orderby, 'menu_order' ) ) || ( isset( $orderby['menu_order'] ) && 'ASC' === $orderby['menu_order'] ) ) {
|
||||
|
||||
$script_name = 'dist/js/simple-page-ordering.js';
|
||||
$script_asset_path = plugin_dir_path( __FILE__ ) . 'dist/js/simple-page-ordering.asset.php';
|
||||
$script_asset = file_exists( $script_asset_path )
|
||||
? require $script_asset_path
|
||||
: false;
|
||||
|
||||
if ( false !== $script_asset ) {
|
||||
$script_url = plugins_url( $script_name, __FILE__ );
|
||||
wp_enqueue_script( 'simple-page-ordering', $script_url, $script_asset['dependencies'], $script_asset['version'], true );
|
||||
|
||||
wp_localize_script(
|
||||
'simple-page-ordering',
|
||||
'simple_page_ordering_localized_data',
|
||||
array(
|
||||
'_wpnonce' => wp_create_nonce( 'simple-page-ordering-nonce' ),
|
||||
/* translators: %1$s is replaced with the post type name */
|
||||
'confirmation_msg' => sprintf( esc_html__( 'Are you sure you want to reset the ordering of the "%1$s" post type?', 'simple-page-ordering' ), $post_type ),
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_style( 'simple-page-ordering', plugins_url( '/dist/css/simple-page-ordering.css', __FILE__ ), [], $script_asset['version'] );
|
||||
} else {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function () {
|
||||
?>
|
||||
<div class="notice notice-warning is-dismissible">
|
||||
<p><?php echo wp_kses_post( __( 'It looks like you are using a development copy of <strong>Simple Page Ordering</strong>. Please run <code>npm i; npm run build</code> to create assets.', 'simple-page-ordering' ) ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add page ordering help to the help tab
|
||||
*/
|
||||
public static function admin_head() {
|
||||
$screen = get_current_screen();
|
||||
$post_type = $screen->post_type ?? 'post';
|
||||
|
||||
$screen->add_help_tab(
|
||||
array(
|
||||
'id' => 'simple_page_ordering_help_tab',
|
||||
'title' => esc_html__( 'Simple Page Ordering', 'simple-page-ordering' ),
|
||||
'content' => sprintf(
|
||||
'<p>%s</p><a href="#" id="simple-page-ordering-reset" data-posttype="%s">%s</a>',
|
||||
esc_html__( 'To reposition an item, simply drag and drop the row by "clicking and holding" it anywhere (outside of the links and form controls) and moving it to its new position.', 'simple-page-ordering' ),
|
||||
esc_attr( get_query_var( 'post_type' ) ),
|
||||
/* translators: %1$s is replaced with the post type name */
|
||||
sprintf( esc_html__( 'Reset %1$s order', 'simple-page-ordering' ), $post_type )
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the row actions for hierarchical post types.
|
||||
*
|
||||
* This adds the actions to change the parent/child relationships.
|
||||
*
|
||||
* @param array $actions An array of row action links.
|
||||
* @param WP_Post $post The post object.
|
||||
*/
|
||||
public static function page_row_actions( $actions, $post ) {
|
||||
$post = get_post( $post );
|
||||
if ( ! $post ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
list( 'top_level_pages' => $top_level_pages, 'children_pages' => $children_pages ) = self::get_walked_pages( $post->post_type );
|
||||
|
||||
$edit_link = get_edit_post_link( $post->ID, 'raw' );
|
||||
$move_under_grandparent_link = add_query_arg(
|
||||
array(
|
||||
'action' => 'spo-move-under-grandparent',
|
||||
'spo_nonce' => wp_create_nonce( "simple-page-ordering-nonce-move-{$post->ID}" ),
|
||||
'post_type' => $post->post_type,
|
||||
),
|
||||
$edit_link
|
||||
);
|
||||
$move_under_sibling_link = add_query_arg(
|
||||
array(
|
||||
'action' => 'spo-move-under-sibling',
|
||||
'spo_nonce' => wp_create_nonce( "simple-page-ordering-nonce-move-{$post->ID}" ),
|
||||
'post_type' => $post->post_type,
|
||||
),
|
||||
$edit_link
|
||||
);
|
||||
|
||||
$parent_id = $post->post_parent;
|
||||
if ( $parent_id ) {
|
||||
$actions['spo-move-under-grandparent'] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( $move_under_grandparent_link ),
|
||||
sprintf(
|
||||
/* translators: %s: parent page/post title */
|
||||
__( 'Move out from under %s', 'simple-page-ordering' ),
|
||||
get_the_title( $parent_id )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get the relevant siblings.
|
||||
if ( 0 === $post->post_parent ) {
|
||||
$siblings = $top_level_pages;
|
||||
} else {
|
||||
$siblings = $children_pages[ $post->post_parent ] ?? [];
|
||||
}
|
||||
|
||||
// Assume no sibling.
|
||||
$sibling = 0;
|
||||
// Check if the post being moved is a top level page.
|
||||
$filtered_siblings = wp_list_filter( $siblings, array( 'ID' => $post->ID ) );
|
||||
if ( ! empty( $filtered_siblings ) ) {
|
||||
// Find the previous page in the sibling tree
|
||||
$key = array_key_first( $filtered_siblings );
|
||||
if ( 0 === $key ) {
|
||||
// It's the first page, can't do anything.
|
||||
$sibling = 0;
|
||||
} else {
|
||||
$previous_page = $siblings[ $key - 1 ];
|
||||
$sibling = $previous_page->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $sibling ) {
|
||||
$actions['spo-move-under-sibling'] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( $move_under_sibling_link ),
|
||||
sprintf(
|
||||
/* translators: %s: sibling page/post title */
|
||||
__( 'Move under %s', 'simple-page-ordering' ),
|
||||
get_the_title( $sibling )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page ordering ajax callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ajax_simple_page_ordering() {
|
||||
// check and make sure we have what we need
|
||||
if ( empty( $_POST['id'] ) || ( ! isset( $_POST['previd'] ) && ! isset( $_POST['nextid'] ) ) ) {
|
||||
die( - 1 );
|
||||
}
|
||||
|
||||
$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'simple-page-ordering-nonce' ) ) {
|
||||
die( -1 );
|
||||
}
|
||||
|
||||
$post_id = empty( $_POST['id'] ) ? false : (int) $_POST['id'];
|
||||
$previd = empty( $_POST['previd'] ) ? false : (int) $_POST['previd'];
|
||||
$nextid = empty( $_POST['nextid'] ) ? false : (int) $_POST['nextid'];
|
||||
$start = empty( $_POST['start'] ) ? 1 : (int) $_POST['start'];
|
||||
$excluded = empty( $_POST['excluded'] ) ? array( $_POST['id'] ) : array_filter( (array) json_decode( $_POST['excluded'] ), 'intval' );
|
||||
|
||||
// real post?
|
||||
$post = empty( $post_id ) ? false : get_post( (int) $post_id );
|
||||
if ( ! $post ) {
|
||||
die( - 1 );
|
||||
}
|
||||
|
||||
// does user have the right to manage these post objects?
|
||||
if ( ! self::check_edit_others_caps( $post->post_type ) ) {
|
||||
die( - 1 );
|
||||
}
|
||||
|
||||
$result = self::page_ordering( $post_id, $previd, $nextid, $start, $excluded );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
die( -1 );
|
||||
}
|
||||
|
||||
die( wp_json_encode( $result ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Page ordering reset ajax callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function ajax_reset_simple_page_ordering() {
|
||||
global $wpdb;
|
||||
|
||||
$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'simple-page-ordering-nonce' ) ) {
|
||||
die( -1 );
|
||||
}
|
||||
|
||||
// check and make sure we have what we need
|
||||
$post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : '';
|
||||
|
||||
if ( empty( $post_type ) ) {
|
||||
die( -1 );
|
||||
}
|
||||
|
||||
// does user have the right to manage these post objects?
|
||||
if ( ! self::check_edit_others_caps( $post_type ) ) {
|
||||
die( -1 );
|
||||
}
|
||||
|
||||
// reset the order of all posts of given post type
|
||||
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ), array( '%d' ), array( '%s' ) );
|
||||
|
||||
die( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Page ordering function
|
||||
*
|
||||
* @param int $post_id The post ID.
|
||||
* @param int $previd The previous post ID.
|
||||
* @param int $nextid The next post ID.
|
||||
* @param int $start The start index.
|
||||
* @param array $excluded Array of post IDs.
|
||||
*
|
||||
* @return object|WP_Error|"children"
|
||||
*/
|
||||
public static function page_ordering( $post_id, $previd, $nextid, $start, $excluded ) {
|
||||
// real post?
|
||||
$post = empty( $post_id ) ? false : get_post( (int) $post_id );
|
||||
if ( ! $post ) {
|
||||
return new WP_Error( 'invalid', __( 'Missing mandatory parameters.', 'simple-page-ordering' ) );
|
||||
}
|
||||
|
||||
// Badly written plug-in hooks for save post can break things.
|
||||
if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
|
||||
error_reporting( 0 ); // phpcs:ignore
|
||||
}
|
||||
|
||||
global $wp_version;
|
||||
|
||||
$previd = empty( $previd ) ? false : (int) $previd;
|
||||
$nextid = empty( $nextid ) ? false : (int) $nextid;
|
||||
$start = empty( $start ) ? 1 : (int) $start;
|
||||
$excluded = empty( $excluded ) ? array( $post_id ) : array_filter( (array) $excluded, 'intval' );
|
||||
|
||||
$new_pos = array(); // store new positions for ajax
|
||||
$return_data = new stdClass();
|
||||
|
||||
do_action( 'simple_page_ordering_pre_order_posts', $post, $start );
|
||||
|
||||
// attempt to get the intended parent... if either sibling has a matching parent ID, use that
|
||||
$parent_id = $post->post_parent;
|
||||
$next_post_parent = $nextid ? wp_get_post_parent_id( $nextid ) : false;
|
||||
|
||||
if ( $previd === $next_post_parent ) { // if the preceding post is the parent of the next post, move it inside
|
||||
$parent_id = $next_post_parent;
|
||||
} elseif ( $next_post_parent !== $parent_id ) { // otherwise, if the next post's parent isn't the same as our parent, we need to study
|
||||
$prev_post_parent = $previd ? wp_get_post_parent_id( $previd ) : false;
|
||||
if ( $prev_post_parent !== $parent_id ) { // if the previous post is not our parent now, make it so!
|
||||
$parent_id = ( false !== $prev_post_parent ) ? $prev_post_parent : $next_post_parent;
|
||||
}
|
||||
}
|
||||
|
||||
// if the next post's parent isn't our parent, it might as well be false (irrelevant to our query)
|
||||
if ( $next_post_parent !== $parent_id ) {
|
||||
$nextid = false;
|
||||
}
|
||||
|
||||
$max_sortable_posts = (int) apply_filters( 'simple_page_ordering_limit', 50 ); // should reliably be able to do about 50 at a time
|
||||
|
||||
if ( $max_sortable_posts < 5 ) { // don't be ridiculous!
|
||||
$max_sortable_posts = 50;
|
||||
}
|
||||
|
||||
// we need to handle all post stati, except trash (in case of custom stati)
|
||||
$post_stati = get_post_stati(
|
||||
array(
|
||||
'show_in_admin_all_list' => true,
|
||||
)
|
||||
);
|
||||
|
||||
$siblings_query = array(
|
||||
'depth' => 1,
|
||||
'posts_per_page' => $max_sortable_posts,
|
||||
'post_type' => $post->post_type,
|
||||
'post_status' => $post_stati,
|
||||
'post_parent' => $parent_id,
|
||||
'post__not_in' => $excluded, // phpcs:ignore
|
||||
'orderby' => array(
|
||||
'menu_order' => 'ASC',
|
||||
'title' => 'ASC',
|
||||
),
|
||||
'update_post_term_cache' => false,
|
||||
'update_post_meta_cache' => false,
|
||||
'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFiltersTrue
|
||||
'ignore_sticky_posts' => true,
|
||||
);
|
||||
|
||||
if ( version_compare( $wp_version, '4.0', '<' ) ) {
|
||||
$siblings_query['orderby'] = 'menu_order title';
|
||||
$siblings_query['order'] = 'ASC';
|
||||
}
|
||||
|
||||
$siblings = new WP_Query( $siblings_query ); // fetch all the siblings (relative ordering)
|
||||
|
||||
// don't waste overhead of revisions on a menu order change (especially since they can't *all* be rolled back at once)
|
||||
remove_action( 'post_updated', 'wp_save_post_revision' );
|
||||
|
||||
foreach ( $siblings->posts as $sibling ) :
|
||||
// don't handle the actual post
|
||||
if ( $sibling->ID === $post->ID ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is the post that comes after our repositioned post, set our repositioned post position and increment menu order
|
||||
if ( $nextid === $sibling->ID ) {
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $post->ID,
|
||||
'menu_order' => $start,
|
||||
'post_parent' => $parent_id,
|
||||
)
|
||||
);
|
||||
|
||||
$ancestors = get_post_ancestors( $post->ID );
|
||||
$new_pos[ $post->ID ] = array(
|
||||
'menu_order' => $start,
|
||||
'post_parent' => $parent_id,
|
||||
'depth' => count( $ancestors ),
|
||||
);
|
||||
|
||||
$start ++;
|
||||
}
|
||||
|
||||
// if repositioned post has been set, and new items are already in the right order, we can stop
|
||||
if ( isset( $new_pos[ $post->ID ] ) && $sibling->menu_order >= $start ) {
|
||||
$return_data->next = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// set the menu order of the current sibling and increment the menu order
|
||||
if ( $sibling->menu_order !== $start ) {
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $sibling->ID,
|
||||
'menu_order' => $start,
|
||||
)
|
||||
);
|
||||
}
|
||||
$new_pos[ $sibling->ID ] = $start;
|
||||
$start ++;
|
||||
|
||||
if ( ! $nextid && $previd === $sibling->ID ) {
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $post->ID,
|
||||
'menu_order' => $start,
|
||||
'post_parent' => $parent_id,
|
||||
)
|
||||
);
|
||||
|
||||
$ancestors = get_post_ancestors( $post->ID );
|
||||
$new_pos[ $post->ID ] = array(
|
||||
'menu_order' => $start,
|
||||
'post_parent' => $parent_id,
|
||||
'depth' => count( $ancestors ),
|
||||
);
|
||||
$start ++;
|
||||
}
|
||||
|
||||
endforeach;
|
||||
|
||||
// max per request
|
||||
if ( ! isset( $return_data->next ) && $siblings->max_num_pages > 1 ) {
|
||||
$return_data->next = array(
|
||||
'id' => $post->ID,
|
||||
'previd' => $previd,
|
||||
'nextid' => $nextid,
|
||||
'start' => $start,
|
||||
'excluded' => array_merge( array_keys( $new_pos ), $excluded ),
|
||||
);
|
||||
} else {
|
||||
$return_data->next = false;
|
||||
}
|
||||
|
||||
do_action( 'simple_page_ordering_ordered_posts', $post, $new_pos );
|
||||
|
||||
if ( ! $return_data->next ) {
|
||||
// if the moved post has children, we need to refresh the page (unless we're continuing)
|
||||
$children = new WP_Query(
|
||||
array(
|
||||
'posts_per_page' => 1,
|
||||
'post_type' => $post->post_type,
|
||||
'post_status' => $post_stati,
|
||||
'post_parent' => $post->ID,
|
||||
'fields' => 'ids',
|
||||
'update_post_term_cache' => false,
|
||||
'update_post_meta_cache' => false,
|
||||
'ignore_sticky' => true,
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $children->have_posts() ) {
|
||||
return 'children';
|
||||
}
|
||||
}
|
||||
|
||||
$return_data->new_pos = $new_pos;
|
||||
|
||||
return $return_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a sort by order link to the post actions
|
||||
*
|
||||
* @param array $views An array of available list table views.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function sort_by_order_link( $views ) {
|
||||
$class = ( get_query_var( 'orderby' ) === 'menu_order title' ) ? 'current' : '';
|
||||
$query_string = remove_query_arg( array( 'orderby', 'order' ) );
|
||||
if ( ! is_post_type_hierarchical( get_post_type() ) ) {
|
||||
$query_string = add_query_arg( 'orderby', 'menu_order title', $query_string );
|
||||
$query_string = add_query_arg( 'order', 'asc', $query_string );
|
||||
$query_string = add_query_arg( 'id', 'simple-page-ordering', $query_string );
|
||||
}
|
||||
$views['byorder'] = sprintf( '<a href="%s" class="%s">%s</a>', esc_url( $query_string ), $class, __( 'Sort by Order', 'simple-page-ordering' ) );
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the current user has the capability to "edit others" for a post type
|
||||
*
|
||||
* @param string $post_type Post type name
|
||||
*
|
||||
* @return bool True or false
|
||||
*/
|
||||
private static function check_edit_others_caps( $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
$edit_others_cap = empty( $post_type_object ) ? 'edit_others_' . $post_type . 's' : $post_type_object->cap->edit_others_posts;
|
||||
|
||||
return apply_filters( 'simple_page_ordering_edit_rights', current_user_can( $edit_others_cap ), $post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the API endpoint for sorting from the REST endpoint
|
||||
*/
|
||||
public static function rest_api_init() {
|
||||
register_rest_route(
|
||||
'simple-page-ordering/v1',
|
||||
'page_ordering',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => array( __CLASS__, 'rest_page_ordering' ),
|
||||
'permission_callback' => array( __CLASS__, 'rest_page_ordering_permissions_check' ),
|
||||
'args' => [
|
||||
'id' => [
|
||||
'description' => __( 'ID of item we want to sort', 'simple-page-ordering' ),
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
],
|
||||
'previd' => [
|
||||
'description' => __( 'ID of item we want to be previous to after sorting', 'simple-page-ordering' ),
|
||||
'required' => true,
|
||||
'type' => [ 'boolean', 'integer' ],
|
||||
],
|
||||
'nextid' => [
|
||||
'description' => __( 'ID of item we want to be next to after sorting', 'simple-page-ordering' ),
|
||||
'required' => true,
|
||||
'type' => [ 'boolean', 'integer' ],
|
||||
],
|
||||
'start' => [
|
||||
'default' => 1,
|
||||
'description' => __( 'Index we start with when sorting', 'simple-page-ordering' ),
|
||||
'required' => false,
|
||||
'type' => 'integer',
|
||||
],
|
||||
'exclude' => [
|
||||
'default' => [],
|
||||
'description' => __( 'Array of IDs we want to exclude', 'simple-page-ordering' ),
|
||||
'required' => false,
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to reorder content.
|
||||
*
|
||||
* This check ensures the current user making the request has
|
||||
* proper permissions to edit the item, that the post type
|
||||
* is allowed in REST requests and the post type is sortable.
|
||||
*
|
||||
* @since 2.5.1
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function rest_page_ordering_permissions_check( \WP_REST_Request $request ) {
|
||||
$post_id = $request->get_param( 'id' );
|
||||
|
||||
// Ensure we have a logged in user that can edit the item.
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_type = get_post_type( $post_id );
|
||||
$post_type_obj = get_post_type_object( $post_type );
|
||||
|
||||
// Ensure the post type is allowed in REST endpoints.
|
||||
if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure this post type is sortable.
|
||||
if ( ! self::is_post_type_sortable( $post_type ) ) {
|
||||
return new WP_Error( 'not_enabled', esc_html__( 'This post type is not sortable.', 'simple-page-ordering' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle REST page sorting
|
||||
*
|
||||
* @param WP_REST_Request $request The REST request object.
|
||||
*/
|
||||
public static function rest_page_ordering( \WP_REST_Request $request ) {
|
||||
$post_id = empty( $request->get_param( 'id' ) ) ? false : (int) $request->get_param( 'id' );
|
||||
$previd = empty( $request->get_param( 'previd' ) ) ? false : (int) $request->get_param( 'previd' );
|
||||
$nextid = empty( $request->get_param( 'nextid' ) ) ? false : (int) $request->get_param( 'nextid' );
|
||||
$start = empty( $request->get_param( 'start' ) ) ? 1 : (int) $request->get_param( 'start' );
|
||||
$excluded = empty( $request->get_param( 'excluded' ) ) ? array( $request->get_param( 'id' ) ) : array_filter( (array) json_decode( $request->get_param( 'excluded' ) ), 'intval' );
|
||||
|
||||
// Check and make sure we have what we need.
|
||||
if ( false === $post_id || ( false === $previd && false === $nextid ) ) {
|
||||
return new WP_Error( 'invalid', __( 'Missing mandatory parameters.', 'simple-page-ordering' ) );
|
||||
}
|
||||
|
||||
$page_ordering = self::page_ordering( $post_id, $previd, $nextid, $start, $excluded );
|
||||
|
||||
if ( is_wp_error( $page_ordering ) ) {
|
||||
return $page_ordering;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'status' => 200,
|
||||
'response' => 'success',
|
||||
'body_response' => $page_ordering,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Simple_Page_Ordering::get_instance();
|
||||
|
||||
endif;
|
||||
@@ -0,0 +1 @@
|
||||
.wp-list-table .ui-sortable tr{cursor:move}.wp-list-table .spo-updating tr,.wp-list-table .ui-sortable tr.inline-editor{cursor:default}.wp-list-table .ui-sortable-placeholder{background:#f1f1f1;outline:1px dashed #bbb;visibility:visible!important}.wp-list-table .ui-sortable-helper{background-color:#fff;outline:1px solid #e1e1e1}.spo-updating-row .check-column{background-position:9px 9px;display:table-cell;float:none;margin:0}.spo-updating-row .check-column input{visibility:hidden}
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('jquery-ui-sortable', 'wp-html-entities'), 'version' => '445f49ebf38f83eb9552');
|
||||
@@ -0,0 +1 @@
|
||||
!function(){"use strict";var e=window.wp.htmlEntities;window["jquery-ui-sortable"];const t=jQuery(".wp-list-table tbody");function n(o){if("children"===o)return void window.location.reload();const i=jQuery.parseJSON(o),{new_pos:r}=i;for(const t in r){if("next"===t)continue;const n=document.getElementById(`inline_${t}`);if(null!==n&&r.hasOwnProperty(t)){const o=n.querySelector(".menu_order");if(void 0!==r[t].menu_order){null!==o&&(o.textContent=r[t].menu_order);const i=n.querySelector(".post_parent");null!==i&&(i.textContent=r[t].post_parent);let l=null;const a=n.querySelector(".post_title");null!==a&&(l=a.innerHTML);let s=0;for(;s<r[t].depth;)l=`— ${l}`,s++;const d=n.parentNode.querySelector(".row-title");null!==d&&null!==l&&(d.textContent=(0,e.decodeEntities)(l))}else null!==o&&(o.textContent=r[t])}}i.next?jQuery.post(window.ajaxurl,{action:"simple_page_ordering",id:i.next.id,previd:i.next.previd,nextid:i.next.nextid,start:i.next.start,_wpnonce:window.simple_page_ordering_localized_data._wpnonce,excluded:JSON.stringify(i.next.excluded)},n):(jQuery(".spo-updating-row").removeClass("spo-updating-row").find(".check-column").removeClass("spinner is-active"),t.removeClass("spo-updating").sortable("enable"))}t.sortable({items:"> tr",cursor:"move",axis:"y",containment:"table.widefat",cancel:"input, textarea, button, select, option, .inline-edit-row",distance:2,opacity:.8,tolerance:"pointer",create(){jQuery(document).keydown((function(e){const n=e.key||e.keyCode;"Escape"!==n&&"Esc"!==n&&27!==n||(t.sortable("option","preventUpdate",!0),t.sortable("cancel"))}))},start(e,t){"undefined"!==typeof inlineEditPost&&inlineEditPost.revert(),t.placeholder.height(t.item.height()),t.placeholder.empty()},helper(e,t){const n=t.children();for(let e=0;e<n.length;e++){const t=jQuery(n[e]);t.width(t.width())}return t},stop(e,n){t.sortable("option","preventUpdate")&&t.sortable("option","preventUpdate",!1),n.item.children().css("width","")},update(e,o){if(t.sortable("option","preventUpdate"))return void t.sortable("option","preventUpdate",!1);t.sortable("disable").addClass("spo-updating"),o.item.addClass("spo-updating-row"),o.item.find(".check-column").addClass("spinner is-active");const i=o.item[0].id.substr(5);let r=!1;const l=o.item.prev();l.length>0&&(r=l.attr("id").substr(5));let a=!1;const s=o.item.next();s.length>0&&(a=s.attr("id").substr(5)),jQuery.post(window.ajaxurl,{action:"simple_page_ordering",id:i,previd:r,nextid:a,_wpnonce:window.simple_page_ordering_localized_data._wpnonce},n);const d=document.querySelectorAll("tr.iedit");let p=d.length;for(;p--;)p%2===0?jQuery(d[p]).addClass("alternate"):jQuery(d[p]).removeClass("alternate")}}),jQuery((function(){jQuery("#simple-page-ordering-reset").on("click",(function(e){e.preventDefault();const t=jQuery(this).data("posttype");window.confirm(window.simple_page_ordering_localized_data.confirmation_msg)&&jQuery.post(window.ajaxurl,{action:"reset_simple_page_ordering",post_type:t,_wpnonce:window.simple_page_ordering_localized_data._wpnonce},(function(){window.location.reload()}))}))}))}();
|
||||
@@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2025 10up
|
||||
# This file is distributed under the GPLv2 or later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Simple Page Ordering 2.7.4\n"
|
||||
"Report-Msgid-Bugs-To: "
|
||||
"https://wordpress.org/support/plugin/simple-page-ordering\n"
|
||||
"POT-Creation-Date: 2025-05-19 15:00:02+00:00\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2025-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"X-Generator: node-wp-i18n 1.2.7\n"
|
||||
|
||||
#: class-simple-page-ordering.php:78 class-simple-page-ordering.php:124
|
||||
msgid "You are not allowed to edit this item."
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:343
|
||||
#. translators: %1$s is replaced with the post type name
|
||||
msgid "Are you sure you want to reset the ordering of the \"%1$s\" post type?"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:354
|
||||
msgid ""
|
||||
"It looks like you are using a development copy of <strong>Simple Page "
|
||||
"Ordering</strong>. Please run <code>npm i; npm run build</code> to create "
|
||||
"assets."
|
||||
msgstr ""
|
||||
|
||||
#. Plugin Name of the plugin/theme
|
||||
msgid "Simple Page Ordering"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:376
|
||||
msgid ""
|
||||
"To reposition an item, simply drag and drop the row by \"clicking and "
|
||||
"holding\" it anywhere (outside of the links and form controls) and moving "
|
||||
"it to its new position."
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:379
|
||||
#. translators: %1$s is replaced with the post type name
|
||||
msgid "Reset %1$s order"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:430
|
||||
#. translators: %s: parent page/post title
|
||||
msgid "Move out from under %s"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:465
|
||||
#. translators: %s: sibling page/post title
|
||||
msgid "Move under %s"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:564 class-simple-page-ordering.php:879
|
||||
msgid "Missing mandatory parameters."
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:762
|
||||
msgid "Sort by Order"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:794
|
||||
msgid "ID of item we want to sort"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:800
|
||||
msgid "ID of item we want to be previous to after sorting"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:805
|
||||
msgid "ID of item we want to be next to after sorting"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:811
|
||||
msgid "Index we start with when sorting"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:817
|
||||
msgid "Array of IDs we want to exclude"
|
||||
msgstr ""
|
||||
|
||||
#: class-simple-page-ordering.php:859
|
||||
msgid "This post type is not sortable."
|
||||
msgstr ""
|
||||
|
||||
#. Plugin URI of the plugin/theme
|
||||
msgid "http://10up.com/plugins/simple-page-ordering-wordpress/"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin/theme
|
||||
msgid ""
|
||||
"Order your pages and hierarchical post types using drag and drop on the "
|
||||
"built in page list. For further instructions, open the \"Help\" tab on the "
|
||||
"Pages screen."
|
||||
msgstr ""
|
||||
|
||||
#. Author of the plugin/theme
|
||||
msgid "10up"
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin/theme
|
||||
msgid "https://10up.com"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,221 @@
|
||||
=== Simple Page Ordering ===
|
||||
Contributors: 10up, jakemgold, welcher, helen, thinkoomph, jeffpaul
|
||||
Donate link: http://10up.com/plugins/simple-page-ordering-wordpress/
|
||||
Tags: order, re-order, ordering, page, menu order
|
||||
Tested up to: 6.8
|
||||
Stable tag: 2.7.4
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Order your pages and other custom post types that support "page-attributes" with drag and drop right from the standard page list.
|
||||
|
||||
== Description ==
|
||||
|
||||
Order your pages, hierarchical custom post types, or custom post types with "page-attributes" with drag and drop right from the built in page list.
|
||||
|
||||
Drag and drop the page into the desired position. No new admin menus pages, no clunky, bolted on user interfaces. Drag and drop on the page or post-type screen.
|
||||
|
||||
The plug-in is "capabilities aware" - only users with the ability to edit others' pages (editors and administrators) will be able to reorder content.
|
||||
|
||||
Integrated help is included: click the "help" tab at the top right of the screen.
|
||||
|
||||
Please note that the plug-in is not compatible with Internet Explorer 7 and earlier, due to limitations within those browsers.
|
||||
|
||||
=== Contributing ===
|
||||
|
||||
We'd love to have you join in on development over on [GitHub](https://github.com/10up/simple-page-ordering).
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Install either via the WordPress.org plugin directory, or by uploading the files to your server.
|
||||
1. Activate the plugin through the 'Plugins' menu in WordPress.
|
||||
1. Get to work reordering your content!
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Why can't I reorder my posts? =
|
||||
|
||||
Generic posts are not displayed by menu order - they're displayed by chronology. You can theoretically add menu ordering to posts in your code (theme functions.php, plug-in) by using:
|
||||
|
||||
`add_post_type_support( 'post', 'page-attributes' );`
|
||||
|
||||
= Can I make my custom post type take advantage of this plug-in? =
|
||||
|
||||
Yep. When you register the post type, include the `page-attributes` feature in the support list. This will add a `Sort by Order` option to the filter links above the drop downs. Once you sort by order, you can drag and drop the content.
|
||||
|
||||
`'supports' => array( 'title', 'editor', 'page-attributes' ),`
|
||||
|
||||
Alternatively, when you register the post type, set `hierarchical` to `true` - hierarchical post types natively order by menu order.
|
||||
|
||||
You can also take advantage of the `simple_page_ordering_is_sortable` filter, which passes the result of the default check and the post type name, to override default behavior.
|
||||
|
||||
= I want my non-hierarchical post type to be sortable. Help! =
|
||||
|
||||
See the previous two answers - just add `page-attributes` to the list of supported post type features.
|
||||
|
||||
= I reordered my posts, but the order didn't change on the front end of my site! =
|
||||
|
||||
This plug-in doesn't change any *behavior* on the front end, it simply changes the menu order stored in WordPress.
|
||||
|
||||
If you want a list of pages or custom post types to display in that defined order, you must change the post query's `orderby` parameter to `menu_order` (if it's not already).
|
||||
|
||||
= I reordered my content, it seemed to work, but when I refreshed, it went back to the old order! =
|
||||
|
||||
This most likely means the AJAX request - the server side code - failed after you dropped the content into the new position. Some shared hosts aggressively time out and limit AJAX requests. Version 2.0 batches these requests so you can try reducing the number of items it updates on each request using a filter in your theme's functions.php or a custom plug-in:
|
||||
|
||||
`add_filter( 'simple_page_ordering_limit', function($number) { return 5; } );`
|
||||
|
||||
Where 5 is the number of items to batch on each request (the default is 50). Note that this example uses PHP 5.3+ callback functions, so if you're still on PHP 5.2, you'll need to add a traditional callback.
|
||||
|
||||
= What happened to the drop down box that let me change the number of items on each page in the admin? =
|
||||
|
||||
This feature is already built into WordPress natively, but a bit tucked away. If you pull down the "Screen Options" tab up top (on the list of post objects) there's a field where you can specify the number of items to show per page. I decided it was not a very good practice to duplicate this.
|
||||
|
||||
= How can I modify sortable post types? =
|
||||
|
||||
Post types can be included or excluded by using the `simple_page_ordering_is_sortable` filter.
|
||||
|
||||
For example, to exclude the `excluded_post_type` custom post type, add the following snippet in the theme function file or custom plugin:
|
||||
|
||||
`
|
||||
add_filter( 'simple_page_ordering_is_sortable', function( $sortable, $post_type ) {
|
||||
if ( 'excluded_post_type' === $post_type ) {
|
||||
return false;
|
||||
}
|
||||
return $sortable;
|
||||
}, 10, 2 );
|
||||
`
|
||||
|
||||
To include the `include_post_type` custom post type, add the following snippet in the theme function file or custom plugin:
|
||||
|
||||
`
|
||||
add_filter( 'simple_page_ordering_is_sortable', function( $sortable, $post_type ) {
|
||||
if ( 'include_post_type' === $post_type ) {
|
||||
return true;
|
||||
}
|
||||
return $sortable;
|
||||
}, 10, 2 );
|
||||
`
|
||||
|
||||
= Can I use REST to order posts? =
|
||||
|
||||
Yes. The plugin registers the REST endpoint `simple-page-ordering/v1/page_ordering`.
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Dragging the page to its new position
|
||||
1. Processing indicator
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 2.7.4 - 2025-05-19 =
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.8 (props [@jeffpaul](https://github.com/jeffpaul) via [#239](https://github.com/10up/simple-page-ordering/pull/239), [#240](https://github.com/10up/simple-page-ordering/pull/240)).
|
||||
* **Changed:** Bump WordPress minimum from 6.5 to 6.6 (props [@jeffpaul](https://github.com/jeffpaul) via [#239](https://github.com/10up/simple-page-ordering/pull/239)).
|
||||
* **Security:** Bump `tar-fs` from 2.1.1 to 3.0.8 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#238](https://github.com/10up/simple-page-ordering/pull/238)).
|
||||
* **Security:** Bump `@babel/runtime` from 7.23.9 to 7.27.0 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#237](https://github.com/10up/simple-page-ordering/pull/237)).
|
||||
|
||||
= 2.7.3 - 2025-03-11 =
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.7 (props [@sudip-md](https://github.com/sudip-md), [@godleman](https://github.com/godleman), [@jeffpaul](https://github.com/jeffpaul) via [#230](https://github.com/10up/simple-page-ordering/pull/230), [#231](https://github.com/10up/simple-page-ordering/pull/231)).
|
||||
* **Changed:** Bump WordPress minimum from 6.4 to 6.5 (props [@sudip-md](https://github.com/sudip-md), [@godleman](https://github.com/godleman), [@jeffpaul](https://github.com/jeffpaul) via [#230](https://github.com/10up/simple-page-ordering/pull/230), [#231](https://github.com/10up/simple-page-ordering/pull/231)).
|
||||
* **Security:** Bump `webpack` from 5.90.0 to 5.94.0 (props [@dependabot](https://github.com/apps/dependabot), [@faisal-alvi](https://github.com/faisal-alvi) via [#224](https://github.com/10up/simple-page-ordering/pull/224)).
|
||||
* **Security:** Bump `serve-static` from 1.15.0 to 1.16.2 and `express` from 4.19.2 to 4.21.0 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#226](https://github.com/10up/simple-page-ordering/pull/226)).
|
||||
* **Security:** Bump `cookie` from 0.6.0 to 0.7.1 and `express` from 4.21.0 to 4.21.1 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#228](https://github.com/10up/simple-page-ordering/pull/228)).
|
||||
* **Security:** Bump `serialize-javascript` from 6.0.0 to 6.0.2 and `mocha` from 10.2.0 to 11.1.0 (props [@dependabot](https://github.com/apps/dependabot), [@dkotter](https://github.com/dkotter) via [#232](https://github.com/10up/simple-page-ordering/pull/232)).
|
||||
|
||||
= 2.7.2 - 2024-08-21 =
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.6 (props [@sudip-md](https://github.com/sudip-md), [@ankitguptaindia](https://github.com/ankitguptaindia), [@jeffpaul](https://github.com/jeffpaul) via [#216](https://github.com/10up/simple-page-ordering/pull/216), [#217](https://github.com/10up/simple-page-ordering/pull/217)).
|
||||
* **Changed:** Bump WordPress minimum from 6.3 to 6.4 (props [@sudip-md](https://github.com/sudip-md), [@ankitguptaindia](https://github.com/ankitguptaindia), [@jeffpaul](https://github.com/jeffpaul) via [#216](https://github.com/10up/simple-page-ordering/pull/216)).
|
||||
* **Fixed:** Issue where an `Undefined array key` error occurs when a post parent ID does not exist in the `$children_pages` array (props [@xDehy](https://github.com/xDehy), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#219](https://github.com/10up/simple-page-ordering/pull/219)).
|
||||
* **Security:** Bump `express` from 4.18.2 to 4.19.2, `follow-redirects` from 1.15.5 to 1.15.6, `postcss` from 7.0.39 to 8.4.33, `10up-toolkit` from 5.2.3 to 6.1.0 and `webpack-dev-middleware` from 5.3.3 to 5.3.4 (props [@dependabot](https://github.com/apps/dependabot), [@faisal-alvi](https://github.com/faisal-alvi) via [#208](https://github.com/10up/simple-page-ordering/pull/208)).
|
||||
* **Security:** Bump `braces` from 3.0.2 to 3.0.3 and `ws` from 7.5.9 to 7.5.10 (props [@dependabot](https://github.com/apps/dependabot), [@iamdharmesh](https://github.com/iamdharmesh) via [#214](https://github.com/10up/simple-page-ordering/pull/214)).
|
||||
|
||||
= 2.7.1 - 2024-06-03 =
|
||||
* **Added:** The missing Text Domain (props [@alexclassroom](https://github.com/alexclassroom), [@dkotter](https://github.com/dkotter) via [#199](https://github.com/10up/simple-page-ordering/pull/199)).
|
||||
* **Added:** The "Testing" section in the `CONTRIBUTING.md` file (props [@kmgalanakis](https://github.com/kmgalanakis), [@jeffpaul](https://github.com/jeffpaul) via [#202](https://github.com/10up/simple-page-ordering/pull/202)).
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.5 (props [@jeffpaul](https://github.com/jeffpaul), [@sudip-md](https://github.com/sudip-md), [@dkotter](https://github.com/dkotter) via [#201](https://github.com/10up/simple-page-ordering/pull/201)).
|
||||
* **Changed:** Bump WordPress minimum from 5.7 to 6.3 (props [@jeffpaul](https://github.com/jeffpaul), [@sudip-md](https://github.com/sudip-md), [@dkotter](https://github.com/dkotter) via [#201](https://github.com/10up/simple-page-ordering/pull/201)).
|
||||
* **Fixed:** Fixed error in call to `get_walked_pages` for custom post types (props [@sissibieber](https://github.com/sissibieber), [@zachgibb](https://github.com/zachgibb), [@peterwilsoncc](https://github.com/peterwilsoncc), [@mjot](https://github.com/mjot), [@jeffpaul](https://github.com/jeffpaul) via [#200](https://github.com/10up/simple-page-ordering/pull/200)).
|
||||
|
||||
= 2.7.0 - 2024-04-03 =
|
||||
* **Added:** Ability to modify the page hierarchy (props [@amityweb](https://github.com/amityweb), [@jeffpaul](https://github.com/jeffpaul), [@peterwilsoncc](https://github.com/peterwilsoncc), [@shannonmfisher](https://github.com/shannonmfisher), [@ankitguptaindia](https://github.com/ankitguptaindia), [@faisal-alvi](https://github.com/faisal-alvi) via [#172](https://github.com/10up/simple-page-ordering/pull/172)).
|
||||
* **Added:** Support for the WordPress.org plugin preview (props [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul) via [#183](https://github.com/10up/simple-page-ordering/pull/183)).
|
||||
* **Changed:** Replaced custom HTML entity decoding code in favor of the `@wordpress/html-entities` package (props [@helen](https://github.com/helen), [@jeffpaul](https://github.com/jeffpaul), [@psorensen](https://github.com/psorensen), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#189](https://github.com/10up/simple-page-ordering/pull/189)).
|
||||
* **Changed:** Bump minimum `node` version from `16` to `20` and clean up NPM dependencies (props [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter) via [#188](https://github.com/10up/simple-page-ordering/pull/188)).
|
||||
* **Changed:** Updated CODEOWNERS (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#186](https://github.com/10up/simple-page-ordering/pull/186)).
|
||||
* **Changed:** Upgrade the download-artifact from v3 to v4 (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#194](https://github.com/10up/simple-page-ordering/pull/194)).
|
||||
* **Changed:** Replaced [lee-dohm/no-response](https://github.com/lee-dohm/no-response) with [actions/stale](https://github.com/actions/stale) to help with closing no-response/stale issues (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [@195](https://github.com/10up/simple-page-ordering/pull/195)).
|
||||
* **Changed:** Disabled auto sync pull requests with target branch (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#196](https://github.com/10up/simple-page-ordering/pull/196)).
|
||||
* **Security:** Bump `@babel/traverse` from `7.20.12` to `7.23.6` (props [@dependabot](https://github.com/apps/dependabot), [@ravinderk](https://github.com/ravinderk) via [#184](https://github.com/10up/simple-page-ordering/pull/184)).
|
||||
* **Security:** Bump `sharp` from `0.30.7` to `0.32.1` (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#182](https://github.com/10up/simple-page-ordering/pull/184)).
|
||||
* **Security:** Bump `10up-toolkit` from `4.3.1` to `5.2.2` (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#182](https://github.com/10up/simple-page-ordering/pull/182)).
|
||||
|
||||
= 2.6.3 - 2023-11-09 =
|
||||
* **Fix:** Deployment issue with version 2.6.2 (props [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter) via [#181](https://github.com/10up/simple-page-ordering/pull/181))
|
||||
|
||||
= 2.6.2 - 2023-11-09 =
|
||||
* **Changed:** Update the `wp-compat-validation-tool` composer package to version `0.3.1` which properly removes the `.git` directory (props [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter) via [#180](https://github.com/10up/simple-page-ordering/pull/180)).
|
||||
|
||||
= 2.6.1 - 2023-11-08 =
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.4 (props [@jeffpaul](https://github.com/jeffpaul), [@qasumitbagthariya](https://github.com/qasumitbagthariya), [@faisal-alvi](https://github.com/faisal-alvi) via [#177](https://github.com/10up/simple-page-ordering/pull/177)).
|
||||
* **Changed:** Remove the .git directory from the `10up-lib` directory (props [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter) via [#175](https://github.com/10up/simple-page-ordering/pull/175)).
|
||||
* **Security:** Bumps `@babel/traverse` from `7.20.12` to `7.23.2` (props [@peterwilsoncc](https://github.com/peterwilsoncc) via [#170](https://github.com/10up/simple-page-ordering/pull/170)).
|
||||
|
||||
= 2.6.0 - 2023-10-25 =
|
||||
* **Added:** A check for minimum required PHP version before loading the plugin (props [@vikrampm1](https://github.com/vikrampm1), [@kmgalanakis](https://github.com/kmgalanakis), [@Sidsector9](https://github.com/Sidsector9) via [#153](https://github.com/10up/simple-page-ordering/pull/153)).
|
||||
* **Added:** Mochawesome reporter added for Cypress test report (props [@iamdharmesh](https://github.com/iamdharmesh), [@jayedul](https://github.com/jayedul), [@faisal-alvi](https://github.com/faisal-alvi) via [#146](https://github.com/10up/simple-page-ordering/pull/146)).
|
||||
* **Added:** Repo Automator GitHub Action (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#158](https://github.com/10up/simple-page-ordering/pull/158)).
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.3 (props [@jeffpaul](https://github.com/jeffpaul), [@QAharshalkadu](https://github.com/QAharshalkadu)).
|
||||
* **Changed:** Slightly change how some of our text is translated, passing in the post type (props [@dkotter](https://github.com/dkotter), [@ravinderk](https://github.com/ravinderk) via [#149](https://github.com/10up/simple-page-ordering/pull/149)).
|
||||
* **Changed:** Updates the Dependency Review GitHub Action to check for GPL-compatible licenses (props [@jeffpaul](https://github.com/jeffpaul), [@Sidsector9](https://github.com/Sidsector9) via [#147](https://github.com/10up/simple-page-ordering/pull/147)).
|
||||
* **Changed:** Updated 10up Cypress Utilities to 0.2.0 (props [@iamdharmesh](https://github.com/iamdharmesh), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#160](https://github.com/10up/simple-page-ordering/pull/160)).
|
||||
* **Fixed:** The "Are you sure..." popup text to be translatable (props [@kebbet](https://github.com/kebbet), [@bmarshall511](https://github.com/bmarshall511), [@dkotter](https://github.com/dkotter) via [#148](https://github.com/10up/simple-page-ordering/pull/148)).
|
||||
* **Fixed:** Remove code that was no longer needed (props [@dkotter](https://github.com/dkotter), [@ravinderk](https://github.com/ravinderk) via [#149](https://github.com/10up/simple-page-ordering/pull/149)).
|
||||
* **Fixed:** Add missing escaping (props [@dkotter](https://github.com/dkotter), [@ravinderk](https://github.com/ravinderk) via [#149](https://github.com/10up/simple-page-ordering/pull/149)).
|
||||
* **Fixed:** Fatal error following the introduction of a namespace (props [@peterwilsoncc](https://github.com/peterwilsoncc), [@iamdharmesh](https://github.com/iamdharmesh), [@dkotter](https://github.com/dkotter) via [#162](https://github.com/10up/simple-page-ordering/pull/162)).
|
||||
* **Fixed:** Hidden pagination in admin screen when Sort by Order is clicked (props [@tlovett1](https://github.com/tlovett1), [@dkotter](https://github.com/dkotter), [@Sidsector9](https://github.com/Sidsector9) via [#165](https://github.com/10up/simple-page-ordering/pull/165)).
|
||||
* **Fixed:** Fatal errors on PHP 5.6 (props [@peterwilsoncc](https://github.com/peterwilsoncc), [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh) via [#166](https://github.com/10up/simple-page-ordering/pull/166)).
|
||||
* **Security:** Bump `word-wrap` from 1.2.3 to 1.2.4 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#](https://github.com/10up/simple-page-ordering/pull/151)).
|
||||
* **Security:** Bump `tough-cookie` from 4.1.2 to 4.1.3 (props [@faisal-alvi](https://github.com/faisal-alvi) via [#152](https://github.com/10up/simple-page-ordering/pull/152)).
|
||||
* **Security:** Bump `node-sass` from 7.0.3 to 9.0.0 (props [@faisal-alvi](https://github.com/faisal-alvi) via [#152](https://github.com/10up/simple-page-ordering/pull/152)).
|
||||
* **Security:** Bump `@cypress/request` from 2.88.11 to 3.0.0 to resolve SSRF issue (props [@faisal-alvi](https://github.com/faisal-alvi), [@iamdharmesh](https://github.com/iamdharmesh), [@peterwilsoncc](https://github.com/peterwilsoncc), [@dkotter](https://github.com/dkotter) via [#152](https://github.com/10up/simple-page-ordering/pull/152), [#160](https://github.com/10up/simple-page-ordering/pull/160)).
|
||||
|
||||
= 2.5.1 - 2023-05-16 =
|
||||
* **Security:** Ensure we check user permissions properly in our REST endpoint (props [@mikhail-net](https://github.com/mikhail-net), [@dkotter](https://github.com/dkotter), [@peterwilsoncc](https://github.com/peterwilsoncc)).
|
||||
|
||||
= 2.5.0 - 2023-04-18 =
|
||||
**Note that this release bumps the minimum required versions of PHP from 5.6 to 7.4 and WordPress from 3.8 to 5.7.**
|
||||
|
||||
* **Added:** Feature to reset page order (props [@pattonwebz](https://github.com/pattonwebz), [@ruscoe](https://github.com/ruscoe), [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter)) via [#129](https://github.com/10up/simple-page-ordering/pull/129).
|
||||
* **Added** JS linting GitHub Action (props [@Sidsector9](https://github.com/Sidsector9), [@kmgalanakis](https://github.com/kmgalanakis), [@peterwilsoncc](https://github.com/peterwilsoncc)) via [#136](https://github.com/10up/simple-page-ordering/pull/136).
|
||||
* **Changed:** Bump minimum PHP version to 7.4 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@ravinderk](https://github.com/ravinderk), [@cadic](https://github.com/cadic)) via [#111](https://github.com/10up/simple-page-ordering/pull/111).
|
||||
* **Changed:** Bump minimum required WordPress version from 3.8 to 5.7 (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@ravinderk](https://github.com/ravinderk), [@cadic](https://github.com/cadic)) via [#111](https://github.com/10up/simple-page-ordering/pull/111).
|
||||
* **Changed:** Bump WordPress "tested up to" version 6.2 (props [@av3nger](https://github.com/av3nger) via [#138](https://github.com/10up/simple-page-ordering/pull/138)).
|
||||
* **Changed:** Run E2E tests on the zip generated by "Build release zip" action (props [@iamdharmesh](https://github.com/iamdharmesh), [@jayedul](https://github.com/jayedul), [@dkotter](https://github.com/dkotter)) via [#135](https://github.com/10up/simple-page-ordering/pull/135).
|
||||
* **Fixed:** Removed a typo in a REST response message (props [@ruscoe](https://github.com/ruscoe), [@Sidsector9](https://github.com/Sidsector9)) via [#133](https://github.com/10up/simple-page-ordering/pull/133).
|
||||
* **Security:** Removed vulnerable NPM dependencies (props [@vikrampm1](https://github.com/vikrampm1), [@Sidsector9](https://github.com/Sidsector9), [@ravinderk](https://github.com/ravinderk), [@cadic](https://github.com/cadic)) via [#111](https://github.com/10up/simple-page-ordering/pull/111).
|
||||
* **Security:** Bump `cypress` from `9.5.2` to `11.2.0` (props [@iamdharmesh](https://github.com/iamdharmesh), [@jayedul](https://github.com/jayedul), [@Sidsector9](https://github.com/Sidsector9)) via [#120](https://github.com/10up/simple-page-ordering/pull/120).
|
||||
* **Security:** Bump `http-cache-semantics` from 4.1.0 to 4.1.1 (props [@peterwilsoncc](https://github.com/peterwilsoncc) via [#131](https://github.com/10up/simple-page-ordering/pull/131)).
|
||||
* **Security:** Bump `webpack` from `5.75.0` to `5.76.1` (props [@Sidsector9](https://github.com/Sidsector9)) via [#134](https://github.com/10up/simple-page-ordering/pull/134).
|
||||
|
||||
= 2.4.4 - 2023-01-10 =
|
||||
* **Changed:** Update Support Level from `Active` to `Stable` (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#123](https://github.com/10up/simple-page-ordering/pull/123)).
|
||||
* **Changed:** Bump WordPress "tested up to" version to 6.1 (props [@jayedul](https://github.com/jayedul), [@dkotter](https://github.com/dkotter) via [#118](https://github.com/10up/simple-page-ordering/pull/118)).
|
||||
* **Changed:** Update the "Build release zip" workflow to use 10up's `build-zip` action (props [@iamdharmesh](https://github.com/iamdharmesh), [@faisal-alvi](https://github.com/faisal-alvi), [@dkotter](https://github.com/dkotter) via [#119](https://github.com/10up/simple-page-ordering/pull/119)).
|
||||
* **Security:** Bump `loader-utils` from 2.0.3 to 2.0.4 (props [@dependabot](https://github.com/apps/dependabot) via [#115](https://github.com/10up/simple-page-ordering/pull/115)).
|
||||
* **Security:** Bump `simple-git` from 3.12.0 to 3.15.1 (props [@dependabot](https://github.com/apps/dependabot) via [#121](https://github.com/10up/simple-page-ordering/pull/121)).
|
||||
|
||||
[View historical changelog details here](https://github.com/10up/simple-page-ordering/blob/develop/CHANGELOG.md).
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 2.7.4 =
|
||||
This release bumps the minimum required version of WordPress from 6.5 to 6.6.
|
||||
|
||||
= 2.7.3 =
|
||||
This release bumps the minimum required version of WordPress from 6.4 to 6.5.
|
||||
|
||||
= 2.7.2 =
|
||||
This release bumps the minimum required version of WordPress from 6.3 to 6.4.
|
||||
|
||||
= 2.5.0 =
|
||||
This release bumps the minimum required versions of PHP from 5.6 to 7.4 and WordPress from 3.8 to 5.7.
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Simple Page Ordering
|
||||
* Plugin URI: http://10up.com/plugins/simple-page-ordering-wordpress/
|
||||
* Description: Order your pages and hierarchical post types using drag and drop on the built in page list. For further instructions, open the "Help" tab on the Pages screen.
|
||||
* Version: 2.7.4
|
||||
* Requires at least: 6.6
|
||||
* Requires PHP: 7.4
|
||||
* Author: 10up
|
||||
* Author URI: https://10up.com
|
||||
* License: GPLv2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: simple-page-ordering
|
||||
*
|
||||
* @package simple-page-ordering
|
||||
*/
|
||||
|
||||
if ( ! is_readable( __DIR__ . '/10up-lib/wp-compat-validation-tool/src/Validator.php' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Useful global constants.
|
||||
require_once '10up-lib/wp-compat-validation-tool/src/Validator.php';
|
||||
|
||||
$compat_checker = new \Simple_Page_Ordering_Validator\Validator();
|
||||
$compat_checker
|
||||
->set_plugin_name( 'Simple Page Ordering' )
|
||||
->set_php_min_required_version( '7.4' );
|
||||
|
||||
if ( ! $compat_checker->is_plugin_compatible() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once 'class-simple-page-ordering.php';
|
||||
@@ -1,5 +1,9 @@
|
||||
== Changelog ==
|
||||
|
||||
= 6.4.0 (2025-12-08) =
|
||||
* `[Security]` Added permission check to ensure the user can edit the post before manually converting an image in the Media Library (CVE-2025-13750)
|
||||
* `[Security]` Added stricter permission checks to REST API endpoints for authenticated users
|
||||
|
||||
= 6.3.2 (2025-11-24) =
|
||||
* `[Added]` Support for WordPress 6.9
|
||||
|
||||
@@ -9,7 +13,6 @@
|
||||
|
||||
= 6.3.0 (2025-10-28) =
|
||||
* `[Changed]` Message about rewrites_uploads_blocked server configuration error
|
||||
* `[Added]` Support for WordPress 6.9
|
||||
|
||||
= 6.2.4 (2025-09-09) =
|
||||
* `[Fixed]` Deprecated notice for implicitly nullable parameter when converting images
|
||||
@@ -417,7 +420,7 @@
|
||||
* `[Changed]` Message after successfully completing conversion of images
|
||||
|
||||
= 4.0.3 (2021-12-20) =
|
||||
* `[Security]` Added URL validation for Pass Thru loading mode ([CVE-2021-25074](https://wpscan.com/vulnerability/f3c0a155-9563-4533-97d4-03b9bac83164/))
|
||||
* `[Security]` Added URL validation for Pass Thru loading mode (CVE-2021-25074)
|
||||
* `[Fixed]` Auto-conversion images with unsupported extensions when uploading files
|
||||
* `[Fixed]` Generating directory paths when ABSPATH constant is invalid
|
||||
|
||||
@@ -688,7 +691,7 @@
|
||||
* `[Changed]` Limits of maximum execution time
|
||||
|
||||
= 1.0.3 (2019-06-26) =
|
||||
* `[Security]` Fixed CSRF vulnerability when saving plugin settings ([CVE-2019-15834](https://wpscan.com/vulnerability/65483794-f22f-41c7-b286-fd70c38ae160/))
|
||||
* `[Security]` Fixed CSRF vulnerability when saving plugin settings (CVE-2019-15834)
|
||||
|
||||
= 1.0.2 (2019-06-25) =
|
||||
* `[Changed]` Error messages
|
||||
|
||||
@@ -5,7 +5,7 @@ Tags: convert webp, webp, optimize images, image optimization, compress images
|
||||
Requires at least: 4.9
|
||||
Tested up to: 6.9
|
||||
Requires PHP: 7.1
|
||||
Stable tag: 6.3.2
|
||||
Stable tag: 6.4.0
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
@@ -309,6 +309,10 @@ Current list of supported CDN servers:
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 6.4.0 (2025-12-08) =
|
||||
* `[Security]` Added permission check to ensure the user can edit the post before manually converting an image in the Media Library (CVE-2025-13750)
|
||||
* `[Security]` Added stricter permission checks to REST API endpoints for authenticated users
|
||||
|
||||
= 6.3.2 (2025-11-24) =
|
||||
* `[Added]` Support for WordPress 6.9
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class CronConversionEndpoint extends EndpointAbstract {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function is_valid_request( string $request_nonce ): bool {
|
||||
public function is_valid_request( string $request_nonce, array $request_params ): bool {
|
||||
$nonce_value = $this->cron_status_manager->get_conversion_request_id();
|
||||
if ( $nonce_value === null ) {
|
||||
return false;
|
||||
|
||||
@@ -12,8 +12,9 @@ abstract class EndpointAbstract implements EndpointInterface {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function is_valid_request( string $request_nonce ): bool {
|
||||
return (bool) wp_verify_nonce( $request_nonce, 'wp_rest' );
|
||||
public function is_valid_request( string $request_nonce, array $request_params ): bool {
|
||||
return ( ( wp_verify_nonce( $request_nonce, 'wp_rest' ) !== false )
|
||||
&& current_user_can( 'manage_options' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,13 +43,14 @@ class EndpointIntegrator implements HookableInterface {
|
||||
'methods' => $this->endpoint_object->get_http_methods(),
|
||||
'permission_callback' => function ( \WP_REST_Request $request ) {
|
||||
$header_value = $request->get_header( $this->endpoint_object->get_route_nonce_header() );
|
||||
$params = $request->get_params();
|
||||
if ( $header_value === null ) {
|
||||
return new \WP_Error(
|
||||
'webpc_rest_token_not_found',
|
||||
__( 'Sorry, you do not have permission to do that.', 'webp-converter-for-media' ),
|
||||
[ 'status' => rest_authorization_required_code() ]
|
||||
);
|
||||
} elseif ( ! $this->endpoint_object->is_valid_request( $header_value ) ) {
|
||||
} elseif ( ! $this->endpoint_object->is_valid_request( $header_value, $params ) ) {
|
||||
return new \WP_Error(
|
||||
'webpc_rest_token_invalid',
|
||||
__( 'Sorry, you do not have permission to do that.', 'webp-converter-for-media' ),
|
||||
|
||||
@@ -24,11 +24,12 @@ interface EndpointInterface {
|
||||
/**
|
||||
* Returns whether request can be executed.
|
||||
*
|
||||
* @param string $request_nonce .
|
||||
* @param string $request_nonce .
|
||||
* @param mixed[] $request_params .
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_request( string $request_nonce ): bool;
|
||||
public function is_valid_request( string $request_nonce, array $request_params ): bool;
|
||||
|
||||
/**
|
||||
* Returns list of params for endpoint.
|
||||
|
||||
@@ -21,6 +21,14 @@ class RegenerateAttachmentEndpoint extends EndpointAbstract {
|
||||
return \WP_REST_Server::CREATABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function is_valid_request( string $request_nonce, array $request_params ): bool {
|
||||
return ( ( wp_verify_nonce( $request_nonce, 'wp_rest' ) !== false )
|
||||
&& current_user_can( 'edit_post', $request_params['post_id'] ?? 0 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -12,10 +12,10 @@ use WebpConverter\Settings\Page\PageIntegrator;
|
||||
*/
|
||||
class BlackFridayNotice extends NoticeAbstract implements NoticeInterface {
|
||||
|
||||
const NOTICE_OPTION = 'webpc_notice_bf2025';
|
||||
const NOTICE_OPTION = 'webpc_notice_bf2026';
|
||||
const NOTICE_VIEW_PATH = 'components/notices/discount-coupon.php';
|
||||
const NOTICE_DATE_START = '2025-11-24';
|
||||
const NOTICE_DATE_END = '2025-12-01';
|
||||
const NOTICE_DATE_START = '2026-11-23';
|
||||
const NOTICE_DATE_END = '2026-11-30';
|
||||
|
||||
/**
|
||||
* @var PluginData
|
||||
@@ -83,9 +83,9 @@ class BlackFridayNotice extends NoticeAbstract implements NoticeInterface {
|
||||
return [
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'close_action' => self::NOTICE_OPTION,
|
||||
'coupon_code' => 'BF2025',
|
||||
'coupon_code' => 'BF2026',
|
||||
'discount_value' => '50%',
|
||||
'button_url' => 'https://url.mattplugins.com/converter-notice-bf2025-button-read',
|
||||
'button_url' => 'https://url.mattplugins.com/converter-notice-bf2026-button-read',
|
||||
'promotion_date' => self::NOTICE_DATE_END,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ class PluginSettingsManager {
|
||||
OptionsAccessManager::delete_option( 'webpc_notice_bf2022' );
|
||||
OptionsAccessManager::delete_option( 'webpc_notice_bf2023' );
|
||||
OptionsAccessManager::delete_option( 'webpc_notice_bf2024' );
|
||||
OptionsAccessManager::delete_option( 'webpc_notice_bf2025' );
|
||||
OptionsAccessManager::delete_option( 'webpc_notice_upgrade' );
|
||||
|
||||
OptionsAccessManager::delete_option( ErrorDetectorAggregator::ERRORS_CACHE_OPTION );
|
||||
|
||||
@@ -117,7 +117,7 @@ class MediaStatusViewer implements HookableInterface {
|
||||
* @internal
|
||||
*/
|
||||
public function print_table_column_value( string $column_name, int $post_id ) {
|
||||
if ( $column_name !== 'webpc_status' ) {
|
||||
if ( ( $column_name !== 'webpc_status' ) || ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'gbiorczyk/webp-converter-for-media',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'ce2723372b8386c8e62e0ba89fe02680a0c41fd2',
|
||||
'reference' => '0ae416ff99cbab871fb23fecc15d9ea6da468344',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -13,7 +13,7 @@
|
||||
'gbiorczyk/webp-converter-for-media' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'ce2723372b8386c8e62e0ba89fe02680a0c41fd2',
|
||||
'reference' => '0ae416ff99cbab871fb23fecc15d9ea6da468344',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
/**
|
||||
* Plugin Name: Converter for Media
|
||||
* Plugin URI: https://mattplugins.com/products/webp-converter-for-media-pro
|
||||
* Description: Speed up your website by using our WebP & AVIF Converter. Optimize images and serve WebP and AVIF images instead of standard formats!
|
||||
* Version: 6.3.2
|
||||
* Version: 6.4.0
|
||||
* Author: matt plugins
|
||||
* Author URI: https://url.mattplugins.com/converter-plugin-author-link
|
||||
* Text Domain: webp-converter-for-media
|
||||
@@ -17,5 +18,5 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
new WebpConverter\WebpConverter(
|
||||
new WebpConverter\PluginInfo( __FILE__, '6.3.2' )
|
||||
new WebpConverter\PluginInfo( __FILE__, '6.4.0' )
|
||||
);
|
||||
|
||||
@@ -249,6 +249,27 @@ class WPSEO_Admin_Asset_Manager {
|
||||
return wp_script_is( $this->prefix . $script );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of Elementor dependencies.
|
||||
*
|
||||
* @return array<string> The array of elementor dependencies.
|
||||
*/
|
||||
protected function get_elementor_dependencies() {
|
||||
$dependencies = [
|
||||
'backbone-marionette',
|
||||
'elementor-common-modules',
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
];
|
||||
// Conditionally add Elementor v2 dependency if available.
|
||||
if ( wp_script_is( 'elementor-v2-editor-app-bar', 'registered' ) ) {
|
||||
$dependencies[] = 'elementor-v2-editor-app-bar';
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scripts that need to be registered.
|
||||
*
|
||||
@@ -265,6 +286,7 @@ class WPSEO_Admin_Asset_Manager {
|
||||
'help-scout-beacon',
|
||||
'redirect-old-features-tab',
|
||||
];
|
||||
$elementor_dependencies = $this->get_elementor_dependencies();
|
||||
$additional_dependencies = [
|
||||
'analysis-worker' => [ self::PREFIX . 'analysis-package' ],
|
||||
'api-client' => [ 'wp-api' ],
|
||||
@@ -272,12 +294,7 @@ class WPSEO_Admin_Asset_Manager {
|
||||
'dashboard-widget' => [ self::PREFIX . 'api-client' ],
|
||||
'wincher-dashboard-widget' => [ self::PREFIX . 'api-client' ],
|
||||
'editor-modules' => [ 'jquery' ],
|
||||
'elementor' => [
|
||||
self::PREFIX . 'api-client',
|
||||
self::PREFIX . 'externals-components',
|
||||
self::PREFIX . 'externals-contexts',
|
||||
self::PREFIX . 'externals-redux',
|
||||
],
|
||||
'elementor' => $elementor_dependencies,
|
||||
'indexation' => [
|
||||
'jquery-ui-core',
|
||||
'jquery-ui-progressbar',
|
||||
|
||||
@@ -36,7 +36,7 @@ class WPSEO_Admin_Utils {
|
||||
* @return string The activation URL. Empty string if the current user doesn't have the proper capabilities.
|
||||
*/
|
||||
public static function get_activation_url( $slug ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ class WPSEO_Gutenberg_Compatibility {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CURRENT_RELEASE = '22.0.0';
|
||||
public const CURRENT_RELEASE = '22.2.0';
|
||||
|
||||
/**
|
||||
* The minimally supported version of Gutenberg by the plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MINIMUM_SUPPORTED = '22.0.0';
|
||||
public const MINIMUM_SUPPORTED = '22.2.0';
|
||||
|
||||
/**
|
||||
* Holds the current version.
|
||||
|
||||
@@ -59,6 +59,8 @@ class Yoast_Notification {
|
||||
* - capabilities: Capabilities that a user must have for this Notification to show.
|
||||
* - capability_check: How to check capability pass: all or any.
|
||||
* - wpseo_page_only: Only display on wpseo page or on every page.
|
||||
* - yoast_branding: Whether to show the Yoast SEO branding in the notification.
|
||||
* - resolve_nonce: Security nonce to use in case of resolving the notification.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -80,6 +82,7 @@ class Yoast_Notification {
|
||||
'capabilities' => [],
|
||||
'capability_check' => self::MATCH_ALL,
|
||||
'yoast_branding' => false,
|
||||
'resolve_nonce' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -177,6 +180,15 @@ class Yoast_Notification {
|
||||
return $this->options['priority'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nonce to resolve the alert.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_resolve_nonce() {
|
||||
return $this->options['resolve_nonce'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the User Meta key to check for dismissal of notification.
|
||||
*
|
||||
|
||||
@@ -244,6 +244,7 @@ class WPSEO_Tracking_Settings_Data implements WPSEO_Collection {
|
||||
'enable_llms_txt',
|
||||
'llms_txt_selection_mode',
|
||||
'configuration_finished_steps',
|
||||
'enable_task_list',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||