first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Yoast
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,97 @@
# whip
A WordPress package to nudge users to upgrade their software versions (starting with PHP)
![Screenshot of the WordPress notice](./images/wp-message.png)
## Requirements
The following versions of PHP are supported:
* PHP 5.3
* PHP 5.4
* PHP 5.5
* PHP 5.6
* PHP 7.0
* PHP 7.1
* PHP 7.2
* PHP 7.3
* PHP 7.4
* PHP 8.0
WordPress is also required for certain functionality:
* The `WPMessagePresenter` requires WordPress or a function called `add_action`, to hook into WordPress.
* The `PHPVersionDetector` requires WordPress or a function called `__`, to translate strings.
## Installation
```bash
$ composer require yoast/whip
```
## Usage
The easiest way to use Whip in WordPress is by using the included function to check the versions. In this case checking if PHP 5.6 or greater is installed:
```php
whip_wp_check_versions( array(
'php' => '>=5.6',
) );
```
This will show a message to all users of your plugin on PHP 5.3 to PHP 5.5. By default the message will be shown on every page of the admin and to every user. It is up to the implementing plugin to restrict this to certain users and/or pages.
### Adding a message as a host
It is possible to add a custom message to the PHP version message by setting specific environment variables:
```php
putenv( "WHIP_NAME_OF_HOST=Name of the host" );
putenv( "WHIP_MESSAGE_FROM_HOST_ABOUT_PHP=A message from the host" );
```
The `WHIP_NAME_OF_HOST` environment variable could be reused in the future for showing messages about different software packages.
Both the name and the message for PHP can also be changed using WordPress filters:
```php
function my_host__name_for_whip() {
return 'Name of the host';
}
add_filter( 'whip_name_of_host', 'my_host__name_for_whip' );
function my_host__php_message_for_whip( $message ) {
return 'A message from the host';
}
add_filter( 'whip_message_from_host_about_php', 'my_host__php_message_for_whip' );
```
The WordPress filters can also read the value previously set by the environment variables.
As a general rule, the filter is the same as the environment variable, but lowercased.
### Linking to the WordPress.org hosting page
We have created a hosting overview page on yoast.com which only contains hosts that we've vetted. The PHP message links to this page by default. If you really prefer to link to the WordPress.org hosting page that is possible. Just use the `whip_hosting_page_url_wordpress` filter:
```php
add_filter( 'whip_hosting_page_url_wordpress', '__return_true' );
```
## Backwards compatibility policy
We follow [semantic versioning][semver] with an extra strict rule for MAJOR versions. We will do a major version bump whenever we add new methods. We have to do this because of the shared namespace in PHP. When this package will be used in multiple plugins we cannot safely add and use a method without bumping a major version. This is because the version without the new method may be autoloaded and then a fatal error occurs.
This also means that any major version bump is accompanied by a change of all class names in the package. So for version 2 of this package all classes will be postfixed with `_v2`. This prevents fatal errors when two plugins include different versions of this package.
## Changelog
## Security
If you discover any security related issues, please email security@yoast.com instead of using the issue tracker.
## Credits
* [Team Yoast](https://github.com/yoast)
[semver]: http://semver.org/

View File

@@ -0,0 +1,61 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_Configuration.
*/
class Whip_Configuration {
/**
* The configuration to use.
*
* @var array
*/
private $configuration;
/**
* Whip_Configuration constructor.
*
* @param array $configuration The configuration to use.
*
* @throws Whip_InvalidType When the $configuration parameter is not of the expected type.
*/
public function __construct( $configuration = array() ) {
if ( ! is_array( $configuration ) ) {
throw new Whip_InvalidType( 'Configuration', $configuration, 'array' );
}
$this->configuration = $configuration;
}
/**
* Retrieves the configured version of a particular requirement.
*
* @param Whip_Requirement $requirement The requirement to check.
*
* @return string|int The version of the passed requirement that was detected as a string.
* If the requirement does not exist, this returns int -1.
*/
public function configuredVersion( Whip_Requirement $requirement ) {
if ( ! $this->hasRequirementConfigured( $requirement ) ) {
return -1;
}
return $this->configuration[ $requirement->component() ];
}
/**
* Determines whether the passed requirement is present in the configuration.
*
* @param Whip_Requirement $requirement The requirement to check.
*
* @return bool Whether or not the requirement is present in the configuration.
*/
public function hasRequirementConfigured( Whip_Requirement $requirement ) {
return array_key_exists( $requirement->component(), $this->configuration );
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Represents a host.
*/
class Whip_Host {
/**
* Key to an environment variable which should be set to the name of the host.
*
* @var string
*/
const HOST_NAME_KEY = 'WHIP_NAME_OF_HOST';
/**
* Filter name for the filter which allows for pointing to the WP hosting page instead of the Yoast version.
*
* @var string
*/
const HOSTING_PAGE_FILTER_KEY = 'whip_hosting_page_url_wordpress';
/**
* Retrieves the name of the host if set.
*
* @return string The name of the host.
*/
public static function name() {
$name = (string) getenv( self::HOST_NAME_KEY );
return self::filterName( $name );
}
/**
* Filters the name if we are in a WordPress context.
* In a non-WordPress content this function just returns the passed name.
*
* @param string $name The current name of the host.
* @return string The filtered name of the host.
*/
private static function filterName( $name ) {
if ( function_exists( 'apply_filters' ) ) {
return (string) apply_filters( strtolower( self::HOST_NAME_KEY ), $name );
}
return $name;
}
/**
* Retrieves the message from the host if set.
*
* @param string $messageKey The key to use as the environment variable.
*
* @return string The message as set by the host.
*/
public static function message( $messageKey ) {
$message = (string) getenv( $messageKey );
return self::filterMessage( $messageKey, $message );
}
/**
* Filters the message if we are in a WordPress context.
* In a non-WordPress content this function just returns the passed message.
*
* @param string $messageKey The key used for the environment variable.
* @param string $message The current message from the host.
*
* @return string
*/
private static function filterMessage( $messageKey, $message ) {
if ( function_exists( 'apply_filters' ) ) {
return (string) apply_filters( strtolower( $messageKey ), $message );
}
return $message;
}
/**
* Returns the URL for the hosting page.
*
* @return string The URL to the hosting overview page.
*/
public static function hostingPageUrl() {
$url = 'https://yoa.st/w3';
return self::filterHostingPageUrl( $url );
}
/**
* Filters the hosting page url if we are in a WordPress context.
* In a non-WordPress context this function just returns a link to the Yoast hosting page.
*
* @param string $url The previous URL.
* @return string The new URL to the hosting overview page.
*/
private static function filterHostingPageUrl( $url ) {
if ( function_exists( 'apply_filters' ) && apply_filters( self::HOSTING_PAGE_FILTER_KEY, false ) ) {
return 'https://wordpress.org/hosting/';
}
return $url;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* A class to dismiss messages.
*/
class Whip_MessageDismisser {
/**
* Storage object to manage the dismissal state.
*
* @var Whip_DismissStorage
*/
protected $storage;
/**
* The current time.
*
* @var string
*/
protected $currentTime;
/**
* The number of seconds the message will be dismissed.
*
* @var int
*/
protected $threshold;
/**
* Whip_MessageDismisser constructor.
*
* @param int $currentTime The current time.
* @param int $threshold The number of seconds the message will be dismissed.
* @param Whip_DismissStorage $storage Storage object to manage the dismissal state.
*/
public function __construct( $currentTime, $threshold, Whip_DismissStorage $storage ) {
$this->currentTime = $currentTime;
$this->threshold = $threshold;
$this->storage = $storage;
}
/**
* Saves the version number to the storage to indicate the message as being dismissed.
*/
public function dismiss() {
$this->storage->set( $this->currentTime );
}
/**
* Checks if the current time is lower than the stored time extended by the threshold.
*
* @return bool True when current time is lower than stored value + threshold.
*/
public function isDismissed() {
return ( $this->currentTime <= ( $this->storage->get() + $this->threshold ) );
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* A helper class to format messages.
*/
final class Whip_MessageFormatter {
/**
* Wraps a piece of text in HTML strong tags.
*
* @param string $toWrap The text to wrap.
* @return string The wrapped text.
*/
public static function strong( $toWrap ) {
return '<strong>' . $toWrap . '</strong>';
}
/**
* Wraps a piece of text in HTML p tags.
*
* @param string $toWrap The text to wrap.
* @return string The wrapped text.
*/
public static function paragraph( $toWrap ) {
return '<p>' . $toWrap . '</p>';
}
/**
* Wraps a piece of text in HTML p and strong tags.
*
* @param string $toWrap The text to wrap.
* @return string The wrapped text.
*/
public static function strongParagraph( $toWrap ) {
return self::paragraph( self::strong( $toWrap ) );
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Manages messages using a global to prevent duplicate messages.
*/
class Whip_MessagesManager {
/**
* Whip_MessagesManager constructor.
*/
public function __construct() {
if ( ! array_key_exists( 'whip_messages', $GLOBALS ) ) {
$GLOBALS['whip_messages'] = array();
}
}
/**
* Adds a message to the Messages Manager.
*
* @param Whip_Message $message The message to add.
*/
public function addMessage( Whip_Message $message ) {
$whipVersion = require dirname( __FILE__ ) . '/configs/version.php';
$GLOBALS['whip_messages'][ $whipVersion ] = $message;
}
/**
* Determines whether or not there are messages available.
*
* @return bool Whether or not there are messages available.
*/
public function hasMessages() {
return isset( $GLOBALS['whip_messages'] ) && count( $GLOBALS['whip_messages'] ) > 0;
}
/**
* Lists the messages that are currently available.
*
* @return array The messages that are currently set.
*/
public function listMessages() {
return $GLOBALS['whip_messages'];
}
/**
* Deletes all messages.
*/
public function deleteMessages() {
unset( $GLOBALS['whip_messages'] );
}
/**
* Gets the latest message.
*
* @return Whip_Message The message. Returns a NullMessage if none is found.
*/
public function getLatestMessage() {
if ( ! $this->hasMessages() ) {
return new Whip_NullMessage();
}
$messages = $this->sortByVersion( $this->listMessages() );
$this->deleteMessages();
return array_pop( $messages );
}
/**
* Sorts the list of messages based on the version number.
*
* @param array $messages The list of messages to sort.
*
* @return array The sorted list of messages.
*/
private function sortByVersion( array $messages ) {
uksort( $messages, 'version_compare' );
return $messages;
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Main controller class to require a certain version of software.
*/
class Whip_RequirementsChecker {
/**
* Requirements the environment should comply with.
*
* @var array
*/
private $requirements;
/**
* The text domain to use for translations.
*
* @var string
*/
private $textdomain;
/**
* Whip_RequirementsChecker constructor.
*
* @param array $configuration The configuration to check.
* @param string $textdomain The text domain to use for translations.
*
* @throws Whip_InvalidType When the $configuration parameter is not of the expected type.
*/
public function __construct( $configuration = array(), $textdomain = 'default' ) {
$this->requirements = array();
$this->configuration = new Whip_Configuration( $configuration );
$this->messageManager = new Whip_MessagesManager();
$this->textdomain = $textdomain;
}
/**
* Adds a requirement to the list of requirements if it doesn't already exist.
*
* @param Whip_Requirement $requirement The requirement to add.
*/
public function addRequirement( Whip_Requirement $requirement ) {
// Only allow unique entries to ensure we're not checking specific combinations multiple times.
if ( $this->requirementExistsForComponent( $requirement->component() ) ) {
return;
}
$this->requirements[] = $requirement;
}
/**
* Determines whether or not there are requirements available.
*
* @return bool Whether or not there are requirements.
*/
public function hasRequirements() {
return $this->totalRequirements() > 0;
}
/**
* Gets the total amount of requirements.
*
* @return int The total amount of requirements.
*/
public function totalRequirements() {
return count( $this->requirements );
}
/**
* Determines whether or not a requirement exists for a particular component.
*
* @param string $component The component to check for.
*
* @return bool Whether or not the component has a requirement defined.
*/
public function requirementExistsForComponent( $component ) {
foreach ( $this->requirements as $requirement ) {
if ( $requirement->component() === $component ) {
return true;
}
}
return false;
}
/**
* Determines whether a requirement has been fulfilled.
*
* @param Whip_Requirement $requirement The requirement to check.
*
* @return bool Whether or not the requirement is fulfilled.
*/
private function requirementIsFulfilled( Whip_Requirement $requirement ) {
$availableVersion = $this->configuration->configuredVersion( $requirement );
$requiredVersion = $requirement->version();
if ( in_array( $requirement->operator(), array( '=', '==', '===' ), true ) ) {
return version_compare( $availableVersion, $requiredVersion, '>=' );
}
return version_compare( $availableVersion, $requiredVersion, $requirement->operator() );
}
/**
* Checks if all requirements are fulfilled and adds a message to the message manager if necessary.
*/
public function check() {
foreach ( $this->requirements as $requirement ) {
// Match against config.
$requirementFulfilled = $this->requirementIsFulfilled( $requirement );
if ( $requirementFulfilled ) {
continue;
}
$this->addMissingRequirementMessage( $requirement );
}
}
/**
* Adds a message to the message manager for requirements that cannot be fulfilled.
*
* @param Whip_Requirement $requirement The requirement that cannot be fulfilled.
*/
private function addMissingRequirementMessage( Whip_Requirement $requirement ) {
switch ( $requirement->component() ) {
case 'php':
$this->messageManager->addMessage( new Whip_UpgradePhpMessage( $this->textdomain ) );
break;
default:
$this->messageManager->addMessage( new Whip_InvalidVersionRequirementMessage( $requirement, $this->configuration->configuredVersion( $requirement ) ) );
break;
}
}
/**
* Determines whether or not there are messages available.
*
* @return bool Whether or not there are messages to display.
*/
public function hasMessages() {
return $this->messageManager->hasMessages();
}
/**
* Gets the most recent message from the message manager.
*
* @return Whip_Message The latest message.
*/
public function getMostRecentMessage() {
return $this->messageManager->getLatestMessage();
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* A value object containing a version requirement for a component version.
*/
class Whip_VersionRequirement implements Whip_Requirement {
/**
* The component name.
*
* @var string
*/
private $component;
/**
* The component version.
*
* @var string
*/
private $version;
/**
* The operator to use when comparing version.
*
* @var string
*/
private $operator;
/**
* Whip_Requirement constructor.
*
* @param string $component The component name.
* @param string $version The component version.
* @param string $operator The operator to use when comparing version.
*/
public function __construct( $component, $version, $operator = '=' ) {
$this->validateParameters( $component, $version, $operator );
$this->component = $component;
$this->version = $version;
$this->operator = $operator;
}
/**
* Retrieves the component name defined for the requirement.
*
* @return string The component name.
*/
public function component() {
return $this->component;
}
/**
* Gets the components version defined for the requirement.
*
* @return string
*/
public function version() {
return $this->version;
}
/**
* Gets the operator to use when comparing version numbers.
*
* @return string The comparison operator.
*/
public function operator() {
return $this->operator;
}
/**
* Creates a new version requirement from a comparison string.
*
* @param string $component The component for this version requirement.
* @param string $comparisonString The comparison string for this version requirement.
*
* @return Whip_VersionRequirement The parsed version requirement.
*
* @throws Whip_InvalidVersionComparisonString When an invalid version comparison string is passed.
*/
public static function fromCompareString( $component, $comparisonString ) {
$matcher = '`
(
>=? # Matches >= and >.
|
<=? # Matches <= and <.
)
([^>=<\s]+) # Matches anything except >, <, =, and whitespace.
`x';
if ( ! preg_match( $matcher, $comparisonString, $match ) ) {
throw new Whip_InvalidVersionComparisonString( $comparisonString );
}
$version = $match[2];
$operator = $match[1];
return new Whip_VersionRequirement( $component, $version, $operator );
}
/**
* Validates the parameters passed to the requirement.
*
* @param string $component The component name.
* @param string $version The component version.
* @param string $operator The operator to use when comparing version.
*
* @throws Whip_EmptyProperty When any of the parameters is empty.
* @throws Whip_InvalidOperatorType When the $operator parameter is invalid.
* @throws Whip_InvalidType When any of the parameters is not of the expected type.
*/
private function validateParameters( $component, $version, $operator ) {
if ( empty( $component ) ) {
throw new Whip_EmptyProperty( 'Component' );
}
if ( ! is_string( $component ) ) {
throw new Whip_InvalidType( 'Component', $component, 'string' );
}
if ( empty( $version ) ) {
throw new Whip_EmptyProperty( 'Version' );
}
if ( ! is_string( $version ) ) {
throw new Whip_InvalidType( 'Version', $version, 'string' );
}
if ( empty( $operator ) ) {
throw new Whip_EmptyProperty( 'Operator' );
}
if ( ! is_string( $operator ) ) {
throw new Whip_InvalidType( 'Operator', $operator, 'string' );
}
$validOperators = array( '=', '==', '===', '<', '>', '<=', '>=' );
if ( ! in_array( $operator, $validOperators, true ) ) {
throw new Whip_InvalidOperatorType( $operator, $validOperators );
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Represents the WordPress option for saving the dismissed messages.
*/
class Whip_WPDismissOption implements Whip_DismissStorage {
/**
* WordPress option name.
*
* @var string
*/
protected $optionName = 'whip_dismiss_timestamp';
/**
* Saves the value to the options.
*
* @param int $dismissedValue The value to save.
*
* @return bool True when successful.
*/
public function set( $dismissedValue ) {
return update_option( $this->optionName, $dismissedValue );
}
/**
* Returns the value of the whip_dismissed option.
*
* @return int Returns the value of the option or an empty string when not set.
*/
public function get() {
$dismissedOption = get_option( $this->optionName );
if ( ! $dismissedOption ) {
return 0;
}
return (int) $dismissedOption;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Listener for dismissing a message.
*/
class Whip_WPMessageDismissListener implements Whip_Listener {
/**
* The name of the dismiss action expected to be passed via $_GET.
*
* @var string
*/
const ACTION_NAME = 'whip_dismiss';
/**
* The object for dismissing a message.
*
* @var Whip_MessageDismisser
*/
protected $dismisser;
/**
* Sets the dismisser attribute.
*
* @param Whip_MessageDismisser $dismisser The object for dismissing a message.
*/
public function __construct( Whip_MessageDismisser $dismisser ) {
$this->dismisser = $dismisser;
}
/**
* Listens to a GET request to fetch the required attributes.
*
* @return void
*/
public function listen() {
$action = filter_input( INPUT_GET, 'action' );
$nonce = filter_input( INPUT_GET, 'nonce' );
if ( $action === self::ACTION_NAME && wp_verify_nonce( $nonce, self::ACTION_NAME ) ) {
$this->dismisser->dismiss();
}
}
/**
* Creates an url for dismissing the notice.
*
* @return string The url for dismissing the message.
*/
public function getDismissURL() {
return sprintf(
admin_url( 'index.php?action=%1$s&nonce=%2$s' ),
self::ACTION_NAME,
wp_create_nonce( self::ACTION_NAME )
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
return array(
'php' => PHP_VERSION,
);

View File

@@ -0,0 +1,8 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
return '1.0.1';

View File

@@ -0,0 +1,21 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class EmptyProperty.
*/
class Whip_EmptyProperty extends Exception {
/**
* EmptyProperty constructor.
*
* @param string $property Property name.
*/
public function __construct( $property ) {
parent::__construct( sprintf( '%s cannot be empty.', (string) $property ) );
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class InvalidOperatorType.
*/
class Whip_InvalidOperatorType extends Exception {
/**
* InvalidOperatorType constructor.
*
* @param string $value Invalid operator.
* @param string[] $validOperators Valid operators.
*/
public function __construct( $value, $validOperators = array( '=', '==', '===', '<', '>', '<=', '>=' ) ) {
parent::__construct(
sprintf(
'Invalid operator of %s used. Please use one of the following operators: %s',
$value,
implode( ', ', $validOperators )
)
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class InvalidType.
*/
class Whip_InvalidType extends Exception {
/**
* InvalidType constructor.
*
* @param string $property Property name.
* @param string $value Property value.
* @param string $expectedType Expected property type.
*/
public function __construct( $property, $value, $expectedType ) {
parent::__construct( sprintf( '%s should be of type %s. Found %s.', $property, $expectedType, gettype( $value ) ) );
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Exception for an invalid version comparison string.
*/
class Whip_InvalidVersionComparisonString extends Exception {
/**
* InvalidVersionComparisonString constructor.
*
* @param string $value The passed version comparison string.
*/
public function __construct( $value ) {
parent::__construct(
sprintf(
'Invalid version comparison string. Example of a valid version comparison string: >=5.3. Passed version comparison string: %s',
$value
)
);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
if ( ! function_exists( 'whip_wp_check_versions' ) ) {
/**
* Facade to quickly check if version requirements are met.
*
* @param array $requirements The requirements to check.
*/
function whip_wp_check_versions( $requirements ) {
// Only show for admin users.
if ( ! is_array( $requirements ) ) {
return;
}
$config = include dirname( __FILE__ ) . '/../configs/default.php';
$checker = new Whip_RequirementsChecker( $config );
foreach ( $requirements as $component => $versionComparison ) {
$checker->addRequirement( Whip_VersionRequirement::fromCompareString( $component, $versionComparison ) );
}
$checker->check();
if ( ! $checker->hasMessages() ) {
return;
}
$dismissThreshold = ( WEEK_IN_SECONDS * 4 );
$dismissMessage = __( 'Remind me again in 4 weeks.', 'default' );
$dismisser = new Whip_MessageDismisser( time(), $dismissThreshold, new Whip_WPDismissOption() );
$presenter = new Whip_WPMessagePresenter( $checker->getMostRecentMessage(), $dismisser, $dismissMessage );
// Prevent duplicate notices across multiple implementing plugins.
if ( ! has_action( 'whip_register_hooks' ) ) {
add_action( 'whip_register_hooks', array( $presenter, 'registerHooks' ) );
}
/**
* Fires during hooks registration for the message presenter.
*
* @param \Whip_WPMessagePresenter $presenter Message presenter instance.
*/
do_action( 'whip_register_hooks', $presenter );
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Interface Whip_DismissStorage.
*/
interface Whip_DismissStorage {
/**
* Saves the value.
*
* @param int $dismissedValue The value to save.
*
* @return bool True when successful.
*/
public function set( $dismissedValue );
/**
* Returns the value.
*
* @return int The stored value.
*/
public function get();
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Interface Whip_Listener.
*/
interface Whip_Listener {
/**
* Method that should implement the listen functionality.
*
* @return void
*/
public function listen();
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Interface Whip_Message.
*/
interface Whip_Message {
/**
* Retrieves the message body.
*
* @return string Message.
*/
public function body();
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Interface Whip_MessagePresenter.
*/
interface Whip_MessagePresenter {
/**
* Renders the message.
*/
public function renderMessage();
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Interface Whip_Requirement.
*/
interface Whip_Requirement {
/**
* Retrieves the component name defined for the requirement.
*
* @return string The component name.
*/
public function component();
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* An interface that represents a version detector and message.
*/
interface Whip_VersionDetector {
/**
* Detects the version of the installed software.
*
* @return string
*/
public function detect();
/**
* Returns the message that should be shown if a version is not deemed appropriate by the implementation.
*
* @return string
*/
public function getMessage();
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_Message.
*/
class Whip_BasicMessage implements Whip_Message {
/**
* Message body.
*
* @var string
*/
private $body;
/**
* Whip_Message constructor.
*
* @param string $body Message body.
*/
public function __construct( $body ) {
$this->validateParameters( $body );
$this->body = $body;
}
/**
* Retrieves the message body.
*
* @return string Message.
*/
public function body() {
return $this->body;
}
/**
* Validates the parameters passed to the constructor of this class.
*
* @param string $body Message body.
*
* @throws Whip_EmptyProperty When the $body parameter is empty.
* @throws Whip_InvalidType When the $body parameter is not of the expected type.
*/
private function validateParameters( $body ) {
if ( empty( $body ) ) {
throw new Whip_EmptyProperty( 'Message body' );
}
if ( ! is_string( $body ) ) {
throw new Whip_InvalidType( 'Message body', $body, 'string' );
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_HostMessage.
*/
class Whip_HostMessage implements Whip_Message {
/**
* Text domain to use for translations.
*
* @var string
*/
private $textdomain;
/**
* The environment key to use to retrieve the message from.
*
* @var string
*/
private $messageKey;
/**
* Whip_Message constructor.
*
* @param string $messageKey The environment key to use to retrieve the message from.
* @param string $textdomain The text domain to use for translations.
*/
public function __construct( $messageKey, $textdomain ) {
$this->textdomain = $textdomain;
$this->messageKey = $messageKey;
}
/**
* Retrieves the message body.
*
* @return string The message body.
*/
public function body() {
$message = array();
$message[] = Whip_MessageFormatter::strong( $this->title() ) . '<br />';
$message[] = Whip_MessageFormatter::paragraph( Whip_Host::message( $this->messageKey ) );
return implode( "\n", $message );
}
/**
* Renders the message title.
*
* @return string The message title.
*/
public function title() {
/* translators: 1: name. */
return sprintf( __( 'A message from %1$s', $this->textdomain ), Whip_Host::name() );
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_InvalidVersionMessage.
*/
class Whip_InvalidVersionRequirementMessage implements Whip_Message {
/**
* Object containing the version requirement for a component.
*
* @var Whip_VersionRequirement
*/
private $requirement;
/**
* Detected version requirement or -1 if not found.
*
* @var string|int
*/
private $detected;
/**
* Whip_InvalidVersionRequirementMessage constructor.
*
* @param Whip_VersionRequirement $requirement Object containing the version requirement for a component.
* @param string|int $detected Detected version requirement or -1 if not found.
*/
public function __construct( Whip_VersionRequirement $requirement, $detected ) {
$this->requirement = $requirement;
$this->detected = $detected;
}
/**
* Retrieves the message body.
*
* @return string Message.
*/
public function body() {
return sprintf(
'Invalid version detected for %s. Found %s but expected %s.',
$this->requirement->component(),
$this->detected,
$this->requirement->version()
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_Message.
*/
class Whip_NullMessage implements Whip_Message {
/**
* Retrieves the message body.
*
* @return string Message.
*/
public function body() {
return '';
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* Class Whip_UpgradePhpMessage
*/
class Whip_UpgradePhpMessage implements Whip_Message {
/**
* The text domain to use for the translations.
*
* @var string
*/
private $textdomain;
/**
* Whip_UpgradePhpMessage constructor.
*
* @param string $textdomain The text domain to use for the translations.
*/
public function __construct( $textdomain ) {
$this->textdomain = $textdomain;
}
/**
* Retrieves the message body to display.
*
* @return string The message to display.
*/
public function body() {
$textdomain = $this->textdomain;
$message = array();
$message[] = Whip_MessageFormatter::strongParagraph( __( 'Your site could be faster and more secure with a newer PHP version.', $textdomain ) ) . '<br />';
$message[] = Whip_MessageFormatter::paragraph( __( 'Hey, we\'ve noticed that you\'re running an outdated version of PHP. PHP is the programming language that WordPress and all its plugins and themes are built on. The version that is currently used for your site is no longer supported. Newer versions of PHP are both faster and more secure. In fact, your version of PHP no longer receives security updates, which is why we\'re sending you to this notice.', $textdomain ) );
$message[] = Whip_MessageFormatter::paragraph( __( 'Hosts have the ability to update your PHP version, but sometimes they don\'t dare to do that because they\'re afraid they\'ll break your site.', $textdomain ) );
$message[] = Whip_MessageFormatter::strongParagraph( __( 'To which version should I update?', $textdomain ) ) . '<br />';
$message[] = Whip_MessageFormatter::paragraph(
sprintf(
/* translators: 1: link open tag; 2: link close tag. */
__( 'You should update your PHP version to either 5.6 or to 7.0 or 7.1. On a normal WordPress site, switching to PHP 5.6 should never cause issues. We would however actually recommend you switch to PHP7. There are some plugins that are not ready for PHP7 though, so do some testing first. We have an article on how to test whether that\'s an option for you %1$shere%2$s. PHP7 is much faster than PHP 5.6. It\'s also the only PHP version still in active development and therefore the better option for your site in the long run.', $textdomain ),
'<a href="https://yoa.st/wg" target="_blank">',
'</a>'
)
);
if ( Whip_Host::name() !== '' ) {
$hostMessage = new Whip_HostMessage( 'WHIP_MESSAGE_FROM_HOST_ABOUT_PHP', $textdomain );
$message[] = $hostMessage->body();
}
$hostingPageUrl = Whip_Host::hostingPageUrl();
$message[] = Whip_MessageFormatter::strongParagraph( __( 'Can\'t update? Ask your host!', $textdomain ) ) . '<br />';
if ( function_exists( 'apply_filters' ) && apply_filters( Whip_Host::HOSTING_PAGE_FILTER_KEY, false ) ) {
$message[] = Whip_MessageFormatter::paragraph(
sprintf(
/* translators: 1: link open tag; 2: link close tag; 3: link open tag. */
__( 'If you cannot upgrade your PHP version yourself, you can send an email to your host. We have %1$sexamples here%2$s. If they don\'t want to upgrade your PHP version, we would suggest you switch hosts. Have a look at one of the recommended %3$sWordPress hosting partners%2$s.', $textdomain ),
'<a href="https://yoa.st/wh" target="_blank">',
'</a>',
sprintf( '<a href="%1$s" target="_blank">', esc_url( $hostingPageUrl ) )
)
);
}
else {
$message[] = Whip_MessageFormatter::paragraph(
sprintf(
/* translators: 1: link open tag; 2: link close tag; 3: link open tag. */
__( 'If you cannot upgrade your PHP version yourself, you can send an email to your host. We have %1$sexamples here%2$s. If they don\'t want to upgrade your PHP version, we would suggest you switch hosts. Have a look at one of our recommended %3$sWordPress hosting partners%2$s, they\'ve all been vetted by the Yoast support team and provide all the features a modern host should provide.', $textdomain ),
'<a href="https://yoa.st/wh" target="_blank">',
'</a>',
sprintf( '<a href="%1$s" target="_blank">', esc_url( $hostingPageUrl ) )
)
);
}
return implode( "\n", $message );
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* WHIP libary file.
*
* @package Yoast\WHIP
*/
/**
* A message presenter to show a WordPress notice.
*/
class Whip_WPMessagePresenter implements Whip_MessagePresenter {
/**
* The string to show to dismiss the message.
*
* @var string
*/
private $dismissMessage;
/**
* The message to be displayed.
*
* @var Whip_Message
*/
private $message;
/**
* Dismisser object.
*
* @var Whip_MessageDismisser
*/
private $dismisser;
/**
* Whip_WPMessagePresenter constructor.
*
* @param Whip_Message $message The message to use in the presenter.
* @param Whip_MessageDismisser $dismisser Dismisser object.
* @param string $dismissMessage The copy to show to dismiss the message.
*/
public function __construct( Whip_Message $message, Whip_MessageDismisser $dismisser, $dismissMessage ) {
$this->message = $message;
$this->dismisser = $dismisser;
$this->dismissMessage = $dismissMessage;
}
/**
* Registers hooks to WordPress.
*
* This is a separate function so you can control when the hooks are registered.
*/
public function registerHooks() {
add_action( 'admin_notices', array( $this, 'renderMessage' ) );
}
/**
* Registers hooks to WordPress.
*
* @deprecated 1.2.0 Use the Whip_WPMessagePresenter::registerHooks() method instead.
* @codeCoverageIgnore
* @phpcs:disable Generic.NamingConventions.CamelCapsFunctionName
*/
public function register_hooks() {
// phpcs:enable
self::registerHooks();
}
/**
* Renders the messages present in the global to notices.
*/
public function renderMessage() {
$dismissListener = new Whip_WPMessageDismissListener( $this->dismisser );
$dismissListener->listen();
if ( $this->dismisser->isDismissed() ) {
return;
}
$dismissButton = sprintf(
'<a href="%2$s">%1$s</a>',
esc_html( $this->dismissMessage ),
esc_url( $dismissListener->getDismissURL() )
);
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- output correctly escaped directly above and in the `kses()` method.
printf(
'<div class="error"><p>%1$s</p><p>%2$s</p></div>',
$this->kses( $this->message->body() ),
$dismissButton
);
// phpcs:enable
}
/**
* Removes content from the message that we don't want to show.
*
* @param string $message The message to clean.
*
* @return string The cleaned message.
*/
public function kses( $message ) {
return wp_kses(
$message,
array(
'a' => array(
'href' => true,
'target' => true,
),
'strong' => true,
'p' => true,
'ul' => true,
'li' => true,
)
);
}
}