Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

190
inc/helpers/class-arr.php Normal file
View File

@ -0,0 +1,190 @@
<?php
/**
* Array Helpers
*
* Heavily inspired on Laravel's Arr helper class and Lodash's PHP implementation.
*
* @see https://github.com/laravel/framework/blob/8.x/src/Illuminate/Collections/Arr.php
* @see https://github.com/me-io/php-lodash/blob/master/src/Traits/Collections.php
*
* @package WP_Ultimo\Helpers
* @since 2.0.11
*/
namespace WP_Ultimo\Helpers;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Helper Array class.
*
* @since 2.0.11
*/
class Arr {
/**
* Returns all results.
*/
const RESULTS_ALL = 0;
/**
* Return only the first result.
*/
const RESULTS_FIRST = 1;
/**
* Result only the last result.
*/
const RESULTS_LAST = 2;
/**
* Filter an array by property or key.
*
* @since 2.0.11
*
* @param array $array The array to filter.
* @param string $property The property to filter by. Dot notation is supported.
* @param mixed $expected_value The expected value to filter by.
* @param integer $flag The flag determining the return type.
* @return mixed
*/
public static function filter_by_property($array, $property, $expected_value, $flag = 0) {
$result = Arr::filter($array, function($value) use ($property, $expected_value) {
return Arr::get($value, $property, null) == $expected_value; // phpcs:ignore
});
if ($flag) {
$result = $flag === Arr::RESULTS_FIRST ? reset($result) : end($result);
} // end if;
return $result;
} // end filter_by_property;
/**
* Filters an array using a callback.
*
* @since 2.0.11
*
* @param array $array The array to search inside.
* @param callable $closure The closure function to call.
* @return array
*/
public static function filter($array, $closure) {
if ($closure) {
$result = array();
foreach ($array as $key => $value) {
if (call_user_func($closure, $value, $key)) {
$result[] = $value;
} // end if;
} // end foreach;
return $result;
} // end if;
return array_filter($array);
} // end filter;
/**
* Get a nested value inside an array. Dot notation is supported.
*
* @since 2.0.11
*
* @param array $array The array to get the value from.
* @param string $key The array key to get. Supports dot notation.
* @param mixed $default The value to return ibn the case the key does not exist.
* @return mixed
*/
public static function get($array, $key, $default = null) {
if (is_null($key)) {
return $array;
} // end if;
if (isset($array[$key])) {
return $array[$key];
} // end if;
foreach (explode('.', $key) as $segment) {
if (!is_array($array) || !array_key_exists($segment, $array)) {
return $default;
} // end if;
$array = $array[$segment];
} // end foreach;
return $array;
} // end get;
/**
* Set a nested value inside an array. Dot notation is supported.
*
* @since 2.0.11
*
* @param array $array The array to modify.
* @param string $key The array key to set. Supports dot notation.
* @param mixed $value The value to set.
* @return array
*/
public static function set(&$array, $key, $value) {
if (is_null($key)) {
return $array = $value; // phpcs:ignore
} // end if;
$keys = explode('.', $key);
while (count($keys) > 1) {
$key = array_shift($keys);
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = array();
} // end if;
$array =& $array[$key];
} // end while;
$array[array_shift($keys)] = $value;
return $array;
} // end set;
/**
* Static class only.
*
* @since 2.0.11
*/
private function __construct() {} // end __construct;
} // end class Arr;

View File

@ -0,0 +1,68 @@
<?php
/**
* Handles hashing to encode ids and prevent spoofing due to auto-increments.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
use WP_Ultimo\Dependencies\Hashids\Hashids;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles hashing to encode ids and prevent spoofing due to auto-increments.
*
* @since 2.0.0
*/
class Hash {
/**
* Hash length.
*/
const LENGTH = 10;
/**
* Static-only class.
*/
private function __construct() {} // end __construct;
/**
* Encodes a number or ID. Do not use to encode strings.
*
* @since 2.0.0
*
* @param integer $number Number to encode.
* @param string $group Hash group. Used to increase entropy.
* @return string
*/
public static function encode($number, $group = 'wp-ultimo') {
$hasher = new Hashids($group, self::LENGTH, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
return $hasher->encode($number);
} // end encode;
/**
* Decodes a hash back into an integer.
*
* @since 2.0.0
*
* @param string $hash Hash to decode.
* @param string $group Hash group. Used to increase entropy.
* @return int
*/
public static function decode($hash, $group = 'wp-ultimo') {
$hasher = new Hashids($group, 10, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
return current($hasher->decode($hash));
} // end decode;
} // end class Hash;

View File

@ -0,0 +1,141 @@
<?php
/**
* Takes screenshots from websites.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
use WP_Ultimo\Dependencies\Psr\Log\LogLevel;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Takes screenshots from websites.
*
* @since 2.0.0
*/
class Screenshot {
/**
* Returns the api link for the screenshot.
*
* @since 2.0.0
*
* @param string $domain Original site domain.
*/
public static function api_url($domain): string {
$domain = esc_url($domain);
$url = wu_with_license_key(sprintf('https://api.wpultimo.com/screenshot?url=%s', $domain));
return $url . '&ext=.png';
} // end api_url;
/**
* Takes in a URL and creates it as an attachment.
*
* @since 2.0.0
*
* @param string $url Image URL to download.
* @return string|false
*/
public static function take_screenshot($url) {
$url = self::api_url($url);
return self::save_image_from_url($url);
} // end take_screenshot;
/**
* Downloads the image from the URL.
*
* @since 2.0.0
*
* @param string $url Image URL to download.
* @return int|false
*/
public static function save_image_from_url($url) {
// translators: %s is the API URL.
$log_prefix = sprintf(__('Downloading image from "%s":'), $url) . ' ';
$response = wp_remote_get($url, array(
'timeout' => 50,
));
if (wp_remote_retrieve_response_code($response) !== 200) {
wu_log_add('screenshot-generator', $log_prefix . wp_remote_retrieve_response_message($response), LogLevel::ERROR);
return false;
} // end if;
if (is_wp_error($response)) {
wu_log_add('screenshot-generator', $log_prefix . $response->get_error_message(), LogLevel::ERROR);
return false;
} // end if;
/*
* Check if the results contain a PNG header.
*/
if (strncmp($response['body'], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", strlen("\x89\x50\x4e\x47\x0d\x0a\x1a\x0a")) !== 0) {
wu_log_add('screenshot-generator', $log_prefix . __('Result is not a PNG file.', 'wp-ultimo'), LogLevel::ERROR);
return false;
} // end if;
$upload = wp_upload_bits('screenshot-' . gmdate('Y-m-d-H-i-s') . '.png', null, $response['body']);
if (!empty($upload['error'])) {
wu_log_add('screenshot-generator', $log_prefix . json_encode($upload['error']), LogLevel::ERROR);
return false;
} // end if;
$file_path = $upload['file'];
$file_name = basename($file_path);
$file_type = wp_check_filetype($file_name, null);
$attachment_title = sanitize_file_name(pathinfo($file_name, PATHINFO_FILENAME));
$wp_upload_dir = wp_upload_dir();
$post_info = array(
'guid' => $wp_upload_dir['url'] . '/' . $file_name,
'post_mime_type' => $file_type['type'],
'post_title' => $attachment_title,
'post_content' => '',
'post_status' => 'inherit',
);
// Create the attachment
$attach_id = wp_insert_attachment($post_info, $file_path);
// Include image.php
require_once(ABSPATH . 'wp-admin/includes/image.php');
// Define attachment metadata
$attach_data = wp_generate_attachment_metadata($attach_id, $file_path);
// Assign metadata to attachment
wp_update_attachment_metadata($attach_id, $attach_data);
wu_log_add('screenshot-generator', $log_prefix . __('Success!', 'wp-ultimo'));
return $attach_id;
} // end save_image_from_url;
} // end class Screenshot;

View File

@ -0,0 +1,229 @@
<?php
/**
* Handles the action o send a admin notice to a sub-site or a email.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
use WP_Ultimo\Models\Email_Template;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles hashing to encode ids and prevent spoofing due to auto-increments.
*
* @since 2.0.0
*/
class Sender {
/**
* Parse attributes against the defaults.
*
* @since 2.0.0
*
* @param array $args The args passed.
* @return array
*/
public static function parse_args($args = array()) {
$default_args = array(
'from' => array(
'name' => wu_get_setting('from_name'),
'email' => wu_get_setting('from_email'),
),
'content' => '',
'subject' => '',
'bcc' => array(),
'payload' => array(),
'attachments' => array(),
'style' => wu_get_setting('email_template_type', 'html'),
);
$args = wp_parse_args($args, $default_args);
return $args;
} // end parse_args;
/**
* Send an email to one or more users.
*
* @since 2.0.0
*
* @param array $from From whom will be send this mail.
* @param string $to To who this email is.
* @param array $args With content, subject and other arguments, has shortcodes, mail type.
* @return array With the send response.
*/
public static function send_mail($from = array(), $to = array(), $args = array()) {
if (!$from) {
$from = array(
'email' => wu_get_setting('from_email'),
'name' => wu_get_setting('from_name'),
);
} // end if;
$args = Sender::parse_args($args);
/*
* First, replace shortcodes.
*/
$payload = wu_get_isset($args, 'payload', array());
$subject = Sender::process_shortcodes(wu_get_isset($args, 'subject', ''), $payload);
$content = Sender::process_shortcodes(wu_get_isset($args, 'content', ''), $payload);
/*
* Content type and template
*/
$headers = array();
if (wu_get_isset($args, 'style', 'html') === 'html') {
$headers[] = 'Content-Type: text/html; charset=UTF-8';
$default_settings = \WP_Ultimo\Admin_Pages\Email_Template_Customize_Admin_Page::get_default_settings();
$template_settings = wu_get_option('email_template', $default_settings);
$template_settings = wp_parse_args($template_settings, $default_settings);
$template = wu_get_template_contents('broadcast/emails/base', array(
'site_name' => get_network_option(null, 'site_name'),
'site_url' => get_site_url(wu_get_main_site_id()),
'logo_url' => wu_get_network_logo(),
'is_editor' => false,
'subject' => $subject,
'content' => $content,
'template_settings' => $template_settings,
));
} else {
$headers[] = 'Content-Type: text/html; charset=UTF-8';
$template = nl2br(strip_tags($content, '<p><a><br>')); // by default, set the plain email content.
} // end if;
$bcc = '';
/*
* Build the recipients list.
*/
if (count($to) > 1) {
$to = array_map(fn($item) => wu_format_email_string(wu_get_isset($item, 'email'), wu_get_isset($item, 'name')), $to);
/*
* Decide which strategy to use, BCC or multiple "to"s.
*
* By default, we use multiple tos, but that can be changed to bcc.
* Depending on the SMTP solution being used, that can make a difference on the number of
* emails sent out.
*/
if (apply_filters('wu_sender_recipients_strategy', 'bcc') === 'bcc') {
$main_to = $to[0];
unset($to[0]);
$bcc_array = array_map(function($item) {
$email = is_array($item) ? $item['email'] : $item;
preg_match('/<([^>]+)>/', $email, $matches);
return !empty($matches[1]) ? $matches[1] : $email;
}, $to);
$bcc = implode(', ', array_filter($bcc_array));
$headers[] = "Bcc: $bcc";
$to = $main_to;
} // end if;
} else {
$to = array(
wu_format_email_string(wu_get_isset($to[0], 'email'), wu_get_isset($to[0], 'name'))
);
} // end if;
/*
* Build From
*/
$from_email = wu_get_isset($from, 'email', wu_get_setting('from_email'));
$from_name = wu_get_isset($from, 'name');
$from_string = wu_format_email_string($from_email, $from_name);
$headers[] = "From: {$from_string}";
$attachments = $args['attachments'];
// if (isset($args['schedule'])) {
// wu_schedule_single_action($args['schedule'], 'wu_send_schedule_system_email', array(
// 'to' => $to,
// 'subject' => $subject,
// 'template' => $template,
// 'headers' => $headers,
// 'attachments' => $attachments,
// ));
// } // end if;
// Send the actual email
return wp_mail($to, $subject, $template, $headers, $attachments);
} // end send_mail;
/**
* Change the shortcodes for values in the content.
*
* @since 2.0.0
*
* @param string $content Content to be rendered.
* @param array $payload Payload with the values to render in the content.
* @return string
*/
public static function process_shortcodes($content, $payload = array()) {
if (empty($payload)) {
return $content;
} // end if;
$match = array();
preg_match_all('/{{(.*?)}}/', $content, $match);
$shortcodes = shortcode_atts(array_flip($match[1]), $payload);
foreach ($shortcodes as $shortcode_key => $shortcode_value) {
$shortcode_str = '{{' . $shortcode_key . '}}';
$content = str_replace($shortcode_str, nl2br((string) $shortcode_value), $content);
} // end foreach;
return $content;
} // end process_shortcodes;
} // end class Sender;

View File

@ -0,0 +1,307 @@
<?php
/**
* Exposes the public API to handle site duplication.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
use WP_Ultimo\Dependencies\Psr\Log\LogLevel;
// Exit if accessed directly
defined('ABSPATH') || exit;
require_once WP_ULTIMO_PLUGIN_DIR . '/inc/duplication/duplicate.php';
if (!defined('MUCD_PRIMARY_SITE_ID')) {
define('MUCD_PRIMARY_SITE_ID', get_current_network_id()); // phpcs:ignore
} // end if;
/**
* Exposes the public API to handle site duplication.
*
* The decision to create a buffer interface (this file), as the API layer
* for the duplication functions is simple: it allows us to swith the duplication
* component used without breaking backwards-compatibility in the future.
*
* @since 2.0.0
*/
class Site_Duplicator {
/**
* Static-only class.
*/
private function __construct() {} // end __construct;
/**
* Duplicate an existing network site.
*
* @since 2.0.0
*
* @param int $from_site_id ID of the site you wish to copy.
* @param string $title Title of the new site.
* @param array $args List of duplication parameters, check Site_Duplicator::process_duplication for reference.
* @return int|\WP_Error ID of the newly created site or error.
*/
public static function duplicate_site($from_site_id, $title, $args = array()) {
$args['from_site_id'] = $from_site_id;
$args['title'] = $title;
$duplicate_site = Site_Duplicator::process_duplication($args);
if (is_wp_error($duplicate_site)) {
// translators: %s id the template site id and %s is the error message returned.
$message = sprintf(__('Attempt to duplicate site %1$d failed: %2$s', 'wp-ultimo'), $from_site_id, $duplicate_site->get_error_message());
wu_log_add('site-duplication', $message, LogLevel::ERROR);
return $duplicate_site;
} // end if;
// translators: %1$d is the ID of the site template used, and %2$d is the id of the new site.
$message = sprintf(__('Attempt to duplicate site %1$d successful - New site id: %2$d', 'wp-ultimo'), $from_site_id, $duplicate_site);
wu_log_add('site-duplication', $message);
return $duplicate_site;
} // end duplicate_site;
/**
* Replace the contents of a site with the contents of another.
*
* @since 2.0.0
*
* @param int $from_site_id Site to get the data from.
* @param int $to_site_id Site to override.
* @param array $args List of duplication parameters, check Site_Duplicator::process_duplication for reference.
* @return int|false ID of the created site.
*/
public static function override_site($from_site_id, $to_site_id, $args = array()) {
$to_site = wu_get_site($to_site_id);
$to_site_membership_id = $to_site->get_membership_id();
$to_site_membership = $to_site->get_membership();
$to_site_customer = $to_site_membership->get_customer();
$args = wp_parse_args($args, array(
'email' => $to_site_customer->get_email_address(),
'title' => $to_site->get_title(),
'path' => $to_site->get_path(),
'from_site_id' => $from_site_id,
'to_site_id' => $to_site_id,
'meta' => $to_site->meta
));
$duplicate_site_id = self::process_duplication($args);
if (is_wp_error($duplicate_site_id)) {
// translators: %s id the template site id and %s is the error message returned.
$message = sprintf(__('Attempt to override site %1$d with data from site %2$d failed: %3$s', 'wp-ultimo'), $from_site_id, $to_site_id, $duplicate_site->get_error_message());
wu_log_add('site-duplication', $message, LogLevel::ERROR);
return false;
} // end if;
$new_to_site = wu_get_site($duplicate_site_id);
$new_to_site->set_membership_id($to_site_membership_id);
$new_to_site->set_customer_id($to_site->get_customer_id());
$new_to_site->set_template_id($from_site_id);
$new_to_site->set_type('customer_owned');
$new_to_site->set_title($to_site->get_title());
$saved = $new_to_site->save();
if ($saved) {
// translators: %1$d is the ID of the site template used, and %2$d is the ID of the overriden site.
$message = sprintf(__('Attempt to override site %1$d with data from site %2$d successful.', 'wp-ultimo'), $from_site_id, $duplicate_site_id);
wu_log_add('site-duplication', $message);
return $saved;
} // end if;
} // end override_site;
/**
* Processes a site duplication.
*
* @since 2.0.0
*
* @param array $args List of parameters of the duplication.
* - email Email of the admin user to be created.
* - title Title of the (new or not) site.
* - path Path of the new site.
* - from_site_id ID of the template site being used.
* - to_site_id ID of the target site. Can be false to create new site.
* - keep_users If we should keep users or not. Defaults to true.
* - copy_files If we should copy the uploaded files or not. Defaults to true.
* - public If the (new or not) site should be public. Defaults to true.
* - domain The domain of the new site.
* - network_id The network ID to allow for multi-network support.
* @return int|WP_Error The Site ID.
*/
protected static function process_duplication($args) {
global $current_site, $wpdb;
$args = wp_parse_args($args, array(
'email' => '', // Required arguments.
'title' => '', // Required arguments.
'path' => '/', // Required arguments.
'from_site_id' => false, // Required arguments.
'to_site_id' => false,
'keep_users' => true,
'public' => true,
'domain' => $current_site->domain,
'copy_files' => wu_get_setting('copy_media', true),
'network_id' => get_current_network_id(),
'meta' => array(),
'user_id' => 0,
));
// Checks
$args = (object) $args;
$site_domain = $args->domain . $args->path;
$wpdb->hide_errors();
if (!$args->from_site_id) {
return new \WP_Error('from_site_id_required', __('You need to provide a valid site to duplicate.', 'wp-ultimo'));
} // end if;
$user_id = !empty($args->user_id) ? $args->user_id : self::create_admin($args->email, $site_domain);
if (is_wp_error($user_id)) {
return $user_id;
} // end if;
if (!$args->to_site_id) {
$meta = array_merge($args->meta, array('public' => $args->public));
$args->to_site_id = wpmu_create_blog($args->domain, $args->path, $args->title, $user_id, $meta, $args->network_id);
$wpdb->show_errors();
} // end if;
if (is_wp_error($args->to_site_id)) {
return $args->to_site_id;
} // end if;
if (!is_numeric($args->to_site_id)) {
return new \WP_Error('site_creation_failed', __('An attempt to create a new site failed.', 'wp-ultimo'));
} // end if;
if (!is_super_admin($user_id) && !get_user_option('primary_blog', $user_id)) {
update_user_option($user_id, 'primary_blog', $args->to_site_id, true);
} // end if;
\MUCD_Duplicate::bypass_server_limit();
if ($args->copy_files) {
$result = \MUCD_Files::copy_files($args->from_site_id, $args->to_site_id);
} // end if;
/**
* Supress email change notification on site duplication processes.
*/
add_filter('send_site_admin_email_change_email', '__return_false');
$result = \MUCD_Data::copy_data($args->from_site_id, $args->to_site_id);
if ($args->keep_users) {
$result = \MUCD_Duplicate::copy_users($args->from_site_id, $args->to_site_id);
} // end if;
wp_cache_flush();
/**
* Allow developers to hook after a site duplication happens.
*
* @since 1.9.4
* @return void
*/
do_action('wu_duplicate_site', array(
'site_id' => $args->to_site_id,
));
return $args->to_site_id;
} // end process_duplication;
/**
* Creates an admin user if no user exists with this email.
*
* @since 2.0.0
* @param string $email The email.
* @param string $domain The domain.
* @return int Id of the user created.
*/
public static function create_admin($email, $domain) {
// Create New site Admin if not exists
$password = 'N/A';
$user_id = email_exists($email);
if (!$user_id) { // Create a new user with a random password
$password = wp_generate_password(12, false);
$user_id = wpmu_create_user($domain, $password, $email);
if (false === $user_id) {
return new \WP_Error('user_creation_error', __('We were not able to create a new admin user for the site being duplicated.', 'wp-ultimo'));
} else {
wp_new_user_notification($user_id);
} // end if;
} // end if;
return $user_id;
} // end create_admin;
} // end class Site_Duplicator;

View File

@ -0,0 +1,195 @@
<?php
/**
* Wraps the validation library being used by WP Ultimo.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
use \WP_Ultimo\Dependencies\Rakit\Validation\Validator as Validator_Helper;
use \WP_Ultimo\Helpers\Validation_Rules\Unique;
use \WP_Ultimo\Helpers\Validation_Rules\Unique_Site;
use \WP_Ultimo\Helpers\Validation_Rules\Exists;
use \WP_Ultimo\Helpers\Validation_Rules\Checkout_Steps;
use \WP_Ultimo\Helpers\Validation_Rules\Price_Variations;
use \WP_Ultimo\Helpers\Validation_Rules\Domain;
use \WP_Ultimo\Helpers\Validation_Rules\Site_Template;
use \WP_Ultimo\Helpers\Validation_Rules\Products;
use \WP_Ultimo\Helpers\Validation_Rules\Country;
use \WP_Ultimo\Helpers\Validation_Rules\State;
use \WP_Ultimo\Helpers\Validation_Rules\City;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Wraps the validation library being used by WP Ultimo.
*
* @since 2.0.0
*/
class Validator {
/**
* Holds an instance of the validator object.
*
* @since 2.0.0
* @var Rakit\Validation\Validator
*/
protected $validator;
/**
* Holds an instance of the validation being performed.
*
* @since 2.0.0
* @var Rakit\Validation\Validation
*/
protected $validation;
/**
* Holds the errors returned from validation.
*
* @since 2.0.0
* @var \WP_Error
*/
protected $errors;
/**
* Sets up the validation library and makes the error messages translatable.
*
* @since 2.0.0
*/
public function __construct() {
// translators: %s is the field name.
$field_required_message = sprintf(__('The %s field is required', 'wp-ultimo'), ':attribute');
$validation_error_messages = apply_filters('wu_validator_error_messages', array(
'required' => $field_required_message,
'required_without' => $field_required_message,
'required_with' => $field_required_message,
// translators: %s is the email field identifier
'email' => sprintf(__('The %s is not valid email', 'wp-ultimo'), ':attribute'),
// translators: 1st %s is the field name; 2nd is the allowed value
'min' => sprintf(__('The %1$s minimum is %2$s', 'wp-ultimo'), ':attribute', ':min'),
// translators: 1st %s is the field name; 2nd is the allowed value
'max' => sprintf(__('The %1$s maximum is %2$s', 'wp-ultimo'), ':attribute', ':max'),
// translators: %s is the field identifier
'alpha_dash' => sprintf(__('The %s only allows a-z, 0-9, _ and -', 'wp-ultimo'), ':attribute'),
// translators: %s is the field identifier
'lowercase' => sprintf(__('The %s must be lowercase', 'wp-ultimo'), ':attribute'),
// translators: %s is the field identifier
'integer' => sprintf(__('The %s must be integer', 'wp-ultimo'), ':attribute')
), $this);
$this->validator = new Validator_Helper($validation_error_messages);
$this->validator->setTranslations(array(
'and' => __('and', 'wp-ultimo'),
'or' => __('or', 'wp-ultimo'),
));
$this->validator->addValidator('unique', new Unique);
$this->validator->addValidator('unique_site', new Unique_Site);
$this->validator->addValidator('exists', new Exists);
$this->validator->addValidator('checkout_steps', new Checkout_Steps);
$this->validator->addValidator('price_variations', new Price_Variations);
$this->validator->addValidator('domain', new Domain);
$this->validator->addValidator('site_template', new Site_Template);
$this->validator->addValidator('products', new Products);
$this->validator->addValidator('country', new Country);
$this->validator->addValidator('state', new State);
$this->validator->addValidator('city', new City);
} // end __construct;
/**
* Validates the data passed according to the rules passed.
*
* @since 2.0.0
* @link https://github.com/rakit/validation#available-rules
*
* @param array $data Data to be validated.
* @param array $rules List of rules to validate against.
* @param array $aliases List of aliases to be used with the validator.
* @return \WP_Ultimo\Helpers\Validator
*/
public function validate($data, $rules = array(), $aliases = array()) {
$this->errors = new \WP_Error;
$this->validation = $this->validator->make($data, $rules);
$this->validation->setAliases($aliases);
$this->validation->validate();
return $this;
} // end validate;
/**
* Returns true when the validation fails.
*
* @since 2.0.0
* @return boolean
*/
public function fails() {
return $this->validation->fails();
} // end fails;
/**
* Returns a WP_Error object containing all validation errors.
*
* @since 2.0.0
* @return WP_Error
*/
public function get_errors() {
$errors = $this->validation->errors()->toArray();
$this->cast_to_wp_error($errors);
return $this->errors;
} // end get_errors;
/**
* Converts the native error structure to a WP_Error object.
*
* @since 2.0.0
*
* @param array $errors Array containing the errors returned.
* @return void
*/
protected function cast_to_wp_error($errors) {
foreach ($errors as $key => $error_messages) {
foreach ($error_messages as $error_message) {
$this->errors->add($key, $error_message);
} // end foreach;
} // end foreach;
} // end cast_to_wp_error;
/**
* Get holds an instance of the validation being performed.
*
* @since 2.0.0
* @return Rakit\Validation\Validation
*/
public function get_validation() {
return $this->validation;
} // end get_validation;
} // end class Validator;

View File

@ -0,0 +1,264 @@
<?php
/**
* Handles modifications to the wp-config.php file, if permissions allow.
*
* @package WP_Ultimo
* @subpackage Helper
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Handles modifications to the wp-config.php file, if permissions allow.
*
* @since 2.0.0
*/
class WP_Config {
use \WP_Ultimo\Traits\Singleton;
/**
* Inject the constant into the wp-config.php file.
*
* @since 2.0.0
*
* @param string $constant The name of the constant. e.g. WP_ULTIMO_CONSTANT.
* @param string|int $value The value of that constant.
* @return bool|\WP_Error
*/
public function inject_wp_config_constant($constant, $value) {
$config_path = $this->get_wp_config_path();
if (!is_writeable($config_path)) {
// translators: %s is the file name.
return new \WP_Error('not-writeable', sprintf(__('The file %s is not writable', 'wp-ultimo'), $config_path));
} // end if;
$config = file($config_path);
$line = $this->find_injected_line($config, $constant);
$content = str_pad(sprintf("define( '%s', '%s' );", $constant, $value), 50) . '// Automatically injected by WP Ultimo;';
if ($line === false) {
// no defined, we just need to inject
$hook_line = $this->find_reference_hook_line($config);
if ($hook_line === false) {
return new \WP_Error('unknown-wpconfig', __("WP Ultimo can't recognize your wp-config.php, please revert it to original state for further process.", 'wp-ultimo'));
} // end if;
$config = $this->inject_contents($config, $hook_line + 1, PHP_EOL . $content . PHP_EOL);
return file_put_contents($config_path, implode('', $config), LOCK_EX);
} else {
list($value, $line) = $line;
if ($value !== true) {
$config[$line] = $content . PHP_EOL;
return file_put_contents($config_path, implode('', $config), LOCK_EX);
} // end if;
} // end if;
return false;
} // end inject_wp_config_constant;
/**
* Actually inserts the new lines into the array of wp-config.php lines.
*
* @since 2.0.0
*
* @param array $content_array Array containing the original lines of the file being edited.
* @param int $line Line number to inject the new content at.
* @param string $value Value to add to that specific line.
* @return array New array containing the lines of the modified file.
*/
public function inject_contents($content_array, $line, $value) {
if (!is_array($value)) {
$value = array($value);
} // end if;
array_splice($content_array, $line, 0, $value);
return $content_array;
} // end inject_contents;
/**
* Gets the correct path to the wp-config.php file.
*
* @since 2.0.0
* @return string
*/
public function get_wp_config_path() {
if (file_exists(ABSPATH . 'wp-config.php')) {
return (ABSPATH . 'wp-config.php');
} elseif (@file_exists(dirname(ABSPATH) . '/wp-config.php') && !@file_exists(dirname(ABSPATH) . '/wp-settings.php')) {
return (dirname(ABSPATH) . '/wp-config.php');
} elseif (defined('WP_TESTS_MULTISITE') && constant('WP_TESTS_MULTISITE') === true) {
return '/tmp/wordpress-tests-lib/wp-tests-config.php';
} // end if;
} // end get_wp_config_path;
/**
* Find reference line for injection.
*
* We need a hook point we can use as reference to inject our constants.
* For now, we are using the line defining the $table_prefix.
* e.g. $table_prefix = 'wp_';
* We retrieve that line via RegEx.
*
* @since 2.0.0
*
* @param array $config Array containing the lines of the config file, for searching.
* @return false|int Line number.
*/
public function find_reference_hook_line($config) {
global $wpdb;
/**
* We check for three patterns when trying to figure our
* where we can inject our constants:
*
* 1. We search for the $table_prefix variable definition;
* 2. We search for more complex $table_prefix definitions - the ones that
* use env variables, for example;
* 3. If that's not available, we look for the 'Happy Publishing' comment;
* 4. If that's also not available, we look for the beginning of the file.
*
* The key represents the pattern and the value the number of lines to add.
* A negative number of lines can be passed to write before the found line,
* instead of writing after it.
*/
$patterns = apply_filters('wu_wp_config_reference_hook_line_patterns', array(
'/^\$table_prefix\s*=\s*[\'|\"]' . $wpdb->prefix . '[\'|\"]/' => 0,
'/^( ){0,}\$table_prefix\s*=.*[\'|\"]' . $wpdb->prefix . '[\'|\"]/' => 0,
'/(\/\* That\'s all, stop editing! Happy publishing\. \*\/)/' => -2,
'/<\?php/' => 0,
));
$line = 1;
foreach ($patterns as $pattern => $lines_to_add) {
foreach ($config as $k => $line) {
if (preg_match($pattern, (string) $line)) {
$line = $k + $lines_to_add;
break 2;
} // end if;
} // end foreach;
} // end foreach;
return $line;
} // end find_reference_hook_line;
/**
* Revert the injection of a constant in wp-config.php
*
* @since 2.0.0
*
* @param string $constant Constant name.
* @return mixed
*/
public function revert($constant) {
$config_path = $this->get_wp_config_path();
if (!is_writeable($config_path)) {
// translators: %s is the file name.
return new \WP_Error('not-writeable', sprintf(__('The file %s is not writable', 'wp-ultimo'), $config_path));
} // end if;
$config = file($config_path);
$line = $this->find_injected_line($config, $constant);
if ($line === false) {
return;
} else {
$value = $line[0];
$line = $line[1];
if ($value === 'true' || $value === '1') {
// value is true, we will remove this
unset($config[$line]);
// save it
return file_put_contents($config_path, implode('', $config), LOCK_EX);
} // end if;
} // end if;
} // end revert;
/**
* Checks for the injected line inside of the wp-config.php file.
*
* @since 2.0.0
*
* @param array $config Array containing the lines of the config file, for searching.
* @param string $constant The constant name.
* @return mixed[]|bool
*/
public function find_injected_line($config, $constant) {
$pattern = "/^define\(\s*['|\"]" . $constant . "['|\"],(.*)\)/";
foreach ($config as $k => $line) {
if (preg_match($pattern, (string) $line, $matches)) {
return array(trim($matches[1]), $k);
} // end if;
} // end foreach;
return false;
} // end find_injected_line;
} // end class WP_Config;

View File

@ -0,0 +1,139 @@
<?php // phpcs:disable
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
use WP_Ultimo\Managers\Signup_Fields_Manager;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @since 2.0.0
*/
class Checkout_Steps extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = ':attribute is wrongly setup.';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array(); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool {
if (is_string($value)) {
$value = maybe_unserialize($value);
} // end if;
$required_fields = Signup_Fields_Manager::get_instance()->get_required_fields();
$required_fields_list = array_keys($required_fields);
if (!$value || is_string($value)) {
return true;
} // end if;
$fields = array_column($value, 'fields');
if (empty($fields)) {
return true;
} // end if;
$all_fields = call_user_func_array('array_merge', $fields);
$all_fields_list = array_column($all_fields, 'type');
/**
* First, we validated that all of our required fields are present.
*/
$all_present = true;
foreach ($required_fields_list as $field_slug) {
if (!in_array($field_slug, $all_fields_list, true)) {
$this->message = sprintf(__('The %s field must be present in at least one of the checkout form steps.', 'wp-ultimo'), wu_slug_to_name($field_slug));
return false;
} // end if;
} // end if;
/**
* Allow developers to bypass the check if a field is auto-submittable.
*
* @since 2.0.0
* @param array $submittable_field_types The list of field types.
* @return array
*/
$submittable_field_types = apply_filters(
'wu_checkout_step_validation_submittable_field_types',
array(
'submit_button',
'pricing_table',
'template_selection',
)
);
/**
* Second, we must validate if every step has a submit button.
*/
foreach ($value as $step) {
$found_submittable_field_types = \WP_Ultimo\Dependencies\Arrch\Arrch::find($step['fields'], array(
'where' => array(
array('type', $submittable_field_types),
),
));
if (empty($found_submittable_field_types)) {
$this->message = sprintf(__('The %s step is missing a submit field', 'wp-ultimo'), $step['name']);
return false;
} // end if;
} // end foreach;
/*
* @todo: Plan, product selection fields must come before the order summary and payment fields.
*/
return true;
} // end check;
} // end class Checkout_Steps;

View File

@ -0,0 +1,64 @@
<?php
/**
* Adds a validation rules for cities
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.11
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
// Exit if accessed directly
defined('ABSPATH') || exit;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
/**
* Validates template sites.
*
* @since 2.0.4
*/
class City extends Rule {
/**
* Parameters that this rule accepts.
*
* @since 2.0.4
* @var array
*/
protected $fillableParams = array('country', 'state'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.11
*
* @param mixed $city The city value detected.
*/
public function check($city) : bool { // phpcs:ignore
$check = true;
$country = $this->parameter('country') ?? wu_request('billing_country');
$state = $this->parameter('state') ?? wu_request('billing_state');
if ($country && $state && $city) {
$state = strtoupper((string) $state);
$allowed_cities = wu_get_country_cities(strtoupper((string) $country), $state, false);
if (!empty($allowed_cities)) {
$check = in_array($city, $allowed_cities, true);
} // end if;
} // end if;
return $check;
} // end check;
} // end class City;

View File

@ -0,0 +1,56 @@
<?php
/**
* Adds a validation rules for countries
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.11
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
// Exit if accessed directly
defined('ABSPATH') || exit;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
/**
* Validates template sites.
*
* @since 2.0.4
*/
class Country extends Rule {
/**
* Parameters that this rule accepts.
*
* @since 2.0.4
* @var array
*/
protected $fillableParams = array(); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.4
*
* @param mixed $country The country value detected.
*/
public function check($country) : bool { // phpcs:ignore
$check = true;
if ($country) {
$country = strtoupper((string) $country);
$allowed_countries = array_keys(wu_get_countries());
$check = in_array($country, $allowed_countries, true);
} // end if;
return $check;
} // end check;
} // end class Country;

View File

@ -0,0 +1,52 @@
<?php
/**
* Adds a validation rules that allows us to check if a given parameter exists.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter exists.
*
* @since 2.0.0
*/
class Domain extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = ':attribute :value is not valid';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array(); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool { // phpcs:ignore
return (bool) preg_match('/^(?!\-)(?:(?:[a-zA-Z\d][a-zA-Z\d\-]{0,61})?[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/', (string) $value);
} // end check;
} // end class Domain;

View File

@ -0,0 +1,62 @@
<?php
/**
* Adds a validation rules that allows us to check if a given parameter exists.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter exists.
*
* @since 2.0.0
*/
class Exists extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = ':attribute :value is not valid';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array('model', 'column', 'except'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool {
$this->requireParameters(array(
'model',
'column'
));
$column = $this->parameter('column');
$model = $this->parameter('model');
// do query
return !!$model::get_by($column, $value);
} // end check;
} // end class Exists;

View File

@ -0,0 +1,125 @@
<?php // phpcs:disable
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @since 2.0.0
*/
class Price_Variations extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = ':attribute is wrongly setup.';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array('duration', 'duration_unit'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool {
if (is_string($value)) {
$value = maybe_unserialize($value);
} // end if;
if (!is_array($value)) {
return false;
} // end if;
foreach ($value as $price_variation) {
/**
* Validation Duration
*/
$duration = wu_get_isset($price_variation, 'duration', false);
if (!is_numeric($duration) || (int) $duration <= 0) {
return false;
} // end if;
/**
* Validation Unit
*/
$unit = wu_get_isset($price_variation, 'duration_unit', false);
$allowed_units = array(
'day',
'week',
'month',
'year',
);
if (!in_array($unit, $allowed_units, true)) {
return false;
} // end if;
/**
* Check if it is the same as the main duration
*/
if ($this->parameter('duration') == $duration && $this->parameter('duration_unit') === $unit) {
$this->message = 'This product cannot have a price variation for the same duration and duration unit values as the product itself.';
return false;
} // end if;
/**
* Validation Amount
*/
$amount = wu_get_isset($price_variation, 'amount', false);
if ($amount) {
$amount = wu_to_float($amount);
} // end if;
if (!is_numeric($amount)) {
return false;
} // end if;
} // end foreach;
return true;
} // end check;
} // end class Price_Variations;

View File

@ -0,0 +1,66 @@
<?php
/**
* Adds a validation rule for products.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.4
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Validates products.
*
* @since 2.0.4
*/
class Products extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.4
* @var string
*/
protected $message = '';
/**
* Parameters that this rule accepts.
*
* @since 2.0.4
* @var array
*/
protected $fillableParams = array(); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.4
*
* @param mixed $products Value being checked.
*/
public function check($products) : bool { // phpcs:ignore
$products = (array) $products;
$product_objects = array_map('wu_get_product', $products);
list($plan, $additional_products) = wu_segregate_products($product_objects);
if ($plan) {
return true;
} // end if;
$this->message = __('A plan is required.', 'wp-ultimo');
return false;
} // end check;
} // end class Products;

View File

@ -0,0 +1,130 @@
<?php
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.4
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use \WP_Ultimo\Dependencies\Rakit\Validation\Rule;
use \WP_Ultimo\Checkout\Checkout;
use \WP_Ultimo\Database\Sites\Site_Type;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Validates template sites.
*
* @since 2.0.4
*/
class Site_Template extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.4
* @var string
*/
protected $message = '';
/**
* Parameters that this rule accepts.
*
* @since 2.0.4
* @var array
*/
protected $fillableParams = array(); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.4
*
* @param mixed $template_id Value being checked.
*/
public function check($template_id) : bool { // phpcs:ignore
$template_id = absint($template_id);
if (!$template_id) {
return true;
} // end if;
$site = wu_get_site($template_id);
if (!$site || ($site->get_type() !== Site_Type::SITE_TEMPLATE && $site->get_type() !== Site_Type::CUSTOMER_OWNED)) {
$this->message = __('The Template ID does not correspond to a valid Template', 'wp-ultimo');
return false;
} // end if;
if ($site->get_type() === Site_Type::CUSTOMER_OWNED) {
if (!wu_get_setting('allow_own_site_as_template')) {
$this->message = __('You can not use your sites as template', 'wp-ultimo');
return false;
} // end if;
$customer = wu_get_current_customer();
if (!$customer || $site->get_customer_id() !== $customer->get_id()) {
$this->message = __('The selected template is not available.', 'wp-ultimo');
return false;
} // end if;
return true;
} // end if;
$allowed_templates = false;
$product_ids_or_slugs = Checkout::get_instance()->request_or_session('products', array());
$product_ids_or_slugs = array_unique($product_ids_or_slugs);
if ($product_ids_or_slugs) {
$products = array_map('wu_get_product', $product_ids_or_slugs);
$limits = new \WP_Ultimo\Objects\Limitations();
list($plan, $additional_products) = wu_segregate_products($products);
$products = array_merge(array($plan), $additional_products);
foreach ($products as $product) {
$limits = $limits->merge($product->get_limitations());
} // end foreach;
$allowed_templates = $limits->site_templates->get_available_site_templates();
} // end if;
if (is_array($allowed_templates) && !in_array($template_id, $allowed_templates)) { // phpcs:ignore
$this->message = __('The selected template is not available for this product.', 'wp-ultimo');
return false;
} // end if;
return true;
} // end check;
} // end class Site_Template;

View File

@ -0,0 +1,62 @@
<?php
/**
* Adds a validation rules for states
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.11
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
// Exit if accessed directly
defined('ABSPATH') || exit;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
/**
* Validates template sites.
*
* @since 2.0.4
*/
class State extends Rule {
/**
* Parameters that this rule accepts.
*
* @since 2.0.4
* @var array
*/
protected $fillableParams = array('country'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.11
*
* @param mixed $state The state value detected.
*/
public function check($state) : bool { // phpcs:ignore
$check = true;
$country = $this->parameter('country') ?? wu_request('billing_country');
if ($country && $state) {
$state = strtoupper((string) $state);
$allowed_states = array_keys(wu_get_country_states(strtoupper((string) $country), false));
if (!empty($allowed_states)) {
$check = in_array($state, $allowed_states, true);
} // end if;
} // end if;
return $check;
} // end check;
} // end class State;

View File

@ -0,0 +1,66 @@
<?php
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @since 2.0.0
*/
class Unique_Site extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = '';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array('self_id'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool { // phpcs:ignore
$this->requireParameters(array());
$self_id = $this->parameter('self_id');
$results = wpmu_validate_blog_signup($value, 'Test Title');
if ($results['errors']->has_errors()) {
$this->message = $results['errors']->get_error_message();
return false;
} // end if;
return true;
} // end check;
} // end class Unique_Site;

View File

@ -0,0 +1,96 @@
<?php
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @package WP_Ultimo
* @subpackage Helpers/Validation_Rules
* @since 2.0.0
*/
namespace WP_Ultimo\Helpers\Validation_Rules;
use WP_Ultimo\Dependencies\Rakit\Validation\Rule;
// Exit if accessed directly
defined('ABSPATH') || exit;
/**
* Adds a validation rules that allows us to check if a given parameter is unique.
*
* @since 2.0.0
*/
class Unique extends Rule {
/**
* Error message to be returned when this value has been used.
*
* @since 2.0.0
* @var string
*/
protected $message = ':attribute :value has been used';
/**
* Parameters that this rule accepts.
*
* @since 2.0.0
* @var array
*/
protected $fillableParams = array('model', 'column', 'self_id'); // phpcs:ignore
/**
* Performs the actual check.
*
* @since 2.0.0
*
* @param mixed $value Value being checked.
*/
public function check($value) : bool {
$this->requireParameters(array(
'model',
'column',
));
$column = $this->parameter('column');
$model = $this->parameter('model');
$self_id = $this->parameter('self_id');
switch ($model) {
case '\WP_User':
$callback = 'get_user_by';
break;
default:
$callback = array($model, 'get_by');
break;
}
// do query
$existing = call_user_func($callback, $column, $value);
$user_models = array(
'\WP_User',
'\WP_Ultimo\Models\Customer',
);
/*
* Customize the error message for the customer.
*/
if (in_array($model, $user_models, true)) {
$this->message = __('A customer with the same email address or username already exists.', 'wp-ultimo');
} // end if;
if (!$existing) {
return true;
} // end if;
$id = method_exists($existing, 'get_id') ? $existing->get_id() : $existing->id;
return absint($id) === absint($self_id);
} // end check;
} // end class Unique;