diff --git a/inc/admin-pages/class-dashboard-admin-page.php b/inc/admin-pages/class-dashboard-admin-page.php
index f81602a..d49fd01 100644
--- a/inc/admin-pages/class-dashboard-admin-page.php
+++ b/inc/admin-pages/class-dashboard-admin-page.php
@@ -488,15 +488,11 @@ class Dashboard_Admin_Page extends Base_Admin_Page {
$month_list = array();
- $start = date_i18n('Y-m-d 00:00:00', strtotime('first day of january this year'));
+ $current_year = date_i18n('Y');
- for ($i = 0; $i < 12; $i++) {
-
- $start_date = wu_date($start);
-
- $month_list[] = date_i18n('M y', $start_date->addMonths($i)->format('U'));
-
- } // end for;
+ for ($i = 1; $i <= 12; $i++) {
+ $month_list[] = date_i18n('M y', mktime(0, 0,0,$i,1, $current_year));
+ }
$statistics = new Dashboard_Statistics(array(
'start_date' => $this->start_date,
diff --git a/inc/admin-pages/class-membership-edit-admin-page.php b/inc/admin-pages/class-membership-edit-admin-page.php
index 82d91c2..801ca87 100644
--- a/inc/admin-pages/class-membership-edit-admin-page.php
+++ b/inc/admin-pages/class-membership-edit-admin-page.php
@@ -128,7 +128,7 @@ class Membership_Edit_Admin_Page extends Edit_Admin_Page {
),
);
- $date = wu_date($swap_order->scheduled_date);
+ $date = new \DateTime($swap_order->scheduled_date);
// translators: %s is the date, using the site format options
$message = sprintf(__('There is a change scheduled to take place on this membership in %s. You can preview the changes here. Scheduled changes are usually created by downgrades.', 'wp-ultimo'), $date->format(get_option('date_format')));
@@ -1078,7 +1078,7 @@ class Membership_Edit_Admin_Page extends Edit_Admin_Page {
),
);
- $date = wu_date($swap_order->scheduled_date);
+ $date = new \DateTime($swap_order->scheduled_date);
// translators: %s is the date, using the site format options
$message = sprintf(__('This is a preview. This page displays the final stage of the membership after the changes scheduled for %s. Saving here will persist these changes, so be careful.', 'wp-ultimo'), $date->format(get_option('date_format')));
diff --git a/inc/deprecated/deprecated.php b/inc/deprecated/deprecated.php
index b044355..12997bb 100644
--- a/inc/deprecated/deprecated.php
+++ b/inc/deprecated/deprecated.php
@@ -1255,7 +1255,7 @@ class WU_Transactions {
_deprecated_function(__CLASS__, '2.0.0', 'wu_date()');
- $date = wu_date();
+ $date = new \DateTime();
return $type === 'mysql' ? $date->format('Y-m-d H:i:s') : $date->format('U');
diff --git a/inc/domain-mapping/class-helper.php b/inc/domain-mapping/class-helper.php
index 830b7b6..c506c79 100644
--- a/inc/domain-mapping/class-helper.php
+++ b/inc/domain-mapping/class-helper.php
@@ -9,11 +9,10 @@
namespace WP_Ultimo\Domain_Mapping;
-use Spatie\SslCertificate\SslCertificate;
use Psr\Log\LogLevel;
// Exit if accessed directly
-defined('ABSPATH') || exit;
+defined( 'ABSPATH' ) || exit;
/**
* Helper class for domain mapping functionality.
@@ -49,7 +48,7 @@ class Helper {
$site_url = site_url();
- $is_development_mode = preg_match('#(localhost|staging.*\.|\.local|\.test)#', $site_url);
+ $is_development_mode = preg_match( '#(localhost|staging.*\.|\.local|\.test)#', $site_url );
/**
* Allow plugin developers to add additional tests
@@ -61,21 +60,19 @@ class Helper {
* @param string $site_url The site URL.
* @return bool
*/
- return apply_filters('wu_is_development_mode', $is_development_mode, $site_url);
-
+ return apply_filters( 'wu_is_development_mode', $is_development_mode, $site_url );
} // end is_development_mode;
- /**
- * Gets the local IP address of the network.
- *
- * Sometimes, this will be the same address as the public one, but we need different methods.
- *
- * @since 2.0.0
- * @return string|bool
- */
- public static function get_local_network_ip() {
-
- return isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : false;
+ /**
+ * Gets the local IP address of the network.
+ *
+ * Sometimes, this will be the same address as the public one, but we need different methods.
+ *
+ * @since 2.0.0
+ * @return string|bool
+ */
+ public static function get_local_network_ip() {
+ return isset( $_SERVER['SERVER_ADDR'] ) ? $_SERVER['SERVER_ADDR'] : false;
} // end get_local_network_ip;
/**
@@ -90,43 +87,38 @@ class Helper {
*/
public static function get_network_public_ip() {
- if (self::is_development_mode()) {
-
+ if ( self::is_development_mode() ) {
$local_ip = self::get_local_network_ip();
/**
* See more about this filter below, on this same method.
*/
- return apply_filters('wu_get_network_public_ip', $local_ip, true);
-
+ return apply_filters( 'wu_get_network_public_ip', $local_ip, true );
} // end if;
- $_ip_address = get_site_transient('wu_public_network_ip');
-
- if (!$_ip_address) {
+ $_ip_address = get_site_transient( 'wu_public_network_ip' );
+ if ( ! $_ip_address ) {
$ip_address = false;
- foreach (self::$providers as $provider_url) {
+ foreach ( self::$providers as $provider_url ) {
+ $response = wp_remote_get(
+ $provider_url,
+ array(
+ 'timeout' => 5,
+ )
+ );
- $response = wp_remote_get($provider_url, array(
- 'timeout' => 5,
- ));
-
- if (!is_wp_error($response)) {
-
- $ip_address = trim(wp_remote_retrieve_body($response));
+ if ( ! is_wp_error( $response ) ) {
+ $ip_address = trim( wp_remote_retrieve_body( $response ) );
continue;
-
} // end if;
-
} // end foreach;
- set_site_transient('wu_public_network_ip', $ip_address, 10 * DAY_IN_SECONDS);
+ set_site_transient( 'wu_public_network_ip', $ip_address, 10 * DAY_IN_SECONDS );
$_ip_address = $ip_address;
-
} // end if;
/**
@@ -143,10 +135,8 @@ class Helper {
* @param bool $local True if this is a local network (localhost, .dev, etc.), false otherwise.
* @return string The new IP address.
*/
- return apply_filters('wu_get_network_public_ip', $_ip_address, false);
-
+ return apply_filters( 'wu_get_network_public_ip', $_ip_address, false );
} // end get_network_public_ip;
-
/**
* Checks if a given domain name has a valid associated SSL certificate.
*
@@ -156,24 +146,93 @@ class Helper {
* @return boolean
*/
public static function has_valid_ssl_certificate($domain = '') {
-
$is_valid = false;
+ // Ensure the domain is not empty.
+ if (empty($domain)) {
+ return $is_valid;
+ }
+
+ // Add 'https://' if not already present to use SSL context properly.
+ $domain = strpos($domain, 'https://') === 0 ? $domain : 'https://' . $domain;
+
try {
+ // Create SSL context to fetch the certificate.
+ $context = stream_context_create(
+ array(
+ 'ssl' => array(
+ 'capture_peer_cert' => true,
+ ),
+ )
+ );
- $certificate = SslCertificate::createForHostName($domain);
+ // Open a stream to the domain over SSL.
+ $stream = @stream_socket_client(
+ 'ssl://' . parse_url($domain, PHP_URL_HOST) . ':443',
+ $errno,
+ $errstr,
+ 10,
+ STREAM_CLIENT_CONNECT,
+ $context
+ );
- $is_valid = $certificate->isValid($domain); // returns bool;
+ // If stream could not be established, SSL is invalid.
+ if (!$stream) {
+ throw new \Exception($errstr);
+ }
+ // Retrieve the certificate and parse its details.
+ $options = stream_context_get_options($context);
+
+ if (isset($options['ssl']['peer_certificate'])) {
+ $cert = openssl_x509_parse($options['ssl']['peer_certificate']);
+
+ if ($cert) {
+ // Verify the certificate's validity period.
+ $current_time = time();
+ $valid_from = $cert['validFrom_time_t'] ?? 0;
+ $valid_to = $cert['validTo_time_t'] ?? 0;
+
+ // Check if the certificate is currently valid.
+ if ($current_time >= $valid_from && $current_time <= $valid_to) {
+ $host = parse_url($domain, PHP_URL_HOST);
+
+ // Check that the domain matches the certificate.
+ $common_name = $cert['subject']['CN'] ?? ''; // Common Name (CN)
+ $alt_names = $cert['extensions']['subjectAltName'] ?? ''; // Subject Alternative Names (SAN)
+
+ // Parse SAN into an array if present.
+ $alt_names_array = array_filter(array_map('trim', explode(',', str_replace('DNS:', '', $alt_names))));
+ $alt_names_array[] = $common_name;
+ // Check if the host matches either the CN, any SAN entry, or supports a wildcard match.
+ if (
+ $host === $common_name ||
+ in_array( $host, $alt_names_array, true )
+ ) {
+ $is_valid = true;
+ } else {
+ foreach ($alt_names_array as $alt_name) {
+ if ( strpos($alt_name, '*.') === 0 && str_ends_with( $host, substr($alt_name, 1) )) {
+ $is_valid = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Close the stream after processing.
+ fclose($stream);
} catch (\Exception $e) {
-
- // translators: %s is the error message returned by the checker.
- wu_log_add('domain-ssl-checks', sprintf(__('Certificate Invalid: %s', 'wp-ultimo'), $e->getMessage()), LogLevel::ERROR);
-
- } // end try;
+ // Log the error message.
+ wu_log_add(
+ 'domain-ssl-checks',
+ sprintf(__('Certificate Invalid: %s', 'wp-ultimo'), $e->getMessage()),
+ LogLevel::ERROR
+ );
+ }
return $is_valid;
-
} // end has_valid_ssl_certificate;
-
} // end class Helper;
diff --git a/inc/functions/date.php b/inc/functions/date.php
index 32c2577..e101e3a 100644
--- a/inc/functions/date.php
+++ b/inc/functions/date.php
@@ -55,17 +55,15 @@ function wu_validate_date($date, $format = 'Y-m-d H:i:s') {
* @see https://carbon.nesbot.com/docs/
*
* @param string|false $date Parsable date string.
- * @return \Carbon\Carbon
+ * @return \DateTime
*/
function wu_date($date = false) {
if (!wu_validate_date($date)) {
-
$date = date_i18n('Y-m-d H:i:s');
+ }
- } // end if;
-
- return \Carbon\Carbon::parse($date);
+ return \DateTime::createFromFormat('Y-m-d H:i:s', $date);
} // end wu_date;
@@ -86,7 +84,9 @@ function wu_get_days_ago($date_1, $date_2 = false) {
$datetime_2 = wu_date($date_2);
- return - $datetime_1->diffInDays($datetime_2, false);
+ $dateIntervar = $datetime_1->diff($datetime_2, false);
+
+ return - $dateIntervar->days;
} // end wu_get_days_ago;
diff --git a/inc/installers/class-core-installer.php b/inc/installers/class-core-installer.php
index 5951391..2f67b68 100644
--- a/inc/installers/class-core-installer.php
+++ b/inc/installers/class-core-installer.php
@@ -40,7 +40,7 @@ class Core_Installer extends Base_Installer {
if (!(defined('SUNRISE') && SUNRISE)) {
// translators: %s is a URL to a documentation link.
- $closte_message = sprintf(__('You are using Closte and they prevent the wp-config.php file from being written to. Follow these instructions to do it manually.'), wu_get_documentation_url('wp-ultimo-closte-config'));
+ $closte_message = sprintf(__('You are using Closte and they prevent the wp-config.php file from being written to. Follow these instructions to do it manually.', 'wp-ultimo' ), wu_get_documentation_url('wp-ultimo-closte-config'));
throw new \Exception($closte_message);
diff --git a/inc/installers/class-migrator.php b/inc/installers/class-migrator.php
index f1d68fb..cf9e43b 100644
--- a/inc/installers/class-migrator.php
+++ b/inc/installers/class-migrator.php
@@ -1851,7 +1851,7 @@ class Migrator extends Base_Installer {
*/
$subscription_creation_date = wu_date($subscription->created_at);
- $trial_end_date = $subscription_creation_date->addDays($subscription->trial)->endOfDay();
+ $trial_end_date = $subscription_creation_date->add( new \DateInterval( 'P' . $subscription->trial . 'D' ) )->setTime( 23, 59, 59 );
$date_trial_end = $trial_end_date->format('Y-m-d 23:59:59');
diff --git a/inc/managers/class-site-manager.php b/inc/managers/class-site-manager.php
index 11cf2e2..b18bcd9 100644
--- a/inc/managers/class-site-manager.php
+++ b/inc/managers/class-site-manager.php
@@ -321,9 +321,9 @@ class Site_Manager extends Base_Manager {
// If membership is cancelled we do not add the grace period
$grace_period = $status !== Membership_Status::CANCELLED ? (int) wu_get_setting('block_frontend_grace_period', 0) : 0;
- $expiration_time = wu_date($membership->get_date_expiration())->timestamp + $grace_period * DAY_IN_SECONDS;
+ $expiration_time = wu_date($membership->get_date_expiration())->getTimestamp() + $grace_period * DAY_IN_SECONDS;
- if ($expiration_time < wu_date()->timestamp) {
+ if ($expiration_time < wu_date()->getTimestamp()) {
$checkout_pages = \WP_Ultimo\Checkout\Checkout_Pages::get_instance();
diff --git a/inc/models/class-discount-code.php b/inc/models/class-discount-code.php
index 36c0625..1e5bbc1 100644
--- a/inc/models/class-discount-code.php
+++ b/inc/models/class-discount-code.php
@@ -519,6 +519,8 @@ class Discount_Code extends Base_Model {
return new \WP_Error('discount_code', __('This coupon code is not valid.', 'wp-ultimo'));
+ return new \WP_Error( 'discount_code', __( 'The coupon code is not valid yet.', 'wp-ultimo' ) );
+
} // end if;
} // end if;
@@ -527,7 +529,7 @@ class Discount_Code extends Base_Model {
$expiration_date_instance = wu_date($expiration_date);
- if ($now > $expiration_date) {
+ if ($now > $expiration_date_instance) {
return new \WP_Error('discount_code', __('This coupon code is not valid.', 'wp-ultimo'));
diff --git a/inc/models/class-membership.php b/inc/models/class-membership.php
index 9e741f1..a10485e 100644
--- a/inc/models/class-membership.php
+++ b/inc/models/class-membership.php
@@ -2477,7 +2477,7 @@ class Membership extends Base_Model {
} // end if;
- return floor($today->diffInDays($expiration_date));
+ return floor($today->diff($expiration_date)->days);
} // end get_remaining_days_in_cycle;
diff --git a/inc/tax/class-dashboard-taxes-tab.php b/inc/tax/class-dashboard-taxes-tab.php
index 8d39e7d..48a1bef 100644
--- a/inc/tax/class-dashboard-taxes-tab.php
+++ b/inc/tax/class-dashboard-taxes-tab.php
@@ -198,15 +198,11 @@ class Dashboard_Taxes_Tab {
$month_list = array();
- $start = date_i18n('Y-m-d 00:00:00', strtotime('first day of january this year'));
+ $current_year = date_i18n('Y');
- for ($i = 0; $i < 12; $i++) {
-
- $start_date = wu_date($start);
-
- $month_list[] = date_i18n('M y', $start_date->addMonths($i)->format('U'));
-
- } // end for;
+ for ($i = 1; $i <= 12; $i++) {
+ $month_list[] = date_i18n('M y', mktime(0, 0,0,$i,1, $current_year));
+ }
wp_register_script('wu-tax-stats', wu_get_asset('tax-statistics.js', 'js'), array('jquery', 'wu-functions', 'wu-ajax-list-table', 'moment', 'wu-block-ui', 'dashboard', 'wu-apex-charts', 'wu-vue-apex-charts'), wu_get_version(), true);
diff --git a/tests/Admin_Pages/Dashboard_Admin_Page_Test.php b/tests/Admin_Pages/Dashboard_Admin_Page_Test.php
new file mode 100644
index 0000000..8d3bfda
--- /dev/null
+++ b/tests/Admin_Pages/Dashboard_Admin_Page_Test.php
@@ -0,0 +1,45 @@
+getMockBuilder( Dashboard_Admin_Page::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'output' ] )
+ ->getMock();
+
+ // Fake dates for testing
+ $dashboard_admin_page->start_date = '2023-01-01';
+ $dashboard_admin_page->end_date = '2023-01-31';
+
+ // Execute register_scripts method
+ $dashboard_admin_page->register_scripts();
+
+ // Assert scripts are registered
+ $this->assertTrue( wp_script_is( 'wu-apex-charts', 'registered' ) );
+ $this->assertTrue( wp_script_is( 'wu-vue-apex-charts', 'registered' ) );
+ $this->assertTrue( wp_script_is( 'wu-dashboard-stats', 'registered' ) );
+
+ // Assert styles are registered
+ $this->assertTrue( wp_style_is( 'wu-apex-charts', 'registered' ) );
+
+ // Assert scripts are enqueued
+ $this->assertTrue( wp_script_is( 'wu-dashboard-stats', 'enqueued' ) );
+
+ // Verify localized script data is correct
+ $localized_vars = wp_scripts()->get_data( 'wu-dashboard-stats', 'data' );
+ echo( $localized_vars );
+ $this->assertStringContainsString( '"month_list":["Jan ', $localized_vars );
+ $this->assertStringContainsString( '"today":"', $localized_vars ); // Check that today is included
+ $this->assertStringContainsString( '"new_mrr":"New MRR"', $localized_vars );
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Admin_Pages/Membership_Edit_Admin_Page_Test.php b/tests/Admin_Pages/Membership_Edit_Admin_Page_Test.php
new file mode 100644
index 0000000..2fb1108
--- /dev/null
+++ b/tests/Admin_Pages/Membership_Edit_Admin_Page_Test.php
@@ -0,0 +1,63 @@
+generate_fake_memberships();
+ $this->swap_time = strtotime( '+100 days' );
+
+ $this->membership = current( $faker->get_fake_data_generated( 'memberships' ) );
+ $cart = new Cart( array() );
+ $this->membership->schedule_swap( $cart, gmdate( 'Y-m-d H:i:s', $this->swap_time ) );
+ // Mock Membership_Edit_Admin_Page with dependencies and methods.
+ $this->membership_edit_admin_page = new Membership_Edit_Admin_Page();
+ }
+
+
+ /**
+ * Tests that page_loaded calls add_swap_notices.
+ */
+ public function test_page_loaded_calls_add_swap_notices() {
+ $_REQUEST['id'] = $this->membership->get_id();
+ $this->membership_edit_admin_page->page_loaded();
+
+ $membership = $this->membership_edit_admin_page->get_object();
+ $this->assertInstanceOf( Membership::class, $membership );
+ $this->assertEquals( $membership->get_id(), $this->membership->get_id() );
+ $this->assertTrue( $this->membership_edit_admin_page->edit );
+
+ $notices = \WP_Ultimo()->notices->get_notices( 'network-admin' );
+ $this->assertNotEmpty( $notices );
+ $notice = array_shift( $notices );
+ $this->assertEquals( 'warning', $notice['type'] );
+ $this->assertFalse( $notice['dismissible_key'] );
+ $this->assertNotEmpty( $notice['actions'] );
+ $this->assertStringContainsString( gmdate( get_option( 'date_format' ), $this->swap_time ), $notice['message'] );
+ }
+}
diff --git a/tests/WP_Ultimo/Date_Functions_Test.php b/tests/WP_Ultimo/Date_Functions_Test.php
new file mode 100644
index 0000000..dcdf0cd
--- /dev/null
+++ b/tests/WP_Ultimo/Date_Functions_Test.php
@@ -0,0 +1,11 @@
+assertEquals( -30, $days );
+ }
+}
diff --git a/tests/WP_Ultimo/Models/Discount_Code_Test.php b/tests/WP_Ultimo/Models/Discount_Code_Test.php
new file mode 100644
index 0000000..c8a3eba
--- /dev/null
+++ b/tests/WP_Ultimo/Models/Discount_Code_Test.php
@@ -0,0 +1,128 @@
+set_active( true );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * Tests that an inactive discount code returns an error.
+ */
+ public function test_is_valid_inactive_discount_code() {
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( false );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertEquals( 'discount_code', $result->get_error_code() );
+ $this->assertEquals( 'This coupon code is not valid.', $result->get_error_message() );
+ }
+
+ /**
+ * Tests that a discount code with max uses returns an error after being used maximum times.
+ */
+ public function test_is_valid_max_uses_exceeded() {
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_max_uses( 5 );
+ $discount_code->set_uses( 5 );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertEquals( 'discount_code', $result->get_error_code() );
+ $this->assertEquals( 'This discount code was already redeemed the maximum amount of times allowed.',
+ $result->get_error_message() );
+ }
+
+ /**
+ * Tests that a discount code before the start date is invalid.
+ */
+ public function test_is_valid_before_start_date() {
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_date_start( date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ) );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertEquals( 'discount_code', $result->get_error_code() );
+ $this->assertEquals( 'This coupon code is not valid.', $result->get_error_message() );
+ }
+
+ /**
+ * Tests that a discount code after the expiration date is invalid.
+ */
+ public function test_is_valid_after_expiration_date() {
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_date_expiration( date( 'Y-m-d H:i:s', strtotime( '-1 day' ) ) );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertEquals( 'discount_code', $result->get_error_code() );
+ $this->assertEquals( 'This coupon code is not valid.', $result->get_error_message() );
+ }
+
+ /**
+ * Tests that a discount code limited to specific products returns true for allowed products.
+ */
+ public function test_is_valid_for_allowed_product() {
+ $product_id = 123;
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_limit_products( true );
+ $discount_code->set_allowed_products( [ $product_id ] );
+
+ $result = $discount_code->is_valid( $product_id );
+
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * Tests that a discount code limited to specific products returns an error for disallowed products.
+ */
+ public function test_is_valid_for_disallowed_product() {
+ $allowed_product_id = 123;
+ $disallowed_product_id = 456;
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_limit_products( true );
+ $discount_code->set_allowed_products( [ $allowed_product_id ] );
+
+ $result = $discount_code->is_valid( $disallowed_product_id );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertEquals( 'discount_code', $result->get_error_code() );
+ $this->assertEquals( 'This coupon code is not valid.', $result->get_error_message() );
+ }
+
+ /**
+ * Tests that a discount code with no product limits returns true.
+ */
+ public function test_is_valid_no_product_limits() {
+ $discount_code = new Discount_Code();
+ $discount_code->set_active( true );
+ $discount_code->set_limit_products( false );
+
+ $result = $discount_code->is_valid();
+
+ $this->assertTrue( $result );
+ }
+}
\ No newline at end of file
diff --git a/tests/WP_Ultimo/Models/Domain_Test.php b/tests/WP_Ultimo/Models/Domain_Test.php
new file mode 100644
index 0000000..17be507
--- /dev/null
+++ b/tests/WP_Ultimo/Models/Domain_Test.php
@@ -0,0 +1,44 @@
+set_domain( 'dogs.4thelols.uk' );
+
+ // Assert that it returns true for a valid SSL certificate.
+ $this->assertTrue( $domain->has_valid_ssl_certificate() );
+ }
+
+ /**
+ * Test that has_valid_ssl_certificate returns false when the SSL certificate is invalid.
+ */
+ public function test_has_valid_ssl_certificate_with_invalid_certificate() {
+ // Mocking a domain with an invalid SSL certificate.
+ $domain = new Domain();
+ $domain->set_domain( 'eeeeeeeeeeeeeeeeauauexample.com' );
+
+ // Assert that it returns false for an invalid SSL certificate.
+ $this->assertFalse( $domain->has_valid_ssl_certificate() );
+ }
+
+ /**
+ * Test that has_valid_ssl_certificate handles empty domain.
+ */
+ public function test_has_valid_ssl_certificate_with_empty_domain() {
+ // Mocking a domain with an empty value.
+ $domain = new Domain();
+ $domain->set_domain( '' );
+
+ // Assert that it returns false for an empty domain.
+ $this->assertFalse( $domain->has_valid_ssl_certificate() );
+ }
+}
\ No newline at end of file
diff --git a/tests/WP_Ultimo/Models/Membership_Test.php b/tests/WP_Ultimo/Models/Membership_Test.php
new file mode 100644
index 0000000..03bb2a2
--- /dev/null
+++ b/tests/WP_Ultimo/Models/Membership_Test.php
@@ -0,0 +1,173 @@
+membership = new Membership();
+
+ // Set a default customer ID.
+ $this->membership->set_customer_id( 123 );
+ }
+
+ /**
+ * Test if the customer is allowed access to the membership.
+ */
+ public function test_is_customer_allowed() {
+ // Admins with 'manage_network' capability should always return true.
+ $admin_user_id = $this->factory()->user->create( array( 'role' => 'administrator' ) );
+ grant_super_admin( $admin_user_id );
+ wp_set_current_user( $admin_user_id );
+ $this->assertTrue( $this->membership->is_customer_allowed(), 'Failed asserting that admin is allowed.' );
+
+ // Regular customers are allowed if IDs match.
+ $customer_id = 123;
+ $this->assertTrue(
+ $this->membership->is_customer_allowed( $customer_id ),
+ 'Failed asserting that customer with matching ID is allowed.'
+ );
+
+ // Regular customers are denied if IDs do not match.
+ $wrong_customer_id = 456;
+ wp_set_current_user( $wrong_customer_id );
+ $this->assertFalse(
+ $this->membership->is_customer_allowed( $wrong_customer_id ),
+ 'Failed asserting that customer with non-matching ID is denied.'
+ );
+ }
+
+ /**
+ * Test adding a product to the membership.
+ */
+ public function test_add_product() {
+ // Add a product with a specific ID and quantity.
+ $quantity = 2;
+ $faker = new Faker();
+ $faker->generate_fake_products();
+ /** @var Product $product */
+ $product = $faker->get_fake_data_generated( 'products' )[0];
+ $product_id = $product->get_id();
+
+ $this->membership->add_product( $product_id, $quantity );
+
+ // Verify that the product is added with the correct quantity.
+ $addon_products = $this->membership->get_addon_ids();
+ $this->assertContains( $product_id, $addon_products, 'Failed asserting that product ID was added.' );
+ $this->assertEquals(
+ $quantity,
+ $this->membership->get_addon_products()[0]['quantity'],
+ 'Failed asserting that the product quantity is correct.'
+ );
+
+ // Add more of the same product and check the updated quantity.
+ $additional_quantity = 3;
+ $this->membership->add_product( $product_id, $additional_quantity );
+
+ $this->assertEquals(
+ $quantity + $additional_quantity,
+ $this->membership->get_addon_products()[0]['quantity'],
+ 'Failed asserting that the quantity was updated correctly for the same product.'
+ );
+ }
+
+ /**
+ * Test removing a product from the membership.
+ */
+ public function test_remove_product() {
+ // Add a product with a specific quantity.
+ $quantity = 5;
+ $faker = new Faker();
+ $faker->generate_fake_products();
+ /** @var Product $product */
+ $product = $faker->get_fake_data_generated( 'products' )[0];
+ $product_id = $product->get_id();
+
+ $this->membership->add_product( $product_id, $quantity );
+
+ // Remove some of the product's quantity.
+ $remove_quantity = 3;
+ $this->membership->remove_product( $product_id, $remove_quantity );
+
+ // Verify the updated quantity.
+ $this->assertEquals(
+ $quantity - $remove_quantity,
+ $this->membership->get_addon_products()[0]['quantity'],
+ 'Failed asserting that the quantity was reduced correctly.'
+ );
+
+ // Remove the remaining quantity and verify it is removed.
+ $this->membership->remove_product( $product_id, $quantity );
+ $addon_products = $this->membership->get_addon_ids();
+ $this->assertNotContains( $product_id, $addon_products, 'Failed asserting that the product was removed.' );
+ }
+
+ /**
+ * Test get_remaining_days_in_cycle() method.
+ */
+ public function test_get_remaining_days_in_cycle() {
+ $this->membership->set_amount( 12.99 );
+ // Case 1: Non-recurring membership should return 10000.
+ $this->membership->set_recurring( false );
+ $this->assertEquals(
+ 10000,
+ $this->membership->get_remaining_days_in_cycle(),
+ 'Failed asserting that non-recurring membership returns 10000.'
+ );
+
+ // Case 2: Invalid expiration date should return 0.
+ $this->membership->set_recurring( true );
+ $this->membership->set_date_expiration( 'invalid-date' ); // Setting an invalid date.
+ $this->assertEquals(
+ 0,
+ $this->membership->get_remaining_days_in_cycle(),
+ 'Failed asserting that an invalid expiration date returns 0.'
+ );
+
+ // Case 3: No expiration date should return 0.
+ $this->membership->set_date_expiration( '' );
+ $this->assertEquals(
+ 0,
+ $this->membership->get_remaining_days_in_cycle(),
+ 'Failed asserting that no expiration date returns 0.'
+ );
+
+ // Case 4: Expiration date is in the future and remaining days are calculated properly.
+ $today = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
+ $today->add( new \DateInterval( 'P10D' ) );
+ $this->membership->set_date_expiration( $today->format( 'Y-m-d H:i:s' ) );
+ $remaining_days = $this->membership->get_remaining_days_in_cycle();
+ $this->assertEquals(
+ 10,
+ $remaining_days,
+ 'Failed asserting that 10 days remain when expiration date is 10 days in the future.'
+ );
+
+ // Case 5: Expiration date is in the past, should return 0.
+ $this->membership->set_date_expiration( date( 'Y-m-d H:i:s', strtotime( '-5 days' ) ) );
+ $remaining_days = $this->membership->get_remaining_days_in_cycle();
+ $this->assertEquals(
+ 0,
+ $remaining_days,
+ 'Failed asserting that remaining days return 0 when the expiration date is in the past.'
+ );
+ }
+}
diff --git a/tests/WP_Ultimo/Tax/Dashboard_Taxes_Tab_Test.php b/tests/WP_Ultimo/Tax/Dashboard_Taxes_Tab_Test.php
new file mode 100644
index 0000000..0f031ca
--- /dev/null
+++ b/tests/WP_Ultimo/Tax/Dashboard_Taxes_Tab_Test.php
@@ -0,0 +1,34 @@
+getMockBuilder( Dashboard_Taxes_Tab::class )
+ ->disableOriginalConstructor()
+ ->setMethods( array( 'output' ) )
+ ->getMock();
+
+ // Execute register_scripts method.
+ $dashboard_admin_page->register_scripts();
+
+ // Assert scripts are registered.
+ $this->assertTrue( wp_script_is( 'wu-tax-stats', 'registered' ) );
+
+ // Assert scripts are enqueued.
+ $this->assertTrue( wp_script_is( 'wu-tax-stats', 'enqueued' ) );
+
+ // Verify localized script data is correct.
+ $localized_vars = wp_scripts()->get_data( 'wu-tax-stats', 'data' );
+ $this->assertStringContainsString( '"month_list":["Jan ', $localized_vars );
+ $this->assertStringContainsString( '"today":"', $localized_vars ); // Check that today is included.
+ $this->assertStringContainsString( '"net_profit_label":"Net Profit"', $localized_vars );
+ }
+}