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,37 @@
<?php
namespace OTGS\Installer\AdminNotices;
class Config {
/**
* @var array
*/
protected $config;
public function __construct( array $config ) {
$this->config = $config;
}
/**
* @param array $messages
* @param string $item
* @param string $type
*
* @return bool
*/
protected function hasItem( array $messages, $item, $type ) {
foreach ( $messages['repo'] as $repo => $ids ) {
foreach ( $ids as $id => $noticeType ) {
$index = is_array( $noticeType ) ? $id : $noticeType;
if ( isset( $this->config['repo'][ $repo ][ $index ][ $type ] )
&& in_array( $item, $this->config['repo'][ $repo ][ $index ][ $type ], true ) ) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace OTGS\Installer\AdminNotices;
class Dismissed {
const STORE_KEY = 'dismissed';
/**
* @param array $dismissedNotices
* @param string $repo
* @param string $id
*
* @return bool
*/
public static function isDismissed( array $dismissedNotices, $repo, $id ) {
return isset( $dismissedNotices['repo'][ $repo ][ $id ] );
}
/**
* @param array $dismissedNotices
* @param callable $timeOut - int -> string -> string -> bool
*
* @return mixed
*/
public static function clearExpired( array $dismissedNotices, callable $timeOut ) {
if ( isset( $dismissedNotices['repo'] ) ) {
foreach ( $dismissedNotices['repo'] as $repo => $ids ) {
foreach ( $ids as $id => $dismissedTimeStamp ) {
if ( $timeOut( $dismissedTimeStamp, $repo, $id ) ) {
unset ( $dismissedNotices['repo'][ $repo ][ $id ] );
}
}
}
}
return $dismissedNotices;
}
public static function dismissNotice() {
$data = filter_var_array( $_POST, [
'repository' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
'noticeType' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
'noticePluginSlug' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
] );
$dismissions = apply_filters( 'otgs_installer_admin_notices_dismissions', [] );
$store = new Store();
$dismissed = $store->get( self::STORE_KEY, [] );
$dismissed = $dismissions[ $data['noticeType'] ]( $dismissed, $data );
$store->save( self::STORE_KEY, $dismissed );
wp_send_json_success( [] );
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace OTGS\Installer\AdminNotices;
class Display {
/**
* @var array
*/
private $currentNotices;
/**
* @var PageConfig
*/
private $pageConfig;
/**
* @var MessageTexts
*/
private $messageTexts;
/**
* @var callable - string -> string -> bool
*/
private $isDismissed;
/**
* @var ScreenConfig
*/
private $screenConfig;
public function __construct(
array $currentNotices,
array $config,
MessageTexts $messageTexts,
callable $isDismissed
) {
$this->currentNotices = $currentNotices;
$this->pageConfig = new PageConfig( $config );
$this->screenConfig = new ScreenConfig( $config );
$this->messageTexts = $messageTexts;
$this->isDismissed = $isDismissed;
}
public function addHooks() {
if ( ! empty( $this->currentNotices ) && $this->isRelevantOnPage() ) {
add_action( 'admin_notices', [ $this, 'addNotices' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'addScripts' ] );
}
}
public function addNotices() {
foreach ( $this->currentNotices['repo'] as $repo => $ids ) {
foreach ( $ids as $id => $type ) {
if ( is_array( $type ) ) {
$index = $id;
$noticesData = $type;
} else {
$index = $type;
$noticesData = [ $type ];
}
if ( $this->pageConfig->shouldShowMessage( $repo, $index ) || $this->screenConfig->shouldShowMessage( $repo, $index ) ) {
foreach ( $noticesData as $noticeData ) {
$this->displayNotice( $repo, $index, $noticeData );
}
}
}
}
}
/**
* @return bool
*/
private function isRelevantOnPage() {
return $this->pageConfig->isAnyMessageOnPage( $this->currentNotices ) ||
$this->screenConfig->isAnyMessageOnPage( $this->currentNotices );
}
/**
* @param string $repo
* @param string $ids
*/
private function displayNotice( $repo, $id, $notice_params = [] ) {
$noticeId = $id;
if ( isset( $notice_params['noticeId'] ) ) {
$noticeId = $notice_params['noticeId'];
}
if ( ! call_user_func( $this->isDismissed, $repo, $noticeId ) ) {
$html = $this->messageTexts->get( $repo, $id, $notice_params );
if ( $html ) {
echo $html;
}
}
}
public function addScripts() {
$installer = OTGS_Installer();
wp_enqueue_style(
'installer-admin-notices',
$installer->res_url() . '/res/css/admin-notices.css',
[],
$installer->version()
);
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace OTGS\Installer\AdminNotices;
use function OTGS\Installer\FP\partial;
class Loader {
/**
* @param bool $isAjax
*/
public static function addHooks( $isAjax ) {
add_action( 'current_screen', self::class . '::initDisplay' );
if ( $isAjax ) {
add_action( 'wp_ajax_installer_dismiss_nag', Dismissed::class . '::dismissNotice' );
}
}
public static function initDisplay() {
remove_action( 'current_screen', self::class . '::initDisplay' );
/**
* Filter and return installer admin notices
*
* @param array - an associative array of messages keyed by repository
* eg.
* [ 'repo' => [ 'wpml' = [ 'message_id_1', 'message_id_2' ... ] ] ]
*/
$messages = apply_filters( 'otgs_installer_admin_notices', [] );
if ( ! empty( $messages ) ) {
/**
* Filter and return configuration of where messages should be displayed
*
* @param array - an associative array keyed by repository
* eg.
* [ 'repo' => [ 'wpml' => [
* 'message_id_1' => [
* 'screens' => [ 'plugins', 'dashboard', ... ],
* 'pages' => [ 'sitepress-multilingual-cms/menu/languages.php', ... ]
* ],
* ...
* ] ] ]
*/
$config = apply_filters( 'otgs_installer_admin_notices_config', [] );
/**
* Filter and return callback functions for retrieving the text for each message
* The message id is passed to the callback function
*
* @param array - an associative array keyed by repository
* eg.
* [ 'repo' => [ 'wpml' => [
* 'message_id_1' => some_callback_function,
* 'message_id_2' => some_callback_function,
* ] ] ]
*
*/
$texts = apply_filters( 'otgs_installer_admin_notices_texts', [] );
$dismissedNotices = self::refreshDismissed();
( new Display(
$messages,
$config,
new MessageTexts( $texts ),
partial( Dismissed::class . '::isDismissed', $dismissedNotices )
) )->addHooks();
}
}
public static function isDismissed( $repository_id, $notice_id ) {
$remainigNotices = static::refreshDismissed();
return Dismissed::isDismissed( $remainigNotices, $repository_id, $notice_id );
}
/**
* @return array
*/
private static function refreshDismissed() {
$store = new Store();
$dismissedMessages = $store->get( Dismissed::STORE_KEY, [] );
$remainingMessages = Dismissed::clearExpired(
$dismissedMessages,
[ self::class, 'timeOut' ]
);
if ( $dismissedMessages !== $remainingMessages ) {
$store->save( Dismissed::STORE_KEY, $remainingMessages );
}
return $remainingMessages;
}
/**
* @param int $start
* @param string $repo
* @param string $id
*
* @return bool
*/
public static function timeOut( $start, $repo, $id ) {
/**
* Filters the default time that a notice stays dismissed for. The default is 2 months
*
* @param int $timeout
* @param string $repo
* @param string id - message id
* return a timestamp in seconds. eg WEEK_IN_SECONDS, etc
*/
$timeout = apply_filters(
'otgs_installer_admin_notices_dismissed_time',
2 * MONTH_IN_SECONDS,
$repo,
$id
);
return time() - $start > $timeout;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace OTGS\Installer\AdminNotices;
class MessageTexts {
/**
* @var array
*/
private $messages;
/**
* MessageTexts constructor.
*
* @param array $messages
*/
public function __construct( array $messages ) {
$this->messages = $messages;
}
/**
* @param string $repo
* @param string $messageId
*
* @return string|null
*/
public function get( $repo, $messageId, $parameters = [] ) {
if ( isset( $this->messages['repo'][ $repo ][ $messageId ] ) ) {
if ( ! empty( $parameters ) ) {
return call_user_func(
$this->messages['repo'][ $repo ][ $messageId ],
$parameters
);
} else {
return call_user_func( $this->messages['repo'][ $repo ][ $messageId ], $messageId );
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace OTGS\Installer\AdminNotices;
class PageConfig extends Config {
/**
* @param array $messages
*
* @return bool
*/
public function isAnyMessageOnPage( array $messages ) {
if ( ! isset( $_GET['page'] ) ) {
return false;
}
return $this->hasItem( $messages, $_GET['page'], 'pages' );
}
/**
* @param string $repo
* @param string $id
*
* @return bool
*/
public function shouldShowMessage( $repo, $id ) {
if ( ! isset( $_GET['page'] ) ) {
return false;
}
if ( isset( $this->config['repo'][ $repo ][ $id ]['pages'] ) ) {
return in_array( $_GET['page'], $this->config['repo'][ $repo ][ $id ]['pages'] );
}
return false;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace OTGS\Installer\AdminNotices;
class ScreenConfig extends Config {
/**
* @param array $messages
*
* @return bool
*/
public function isAnyMessageOnPage( array $messages ) {
$currentScreen = get_current_screen();
if ( ! $currentScreen instanceof \WP_Screen ) {
return false;
}
return $this->hasItem( $messages, $currentScreen->id, 'screens' );
}
/**
* @param string $repo
* @param string $id
*
* @return bool
*/
public function shouldShowMessage( $repo, $id ) {
$currentScreen = get_current_screen();
if ( $currentScreen instanceof \WP_Screen ) {
if ( isset( $this->config['repo'][ $repo ][ $id ]['screens'] ) ) {
return in_array( $currentScreen->id, $this->config['repo'][ $repo ][ $id ]['screens'] );
}
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace OTGS\Installer\AdminNotices;
class Store {
const ADMIN_NOTICES_OPTION = 'otgs_installer_admin_notices';
/**
* @param string $key
* @param $data
*/
public function save( $key, $data ) {
$current = get_option( self::ADMIN_NOTICES_OPTION, [] );
$current[ $key ] = $data;
update_option( self::ADMIN_NOTICES_OPTION, $current, 'no' );
}
/**
* @param string $key
*
* @return mixed
*/
public function get( $key, $default ) {
$current = get_option( self::ADMIN_NOTICES_OPTION, [] );
return isset( $current[$key] ) ? $current[$key] : $default;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace OTGS\Installer\AdminNotices;
class TMConfig {
public static function pages() {
if ( ! defined( 'WPML_TM_FOLDER' ) ) {
return [];
}
return [
WPML_TM_FOLDER . '/menu/settings',
WPML_TM_FOLDER . '/menu/main.php',
WPML_TM_FOLDER . '/menu/translations-queue.php',
WPML_TM_FOLDER . '/menu/string-translation.php',
WPML_TM_FOLDER . '/menu/settings.php',
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace OTGS\Installer\AdminNotices;
class ToolsetConfig {
public static function pages() {
return [
'toolset-dashboard',
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace OTGS\Installer\AdminNotices;
class WPMLConfig {
public static function pages() {
if ( ! defined( 'WPML_PLUGIN_FOLDER' ) ) {
return [];
}
return [
WPML_PLUGIN_FOLDER . '/menu/languages.php',
WPML_PLUGIN_FOLDER . '/menu/theme-localization.php',
WPML_PLUGIN_FOLDER . '/menu/settings.php',
WPML_PLUGIN_FOLDER . '/menu/support.php',
];
}
}

View File

@@ -0,0 +1,210 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use OTGS\Installer\AdminNotices\Loader;
use OTGS\Installer\AdminNotices\Store;
use OTGS\Installer\AdminNotices\ToolsetConfig;
use OTGS\Installer\AdminNotices\WPMLConfig;
use OTGS\Installer\Collection;
use function OTGS\Installer\FP\partial;
class Account {
const NOT_REGISTERED = 'not-registered';
const EXPIRED = 'expired';
const REFUNDED = 'refunded';
const GET_FIRST_INSTALL_TIME = 'get_first_install_time';
const DEVELOPMENT_MODE = 'development_mode';
/**
* @param \WP_Installer $installer
* @param array $initialNotices
*
* @return array
*/
public static function getCurrentNotices( \WP_Installer $installer, array $initialNotices ) {
$config = $installer->get_site_key_nags_config();
$noticeTypes = [
self::NOT_REGISTERED => [ Account::class, 'shouldShowNotRegistered' ],
self::EXPIRED => [ Account::class, 'shouldShowExpired' ],
self::REFUNDED => [ Account::class, 'shouldShowRefunded' ],
self::DEVELOPMENT_MODE => [ Account::class, 'shouldShowDevelopmentBanner' ],
];
return collection::of( $noticeTypes )
->entities()
->reduce( Notice::addNoticesForType( $installer, $config ), Collection::of( $initialNotices ) )
->get();
}
/**
* @param \WP_Installer $installer
* @param array $nag
*
* @return bool
*/
public static function shouldShowNotRegistered( \WP_Installer $installer, array $nag ) {
$shouldShow = ! self::isDevelopmentSite( $installer->get_installer_site_url( $nag['repository_id'] ) ) &&
! $installer->repository_has_subscription( $nag['repository_id'] ) &&
( isset( $nag['condition_cb'] ) ? $nag['condition_cb']() : true );
if ( $shouldShow ) {
$shouldShow = ! self::maybeDelayOneWeekOnNewInstalls( $nag['repository_id'] );
}
return $shouldShow;
}
/**
* @param \WP_Installer $installer
* @param array $nag
*
* @return bool
*/
public static function shouldShowExpired( \WP_Installer $installer, array $nag ) {
return $installer->repository_has_expired_subscription( $nag['repository_id'], 30 * DAY_IN_SECONDS );
}
/**
* @param \WP_Installer $installer
* @param array $nag
*
* @return bool
*/
public static function shouldShowDevelopmentBanner( \WP_Installer $installer, array $nag ) {
$showDevelopmentBanner = $installer->repository_has_development_site_key( $nag['repository_id'] );
$isDismissed = Loader::isDismissed( $nag[ 'repository_id' ], Account::DEVELOPMENT_MODE );
if ( $showDevelopmentBanner && $isDismissed && $nag['repository_id'] === 'wpml' ) {
wp_enqueue_style( 'installer-admin-notices', $installer->res_url() . '/res/css/admin-notices.css', array(), $installer->version() );
add_action( 'wp_before_admin_bar_render', [ static::class, 'addWpmlDevelopmentAdminBar' ], 100);
}
return $showDevelopmentBanner;
}
public static function addWpmlDevelopmentAdminBar() {
/** @var \WP_Admin_Bar $wp_admin_bar */
global $wp_admin_bar;
$helpText = __( 'This site is registered on wpml.org as a development site.', 'installer' );
$text = __( 'Development Site', 'installer' );
$wp_admin_bar->add_menu(
array(
'parent' => false,
'id' => 'otgs-wpml-development',
'title' => '<i class="otgs-ico-sitepress-multilingual-cms js-otgs-popover-tooltip" data-tippy-zIndex="999999" title="' . $helpText . '" > ' . $text . '</i>',
'href' => false,
)
);
}
/**
* @param \WP_Installer $installer
* @param array $nag
*
* @return bool
*/
public static function shouldShowRefunded( \WP_Installer $installer, array $nag ) {
return $installer->repository_has_refunded_subscription( $nag['repository_id'] );
}
public static function config( array $initialConfig ) {
return self::pages( self::screens( $initialConfig ) );
}
public static function pages( array $initialPages ) {
$wpmlPages = [ 'pages' => WPMLConfig::pages() ];
$toolsetPages = [ 'pages' => ToolsetConfig::pages() ];
return array_merge_recursive( $initialPages, [
'repo' => [
'wpml' => [
Account::NOT_REGISTERED => $wpmlPages,
Account::EXPIRED => $wpmlPages,
Account::REFUNDED => $wpmlPages,
Account::DEVELOPMENT_MODE => $wpmlPages,
],
'toolset' => [
Account::NOT_REGISTERED => $toolsetPages,
Account::EXPIRED => $toolsetPages,
Account::REFUNDED => $toolsetPages,
Account::DEVELOPMENT_MODE => $toolsetPages,
],
],
] );
}
public static function screens( array $screens ) {
$config = [
Account::NOT_REGISTERED => [ 'screens' => [ 'plugins' ] ],
Account::EXPIRED => [ 'screens' => [ 'plugins' ] ],
Account::REFUNDED => [ 'screens' => [ 'plugins', 'dashboard' ] ],
Account::DEVELOPMENT_MODE => [ 'screens' => [ 'plugins', 'dashboard', 'plugin-install' ] ],
];
return array_merge_recursive( $screens, [
'repo' => [
'wpml' => $config,
'toolset' => $config,
],
] );
}
public static function texts( array $initialTexts ) {
return array_merge_recursive( $initialTexts, [
'repo' => [
'wpml' => [
Account::NOT_REGISTERED => WPMLTexts::class . '::notRegistered',
Account::EXPIRED => WPMLTexts::class . '::expired',
Account::REFUNDED => WPMLTexts::class . '::refunded',
Account::DEVELOPMENT_MODE => WPMLTexts::class . '::developmentBanner',
],
'toolset' => [
Account::NOT_REGISTERED => ToolsetTexts::class . '::notRegistered',
Account::EXPIRED => ToolsetTexts::class . '::expired',
Account::REFUNDED => ToolsetTexts::class . '::refunded',
Account::DEVELOPMENT_MODE => ToolsetTexts::class . '::developmentBanner',
],
],
] );
}
public static function dismissions( array $initialDismissions ) {
return array_merge_recursive(
$initialDismissions,
[
Account::NOT_REGISTERED => Dismissions::class . '::dismissAccountNotice',
Account::DEVELOPMENT_MODE => Dismissions::class . '::dismissAccountNotice',
Account::EXPIRED => Dismissions::class . '::dismissAccountNotice',
Account::REFUNDED => Dismissions::class . '::dismissAccountNotice',
]
);
}
private static function isDevelopmentSite( $url ) {
$endsWith = function ( $haystack, $needle ) {
return substr_compare( $haystack, $needle, - strlen( $needle ) ) === 0;
};
$host = parse_url( $url, PHP_URL_HOST );
return $endsWith( $host, '.dev' ) ||
$endsWith( $host, '.local' ) ||
$endsWith( $host, '.test' );
}
private static function maybeDelayOneWeekOnNewInstalls( $repo ) {
$store = new Store();
$installTime = $store->get( self::GET_FIRST_INSTALL_TIME, [] );
if ( ! isset( $installTime[ $repo ] ) ) {
$installTime[ $repo ] = time();
$store->save( self::GET_FIRST_INSTALL_TIME, $installTime );
}
return time() - $installTime[ $repo ] < WEEK_IN_SECONDS;
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use OTGS\Installer\AdminNotices\ToolsetConfig;
use OTGS\Installer\AdminNotices\WPMLConfig;
use OTGS\Installer\Collection;
class ApiConnection {
const CONNECTION_ISSUES = 'connection-issues';
/**
* @param \WP_Installer $installer
* @param array $initialNotices
*
* @return array
*/
public static function getCurrentNotices( \WP_Installer $installer, array $initialNotices ) {
$config = $installer->getRepositories();
$noticeTypes = [
self::CONNECTION_ISSUES => [ApiConnection::class, 'shouldShowConnectionIssues'],
];
return collection::of( $noticeTypes )
->entities()
->reduce( Notice::addNoticesForType($installer, $config), Collection::of( $initialNotices ) )
->get();
}
/**
* @param \WP_Installer $installer
* @param array $nag
*
* @return bool
*/
public static function shouldShowConnectionIssues( \WP_Installer $installer, array $nag ) {
return $installer->shouldDisplayConnectionIssueMessage( $nag['repository_id'] );
}
public static function config( array $initialConfig ) {
return self::pages( self::screens( $initialConfig ) );
}
public static function pages( array $initialPages ) {
$wpmlPages = [ 'pages' => WPMLConfig::pages() ];
$toolsetPages = [ 'pages' => ToolsetConfig::pages() ];
return array_merge_recursive( $initialPages, [
'repo' => [
'wpml' => [
ApiConnection::CONNECTION_ISSUES => $wpmlPages,
],
'toolset' => [
ApiConnection::CONNECTION_ISSUES => $toolsetPages,
],
],
] );
}
public static function screens( array $screens ) {
$config = [
ApiConnection::CONNECTION_ISSUES => [ 'screens' => [ 'plugins', 'plugin-install' ] ],
];
return array_merge_recursive( $screens, [
'repo' => [
'wpml' => $config,
'toolset' => $config,
],
] );
}
public static function texts( array $initialTexts ) {
return array_merge_recursive( $initialTexts, [
'repo' => [
'wpml' => [
ApiConnection::CONNECTION_ISSUES => WPMLTexts::class . '::connectionIssues',
],
'toolset' => [
ApiConnection::CONNECTION_ISSUES => ToolsetTexts::class . '::connectionIssues',
],
],
] );
}
public static function dismissions( array $initialDismissions ) {
return $initialDismissions;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use OTGS\Installer\Recommendations\Storage;
class Dismissions {
/**
* @param array $dismissed already dismissed notices.
* @param array $data dismissed notice parameters.
*
* @return array
*/
public static function dismissAccountNotice( $dismissed, $data ) {
$dismissed['repo'][ $data['repository'] ][ $data['noticeType'] ] = time();
return $dismissed;
}
/**
* @param array $dismissed already dismissed notices.
* @param array $data dismissed notice parameters.
*
* @return array
*/
public static function dismissRecommendationNotice( $dismissed, $data ) {
Storage::delete( $data['noticePluginSlug'], $data['repository'] );
return $dismissed;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use function OTGS\Installer\FP\partial;
class Hooks {
public static function addHooks( $class, \WP_Installer $installer ) {
add_filter( 'otgs_installer_admin_notices_config', [$class, 'config'] );
add_filter( 'otgs_installer_admin_notices_texts', [$class, 'texts'] );
add_filter( 'otgs_installer_admin_notices_dismissions', [$class, 'dismissions'] );
add_filter(
'otgs_installer_admin_notices',
partial( [$class, 'getCurrentNotices'], $installer )
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use OTGS\Installer\Collection;
use function OTGS\Installer\FP\partial;
class Notice {
/**
* @param \WP_Installer $installer
* @param array $config
*
* @return \Closure
*/
public static function addNoticesForType( $installer, $config ) {
return function ( Collection $notices, array $data ) use ( $installer, $config ) {
list( $type, $fn ) = $data;
$addNotice = partial( self::class . '::addNotice', $type );
$shouldShow = partial( $fn, $installer );
return $notices->mergeRecursive( Collection::of( $config )
->filter( $shouldShow )
->pluck( 'repository_id' )
->reduce( $addNotice, [] ) );
};
}
/**
* @param string $noticeId
* @param array $notices
* @param string $repoId
*
* @return array
*/
public static function addNotice( $noticeId, array $notices, $repoId ) {
return array_merge_recursive( $notices, [ 'repo' => [ $repoId => [ $noticeId ] ] ] );
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
use OTGS\Installer\Collection;
use OTGS\Installer\Recommendations\RecommendationsManager;
use OTGS\Installer\Recommendations\Storage;
use function OTGS\Installer\FP\partial;
class Recommendation {
const PLUGIN_ACTIVATED = 'plugin-activated';
public static function addHooks() {
add_filter( 'otgs_installer_admin_notices_config', self::class . '::config' );
add_filter( 'otgs_installer_admin_notices_texts', self::class . '::texts' );
add_filter( 'otgs_installer_admin_notices_dismissions', self::class . '::dismissions' );
add_filter(
'otgs_installer_admin_notices',
self::class . '::getCurrentNotices'
);
}
/**
* @param callable $getActivatedPlugins
* @param array $initialNotices
*
* @return array
*/
public static function getCurrentNotices( array $initialNotices ) {
$activatedPluginsConfig = apply_filters('wpml_installer_get_stored_recommendation_notices', []);
$addNoticeIdField = function ( $item ) {
$item['noticeId'] = self::PLUGIN_ACTIVATED . '-' . $item['glue_check_slug'];
return $item;
};
$updatedConfig = Collection::of( $activatedPluginsConfig )
->map( function ( $items ) use ( $addNoticeIdField ) {
$items = Collection::of( $items )->map( $addNoticeIdField )->get();
return [ self::PLUGIN_ACTIVATED => $items ];
} );
if ( ! empty( $updatedConfig->get() ) ) {
$activatedPluginsConfig = [ 'repo' => $updatedConfig->get() ];
} else {
$activatedPluginsConfig = [];
}
return array_merge_recursive( $initialNotices, $activatedPluginsConfig );
}
/**
* @param array $initialConfig
*
* @return array
*/
public static function config( array $initialConfig ) {
return self::screens( $initialConfig );
}
/**
* @param array $screens
*
* @return array
*/
public static function screens( array $screens ) {
$config = [
self::PLUGIN_ACTIVATED => [ 'screens' => [ 'plugins' ] ],
];
return array_merge_recursive( $screens, [
'repo' => [
'wpml' => $config,
],
] );
}
/**
* @param array $initialTexts
*
* @return array
*/
public static function texts( array $initialTexts ) {
return array_merge_recursive(
$initialTexts,
[
'repo' => [
'wpml' => [
self::PLUGIN_ACTIVATED => WPMLTexts::class . '::pluginActivatedRecommendation',
],
],
]
);
}
/**
* @param array $initialDismissions
*
* @return array
*/
public static function dismissions( array $initialDismissions ) {
return array_merge_recursive(
$initialDismissions,
[
self::PLUGIN_ACTIVATED => Dismissions::class . '::dismissRecommendationNotice',
]
);
}
}

View File

@@ -0,0 +1,345 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
class Texts {
protected static $repo;
protected static $product;
protected static $productURL;
protected static $apiHost;
protected static $communicationDetailsLink;
protected static $supportLink;
protected static $publishLink;
protected static $learnMoreDevKeysLink;
public static function notRegistered() {
// translators: %s Product name
$headingHTML = self::getHeadingHTML( __( 'You are using an unregistered version of %s and are not receiving compatibility and security updates', 'installer' ) );
// translators: %s Product name
$bodyHTML = self::getBodyHTML( __( '%s plugin must be registered in order to receive stability and security updates. Without these updates, the plugin may become incompatible with new versions of WordPress, which include security patches.', 'installer' ) ) .
self::inButtonAreaHTML( self::getNotRegisteredButtons() ) .
self::getDismissHTML( Account::NOT_REGISTERED );
return self::insideDiv( 'register', $headingHTML . $bodyHTML );
}
public static function expired() {
// translators: %s Product name
$headingHTML = self::getHeadingHTML( __( 'You are using an expired %s account.', 'installer' ) );
// translators: %s Product name
$bodyHTML = self::getBodyHTML( __( "Your site is using an expired %s account, which means you won't receive updates. This can lead to stability and security issues.", 'installer' ) ) .
self::inButtonAreaHTML( self::getExpiredButtons() ) .
self::getDismissHTML( Account::EXPIRED );
return self::insideDiv( 'expire', $headingHTML . $bodyHTML );
}
public static function developmentBanner() {
// translators: %s Product url
$dismissHTML = self::getDismissHTML( Account::DEVELOPMENT_MODE );
$headingHTML = '<h2>' . esc_html( sprintf( __( 'This site is registered on %s as a development site.', 'installer' ), static::$productURL ) ) . '</h2>';
// translators: %1$s is the text "update the site key" inside a link and %2$s is the text "Learn more" inside a link
$bodyText = esc_html__( 'When this site goes live, remember to %1$s from "development" to "production" to remove this message. %2$s', 'installer' );
$bodyHTML = '<p>' . sprintf(
$bodyText,
self::getPublishLinkHTML( __( 'update the site key', 'installer' ) ),
self::getLearnMoveDevKeysLinkHTML( __( 'Learn more', 'installer' ) )
) . '</p>';
return self::insideDiv( 'notice', $dismissHTML . $headingHTML . $bodyHTML );
}
public static function refunded() {
// translators: %s Product name
$headingHTML = self::getHeadingHTML( __( 'Remember to remove %s from this website', 'installer' ) );
// translators: %s Product name
$body = self::getBodyHTML( __( 'This site is using the %s plugin, which has not been paid for. After receiving a refund, you should remove this plugin from your sites. Using unregistered plugins means you are not receiving stability and security updates and will ultimately lead to problems running the site.', 'installer' ) ) .
self::inButtonAreaHTML( self::getRefundedButtons() );
return self::insideDiv( 'refund', $headingHTML . $body );
}
public static function connectionIssues() {
// translators: %1$s Product name %2$s host name (ex. wpml.org)
$headingHTML = self::getConnectionIssueHeadingHTML( __( '%1$s plugin cannot connect to %2$s', 'installer' ) );
// translators: %1$s Product name %2$s host name (ex. wpml.org)
$body = self::getConnectionIssueBodyHTML( __( '%1$s needs to connect to its server to check for new releases and security updates. Something in the network or security settings is preventing this. Please allow outgoing communication to %2$s to remove this notice.', 'installer' ) ) .
self::inLinksAreaHTML(
__( 'Need help?', 'installer' ),
// translators: %1$s is `communication error details` %2$s is ex. wpml.org technical support
__( 'See the %1$s and let us know in %2$s.', 'installer' ),
self::getCommunicationDetailsLinkHTML( __( 'communication error details', 'installer' ) ),
// translators: %s is host name (ex. wpml.org)
self::getSupportLinkHTML( __( '%s technical support', 'installer' ) )
);
return self::insideDiv( 'connection-issues', $headingHTML . $body );
}
public static function pluginActivatedRecommendation( $parameters ) {
$heading_html = self::getHeadingHTML( $parameters['recommendation_notification'] );
$body_text = sprintf(
__( 'Please install %s to allow translating %s.', 'installer' ),
strip_tags( $parameters['glue_plugin_name'] ),
$parameters['glue_check_name']
);
$body_html = self::getBodyHTML( $body_text ) .
self::inButtonAreaHTML( self::getRecommendationButtons( $parameters ) );
return self::insideDiv( 'plugin-recommendation', $heading_html . $body_html );
}
/**
* @param string $type The type is used as a suffix of the `otgs-installer-notice-` CSS class.
* @param string $html An unescaped HTML string but with escaped data (e.g. attributes, URLs, or strings in the HTML produced from any input).
*
* @return string
*/
protected static function insideDiv( $type, $html ) {
$classes = [
'notice',
'otgs-installer-notice',
'otgs-installer-notice-' . esc_attr( static::$repo ),
'otgs-installer-notice-' . esc_attr( $type ),
];
$notDismissable = [ 'refund', 'connection-issues', 'development' ];
if ( ! in_array( $type, $notDismissable ) ) {
$classes[] = 'otgs-is-dismissible';
}
return '<div class="' . implode( ' ', $classes ) . '">' .
'<div class="otgs-installer-notice-content">' .
$html .
'</div>' .
'</div>';
}
private static function getRecommendationButtons( $parameters ) {
$installButton = __( "Install and activate", 'installer' );
$dismiss = __( "Ignore and don't ask me again", 'installer' );
return self::getRecommendationInstallButtonHTML( $installButton, $parameters ) .
self::getRecommendationDismissHTML( $dismiss, $parameters );
}
/**
* @return string
*/
protected static function getNotRegisteredButtons() {
$registerUrl = \WP_Installer::menu_url();
$register = __( 'Register', 'installer' );
$stagingSite = __( 'This is a development / staging site', 'installer' );
return self::getPrimaryButtonHTML( $registerUrl, $register ) .
self::getStagingButtonHTML( $stagingSite );
}
/**
* @return string
*/
protected static function getExpiredButtons() {
$checkOrderStatusUrl = \WP_Installer::menu_url() . '&validate_repository=' . static::$repo;
$accountButton = __( 'Extend your subscription', 'installer' );
$checkButton = __( 'Check my order status', 'installer' );
$statusText = __( 'Got renewal already?', 'installer' );
$productUrl = \WP_Installer::instance()->get_product_data( static::$repo, 'url' );
return self::getPrimaryButtonHTML( $productUrl . '/account', $accountButton ) .
self::getStatusHTML( $statusText ) .
self::getRefreshButtonHTML( $checkOrderStatusUrl, $checkButton );
}
/**
* @return string
*/
private static function getRefundedButtons() {
$checkOrderStatusUrl = \WP_Installer::menu_url() . '&validate_repository=' . static::$repo;
$checkButton = __( 'Check my order status', 'installer' );
$status = __( 'Bought again?', 'installer' );
return self::getStatusHTML( $status ) .
self::getPrimaryButtonHTML( $checkOrderStatusUrl, $checkButton );
}
/**
* @param string $notice_type The method takes care of escaping the string.
*
* @return string
*/
protected static function getDismissHTML( $notice_type ) {
return '<span class="installer-dismiss-nag notice-dismiss" ' . self::getDismissedAttributes( $notice_type ) . '>'
. '<span class="screen-reader-text">' . esc_html__( 'Dismiss', 'installer' ) . '</span></span>';
}
/**
* @param string $notice_type The method takes care of escaping the string.
*
* @return string
*/
private static function getDismissedAttributes( $notice_type, $noticeId = null ) {
$dismissedAttributes = 'data-repository="' . esc_attr( static::$repo ) . '" data-notice-type="' . esc_attr( $notice_type ) . '"';
if ( $noticeId ) {
$dismissedAttributes .= '" data-notice-plugin-slug="' . esc_attr( $noticeId ) . '"';
}
return $dismissedAttributes;
}
/**
* @param string $url The method takes care of escaping the string.
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
protected static function getPrimaryButtonHTML( $url, $text ) {
return '<a class="otgs-installer-notice-status-item otgs-installer-notice-status-item-btn" href="' . esc_url( $url ) . '">' . esc_html( $text ) . '</a>';
}
/**
* @param string $url The method takes care of escaping the string.
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
protected static function getRecommendationInstallButtonHTML( $text, $parameters ) {
return
wp_nonce_field( 'recommendation_success_nonce', 'recommendation_success_nonce' ) .
'<input type="hidden" id="originalPluginData" value="' . base64_encode( json_encode( [
'slug' => $parameters['glue_check_slug'],
'repository_id' => $parameters['repository_id'],
] ) ) . '">' .
'<button class="js-install-recommended otgs-installer-notice-status-item otgs-installer-notice-status-item-btn" value="' . base64_encode( json_encode( $parameters['download_data'] ) ) . '">' . esc_html( $text ) . '</button><span class="spinner"></span>';
}
/**
* @param string $url The method takes care of escaping the string.
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
protected static function getRefreshButtonHTML( $url, $text ) {
return '<a class="otgs-installer-notice-status-item otgs-installer-notice-status-item-link otgs-installer-notice-status-item-link-refresh" href="' . esc_url( $url ) . '">' . esc_html( $text ) . '</a>';
}
/**
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
protected static function getStatusHTML( $text ) {
return '<p class="otgs-installer-notice-status-item">' . esc_html( $text ) . '</p>';
}
/**
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
protected static function getRecommendationDismissHTML( $text, $parameters ) {
return '<a class="installer-dismiss-nag otgs-installer-notice-status-item-link" ' . self::getDismissedAttributes( Recommendation::PLUGIN_ACTIVATED, $parameters['glue_check_slug'] ) . ' href="#">'
. esc_html( $text ) . '</a>';
}
/**
* @param string $html An unescaped HTML string but with escaped data (e.g. attributes, URLs, or strings in the HTML produced from any input).
*
* @return string
*/
private static function inButtonAreaHTML( $html ) {
return '<div class="otgs-installer-notice-status">' . $html . '</div>';
}
/**
* @param string $text
*
* @return string
*/
private static function inLinksAreaHTML( $title, $text, $communicationDetails, $supportLink ) {
return '<div class="otgs-installer-notice-status">
<p class="otgs-installer-notice-status-item">' . esc_html( $title ) . '</p>
<p class="otgs-installer-notice-status-item">' . sprintf( esc_html( $text ), $communicationDetails, $supportLink ) . '</p>
</div>';
}
/**
* @param string $text The method takes care of escaping the string.
* If the string contains a placeholder, it will be replaced with the value of `static::$product`.
*
* @return string
*/
protected static function getHeadingHTML( $text ) {
return '<h2>' . esc_html( sprintf( $text, static::$product ) ) . '</h2>';
}
/**
* @param string $text
*
* @return string
*/
protected static function getConnectionIssueHeadingHTML( $text ) {
return '<h2>' . esc_html( sprintf( $text, static::$product, static::$apiHost ) ) . '</h2>';
}
/**
* @param string $text The method takes care of escaping the string.
* If the string contains a placeholder, it will be replaced with the value of `static::$product`.
*
* @return string
*/
protected static function getBodyHTML( $text ) {
return '<p>' . esc_html( sprintf( $text, static::$product ) ) . '</p>';
}
/**
* @param string $text The method takes care of escaping the string.
* If the string contains a placeholder, it will be replaced with the value of `static::$product`.
*
* @return string
*/
protected static function getConnectionIssueBodyHTML( $text ) {
return '<p>' . esc_html( sprintf( $text, static::$product, static::$apiHost ) ) . '</p>';
}
/**
* @param string $text The method takes care of escaping the string.
*
* @return string
*/
private static function getStagingButtonHTML( $text ) {
return '<a class="otgs-installer-notice-status-item otgs-installer-notice-status-item-link installer-dismiss-nag" ' . self::getDismissedAttributes( Account::NOT_REGISTERED ) . '>' . esc_html( $text ) . '</a>';
}
private static function getCommunicationDetailsLinkHTML( $text ) {
return '<a href="' . esc_url( admin_url( static::$communicationDetailsLink ) ) . '">' . esc_html( $text ) . '</a>';
}
private static function getSupportLinkHTML( $text ) {
return '<a href="' . esc_url( static::$supportLink ) . '">' . esc_html( sprintf( $text, static::$product ) ) . '</a>';
}
/**
* @param string $text
*
* @return string
*/
private static function getPublishLinkHTML( $text ) {
$publishLink = static::$publishLink . \WP_Installer::instance()->get_site_key( static::$repo );
return self::makeLink( $publishLink, $text );
}
private static function getLearnMoveDevKeysLinkHTML( $text ) {
return self::makeLink( static::$learnMoreDevKeysLink, $text );
}
private static function makeLink( $url, $text ) {
return '<a href="' . esc_url( $url ) . '" target="_blank">' . esc_html( $text ) . '</a>';
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
class ToolsetTexts extends Texts {
protected static $repo = 'toolset';
protected static $product = 'Toolset';
protected static $productURL = 'Toolset.com';
protected static $apiHost = 'toolset.com';
protected static $communicationDetailsLink = '/admin.php?page=otgs-installer-support';
protected static $supportLink = 'https://toolset.com/forums/forum/professional-support/';
protected static $publishLink = 'https://toolset.com/account/sites/?publish=';
protected static $learnMoreDevKeysLink = 'https://toolset.com/faq/how-to-install-and-register-toolset/?utm_source=plugin&utm_medium=gui&utm_campaign=types#registering-toolset-in-a-development-environment';
}

View File

@@ -0,0 +1,15 @@
<?php
namespace OTGS\Installer\AdminNotices\Notices;
class WPMLTexts extends Texts {
protected static $repo = 'wpml';
protected static $product = 'WPML';
protected static $productURL = 'WPML.org';
protected static $apiHost = 'wpml.org';
protected static $communicationDetailsLink = '/admin.php?page=otgs-installer-support';
protected static $supportLink = 'https://wpml.org/forums/';
protected static $publishLink = 'https://wpml.org/account/sites/?publish=';
protected static $learnMoreDevKeysLink = 'https://wpml.org/faq/install-wpml/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore/#register-development-sites';
}