This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,388 @@
<?php
namespace Vrts\Core;
use Exception;
use WP_Filesystem_Direct;
use Vrts\Core\Settings\Manager;
use Vrts\Core\Traits\Singleton;
use Vrts\Core\Traits\Macroable;
/**
* Main class responsible for defining all plugin functionalities.
*
* It has methods that are definied in other classes and made available here using the Macroable.
*
* @method string thisMenuHasMacro() Added in Vrts\Features\Menu
*/
class Plugin {
use Singleton;
use Macroable;
/**
* Plugin file.
*
* @var string
*/
protected $plugin_file;
/**
* Plugin identifier.
*
* @var string
*/
protected $plugin_identifier;
/**
* Modules and objects instances list
*
* @var array
*/
protected $factory = [];
/**
* Load plugin functions and features.
*
* @param string $plugin_identifier Plugin identifier.
* @param array $features Plugin features.
*/
public function setup( $plugin_identifier, $features ) {
$this->plugin_identifier = $plugin_identifier;
$this->plugin_file = VRTS_PLUGIN_FILE;
$this->features( $features );
}
/**
* Get Plugin identifier.
*
* @return string Plugin identifier.
*/
public function get_plugin_identifier() {
return str_replace( '-', '_', $this->plugin_identifier );
}
/**
* Get the plugin url.
*
* @param string $file File to return the url for in the plugin directory.
*
* @return string
*/
public function get_plugin_url( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$url = plugin_dir_url( $this->plugin_file );
} else {
$url = plugin_dir_url( $this->plugin_file ) . $file;
}
return $url;
}
/**
* Get the plugin path.
*
* @param string $file File to return the path for in the plugin directory.
*
* @return string
*/
public function get_plugin_path( $file = '' ) {
$file = ltrim( $file, '/' );
if ( empty( $file ) ) {
$path = plugin_dir_path( $this->plugin_file );
} else {
$path = plugin_dir_path( $this->plugin_file ) . $file;
}
return $path;
}
/**
* Get the plugin file.
*
* @return string
*/
public function get_plugin_file() {
return $this->plugin_file;
}
/**
* Auto include files from defined directories.
*
* @param array $directories Directories to auto include files from.
*/
protected function includes( $directories ) {
foreach ( $directories as $directory ) {
foreach ( glob( $this->get_plugin_path( "{$directory}/*.php" ) ) as $filename ) {
require_once $filename;
}
}
}
/**
* Load plugin features.
*
* @param array $features Plugin features.
*/
protected function features( $features ) {
foreach ( $features as $namespace => $directory ) {
foreach ( glob( $this->get_plugin_path( "{$directory}/class-*.php" ) ) as $filename ) {
$dir = $this->get_plugin_path( $directory );
$filename = str_replace( $dir, '', $filename );
$class_name = str_replace( [ '/class-', '.php' ], '', $filename );
$class_name = ucwords( $class_name, '-' );
$class_name = str_replace( '-', '_', $class_name );
$class_name = $namespace . $class_name;
new $class_name();
}
}
}
/**
* Create objects.
*
* @param string $key Array key that the object will be accessible.
* @param object $instance object that needs to created.
*
* @return object Created object.
*/
protected function factory( $key, $instance ) {
if ( ! isset( $this->factory[ $key ] ) ) {
$this->factory[ $key ] = $instance;
}
return $this->factory[ $key ];
}
/**
* Get Settings Manager.
*
* @return Manager Settings Manager.
*/
public function settings() {
return $this->factory( 'settings', new Manager() );
}
/**
* Get the plugin informations.
*
* @param string $info What information do we want.
*
* @return string Desired plugin information.
*/
public function get_plugin_info( $info ) {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin = get_plugin_data( $this->plugin_file );
$infos = [
'name' => 'Name',
'version' => 'Version',
'uri' => 'PluginURI',
'author' => 'Author',
'author_uri' => 'AuthorURI',
'description' => 'Description',
'requires_wp' => 'RequiresWP',
'requires_php' => 'RequiresPHP',
'text_domain' => 'TextDomain',
'domain_path' => 'DomainPath',
];
return isset( $infos[ $info ] ) ? $plugin[ $infos[ $info ] ] : '';
}
/**
* Get a component with passing arguments.
*
* Makes it easy for a plugin to reuse sections of code.
*
* @param string $name The component name.
* @param array $data Pass data with the component load.
*
* @return string Component markup
*
* @throws Exception If there is no file found.
*/
public function get_component( $name, $data = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $data is used by included template files.
ob_start();
if ( file_exists( $this->get_plugin_path( "components/{$name}/index.php" ) ) ) {
include $this->get_plugin_path( "components/{$name}/index.php" );
} else {
throw new Exception( esc_html( "Components file 'components/{$name}/index.php' cannot be located" ) );
}
return ob_get_clean();
}
/**
* Load a component with passing arguments.
*
* Makes it easy for a plugin to reuse sections of code.
*
* @param string $name The component name.
* @param array $data Pass data with the component load.
*
* @throws Exception If there is no file found.
*/
public function component( $name, $data = [] ) {
$safe_escaped_component = $this->get_component( $name, $data );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- It's escaped.
echo $safe_escaped_component;
}
/**
* Get an icon.
*
* @param string $icon The icon name.
* @param bool $escape If true it will escape the icon.
*
* @return string Icon.
*/
public function get_icon( $icon, $escape = true ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$filesystem = new WP_Filesystem_Direct( null );
$icon = $filesystem->get_contents( $this->get_plugin_path( "assets/icons/{$icon}.svg" ) );
if ( $escape ) {
return wp_kses( $icon, $this->wp_kses_svg() );
}
return $icon;
}
/**
* Load an icon.
*
* @param string $icon The icon name.
*/
public function icon( $icon ) {
echo wp_kses(
$this->get_icon( $icon, false ),
$this->wp_kses_svg()
);
}
/**
* Get the plugin logo.
*
* @param bool $escape If true it will escape the logog.
*
* @return string the logo as string.
*/
public function get_logo( $escape = true ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$filesystem = new WP_Filesystem_Direct( null );
$logo = $filesystem->get_contents( $this->get_plugin_path( 'assets/images/vrts-logo.svg' ) );
if ( $escape ) {
return wp_kses( $logo, $this->wp_kses_svg() );
}
return $logo;
}
/**
* The plugin logo.
*/
public function logo() {
echo wp_kses(
$this->get_logo( false ),
$this->wp_kses_svg()
);
}
/**
* Get public post types.
*
* @return array public post types.
*/
public function get_public_post_types() {
$default_post_types = [ 'post', 'page' ];
$custom_post_types = get_post_types( [
'public' => true,
'_builtin' => false,
] );
return array_merge( $default_post_types, $custom_post_types );
}
/**
* Get snapshot placeholder image.
*
* @param boolean $base64 return base 64 encoded or not.
*
* @return string the placeholder image as string.
*/
public function get_snapshot_placeholder_image( $base64 = true ) {
$icon_path = $this->get_plugin_path( 'assets/images/vrts-snapshot-placeholder.svg' );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- It's a file.
$svg = file_get_contents( $icon_path );
if ( $base64 ) {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- It's a file.
return 'data:image/svg+xml;base64,' . base64_encode( $svg );
}
return $svg;
}
/**
* Get allowed SVG tags and attributes.
*
* @return array Allowed SVG tags and attributes.
*/
private function wp_kses_svg() {
return apply_filters( 'vrts_wp_kses_svg', [
'svg' => [
'class' => true,
'width' => true,
'height' => true,
'version' => true,
'fill' => true,
'viewbox' => true,
'xmlns' => true,
'aria-hidden' => true,
'focusable' => true,
'style' => [],
'xml:space' => [],
],
'path' => [
'd' => true,
'fill' => true,
'stroke' => true,
'stroke-linecap' => true,
'stroke-linejoin' => true,
'stroke-width' => true,
'vector-effect' => true,
'transform-origin' => true,
],
'circle' => [
'fill' => true,
'stroke' => true,
'cx' => true,
'cy' => true,
'r' => true,
'vector-effect' => true,
],
'rect' => [
'x' => true,
'y' => true,
'width' => true,
'height' => true,
'fill' => true,
'class' => true,
'rx' => true,
],
] );
}
}
@@ -0,0 +1,138 @@
<?php
namespace Vrts\Core\Settings;
class Manager {
/**
* Sections
*
* @var array
*/
protected $sections = [];
/**
* Settings.
*
* @var array
*/
protected $settings = [];
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_init', [ $this, 'register_settings' ] );
}
/**
* Register settings.
*/
public function register_settings() {
$this->add_sections();
$this->add_settings();
}
/**
* Get option.
*
* @param string $id Settings setting ID.
* @param bool $transform_value Transform value.
*
* @return mixed Option value.
*/
public function get_option( $id, $transform_value = true ) {
$default_value = isset( $this->settings[ $id ] ) ? $this->settings[ $id ]['default'] ?? '' : false;
$value = get_option( $id, $default_value );
if ( $transform_value && isset( $this->settings[ $id ]['return_value_callback'] ) ) {
$value = call_user_func( $this->settings[ $id ]['return_value_callback'], $value );
}
return $value;
}
/**
* Get settings section page.
*
* @param string $id Setting section ID.
*
* @return string
*/
private function get_section_page( $id ) {
return isset( $this->sections[ $id ] ) ? $this->sections[ $id ]['page'] : false;
}
/**
* Output field.
*
* @param array $args Setting args.
*/
private function get_field( $args ) {
$value = $args['value'] ?? $this->get_option( $args['id'], false );
include vrts()->get_plugin_path( "includes/core/settings/field-{$args['type']}/index.php" );
}
/**
* Add all sections.
*/
private function add_sections() {
foreach ( $this->sections as $id => $args ) {
add_settings_section(
$id,
$args['title'] ?? '',
$args['callback'] ?? '__return_false',
$args['page'] ?? 'general'
);
}
}
/**
* Add all settings.
*/
private function add_settings() {
foreach ( $this->settings as $id => $args ) {
register_setting(
$this->get_section_page( $args['section'] ),
$id,
[
'type' => $args['value_type'] ?? 'string',
'sanitize_callback' => $args['sanitize_callback'] ?? null,
'show_in_rest' => $args['show_in_rest'] ?? false,
'default' => $args['default'] ?? '',
]
);
add_settings_field(
$id,
$args['title'] ?? '',
function () use ( $args ) {
$this->get_field( $args );
},
$this->get_section_page( $args['section'] ),
$args['section'],
[
'class' => "vrts-settings-{$args['type']}",
'label_for' => ! in_array( $args['type'], [ 'info', 'checkbox', 'radio' ], true ) ? $id : '',
]
);
}//end foreach
}
/**
* Add a settings section.
*
* @param array $args Array of properties for the new section object.
*/
public function add_section( $args = [] ) {
$this->sections[ $args['id'] ] = $args;
}
/**
* Add a settings setting.
*
* @param array $args Array of properties for the new setting and control object.
*/
public function add_setting( $args = [] ) {
$this->settings[ $args['id'] ] = $args;
}
}
@@ -0,0 +1,28 @@
<fieldset <?php echo isset( $args['is_pro'] ) && false === $args['is_pro'] ? 'data-a11y-dialog-show="vrts-modal-pro-settings"' : ''; ?>>
<legend class="screen-reader-text"><?php echo esc_html( $args['title'] ); ?></legend>
<label>
<input
type="checkbox"
id="<?php echo esc_attr( $args['id'] ); ?>"
name="<?php echo esc_html( $args['id'] ); ?>"
value="1"
<?php checked( $value, 1 ); ?>
<?php wp_readonly( isset( $args['readonly'] ) && $args['readonly'] ); ?>
<?php disabled( isset( $args['disabled'] ) && $args['disabled'] ); ?>>
<?php echo wp_kses_post( $args['label'] ); ?>
</label>
<?php
if ( isset( $args['is_pro'] ) && false === $args['is_pro'] ) :
?>
<span class="vrts-settings__pro-label"><?php esc_html_e( 'Pro', 'visual-regression-tests' ); ?></span>
<?php
endif;
?>
</fieldset>
<?php
if ( isset( $args['description'] ) ) :
?>
<p class="description"><?php echo wp_kses_post( $args['description'] ); ?></p>
<?php
endif;
@@ -0,0 +1,10 @@
<fieldset>
<?php echo wp_kses_post( $args['description'] ); ?>
<?php
if ( isset( $args['is_pro'] ) && false === $args['is_pro'] ) :
?>
<span class="vrts-settings__pro-label"><?php esc_html_e( 'Pro', 'visual-regression-tests' ); ?></span>
<?php
endif;
?>
</fieldset>
@@ -0,0 +1,23 @@
<fieldset <?php echo isset( $args['is_pro'] ) && false === $args['is_pro'] ? 'data-a11y-dialog-show="vrts-modal-pro-settings"' : ''; ?>>
<input
type="password" id="<?php echo esc_attr( $args['id'] ); ?>"
class="regular-text"
name="<?php echo esc_attr( $args['id'] ); ?>"
value="<?php echo esc_attr( $value ); ?>"
placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>"
<?php wp_readonly( isset( $args['readonly'] ) && $args['readonly'] ); ?>
<?php disabled( isset( $args['disabled'] ) && $args['disabled'] ); ?>>
<?php
if ( isset( $args['is_pro'] ) && false === $args['is_pro'] ) :
?>
<span class="vrts-settings__pro-label"><?php esc_html_e( 'Pro', 'visual-regression-tests' ); ?></span>
<?php
endif;
?>
</fieldset>
<?php
if ( isset( $args['description'] ) ) :
?>
<p class="description"><?php echo wp_kses_post( $args['description'] ); ?></p>
<?php
endif;
@@ -0,0 +1,26 @@
<fieldset <?php echo isset( $args['is_pro'] ) && false === $args['is_pro'] ? 'data-a11y-dialog-show="vrts-modal-pro-settings"' : ''; ?>>
<select
name="<?php echo esc_attr( $args['id'] ); ?>"
id="<?php echo esc_attr( $args['id'] ); ?>"
<?php wp_readonly( isset( $args['readonly'] ) && $args['readonly'] ); ?>
<?php disabled( isset( $args['disabled'] ) && $args['disabled'] ); ?>>
<?php
foreach ( $args['choices'] as $key => $name ) :
?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $key, $value ); ?>><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
</select>
<?php
if ( isset( $args['is_pro'] ) && false === $args['is_pro'] ) :
?>
<span class="vrts-settings__pro-label"><?php esc_html_e( 'Pro', 'visual-regression-tests' ); ?></span>
<?php
endif;
?>
</fieldset>
<?php
if ( isset( $args['description'] ) ) :
?>
<p class="description"><?php echo wp_kses_post( $args['description'] ); ?></p>
<?php
endif;
@@ -0,0 +1,23 @@
<fieldset <?php echo isset( $args['is_pro'] ) && false === $args['is_pro'] ? 'data-a11y-dialog-show="vrts-modal-pro-settings"' : ''; ?>>
<input
type="text" id="<?php echo esc_attr( $args['id'] ); ?>"
class="regular-text"
name="<?php echo esc_attr( $args['id'] ); ?>"
value="<?php echo esc_attr( $value ); ?>"
placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>"
<?php wp_readonly( isset( $args['readonly'] ) && $args['readonly'] ); ?>
<?php disabled( isset( $args['disabled'] ) && $args['disabled'] ); ?>>
<?php
if ( isset( $args['is_pro'] ) && false === $args['is_pro'] ) :
?>
<span class="vrts-settings__pro-label"><?php esc_html_e( 'Pro', 'visual-regression-tests' ); ?></span>
<?php
endif;
?>
</fieldset>
<?php
if ( isset( $args['description'] ) ) :
?>
<p class="description"><?php echo wp_kses_post( $args['description'] ); ?></p>
<?php
endif;
@@ -0,0 +1,103 @@
<?php
namespace Vrts\Core\Traits;
use Closure;
use ReflectionClass;
use ReflectionMethod;
use BadMethodCallException;
trait Macroable {
/**
* Macros.
*
* @var array
*/
protected static $macros = [];
/**
* Registerd macro.
*
* @param string $name Name of macro function.
* @param callable $macro Callback function.
*/
public static function macro( $name, $macro ) {
static::$macros[ $name ] = $macro;
}
/**
* Register mixins.
*
* @param class $mixin Class with mixins.
*/
public static function mixin( $mixin ) {
$methods = ( new ReflectionClass( $mixin ) )->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ( $methods as $method ) {
$method->setAccessible( true );
static::macro( $method->name, $method->invoke( $mixin ) );
}
}
/**
* Return true if has macro.
*
* @param string $name Name of macro function.
*
* @return bool
*/
public static function has_macro( $name ) {
return isset( static::$macros[ $name ] );
}
/**
* Call static method.
*
* @param string $method The function to be called.
* @param array $parameters The parameters to be passed to the function, as an indexed array.
* @return callable The function to be called.
*
* @throws BadMethodCallException When method doesn't exist.
*/
public static function __callStatic( $method, $parameters ) {
if ( ! static::has_macro( $method ) ) {
throw new BadMethodCallException( esc_html( "Method {$method} does not exist." ) );
}
$macro = static::$macros[ $method ];
if ( $macro instanceof Closure ) {
return call_user_func_array( Closure::bind( $macro, null, static::class ), $parameters );
}
return call_user_func_array( $macro, $parameters );
}
/**
* Call method.
*
* @param string $method The function to be called.
* @param array $parameters The parameters to be passed to the function, as an indexed array.
* @return callable The function to be called.
*
* @throws BadMethodCallException When method doesn't exist.
*/
public function __call( $method, $parameters ) {
if ( ! static::has_macro( $method ) ) {
throw new BadMethodCallException( esc_html( "Method {$method} does not exist." ) );
}
$macro = static::$macros[ $method ];
if ( $macro instanceof Closure ) {
return call_user_func_array( $macro->bindTo( $this, static::class ), $parameters );
}
return call_user_func_array( $macro, $parameters );
}
}
@@ -0,0 +1,43 @@
<?php
namespace Vrts\Core\Traits;
trait Singleton {
/**
* The reference to Singleton instance of this class.
*
* @var Singleton
*/
protected static $instance;
/**
* Returns the Singleton instance of this class.
*
* @return Singleton The Singleton instance.
*/
public static function get_instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Protected constructor to prevent creating a new instance of the
* Singleton via the `new` operator from outside of this class.
*/
protected function __construct() {}
/**
* Private clone method to prevent cloning of the instance of the
* Singleton instance.
*/
private function __clone() {}
/**
* Public unserialize method to prevent unserializing of the Singleton
* instance.
*/
public function __wakeup() {}
}
@@ -0,0 +1,121 @@
<?php
namespace Vrts\Core\Utilities;
class Array_Helpers {
/**
* Parses the string into variables without the max_input_vars limitation.
*
* @param string $str String.
*
* @return array Parsed array.
*/
public static function parse_str( $str ) {
if ( '' === $str ) {
return false;
}
$result = [];
$pairs = explode( '&', $str );
foreach ( $pairs as $key => $pair ) {
// use the original parse_str() on each element.
parse_str( $pair, $params );
$k = key( $params );
if ( ! isset( $result[ $k ] ) ) {
$result += $params;
} else {
$result[ $k ] = self::array_merge_recursive( $result[ $k ], $params[ $k ] );
}
}
return $result;
}
/**
* Merge arrays without converting values with duplicate keys to arrays as array_merge_recursive does.
*
* As seen here http://php.net/manual/en/function.array-merge-recursive.php#92195
*
* @param array $array1 First array.
* @param array $array2 Second array.
*
* @return array Merged array.
*/
public static function array_merge_recursive( array $array1, array $array2 ) {
$merged = $array1;
foreach ( $array2 as $key => $value ) {
if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
$merged[ $key ] = self::array_merge_recursive( $merged[ $key ], $value );
} elseif ( is_numeric( $key ) && isset( $merged[ $key ] ) ) {
$merged[] = $value;
} else {
$merged[ $key ] = $value;
}
}
return $merged;
}
/**
* Recursive wp_parse_args for multidimensional arrays.
*
* @see http://mekshq.com/recursive-wp-parse-args-wordpress-function/.
*
* @param array $args Value to merge with $defaults.
* @param array $defaults Array that serves as the defaults.
*
* @return array Merged user defined values with defaults.
*/
public static function parse_args( $args, $defaults ) {
$args = (array) $args;
$defaults = (array) $defaults;
$result = $defaults;
foreach ( $args as $k => $v ) {
if ( is_array( $v ) && isset( $result[ $k ] ) ) {
$result[ $k ] = self::parse_args( $v, $result[ $k ] );
} else {
$result[ $k ] = $v;
}
}
return $result;
}
/**
* Implode array keys with desired value.
*
* @param array $arr Array to implode.
* @param string $value Checked for this value.
* @param string|array $remove Array key or keys to remove before implode.
* @param string $before Value to prepend to array keys.
* @param string $after Value to append to array keys.
*
* @return string Imploded array keys with value.
*/
public static function implode_array_keys( $arr, $value, $remove = false, $before = '', $after = '' ) {
if ( is_array( $remove ) ) {
foreach ( $remove as $key ) {
unset( $arr[ $key ] );
}
} elseif ( $remove ) {
unset( $arr[ $remove ] );
}
$new_array = [];
foreach ( $arr as $key => $item ) {
if ( $arr[ $key ] === $value ) {
$new_array[ $before . $key . $after ] = $item;
}
}
return implode( ' ', array_keys( $new_array ) );
}
}
@@ -0,0 +1,177 @@
<?php
namespace Vrts\Core\Utilities;
/**
* Async Request.
*
* @see https://github.com/deliciousbrains/wp-background-processing
*/
abstract class Async_Request {
/**
* Prefix.
*
* @var string
*/
protected $prefix;
/**
* Action.
*
* @var string
*/
protected $action = 'async_request';
/**
* Identifier.
*
* @var mixed
*/
protected $identifier;
/**
* Data.
*
* @var array
*/
protected $data = [];
/**
* Initiate new async request.
*/
public function __construct() {
// if the prefix hasn't been set explicity use the theme identifier.
if ( is_null( $this->prefix ) ) {
$this->prefix = vrts()->get_plugin_identifier();
}
// Uses unique prefix per blog so each blog has separate queue.
$this->prefix = $this->prefix . '_' . get_current_blog_id();
$this->identifier = $this->prefix . '_' . $this->action;
add_action( 'wp_ajax_' . $this->identifier, [ $this, 'maybe_handle' ] );
add_action( 'wp_ajax_nopriv_' . $this->identifier, [ $this, 'maybe_handle' ] );
}
/**
* Set data used during the request.
*
* @param array $data Data.
*
* @return $this
*/
public function data( $data ) {
$this->data = $data;
return $this;
}
/**
* Dispatch the async request.
*
* @return array|WP_Error
*/
public function dispatch() {
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
$args = $this->get_post_args();
return wp_remote_post( esc_url_raw( $url ), $args );
}
/**
* Get query args.
*
* @return array
*/
protected function get_query_args() {
if ( property_exists( $this, 'query_args' ) ) {
return $this->query_args;
}
$args = [
'action' => $this->identifier,
'nonce' => wp_create_nonce( $this->identifier ),
];
/**
* Filters the post arguments used during an async request.
*
* @param array $url
*/
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
return apply_filters( $this->identifier . '_query_args', $args );
}
/**
* Get query URL.
*
* @return string
*/
protected function get_query_url() {
if ( property_exists( $this, 'query_url' ) ) {
return $this->query_url;
}
$url = admin_url( 'admin-ajax.php' );
/**
* Filters the post arguments used during an async request.
*
* @param string $url
*/
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
return apply_filters( $this->identifier . '_query_url', $url );
}
/**
* Get post args.
*
* @return array
*/
protected function get_post_args() {
if ( property_exists( $this, 'post_args' ) ) {
return $this->post_args;
}
$args = [
'timeout' => 0.01,
'blocking' => false,
'body' => $this->data,
'cookies' => $_COOKIE,
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using default wp hook.
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
];
/**
* Filters the post arguments used during an async request.
*
* @param array $args
*/
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
return apply_filters( $this->identifier . '_post_args', $args );
}
/**
* Maybe handle.
*
* Check for correct nonce and pass to handler.
*/
public function maybe_handle() {
// Don't lock up other requests while processing.
session_write_close();
check_ajax_referer( $this->identifier, 'nonce' );
$this->handle();
wp_die();
}
/**
* Handle.
*
* Override this method to perform any actions required
* during the async request.
*/
abstract protected function handle();
}
@@ -0,0 +1,480 @@
<?php
namespace Vrts\Core\Utilities;
/**
* Background Process.
*
* @see https://github.com/deliciousbrains/wp-background-processing
*/
abstract class Background_Process extends Async_Request {
/**
* Action name.
*
* @var string
*/
protected $action = 'background_process';
/**
* Start time of current process.
*
* @var int
*/
protected $start_time = 0;
/**
* Cron_hook_identifier.
*
* @var mixed
*/
protected $cron_hook_identifier;
/**
* Cron_interval_identifier.
*
* @var mixed
*/
protected $cron_interval_identifier;
/**
* Initiate new background process.
*/
public function __construct() {
parent::__construct();
$this->cron_hook_identifier = $this->identifier . '_cron';
$this->cron_interval_identifier = $this->identifier . '_cron_interval';
add_action( $this->cron_hook_identifier, [ $this, 'handle_cron_healthcheck' ] );
// phpcs:ignore WordPress.WP.CronInterval -- Verified 5 min.
add_filter( 'cron_schedules', [ $this, 'schedule_cron_healthcheck' ] );
}
/**
* Dispatch.
*/
public function dispatch() {
// Schedule the cron healthcheck.
$this->schedule_event();
// Perform remote post.
return parent::dispatch();
}
/**
* Push to queue.
*
* @param mixed $data Data.
*
* @return $this
*/
public function push_to_queue( $data ) {
$this->data[] = $data;
return $this;
}
/**
* Save queue.
*
* @return $this
*/
public function save() {
$key = $this->generate_key();
if ( ! empty( $this->data ) ) {
update_site_option( $key, $this->data );
}
return $this;
}
/**
* Update queue.
*
* @param string $key Key.
* @param array $data Data.
*
* @return $this
*/
public function update( $key, $data ) {
if ( ! empty( $data ) ) {
update_site_option( $key, $data );
}
return $this;
}
/**
* Delete queue.
*
* @param string $key Key.
*
* @return $this
*/
public function delete( $key ) {
delete_site_option( $key );
return $this;
}
/**
* Generates a unique key based on microtime. Queue items are
* given a unique key so that they can be merged upon save.
*
* @param int $length Length.
*
* @return string
*/
protected function generate_key( $length = 64 ) {
$unique = md5( microtime() . wp_rand() );
$prepend = $this->identifier . '_batch_';
return substr( $prepend . $unique, 0, $length );
}
/**
* Checks whether data exists within the queue and that
* the process is not already running.
*/
public function maybe_handle() {
// Don't lock up other requests while processing.
session_write_close();
if ( $this->is_process_running() ) {
// Background process already running.
wp_die();
}
if ( $this->is_queue_empty() ) {
// No data to process.
wp_die();
}
check_ajax_referer( $this->identifier, 'nonce' );
$this->handle();
wp_die();
}
/**
* Is queue empty.
*
* @return bool
*/
protected function is_queue_empty() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
// Interpolated variables $table and $column are OK to use like this.
// @codingStandardsIgnoreStart.
$count = $wpdb->get_var( $wpdb->prepare( "
SELECT COUNT(*)
FROM {$table}
WHERE {$column} LIKE %s
", $key ) );
// @codingStandardsIgnoreEnd.
return ( $count > 0 ) ? false : true;
}
/**
* Check whether the current process is already running
* in a background process.
*/
protected function is_process_running() {
if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
// Process already running.
return true;
}
return false;
}
/**
* Lock the process so that multiple instances can't run simultaneously.
* Override if applicable, but the duration should be greater than that
* defined in the time_exceeded() method.
*/
protected function lock_process() {
// Set start time of current process.
$this->start_time = time();
// 1 minute
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60;
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
}
/**
* Unlock the process so that other instances can spawn.
*
* @return $this
*/
protected function unlock_process() {
delete_site_transient( $this->identifier . '_process_lock' );
return $this;
}
/**
* Get batch.
*
* @return stdClass Return the first batch from the queue
*/
protected function get_batch() {
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
$key_column = 'option_id';
$value_column = 'option_value';
if ( is_multisite() ) {
$table = $wpdb->sitemeta;
$column = 'meta_key';
$key_column = 'meta_id';
$value_column = 'meta_value';
}
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
// Interpolated variables $table, $column and $key_column are OK to use like this.
// @codingStandardsIgnoreStart.
$query = $wpdb->get_row( $wpdb->prepare( "
SELECT *
FROM {$table}
WHERE {$column} LIKE %s
ORDER BY {$key_column} ASC
LIMIT 1
", $key ) );
// @codingStandardsIgnoreEnd.
$batch = new \stdClass();
$batch->key = $query->$column;
$batch->data = maybe_unserialize( $query->$value_column );
return $batch;
}
/**
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle() {
$this->lock_process();
do {
$batch = $this->get_batch();
foreach ( $batch->data as $key => $value ) {
$task = $this->task( $value );
if ( false !== $task ) {
$batch->data[ $key ] = $task;
} else {
unset( $batch->data[ $key ] );
}
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
// Batch limits reached.
break;
}
}
// Update or delete current batch.
if ( ! empty( $batch->data ) ) {
$this->update( $batch->key, $batch->data );
} else {
$this->delete( $batch->key );
}
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
$this->unlock_process();
// Start next batch or complete process.
if ( ! $this->is_queue_empty() ) {
$this->dispatch();
} else {
$this->complete();
}
wp_die();
}
/**
* Ensures the batch process never exceeds 90%
* of the maximum WordPress memory.
*
* @return bool
*/
protected function memory_exceeded() {
$memory_limit = $this->get_memory_limit() * 0.9;
// 90% of max memory.
$current_memory = memory_get_usage( true );
$return = false;
if ( $current_memory >= $memory_limit ) {
$return = true;
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
return apply_filters( $this->identifier . '_memory_exceeded', $return );
}
/**
* Get memory limit.
*
* @return int
*/
protected function get_memory_limit() {
if ( function_exists( 'ini_get' ) ) {
$memory_limit = ini_get( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M';
}
if ( ! $memory_limit || - 1 === intval( $memory_limit ) ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
return wp_convert_hr_to_bytes( $memory_limit );
}
/**
* Ensures the batch never exceeds a sensible time limit.
* A timeout limit of 30s is common on shared hosting.
*
* @return bool
*/
protected function time_exceeded() {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
$finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 );
// 20 seconds.
$return = false;
if ( time() >= $finish ) {
$return = true;
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
return apply_filters( $this->identifier . '_time_exceeded', $return );
}
/**
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
// Unschedule the cron healthcheck.
$this->clear_scheduled_event();
}
/**
* Schedule cron healthcheck.
*
* @access public
*
* @param mixed $schedules Schedules.
*
* @return mixed
*/
public function schedule_cron_healthcheck( $schedules ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
if ( property_exists( $this, 'cron_interval' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Prefix is theme identifier and action name.
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
}
// Adds every 5 minutes to the existing schedules.
$schedules[ $this->identifier . '_cron_interval' ] = [
'interval' => MINUTE_IN_SECONDS * $interval,
/* translators: Cron Interval in minutes. */
'display' => sprintf( esc_html_x( 'Every %d Minutes', 'cron interval description', 'colorio' ), $interval ),
];
return $schedules;
}
/**
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
if ( $this->is_process_running() ) {
// Background process already running.
exit;
}
if ( $this->is_queue_empty() ) {
// No data to process.
$this->clear_scheduled_event();
exit;
}
$this->handle();
exit;
}
/**
* Schedule event.
*/
protected function schedule_event() {
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
}
}
/**
* Clear scheduled event.
*/
protected function clear_scheduled_event() {
$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
}
}
/**
* Stop processing queue items, clear cronjob and delete batch.
*/
public function cancel_process() {
if ( ! $this->is_queue_empty() ) {
$batch = $this->get_batch();
$this->delete( $batch->key );
wp_clear_scheduled_hook( $this->cron_hook_identifier );
}
}
/**
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param mixed $item Queue item to iterate over.
*
* @return mixed
*/
abstract protected function task( $item );
}
@@ -0,0 +1,103 @@
<?php
namespace Vrts\Core\Utilities;
class Color_Helpers {
/**
* Converts a color from hex to rgba.
*
* @param string $color The color to convert.
* @param int $opacity The color opacity.
* @param string $return_type The return format, string or array.
*
* @return string|array
*/
public static function hex_to_rgba( $color, $opacity = 1, $return_type = 'array' ) {
$color = str_replace( '#', '', $color );
if ( strlen( $color ) === 3 ) {
$r = hexdec( substr( $color, 0, 1 ) . substr( $color, 0, 1 ) );
$g = hexdec( substr( $color, 1, 1 ) . substr( $color, 1, 1 ) );
$b = hexdec( substr( $color, 2, 1 ) . substr( $color, 2, 1 ) );
} else {
$r = hexdec( substr( $color, 0, 2 ) );
$g = hexdec( substr( $color, 2, 2 ) );
$b = hexdec( substr( $color, 4, 2 ) );
}
$rgba = [ $r, $g, $b, $opacity ];
if ( 'string' === $return_type ) {
return 'rgba(' . implode( ',', $rgba ) . ')';
}
return $rgba;
}
/**
* Converts a color from rgba to hsla.
*
* @param string $color The color to convert.
* @param int $opacity The color opacity.
* @param string $return_type The return format, string or array.
*
* @return string|array
*/
public static function rgba_to_hsla( $color, $opacity = 1, $return_type = 'array' ) {
$r = $color[0] / 255;
$g = $color[1] / 255;
$b = $color[2] / 255;
$min = min( $r, min( $g, $b ) );
$max = max( $r, max( $g, $b ) );
$delta = $max - $min;
$h = 0;
$s = 0;
$l = 0;
if ( $delta > 0 ) {
if ( $max === $r ) {
$h = fmod( ( ( $g - $b ) / $delta ), 6 );
} elseif ( $max === $g ) {
$h = ( $b - $r ) / $delta + 2;
} else {
$h = ( $r - $g ) / $delta + 4;
}
}
$h = round( $h * 60 );
if ( $h < 0 ) {
$h += 360;
}
$l = ( $min + $max ) / 2;
$s = 0 === $delta ? 0 : $delta / ( 1 - abs( 2 * $l - 1 ) );
$s = round( $s * 100, 1 );
$l = round( $l * 100, 1 );
$hsla = [ $h, "$s%", "$l%", $opacity ];
if ( 'string' === $return_type ) {
return 'hsla(' . implode( ',', $hsla ) . ')';
}
return $hsla;
}
/**
* Converts a color from hex to hsla.
*
* @param string $color The color to convert.
* @param int $opacity The color opacity.
* @param string $return_type The return format, string or array.
*
* @return string|array
*/
public static function hex_to_hsla( $color, $opacity = 1, $return_type = 'array' ) {
$rgba = self::hex_to_rgba( $color, $opacity );
return self::rgba_to_hsla( $rgba, $opacity, $return_type );
}
}
@@ -0,0 +1,111 @@
<?php
namespace Vrts\Core\Utilities;
use DateTime;
use DateTimeZone;
class Date_Time_Helpers {
/**
* Get the date and time WordPress native formatted.
*
* @param mixed $date a DateTime string.
*
* @return string Formatted date and time.
*/
public static function get_formatted_date_time( $date = null ) {
if ( null === $date ) {
return null;
}
$date = self::date_from_gmt( $date );
$formatted_date = sprintf(
/* translators: 1: Date, 2: Time. */
esc_html_x( '%1$s at %2$s', 'date at time', 'visual-regression-tests' ),
/* translators: Date format. See https://www.php.net/manual/datetime.format.php */
date_format( $date, esc_html_x( 'Y/m/d', 'date format', 'visual-regression-tests' ) ),
/* translators: Time format. See https://www.php.net/manual/datetime.format.php */
date_format( $date, esc_html_x( 'g:i a', 'time format', 'visual-regression-tests' ) )
);
return $formatted_date;
}
/**
* Get the date and time WordPress native formatted.
*
* @param mixed $date a DateTime string.
*
* @return string Formatted date and time.
*/
public static function get_formatted_relative_date_time( $date = null ) {
if ( null === $date ) {
return null;
}
$date = self::date_from_gmt( $date );
$formatted_date = sprintf(
/* translators: 1: Date, 2: Time. */
esc_html_x( '%1$s at %2$s', 'date at time', 'visual-regression-tests' ),
/* translators: Date format. See https://www.php.net/manual/datetime.format.php */
static::extract_date( $date ),
/* translators: Time format. See https://www.php.net/manual/datetime.format.php */
static::extract_time( $date )
);
return '<time datetime="' . date_format( $date, 'c' ) . '">' . $formatted_date . '</time>';
}
/**
* Get the date WordPress native formatted.
*
* @param mixed $date a DateTime string.
*
* @return DateTime DateTime instance.
*/
private static function date_from_gmt( $date ) {
$date = date_create( $date, new DateTimeZone( 'UTC' ) );
$date->setTimezone( wp_timezone() );
return $date;
}
/**
* Extract the date from a DateTime object.
*
* @param DateTime $input_date a DateTime object.
*
* @return string Formatted date.
*/
private static function extract_date( $input_date ) {
// Get today's date at midnight in the site's timezone.
$today = new DateTime( 'today', wp_timezone() );
// Clone input date and set time to midnight.
$comparison_date = clone $input_date;
$comparison_date->setTime( 0, 0, 0 );
// Calculate the difference in days.
$difference_in_seconds = $comparison_date->getTimestamp() - $today->getTimestamp();
$difference_in_days = (int) round( $difference_in_seconds / ( 60 * 60 * 24 ) );
// Determine if the date is today, tomorrow, or yesterday.
if ( 0 === $difference_in_days ) {
return __( 'Today', 'visual-regression-testing' );
} elseif ( 1 === $difference_in_days ) {
return __( 'Tomorrow', 'visual-regression-testing' );
} elseif ( -1 === $difference_in_days ) {
return __( 'Yesterday', 'visual-regression-testing' );
}
return $input_date->format( 'D, Y/m/d' );
}
/**
* Extract the time from a DateTime object.
*
* @param DateTime $input_date a DateTime object.
*
* @return string Formatted time.
*/
private static function extract_time( $input_date ) {
return $input_date->setTimezone( wp_timezone() )->format( 'g:i a' );
}
}
@@ -0,0 +1,68 @@
<?php
namespace Vrts\Core\Utilities;
class Image_Helpers {
/**
* Get the image height and width string.
*
* @param object $alert The alert object.
*
* @return string
*/
public static function alert_image_hwstring( $alert ) {
$meta = maybe_unserialize( $alert->meta );
$width = $meta['width'] ?? 1265;
$height = $meta['height'] ?? 1800;
return image_hwstring( $width, $height );
}
/**
* Get the image aspect ratio string.
*
* @param object $alert The alert object.
*
* @return int
*/
public static function alert_image_aspect_ratio( $alert ) {
$meta = maybe_unserialize( $alert->meta );
if ( isset( $meta['width'], $meta['height'] ) ) {
return round( $meta['width'] / $meta['height'], 2 );
}
return 1.25;
}
/**
* Get screenshot URL.
*
* @param object $item The alert or test object.
* @param string $type Image type - base, target, comparison.
* @param string $size The size of the image.
*
* @return string
*/
public static function get_screenshot_url( $item, $type, $size = 'full' ) {
$property = "{$type}_screenshot_url";
if ( ! property_exists( $item, $property ) ) {
return '';
}
$url = 'preview' === $size ? maybe_unserialize( $item->meta )['preview_url'] ?? $item->$property : $item->$property;
return self::get_cloudfront_url( $url );
}
/**
* Get the cloudfront URL.
*
* @param string $url The URL.
*
* @return string
*/
public static function get_cloudfront_url( $url ) {
return str_replace( 'https://screenshotter-dev.s3.eu-central-1.amazonaws.com/', 'https://images.vrts.app/', $url );
}
}
@@ -0,0 +1,73 @@
<?php
namespace Vrts\Core\Utilities;
class Sanitization {
/**
* Checkbox sanitization callback.
*
* @param bool $checked Whether the checkbox is checked.
*
* @return bool Whether the checkbox is checked.
*/
public static function sanitize_checkbox( $checked ) {
return $checked ? true : false;
}
/**
* HTML sanitization callback.
*
* @param string $html HTML to sanitize.
*
* @return string
*/
public static function sanitize_html( $html ) {
return wp_filter_post_kses( $html );
}
/**
* No-HTML sanitization callback.
*
* @param string $nohtml The no-HTML content to sanitize.
*
* @return string
*/
public static function sanitize_nohtml( $nohtml ) {
return wp_filter_nohtml_kses( $nohtml );
}
/**
* Number sanitization callback.
*
* @param int $number Number to sanitize.
*
* @return int
*/
public static function sanitize_number_absint( $number ) {
// Ensure $number is an absolute integer (whole number, zero or greater).
return absint( $number );
}
/**
* URL sanitization callback.
*
* @param string $url URL to sanitize.
*
* @return string
*/
public static function sanitize_url( $url ) {
return esc_url_raw( $url );
}
/**
* Multiple emails sanitization callback.
*
* @param string $emails Comma-separated list of emails.
*
* @return string
*/
public static function sanitize_multiple_emails( $emails ) {
$emails = array_filter( array_map( 'sanitize_email', explode( ',', $emails ) ) );
return implode( ', ', $emails );
}
}
@@ -0,0 +1,164 @@
<?php
namespace Vrts\Core\Utilities;
class String_Helpers {
/**
* Converts a string from camel case to kebap case.
*
* @param string $str The string to convert.
*
* @return string
*/
public static function camel_case_to_kebap( $str ) {
return strtolower( preg_replace( '/([a-zA-Z])(?=[A-Z])/', '$1-', $str ) );
}
/**
* Strips all HTML tags including script and style,
* and trims text to a certain number of words.
*
* @param string $str The string to trim and strip.
* @param int $length The string length to return.
*
* @return string
*/
public static function trim_strip( $str = '', $length = 25 ) {
return wp_trim_words( wp_strip_all_tags( $str ), $length, '&hellip;' );
}
/**
* Splits a camel case string.
*
* @param string $str The string to split.
*
* @return string
*/
public static function split_camel_case( $str ) {
$a = preg_split(
'/(^[^A-Z]+|[A-Z][^A-Z]+)/',
$str,
-1,
// no limit for replacement count.
PREG_SPLIT_NO_EMPTY
// don't return empty elements.
| PREG_SPLIT_DELIM_CAPTURE
// don't strip anything from output array.
);
return implode( ' ', $a );
}
/**
* Converts a string from kebap case to camel case.
*
* @param string $str The string to convert.
* @param boolean $capitalize_first_character Sets if the first character should be capitalized.
*
* @return string
*/
public static function kebap_case_to_camel_case( $str, $capitalize_first_character = false ) {
$str = str_replace( ' ', '', ucwords( str_replace( '-', ' ', $str ) ) );
if ( false === $capitalize_first_character ) {
$str[0] = strtolower( $str[0] );
}
return $str;
}
/**
* Removes a prefix from a string.
*
* @param string $prefix The prefix to be removed.
* @param string $str The string to manipulate.
*
* @return string
*/
public static function remove_prefix( $prefix, $str ) {
if ( self::starts_with( $prefix, $str ) ) {
return substr( $str, strlen( $prefix ) );
}
return $str;
}
/**
* Checks if a string starts with a certain string.
*
* @param string $search The string to search for.
* @param string $str The string to look into.
*
* @return boolean Returns true if the subject string starts with the search string.
*/
public static function starts_with( $search, $str ) {
$starts_with = substr( $str, 0, strlen( $search ) );
return $search === $starts_with;
}
/**
* Checks if a string ends with a certain string.
*
* @param string $search The string to search for.
* @param string $str The string to look into.
*
* @return boolean Returns true if the subject string ends with the search string.
*/
public static function ends_with( $search, $str ) {
$search_length = strlen( $search );
$str_length = strlen( $str );
if ( $search_length > $str_length ) {
return false;
}
return 0 === substr_compare( $str, $search, $str_length - $search_length, $search_length );
}
/**
* Remove white space in string.
*
* @param string $str The string to look into.
*
* @return string String without whitespace.
*/
public function remove_white_space( $str ) {
$str = str_replace( "\t", ' ', $str );
$str = str_replace( "\n", '', $str );
$str = str_replace( "\r", '', $str );
while ( stristr( $str, ' ' ) ) {
$str = str_replace( ' ', '', $str );
}
return $str;
}
/**
* Format lines.
*
* @param array $lines Lines to format.
* @param int $tabs Number of tabs for the offest.
*
* @return string Formated lines.
*/
public static function format_lines( $lines, $tabs = 1 ) {
$line_tabs = str_repeat( "\t", $tabs );
$end_tabs = str_repeat( "\t", $tabs - 1 );
$lines = array_map( function ( $line ) use ( $line_tabs ) {
return "\n{$line_tabs}{$line}";
}, $lines);
return implode( '', $lines ) . "\n{$end_tabs}";
}
/**
* Truncate a string.
*
* @param string $str The string to truncate.
* @param int $length The length to truncate to.
* @param string $append The string to append.
*
* @return string
*/
public static function truncate( $str, $length = 100, $append = '...' ) {
$str = trim( $str );
return ( strlen( $str ) > $length ) ? substr( $str, 0, $length - strlen( $append ) ) . $append : $str;
}
}
@@ -0,0 +1,132 @@
<?php
namespace Vrts\Core\Utilities;
use Vrts\Models\Alert;
use Vrts\Models\Test_Run;
class Url_Helpers {
/**
* Get the relative permalink of a post.
*
* @param int $post_id Post ID.
*
* @return string
*/
public static function get_relative_permalink( $post_id ) {
$permalink = get_permalink( $post_id );
return self::make_relative( $permalink );
}
/**
* Make a permalink relative.
*
* @param int $permalink A permalink.
*
* @return string
*/
public static function make_relative( $permalink ) {
$home_url = home_url();
if ( 0 === strpos( $permalink, $home_url ) ) {
$permalink = str_replace( $home_url, '', $permalink );
}
return $permalink;
}
/**
* Get the page URL.
*
* @param string $page Page.
*
* @return string
*/
public static function get_page_url( $page ) {
$page = 'tests' === $page ? 'vrts' : 'vrts-' . $page;
return admin_url( 'admin.php?page=' . $page );
}
/**
* Get the alert page URL.
*
* @param int|Alert $alert_id Alert ID.
* @param int $test_run_id Test run ID.
*
* @return string
*/
public static function get_alert_page( $alert_id, $test_run_id = null ) {
if ( is_null( $test_run_id ) ) {
$alert = Alert::get_item( $alert_id );
$test_run_id = $alert->test_run_id;
}
return add_query_arg( [
'page' => 'vrts-runs',
'run_id' => $test_run_id,
'alert_id' => $alert_id,
], admin_url( 'admin.php' ) );
}
/**
* Get the alerts page URL.
*
* @param int|Test_Run $test_run Test run.
*
* @return string
*/
public static function get_alerts_page( $test_run ) {
$test_run_id = is_object( $test_run ) ? $test_run->id : $test_run;
return add_query_arg( [
'page' => 'vrts-runs',
'run_id' => $test_run_id,
], admin_url( 'admin.php' ) );
}
/**
* Get the run manual test page URL.
*
* @param int $test_id Test id.
*
* @return string
*/
public static function get_run_manual_test_url( $test_id ) {
return add_query_arg( [
'page' => 'vrts',
'action' => 'run-manual-test',
'test_id' => $test_id,
], admin_url( 'admin.php' ) );
}
/**
* Get the disable testing test page URL.
*
* @param int $test_id Test id.
*
* @return string
*/
public static function get_disable_testing_url( $test_id ) {
return add_query_arg( [
'page' => 'vrts',
'action' => 'disable-testing',
'test_id' => $test_id,
], admin_url( 'admin.php' ) );
}
/**
* Get the test run page URL.
*
* @param int $test_run_id Test run id.
*
* @return string
*/
public static function get_test_run_page( $test_run_id ) {
if ( is_object( $test_run_id ) ) {
$test_run_id = $test_run_id->id;
}
return add_query_arg( [
'page' => 'vrts-runs',
'run_id' => $test_run_id,
], admin_url( 'admin.php' ) );
}
}