diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2d3bbc95..b8cf061b 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,107 +1,18 @@ #!/bin/bash -# Pre-commit hook for running phpcs and phpstan on changed files -# This hook runs PHPCS and PHPStan on staged PHP files +# Pre-commit hook for Ultimate Multisite +# Runs ESLint and Stylelint with auto-fix on staged files set -e -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color +echo "Running pre-commit checks..." -echo -e "${GREEN}Running pre-commit checks...${NC}" - -# Get list of staged PHP files -STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' | grep -v '^vendor/' | grep -v '^tests/' || true) - -if [ -z "$STAGED_FILES" ]; then - echo -e "${GREEN}No PHP files to check.${NC}" - exit 0 -fi - -echo -e "${YELLOW}Checking PHP files:${NC}" -echo "$STAGED_FILES" - -# Check if composer dependencies are installed -if [ ! -f "vendor/bin/phpcs" ] || [ ! -f "vendor/bin/phpstan" ]; then - echo -e "${RED}Error: Please run 'composer install' to install development dependencies.${NC}" - exit 1 -fi - -# Run PHPCS on staged files -echo -e "${YELLOW}Running PHPCS...${NC}" -HAS_PHPCS_ERRORS=0 -PHPCS_FAILED_FILES="" -for FILE in $STAGED_FILES; do - if [ -f "$FILE" ]; then - if ! vendor/bin/phpcs --colors "$FILE"; then - PHPCS_FAILED_FILES="$PHPCS_FAILED_FILES $FILE" - HAS_PHPCS_ERRORS=1 - fi - fi -done - -# If PHPCS found errors, try to auto-fix them with PHPCBF -if [ $HAS_PHPCS_ERRORS -ne 0 ]; then - echo -e "${YELLOW}PHPCS found errors. Running PHPCBF to auto-fix...${NC}" - - FIXED_FILES="" - for FILE in $PHPCS_FAILED_FILES; do - if [ -f "$FILE" ]; then - # Run phpcbf (it returns 1 if it made changes, 0 if no changes needed) - vendor/bin/phpcbf "$FILE" || true - - # Re-run phpcs to check if the file is now clean - if vendor/bin/phpcs --colors "$FILE" 2>&1; then - echo -e "${GREEN}✓ Auto-fixed: $FILE${NC}" - FIXED_FILES="$FIXED_FILES $FILE" - # Stage the fixed file - git add "$FILE" - else - echo -e "${RED}✗ Could not fully fix: $FILE${NC}" - fi - fi - done - - # Re-check if there are still errors after auto-fixing - HAS_PHPCS_ERRORS=0 - for FILE in $STAGED_FILES; do - if [ -f "$FILE" ]; then - vendor/bin/phpcs --colors "$FILE" > /dev/null 2>&1 || HAS_PHPCS_ERRORS=1 - fi - done - - if [ $HAS_PHPCS_ERRORS -eq 0 ]; then - echo -e "${GREEN}All PHPCS errors have been auto-fixed!${NC}" - fi +# Check if lint-staged is available +if command -v npx &> /dev/null; then + echo "Running lint-staged..." + npx lint-staged + echo "Pre-commit checks passed!" +else + echo "Warning: npx not found. Skipping lint-staged." + echo "Run 'npm install' to set up the development environment." fi - -# Run PHPStan on staged files -echo -e "${YELLOW}Running PHPStan...${NC}" -HAS_PHPSTAN_ERRORS=0 -PHPSTAN_FILES="" -for FILE in $STAGED_FILES; do - if [ -f "$FILE" ] && [[ "$FILE" =~ ^inc/ ]]; then - PHPSTAN_FILES="$PHPSTAN_FILES $FILE" - fi -done - -if [ -n "$PHPSTAN_FILES" ]; then - vendor/bin/phpstan analyse --no-progress --error-format=table $PHPSTAN_FILES || HAS_PHPSTAN_ERRORS=1 -fi - -# Exit with error if any checks failed -if [ $HAS_PHPCS_ERRORS -ne 0 ] || [ $HAS_PHPSTAN_ERRORS -ne 0 ]; then - echo -e "${RED}Pre-commit checks failed!${NC}" - if [ $HAS_PHPCS_ERRORS -ne 0 ]; then - echo -e "${YELLOW}Some PHPCS errors could not be auto-fixed. Please fix them manually.${NC}" - echo -e "${YELLOW}Run 'vendor/bin/phpcs' to see remaining errors.${NC}" - fi - echo -e "${YELLOW}To bypass these checks, use: git commit --no-verify${NC}" - exit 1 -fi - -echo -e "${GREEN}All pre-commit checks passed!${NC}" -exit 0 \ No newline at end of file diff --git a/assets/css/password.css b/assets/css/password.css new file mode 100644 index 00000000..027fdae2 --- /dev/null +++ b/assets/css/password.css @@ -0,0 +1,197 @@ +/** + * Password field styles. + * + * Styles for password visibility toggle, strength meter, + * and related password field components. + * + * @since 2.4.0 + */ + +/** + * CSS Custom Properties for password field theming. + * + * Uses a smart fallback cascade to automatically pick up theme colors from: + * - Elementor: --e-global-color-primary, --e-global-color-accent + * - Kadence Theme: --global-palette1, --global-palette2 + * - Beaver Builder: --fl-global-primary-color + * - Block Themes (theme.json): --wp--preset--color--primary, --wp--preset--color--accent + * - WordPress Admin: --wp-admin-theme-color + * + * Themes can also override directly by setting --wu-password-icon-color. + */ +:root { + /* + * Internal intermediate variables to build the cascade. + * CSS doesn't support long fallback chains, so we build it in layers. + */ + + /* Layer 1: WordPress core fallbacks */ + --wu-pwd-fallback-wp: var( + --wp--preset--color--accent, + var( + --wp--preset--color--primary, + var(--wp-admin-theme-color, #2271b1) + ) + ); + + /* Layer 2: Beaver Builder -> WordPress fallback */ + --wu-pwd-fallback-bb: var(--fl-global-primary-color, var(--wu-pwd-fallback-wp)); + + /* Layer 3: Kadence -> Beaver Builder fallback */ + --wu-pwd-fallback-kadence: var(--global-palette1, var(--wu-pwd-fallback-bb)); + + /* Layer 4: Elementor -> Kadence fallback (final cascade) */ + --wu-pwd-fallback-final: var( + --e-global-color-accent, + var( + --e-global-color-primary, + var(--wu-pwd-fallback-kadence) + ) + ); + + /* + * Primary icon color. + * Themes can override this directly, otherwise uses the cascade above. + */ + --wu-password-icon-color: var(--wu-pwd-fallback-final); + + --wu-password-toggle-size: 20px; + --wu-password-strength-weak: #dc3232; + --wu-password-strength-medium: #f0b849; + --wu-password-strength-strong: #46b450; +} + +/** + * Password field container. + * + * Ensures the toggle button can be absolutely positioned. + */ +.wu-password-field-container { + position: relative; +} + +/** + * Password input with space for toggle. + */ +.wu-password-input { + padding-right: 40px !important; +} + +/** + * Password visibility toggle button. + * + * Positioned absolutely within the input container, + * vertically centered. + */ +.wu-pwd-toggle { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + padding: 4px; + background: transparent; + border: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + box-shadow: none; +} + +/** + * Toggle button icon styling. + * + * Uses theme primary color with hover effect. + */ +.wu-pwd-toggle .dashicons { + font-size: var(--wu-password-toggle-size); + width: var(--wu-password-toggle-size); + height: var(--wu-password-toggle-size); + transition: color 0.2s ease; +} + +.wu-pwd-toggle:hover .dashicons, +.wu-pwd-toggle:focus .dashicons { + color: var(--wu-password-icon-color); +} + +/** + * Active state when password is visible. + */ +.wu-pwd-toggle[data-toggle="1"] .dashicons { + color: var(--wu-password-icon-color); +} + +/** + * Password strength meter container. + */ +.wu-password-strength-wrapper { + display: block; + margin-top: 8px; +} + +/** + * Password strength result display. + */ +#pass-strength-result { + display: block; + padding: 8px 12px; + border-radius: 4px; + font-size: 13px; + text-align: center; + transition: background-color 0.2s ease, color 0.2s ease; +} + +/** + * Strength meter states. + * + * Override default WordPress colors for consistency. + */ +#pass-strength-result.short, +#pass-strength-result.bad { + background-color: #fce4e4; + color: var(--wu-password-strength-weak); +} + +#pass-strength-result.good { + background-color: #fff8e1; + color: #d88a00; +} + +#pass-strength-result.strong { + background-color: #e8f5e9; + color: var(--wu-password-strength-strong); +} + +#pass-strength-result.mismatch { + background-color: #fce4e4; + color: var(--wu-password-strength-weak); +} + +/** + * Empty state for strength meter. + */ +#pass-strength-result.empty, +#pass-strength-result:empty { + background-color: transparent; +} + +/** + * Focus visibility for accessibility. + */ +.wu-pwd-toggle:focus { + outline: 2px solid var(--wu-password-icon-color); + outline-offset: 2px; + border-radius: 2px; +} + +.wu-pwd-toggle:focus:not(:focus-visible) { + outline: none; +} + +.wu-pwd-toggle:focus-visible { + outline: 2px solid var(--wu-password-icon-color); + outline-offset: 2px; + border-radius: 2px; +} diff --git a/assets/css/password.min.css b/assets/css/password.min.css new file mode 100644 index 00000000..2cdb4842 --- /dev/null +++ b/assets/css/password.min.css @@ -0,0 +1,13 @@ +:root{--wu-pwd-fallback-wp:var( + --wp--preset--color--accent, + var( + --wp--preset--color--primary, + var(--wp-admin-theme-color, #2271b1) + ) + );--wu-pwd-fallback-bb:var(--fl-global-primary-color, var(--wu-pwd-fallback-wp));--wu-pwd-fallback-kadence:var(--global-palette1, var(--wu-pwd-fallback-bb));--wu-pwd-fallback-final:var( + --e-global-color-accent, + var( + --e-global-color-primary, + var(--wu-pwd-fallback-kadence) + ) + );--wu-password-icon-color:var(--wu-pwd-fallback-final);--wu-password-toggle-size:20px;--wu-password-strength-weak:#dc3232;--wu-password-strength-medium:#f0b849;--wu-password-strength-strong:#46b450}.wu-password-field-container{position:relative}.wu-password-input{padding-right:40px!important}.wu-pwd-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);padding:4px;background:0 0;border:0;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;box-shadow:none}.wu-pwd-toggle .dashicons{font-size:var(--wu-password-toggle-size);width:var(--wu-password-toggle-size);height:var(--wu-password-toggle-size);transition:color .2s ease}.wu-pwd-toggle:focus .dashicons,.wu-pwd-toggle:hover .dashicons{color:var(--wu-password-icon-color)}.wu-pwd-toggle[data-toggle="1"] .dashicons{color:var(--wu-password-icon-color)}.wu-password-strength-wrapper{display:block;margin-top:8px}#pass-strength-result{display:block;padding:8px 12px;border-radius:4px;font-size:13px;text-align:center;transition:background-color .2s ease,color .2s ease}#pass-strength-result.bad,#pass-strength-result.short{background-color:#fce4e4;color:var(--wu-password-strength-weak)}#pass-strength-result.good{background-color:#fff8e1;color:#d88a00}#pass-strength-result.strong{background-color:#e8f5e9;color:var(--wu-password-strength-strong)}#pass-strength-result.mismatch{background-color:#fce4e4;color:var(--wu-password-strength-weak)}#pass-strength-result.empty,#pass-strength-result:empty{background-color:transparent}.wu-pwd-toggle:focus{outline:2px solid var(--wu-password-icon-color);outline-offset:2px;border-radius:2px}.wu-pwd-toggle:focus:not(:focus-visible){outline:0}.wu-pwd-toggle:focus-visible{outline:2px solid var(--wu-password-icon-color);outline-offset:2px;border-radius:2px} \ No newline at end of file diff --git a/assets/js/checkout.min.js b/assets/js/checkout.min.js index adbb9006..7f42f880 100644 --- a/assets/js/checkout.min.js +++ b/assets/js/checkout.min.js @@ -1 +1 @@ -((i,s,n)=>{window.history.replaceState&&window.history.replaceState(null,null,wu_checkout.baseurl),s.addAction("wu_on_create_order","nextpress/wp-ultimo",function(e,t){void 0!==t.order.extra.template_id&&t.order.extra.template_id&&(e.template_id=t.order.extra.template_id)}),s.addAction("wu_checkout_loaded","nextpress/wp-ultimo",function(e){void 0!==window.wu_auto_submittable_field&&window.wu_auto_submittable_field&&e.$watch(window.wu_auto_submittable_field,function(){jQuery(this.$el).submit()},{deep:!0})}),s.addAction("wu_checkout_loaded","nextpress/wp-ultimo",function(t){wu_create_cookie("wu_template",""),wu_create_cookie("wu_selected_products",""),wu_listen_to_cookie_change("wu_template",function(e){e&&(t.template_id=e)})}),i(document).on("click",'[href|="#wu-checkout-add"]',function(e){e.preventDefault();var e=i(this),t=e.attr("href").split("#").pop().replace("wu-checkout-add-","");"undefined"!=typeof wu_checkout_form&&-1===wu_checkout_form.products.indexOf(t)&&(wu_checkout_form.add_product(t),e.html(wu_checkout.i18n.added_to_order))}),window.addEventListener("pageshow",function(e){e.persisted&&this.window.wu_checkout_form&&this.window.wu_checkout_form.unblock()}),i(document).ready(function(){var e;void 0!==window.Vue&&(Object.defineProperty(Vue.prototype,"$moment",{value:moment}),e={plan:(e=function(e){return isNaN(e)?e:parseInt(e,10)})(wu_checkout.plan),errors:[],order:wu_checkout.order,products:n.map(wu_checkout.products,e),template_id:wu_checkout.template_id,template_category:"",gateway:wu_checkout.gateway,request_billing_address:wu_checkout.request_billing_address,country:wu_checkout.country,state:"",city:"",site_url:wu_checkout.site_url,site_domain:wu_checkout.site_domain,is_subdomain:wu_checkout.is_subdomain,discount_code:wu_checkout.discount_code,toggle_discount_code:0,payment_method:"",username:"",email_address:"",payment_id:wu_checkout.payment_id,membership_id:wu_checkout.membership_id,cart_type:"new",auto_renew:1,duration:wu_checkout.duration,duration_unit:wu_checkout.duration_unit,prevent_submission:!1,valid_password:!0,stored_templates:{},state_list:[],city_list:[],labels:{},show_login_prompt:!1,login_prompt_field:"",checking_user_exists:!1,logging_in:!1,login_error:"",inline_login_password:""},s.applyFilters("wu_before_form_init",e),jQuery("#wu_form").length)&&(Vue.component("colorPicker",{props:["value"],template:'',mounted(){let o=this;i(this.$el).val(this.value).wpColorPicker({width:200,defaultColor:this.value,change(e,t){o.$emit("input",t.color.toString())}})},watch:{value(e){i(this.$el).wpColorPicker("color",e)}},destroyed(){i(this.$el).off().wpColorPicker("destroy")}}),window.wu_checkout_form=new Vue({el:"#wu_form",data:e,directives:{init:{bind(e,t,o){o.context[t.arg]=t.value}}},components:{dynamic:{functional:!0,template:"#dynamic",props:["template"],render(e,t){t=t.props.template;return e(t?{template:t}:"
%s
', + $custom_message, + esc_url($help_url), + $help_text + ); + + return $message; + } + + /** + * Prepare error data for sending. + * + * @since 2.5.0 + * @param string $handle The log handle. + * @param string $message The error message. + * @param string $log_level The PSR-3 log level. + * @return array + */ + protected function prepare_error_data(string $handle, string $message, string $log_level = ''): array { + + return [ + 'tracker_version' => '1.0.0', + 'timestamp' => time(), + 'site_hash' => $this->get_site_hash(), + 'type' => 'error', + 'log_level' => $log_level, + 'handle' => $this->sanitize_log_handle($handle), + 'message' => $this->sanitize_error_message($message), + 'environment' => [ + 'php_version' => PHP_VERSION, + 'wp_version' => get_bloginfo('version'), + 'plugin_version' => wu_get_version(), + 'is_subdomain' => is_subdomain_install(), + ], + ]; + } + + /** + * Sanitize log handle for sending. + * + * @since 2.5.0 + * @param string $handle The log handle. + * @return string + */ + protected function sanitize_log_handle(string $handle): string { + + return sanitize_key($handle); + } + + /** + * Sanitize error message to remove sensitive data. + * + * @since 2.5.0 + * @param string $message The error message. + * @return string + */ + protected function sanitize_error_message(string $message): string { + + // Remove file paths (Unix and Windows) + $message = preg_replace('/\/[^\s\'"]+/', '[path]', $message); + $message = preg_replace('/[A-Z]:\\\\[^\s\'"]+/', '[path]', $message); + + // Remove potential domain names + $message = preg_replace('/https?:\/\/[^\s\'"]+/', '[url]', $message); + $message = preg_replace('/[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}/', '[domain]', $message); + + // Remove potential email addresses + $message = preg_replace('/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/', '[email]', $message); + + // Remove potential IP addresses + $message = preg_replace('/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/', '[ip]', $message); + + // Limit message length + return substr($message, 0, 1000); + } + + /** + * Send data to the API endpoint. + * + * @since 2.5.0 + * @param array $data The data to send. + * @param string $type The type of data (usage|error). + * @return array|\WP_Error + */ + protected function send_to_api(array $data, string $type) { + + $url = add_query_arg('type', $type, self::API_URL); + + $response = wp_safe_remote_post( + $url, + [ + 'method' => 'POST', + 'timeout' => 15, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'headers' => [ + 'Content-Type' => 'application/json', + 'User-Agent' => 'UltimateMultisite/' . wu_get_version(), + ], + 'body' => wp_json_encode($data), + ] + ); + + if (is_wp_error($response)) { + Logger::add('tracker', 'Failed to send tracking data: ' . $response->get_error_message()); + } + + return $response; + } + + /** + * Send data to the API asynchronously. + * + * @since 2.5.0 + * @param array $data The data to send. + * @param string $type The type of data. + * @return void + */ + protected function send_to_api_async(array $data, string $type): void { + + $url = add_query_arg('type', $type, self::API_URL); + + wp_safe_remote_post( + $url, + [ + 'method' => 'POST', + 'timeout' => 0.01, // Non-blocking + 'redirection' => 0, + 'httpversion' => '1.1', + 'blocking' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + 'User-Agent' => 'UltimateMultisite/' . wu_get_version(), + ], + 'body' => wp_json_encode($data), + ] + ); + } +} diff --git a/inc/class-wp-ultimo.php b/inc/class-wp-ultimo.php index 0057eff1..2288f8fe 100644 --- a/inc/class-wp-ultimo.php +++ b/inc/class-wp-ultimo.php @@ -610,6 +610,11 @@ function () { \WP_Ultimo\Compat\Honeypot_Compat::get_instance(); + /* + * WooCommerce Subscriptions compatibility + */ + \WP_Ultimo\Compat\WooCommerce_Subscriptions_Compat::get_instance(); + /* * Loads Basic White-labeling */ @@ -646,6 +651,11 @@ function () { */ \WP_Ultimo\Cron::get_instance(); + /* + * Usage Tracker (opt-in telemetry) + */ + \WP_Ultimo\Tracker::get_instance(); + \WP_Ultimo\MCP_Adapter::get_instance(); } @@ -929,6 +939,11 @@ protected function load_managers(): void { WP_Ultimo\Orphaned_Tables_Manager::get_instance(); WP_Ultimo\Orphaned_Users_Manager::get_instance(); + /* + * Loads the Rating Notice manager. + */ + WP_Ultimo\Managers\Rating_Notice_Manager::get_instance(); + /** * Loads views overrides */ diff --git a/inc/compat/class-woocommerce-subscriptions-compat.php b/inc/compat/class-woocommerce-subscriptions-compat.php new file mode 100644 index 00000000..9c889709 --- /dev/null +++ b/inc/compat/class-woocommerce-subscriptions-compat.php @@ -0,0 +1,162 @@ +reset_staging_mode((int) $site['site_id']); + } + + /** + * Resets WooCommerce Subscriptions staging mode when a primary domain is set. + * + * @since 2.0.0 + * + * @param \WP_Ultimo\Models\Domain $domain The domain that became primary. + * @param int $blog_id The blog ID of the affected site. + * @param bool $was_new Whether this is a newly created domain. + * @return void + */ + public function reset_staging_mode_on_primary_domain_change($domain, int $blog_id, bool $was_new): void { + + $this->reset_staging_mode($blog_id); + } + + /** + * Resets WooCommerce Subscriptions staging mode detection for a site. + * + * @since 2.0.0 + * + * @param int $site_id The ID of the site. + * @return void + */ + public function reset_staging_mode(int $site_id): void { + + if (! $site_id) { + return; + } + + if (! $this->is_woocommerce_subscriptions_active($site_id)) { + return; + } + + switch_to_blog($site_id); + + try { + $site_url = get_site_url(); + + if (empty($site_url) || ! is_string($site_url)) { + return; + } + + $scheme = wp_parse_url($site_url, PHP_URL_SCHEME); + + if (empty($scheme) || ! is_string($scheme)) { + return; + } + + /* + * Generate the obfuscated key that WooCommerce Subscriptions uses. + * It inserts '_[wc_subscriptions_siteurl]_' in the middle of the URL. + */ + $scheme_with_separator = $scheme . '://'; + $site_url_without_scheme = str_replace($scheme_with_separator, '', $site_url); + + if (empty($site_url_without_scheme) || ! is_string($site_url_without_scheme)) { + return; + } + + $obfuscated_url = $scheme_with_separator . substr_replace( + $site_url_without_scheme, + '_[wc_subscriptions_siteurl]_', + intval(strlen($site_url_without_scheme) / 2), + 0 + ); + + update_option('wc_subscriptions_siteurl', $obfuscated_url); + + delete_option('wcs_ignore_duplicate_siteurl_notice'); + } finally { + restore_current_blog(); + } + } + + /** + * Checks if WooCommerce Subscriptions is active on a site. + * + * @since 2.0.0 + * + * @param int $site_id The ID of the site to check. + * @return bool True if WooCommerce Subscriptions is active, false otherwise. + */ + protected function is_woocommerce_subscriptions_active(int $site_id): bool { + + if (! function_exists('is_plugin_active_for_network')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + if (is_plugin_active_for_network('woocommerce-subscriptions/woocommerce-subscriptions.php')) { + return true; + } + + switch_to_blog($site_id); + + $active_plugins = get_option('active_plugins', []); + + restore_current_blog(); + + return in_array('woocommerce-subscriptions/woocommerce-subscriptions.php', $active_plugins, true); + } +} diff --git a/inc/helpers/class-site-duplicator.php b/inc/helpers/class-site-duplicator.php index 683d488a..2e2ffee1 100644 --- a/inc/helpers/class-site-duplicator.php +++ b/inc/helpers/class-site-duplicator.php @@ -272,12 +272,6 @@ protected static function process_duplication($args) { ] ); - /* - * Reset WooCommerce Subscriptions staging mode detection - * to prevent the duplicated site from being locked in staging mode. - */ - self::reset_woocommerce_subscriptions_staging_mode($args->to_site_id); - return $args->to_site_id; } @@ -311,90 +305,4 @@ public static function create_admin($email, $domain) { return $user_id; } - - /** - * Resets WooCommerce Subscriptions staging mode detection for a duplicated site. - * - * When a site is duplicated, WooCommerce Subscriptions detects the URL change - * and enters "staging mode", which disables automatic payments and subscription - * emails. This method resets the stored site URL to match the new site's URL, - * preventing the staging mode from being triggered. - * - * @since 2.0.0 - * - * @param int $site_id The ID of the newly duplicated site. - * @return void - */ - protected static function reset_woocommerce_subscriptions_staging_mode($site_id) { - - if ( ! $site_id) { - return; - } - // Ensure plugin.php is loaded for is_plugin_active_for_network() - if ( ! function_exists('is_plugin_active_for_network')) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - // Check if WooCommerce Subscriptions is active on the site - if ( ! is_plugin_active_for_network('woocommerce-subscriptions/woocommerce-subscriptions.php')) { - switch_to_blog($site_id); - - $active_plugins = get_option('active_plugins', []); - - restore_current_blog(); - - if ( ! in_array('woocommerce-subscriptions/woocommerce-subscriptions.php', $active_plugins, true)) { - return; - } - } - - // Switch to the duplicated site context - switch_to_blog($site_id); - - try { - // Get the current site URL - $site_url = get_site_url(); - - // Validate that we have a non-empty site URL - if (empty($site_url) || ! is_string($site_url)) { - // Skip updates if site URL is invalid - return; - } - - // Parse the URL scheme and validate the result - $scheme = wp_parse_url($site_url, PHP_URL_SCHEME); - - // Validate wp_parse_url returned a valid scheme - if (empty($scheme) || ! is_string($scheme)) { - // Skip updates if URL parsing failed - return; - } - - // Generate the obfuscated key that WooCommerce Subscriptions uses - // It inserts '_[wc_subscriptions_siteurl]_' in the middle of the URL - $scheme_with_separator = $scheme . '://'; - $site_url_without_scheme = str_replace($scheme_with_separator, '', $site_url); - - // Validate the URL without scheme is a non-empty string - if (empty($site_url_without_scheme) || ! is_string($site_url_without_scheme)) { - // Skip updates if URL manipulation failed - return; - } - - $obfuscated_url = $scheme_with_separator . substr_replace( - $site_url_without_scheme, - '_[wc_subscriptions_siteurl]_', - intval(strlen($site_url_without_scheme) / 2), - 0 - ); - - // Update the WooCommerce Subscriptions site URL option - update_option('wc_subscriptions_siteurl', $obfuscated_url); - - // Delete the "ignore notice" option to ensure a clean state - delete_option('wcs_ignore_duplicate_siteurl_notice'); - } finally { - // Always restore the original blog context, even if errors or exceptions occur - restore_current_blog(); - } - } } diff --git a/inc/managers/class-membership-manager.php b/inc/managers/class-membership-manager.php index 1e483263..c84248b5 100644 --- a/inc/managers/class-membership-manager.php +++ b/inc/managers/class-membership-manager.php @@ -104,12 +104,18 @@ public function publish_pending_site(): void { ignore_user_abort(true); - // Don't make the request block till we finish, if possible. - if ( function_exists('fastcgi_finish_request') && version_compare(phpversion(), '7.0.16', '>=') ) { - wp_send_json(['status' => 'creating-site']); + // Send JSON response to client. + // Don't use wp_send_json because it will exit prematurely. + header('Content-Type: application/json; charset=' . get_option('blog_charset')); + echo wp_json_encode(['status' => 'creating-site']); + // Don't make the request block till we finish, if possible. + if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); + // Response is sent, but the php process continues to run and update the site. } + // Note: When fastcgi_finish_request is unavailable, the client will wait + // for the operation to complete but still receives the JSON response. $membership_id = wu_request('membership_id'); diff --git a/inc/managers/class-rating-notice-manager.php b/inc/managers/class-rating-notice-manager.php new file mode 100644 index 00000000..9fc2c8b4 --- /dev/null +++ b/inc/managers/class-rating-notice-manager.php @@ -0,0 +1,151 @@ +should_show_notice()) { + return; + } + + $this->add_rating_notice(); + } + + /** + * Determines if the rating notice should be shown. + * + * @since 2.4.10 + * @return bool + */ + protected function should_show_notice(): bool { + + $installation_timestamp = get_network_option(null, self::INSTALLATION_TIMESTAMP_OPTION); + + if (empty($installation_timestamp)) { + return false; + } + + $days_since_installation = (time() - $installation_timestamp) / DAY_IN_SECONDS; + + return $days_since_installation >= self::DAYS_BEFORE_NOTICE; + } + + /** + * Adds the rating reminder notice. + * + * @since 2.4.10 + * @return void + */ + protected function add_rating_notice(): void { + + $review_url = 'https://wordpress.org/support/plugin/developer/reviews/#new-post'; + + $message = sprintf( + /* translators: %1$s opening strong tag, %2$s closing strong tag, %3$s review link opening tag, %4$s link closing tag */ + __('Hey there! You\'ve been using %1$sUltimate Multisite%2$s for a while now. If it\'s been helpful for your network, we\'d really appreciate a quick review on WordPress.org. Your feedback helps other users discover the plugin and motivates us to keep improving it. %3$sLeave a review%4$s', 'ultimate-multisite'), + '', + '', + '', + ' →' + ); + + $actions = [ + [ + 'title' => __('Leave a Review', 'ultimate-multisite'), + 'url' => $review_url, + ], + ]; + + \WP_Ultimo()->notices->add( + $message, + 'info', + 'network-admin', + self::NOTICE_DISMISSIBLE_KEY, + $actions + ); + } +} diff --git a/inc/models/class-base-model.php b/inc/models/class-base-model.php index f0a09e9b..ee53f7a1 100644 --- a/inc/models/class-base-model.php +++ b/inc/models/class-base-model.php @@ -292,7 +292,6 @@ public function load_attributes_from_post() { * * @since 2.0.0 * @return Schema - * @throws \ReflectionException When reflection operations fail on the query class. */ public static function get_schema() { @@ -300,13 +299,7 @@ public static function get_schema() { $query_class = new $instance->query_class(); - $reflector = new \ReflectionObject($query_class); - - $method = $reflector->getMethod('get_columns'); - - $method->setAccessible(true); - - $columns = $method->invoke($query_class); + $columns = $query_class->get_columns(); return array_map( fn($column) => $column->to_array(), diff --git a/inc/models/class-domain.php b/inc/models/class-domain.php index d146f34f..bcdc5889 100644 --- a/inc/models/class-domain.php +++ b/inc/models/class-domain.php @@ -543,6 +543,21 @@ public function save() { ); do_action('wu_async_remove_old_primary_domains', $old_primary_domains); + + /** + * Fires when a domain becomes the primary domain for a site. + * + * This action is triggered when a domain's primary_domain flag is set to true, + * either when creating a new primary domain or when updating an existing domain + * to become primary. + * + * @since 2.0.0 + * + * @param \WP_Ultimo\Models\Domain $domain The domain that became primary. + * @param int $blog_id The blog ID of the affected site. + * @param bool $was_new Whether this is a newly created domain. + */ + do_action('wu_domain_became_primary', $this, $this->blog_id, $was_new); } } diff --git a/inc/models/class-membership.php b/inc/models/class-membership.php index 4272774b..ee0e60fb 100644 --- a/inc/models/class-membership.php +++ b/inc/models/class-membership.php @@ -1934,7 +1934,7 @@ public function publish_pending_site_async(): void { 'headers' => $headers, ]; - if ( ! function_exists('fastcgi_finish_request') || ! version_compare(phpversion(), '7.0.16', '>=')) { + if ( ! function_exists('fastcgi_finish_request')) { // We do not have fastcgi but can make the request continue without listening with blocking = false. $request_args['blocking'] = false; } diff --git a/inc/stuff.php b/inc/stuff.php index b357ffe4..d170a4dc 100644 --- a/inc/stuff.php +++ b/inc/stuff.php @@ -1,5 +1,5 @@ 'DxwG0MahbXelGlsldpiNJFRPUkNkZUkxUlViZmlucjJBalkrMlozRzVVQkVOTWxzbVByWkhwM0dtMmNaVkdHeGFjck9hdWlucVVWbklLUEQ=', - 1 => '1ALfP+a48YnA9BacIeEssW9obVJ0WTYrVjEwdm8xK1grVk91bm5UTXF3WXJjQ0FqNGYyQXZya1NYb1lla1lQcFo0NGhEeUd1SlpLalZoK0s=', + 0 => 'JOgRkxnYU/T77rarLGeUH2VENDdVc1d4ajdFeklhSm5SRFlVaW11M0k1WnFZMithRWpZZlZvMDVxbk8xR0RwejQwbjZMOEJRYmNGb3A4a0Q=', + 1 => 'T/CdTxvsrndQXyrK46n4gnRxYSt0OTFiZEk2V3k2aWptRHNSS0NKMFh0TGd2dko1eDI0OG14OGFwN243c1gvWWkzN3FzdlpxY2kvQlpsR1I=', ); diff --git a/inc/ui/class-login-form-element.php b/inc/ui/class-login-form-element.php index 9536d838..1374379b 100644 --- a/inc/ui/class-login-form-element.php +++ b/inc/ui/class-login-form-element.php @@ -302,17 +302,11 @@ public function register_scripts(): void { wp_enqueue_style('wu-admin'); - // Enqueue dashicons for password toggle. - wp_enqueue_style('dashicons'); + // Enqueue password styles (includes dashicons as dependency). + wp_enqueue_style('wu-password'); // Enqueue password toggle script. - wp_enqueue_script( - 'wu-password-toggle', - wu_get_asset('wu-password-toggle.js', 'js'), - ['wp-i18n'], - wu_get_version(), - true - ); + wp_enqueue_script('wu-password-toggle'); wp_set_script_translations('wu-password-toggle', 'ultimate-multisite'); @@ -702,11 +696,11 @@ public function output($atts, $content = null): void { ], 'rp_key' => [ 'type' => 'hidden', - 'value' => $rp_key, + 'value' => $rp_key ?? '', ], 'user_login' => [ 'type' => 'hidden', - 'value' => $rp_login, + 'value' => $rp_login ?? '', ], 'redirect_to' => [ 'type' => 'hidden', diff --git a/inc/ui/class-site-actions-element.php b/inc/ui/class-site-actions-element.php index b1c5cfe4..97e3e3bb 100644 --- a/inc/ui/class-site-actions-element.php +++ b/inc/ui/class-site-actions-element.php @@ -888,6 +888,10 @@ public function render_cancel_payment_method(): void { 'v-model' => 'confirmed', ], ], + 'wu-when' => [ + 'type' => 'hidden', + 'value' => base64_encode('init'), // phpcs:ignore + ], 'submit_button' => [ 'type' => 'submit', 'title' => __('Cancel Payment Method', 'ultimate-multisite'), @@ -1053,6 +1057,10 @@ public function render_cancel_membership(): void { 'v-show' => 'cancellation_reason === "other"', ], ], + 'wu-when' => [ + 'type' => 'hidden', + 'value' => base64_encode('init'), // phpcs:ignore + ], 'confirm' => [ 'type' => 'text', 'title' => __('TypeCANCEL to confirm this membership cancellation.', 'ultimate-multisite'),
diff --git a/lang/ultimate-multisite.pot b/lang/ultimate-multisite.pot
index 20b4d05a..7d856009 100644
--- a/lang/ultimate-multisite.pot
+++ b/lang/ultimate-multisite.pot
@@ -1,4 +1,4 @@
-# Copyright (C) 2025 Ultimate Multisite Community
+# Copyright (C) 2026 Ultimate Multisite Community
# This file is distributed under the GPL2.
msgid ""
msgstr ""
@@ -9,7 +9,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2025-12-23T22:04:15+00:00\n"
+"POT-Creation-Date: 2026-01-14T17:26:39+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.12.0\n"
"X-Domain: ultimate-multisite\n"
@@ -143,8 +143,8 @@ msgid "Growth & Scaling"
msgstr ""
#: inc/admin-pages/class-addons-admin-page.php:470
-#: inc/class-settings.php:1500
-#: inc/class-settings.php:1501
+#: inc/class-settings.php:1526
+#: inc/class-settings.php:1527
msgid "Integrations"
msgstr ""
@@ -168,7 +168,7 @@ msgstr ""
msgid "Marketplace"
msgstr ""
-#: inc/admin-pages/class-base-admin-page.php:656
+#: inc/admin-pages/class-base-admin-page.php:658
msgid "Documentation"
msgstr ""
@@ -545,7 +545,7 @@ msgstr ""
#: inc/admin-pages/class-email-list-admin-page.php:105
#: inc/admin-pages/class-email-list-admin-page.php:116
#: inc/admin-pages/class-email-list-admin-page.php:127
-#: inc/admin-pages/class-settings-admin-page.php:173
+#: inc/admin-pages/class-settings-admin-page.php:174
msgid "System Emails"
msgstr ""
@@ -785,7 +785,7 @@ msgstr ""
#: inc/admin-pages/class-checkout-form-edit-admin-page.php:1245
#: inc/admin-pages/class-discount-code-edit-admin-page.php:230
#: inc/admin-pages/class-email-edit-admin-page.php:294
-#: inc/class-settings.php:1612
+#: inc/class-settings.php:1748
msgid "Advanced Options"
msgstr ""
@@ -957,9 +957,9 @@ msgstr ""
#: inc/admin-pages/class-checkout-form-list-admin-page.php:238
#: inc/admin-pages/class-checkout-form-list-admin-page.php:249
#: inc/admin-pages/class-checkout-form-list-admin-page.php:260
-#: inc/admin-pages/class-settings-admin-page.php:141
-#: inc/admin-pages/class-settings-admin-page.php:201
-#: inc/admin-pages/class-settings-admin-page.php:205
+#: inc/admin-pages/class-settings-admin-page.php:142
+#: inc/admin-pages/class-settings-admin-page.php:202
+#: inc/admin-pages/class-settings-admin-page.php:206
#: inc/installers/class-migrator.php:353
#: inc/list-tables/class-checkout-form-list-table.php:40
#: views/ui/jumper.php:76
@@ -1154,7 +1154,7 @@ msgstr ""
#: inc/admin-pages/class-product-edit-admin-page.php:762
#: inc/checkout/signup-fields/class-signup-field-period-selection.php:216
#: inc/checkout/signup-fields/class-signup-field-select.php:179
-#: inc/class-settings.php:1111
+#: inc/class-settings.php:1137
#: inc/list-tables/class-membership-line-item-list-table.php:139
#: inc/list-tables/class-payment-line-item-list-table.php:82
#: views/checkout/templates/order-bump/simple.php:49
@@ -1243,8 +1243,8 @@ msgstr ""
#: inc/admin-pages/class-membership-list-admin-page.php:279
#: inc/admin-pages/class-membership-list-admin-page.php:290
#: inc/admin-pages/class-membership-list-admin-page.php:301
-#: inc/class-settings.php:924
-#: inc/class-settings.php:925
+#: inc/class-settings.php:950
+#: inc/class-settings.php:951
#: inc/debug/class-debug.php:195
#: inc/list-tables/class-customer-list-table.php:244
#: inc/list-tables/class-membership-list-table-widget.php:42
@@ -1339,8 +1339,8 @@ msgstr ""
#: inc/admin-pages/class-payment-list-admin-page.php:255
#: inc/admin-pages/class-payment-list-admin-page.php:266
#: inc/admin-pages/class-top-admin-nav-menu.php:115
-#: inc/class-settings.php:1331
-#: inc/class-settings.php:1332
+#: inc/class-settings.php:1357
+#: inc/class-settings.php:1358
#: inc/debug/class-debug.php:263
#: inc/list-tables/class-payment-list-table-widget.php:42
#: inc/list-tables/class-payment-list-table.php:42
@@ -1353,8 +1353,8 @@ msgstr ""
#: inc/admin-pages/class-site-list-admin-page.php:517
#: inc/admin-pages/class-site-list-admin-page.php:528
#: inc/admin-pages/class-site-list-admin-page.php:539
-#: inc/class-settings.php:1171
-#: inc/class-settings.php:1172
+#: inc/class-settings.php:1197
+#: inc/class-settings.php:1198
#: inc/debug/class-debug.php:212
#: inc/list-tables/class-site-list-table.php:45
#: inc/managers/class-limitation-manager.php:276
@@ -2545,7 +2545,7 @@ msgid "Add System Email"
msgstr ""
#: inc/admin-pages/class-email-list-admin-page.php:651
-#: inc/admin-pages/class-settings-admin-page.php:181
+#: inc/admin-pages/class-settings-admin-page.php:182
msgid "Email Template"
msgstr ""
@@ -2744,8 +2744,8 @@ msgstr ""
#: inc/admin-pages/class-email-template-customize-admin-page.php:524
#: inc/admin-pages/class-email-template-customize-admin-page.php:547
#: inc/admin-pages/class-email-template-customize-admin-page.php:548
-#: inc/admin-pages/class-settings-admin-page.php:416
-#: inc/admin-pages/class-settings-admin-page.php:420
+#: inc/admin-pages/class-settings-admin-page.php:417
+#: inc/admin-pages/class-settings-admin-page.php:421
msgid "Customize Email Template"
msgstr ""
@@ -2829,6 +2829,7 @@ msgstr ""
#: inc/ui/class-domain-mapping-element.php:304
#: views/wizards/host-integrations/cloudflare-instructions.php:10
#: views/wizards/host-integrations/gridpane-instructions.php:9
+#: views/wizards/host-integrations/rocket-instructions.php:9
#: views/wizards/host-integrations/runcloud-instructions.php:9
msgid "Instructions"
msgstr ""
@@ -4240,88 +4241,88 @@ msgstr ""
msgid "Search Product"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:149
+#: inc/admin-pages/class-settings-admin-page.php:150
msgid "Template Previewer"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:157
+#: inc/admin-pages/class-settings-admin-page.php:158
msgid "Placeholder Editor"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:165
+#: inc/admin-pages/class-settings-admin-page.php:166
#: inc/ui/class-invoices-element.php:84
#: inc/ui/class-invoices-element.php:133
#: inc/ui/class-invoices-element.php:194
msgid "Invoices"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:209
+#: inc/admin-pages/class-settings-admin-page.php:210
msgid "You can create multiple Checkout Forms for different occasions (seasonal campaigns, launches, etc)!"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:218
+#: inc/admin-pages/class-settings-admin-page.php:219
msgid "Manage Checkout Forms →"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:244
-#: inc/admin-pages/class-settings-admin-page.php:248
+#: inc/admin-pages/class-settings-admin-page.php:245
+#: inc/admin-pages/class-settings-admin-page.php:249
msgid "Customize the Template Previewer"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:252
+#: inc/admin-pages/class-settings-admin-page.php:253
msgid "Did you know that you can customize colors, logos, and more options of the Site Template Previewer top-bar?"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:261
-#: inc/admin-pages/class-settings-admin-page.php:347
+#: inc/admin-pages/class-settings-admin-page.php:262
+#: inc/admin-pages/class-settings-admin-page.php:348
msgid "Go to Customizer →"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:287
-#: inc/admin-pages/class-settings-admin-page.php:291
+#: inc/admin-pages/class-settings-admin-page.php:288
+#: inc/admin-pages/class-settings-admin-page.php:292
msgid "Customize the Template Placeholders"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:295
+#: inc/admin-pages/class-settings-admin-page.php:296
msgid "If you are using placeholder substitutions inside your site templates, use this tool to add, remove, or change the default content of those placeholders."
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:304
+#: inc/admin-pages/class-settings-admin-page.php:305
msgid "Edit Placeholders →"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:330
-#: inc/admin-pages/class-settings-admin-page.php:334
+#: inc/admin-pages/class-settings-admin-page.php:331
+#: inc/admin-pages/class-settings-admin-page.php:335
msgid "Customize the Invoice Template"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:338
+#: inc/admin-pages/class-settings-admin-page.php:339
msgid "Did you know that you can customize colors, logos, and more options of the Invoice PDF template?"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:373
-#: inc/admin-pages/class-settings-admin-page.php:377
+#: inc/admin-pages/class-settings-admin-page.php:374
+#: inc/admin-pages/class-settings-admin-page.php:378
msgid "Customize System Emails"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:381
+#: inc/admin-pages/class-settings-admin-page.php:382
msgid "You can completely customize the contents of the emails sent out by Ultimate Multisite when particular events occur, such as Account Creation, Payment Failures, etc."
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:390
+#: inc/admin-pages/class-settings-admin-page.php:391
msgid "Customize System Emails →"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:424
+#: inc/admin-pages/class-settings-admin-page.php:425
msgid "If your network is using the HTML email option, you can customize the look and feel of the email template."
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:433
+#: inc/admin-pages/class-settings-admin-page.php:434
msgid "Customize Email Template →"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:454
-#: inc/admin-pages/class-settings-admin-page.php:465
+#: inc/admin-pages/class-settings-admin-page.php:455
+#: inc/admin-pages/class-settings-admin-page.php:466
#: inc/admin-pages/class-top-admin-nav-menu.php:151
#: inc/installers/class-migrator.php:288
#: views/ui/branding/footer.php:37
@@ -4329,14 +4330,48 @@ msgstr ""
msgid "Settings"
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:522
+#: inc/admin-pages/class-settings-admin-page.php:523
msgid "You do not have the permissions required to change settings."
msgstr ""
-#: inc/admin-pages/class-settings-admin-page.php:584
+#: inc/admin-pages/class-settings-admin-page.php:585
msgid "Save Settings"
msgstr ""
+#: inc/admin-pages/class-settings-admin-page.php:646
+msgid "You do not have permission to export settings."
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:693
+msgid "Upload Settings File"
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:694
+msgid "Select a JSON file previously exported from Ultimate Multisite."
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:702
+msgid "I understand this will replace all current settings"
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:703
+msgid "This action cannot be undone. Make sure you have a backup of your current settings."
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:711
+#: inc/class-settings.php:1608
+#: inc/class-settings.php:1621
+msgid "Import Settings"
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:791
+msgid "Something is wrong with the uploaded file."
+msgstr ""
+
+#: inc/admin-pages/class-settings-admin-page.php:842
+msgid "Settings successfully imported!"
+msgstr ""
+
#: inc/admin-pages/class-setup-wizard-admin-page.php:245
msgid "Permission denied."
msgstr ""
@@ -4518,12 +4553,12 @@ msgid "Complete Setup"
msgstr ""
#: inc/admin-pages/class-setup-wizard-admin-page.php:896
-#: inc/class-scripts.php:196
+#: inc/class-scripts.php:227
msgid "Select an Image."
msgstr ""
#: inc/admin-pages/class-setup-wizard-admin-page.php:897
-#: inc/class-scripts.php:197
+#: inc/class-scripts.php:228
msgid "Use this image"
msgstr ""
@@ -4539,6 +4574,7 @@ msgid "integer"
msgstr ""
#: inc/admin-pages/class-shortcodes-admin-page.php:160
+#: inc/class-scripts.php:177
msgid "number"
msgstr ""
@@ -4568,7 +4604,7 @@ msgid "This will start the transfer of assets from one membership to another."
msgstr ""
#: inc/admin-pages/class-site-edit-admin-page.php:260
-#: inc/list-tables/class-site-list-table.php:399
+#: inc/list-tables/class-site-list-table.php:407
#: inc/managers/class-site-manager.php:369
msgid "Site not found."
msgstr ""
@@ -4581,6 +4617,7 @@ msgid "Site Type"
msgstr ""
#: inc/admin-pages/class-site-edit-admin-page.php:328
+#: views/wizards/host-integrations/rocket-instructions.php:12
msgid "Site ID"
msgstr ""
@@ -4594,7 +4631,7 @@ msgid "Tell your customers what this site is about."
msgstr ""
#: inc/admin-pages/class-site-edit-admin-page.php:358
-#: inc/class-settings.php:1181
+#: inc/class-settings.php:1207
msgid "Site Options"
msgstr ""
@@ -4888,7 +4925,7 @@ msgstr ""
#: inc/admin-pages/class-system-info-admin-page.php:479
#: inc/admin-pages/class-system-info-admin-page.php:484
#: inc/admin-pages/class-system-info-admin-page.php:489
-#: inc/class-settings.php:1590
+#: inc/class-settings.php:1726
msgid "Disabled"
msgstr ""
@@ -5577,8 +5614,8 @@ msgstr ""
#: inc/apis/schemas/customer-update.php:91
#: inc/apis/schemas/discount-code-create.php:116
#: inc/apis/schemas/discount-code-update.php:116
-#: inc/apis/schemas/domain-create.php:70
-#: inc/apis/schemas/domain-update.php:70
+#: inc/apis/schemas/domain-create.php:73
+#: inc/apis/schemas/domain-update.php:73
#: inc/apis/schemas/email-create.php:135
#: inc/apis/schemas/email-update.php:135
#: inc/apis/schemas/event-create.php:67
@@ -5671,8 +5708,8 @@ msgstr ""
#: inc/apis/schemas/customer-update.php:96
#: inc/apis/schemas/discount-code-create.php:121
#: inc/apis/schemas/discount-code-update.php:121
-#: inc/apis/schemas/domain-create.php:75
-#: inc/apis/schemas/domain-update.php:75
+#: inc/apis/schemas/domain-create.php:78
+#: inc/apis/schemas/domain-update.php:78
#: inc/apis/schemas/email-create.php:140
#: inc/apis/schemas/email-update.php:140
#: inc/apis/schemas/event-create.php:72
@@ -5752,8 +5789,8 @@ msgstr ""
#: inc/apis/schemas/customer-update.php:86
#: inc/apis/schemas/discount-code-create.php:111
#: inc/apis/schemas/discount-code-update.php:111
-#: inc/apis/schemas/domain-create.php:65
-#: inc/apis/schemas/domain-update.php:65
+#: inc/apis/schemas/domain-create.php:68
+#: inc/apis/schemas/domain-update.php:68
#: inc/apis/schemas/event-create.php:62
#: inc/apis/schemas/event-update.php:62
#: inc/apis/schemas/payment-create.php:111
@@ -5895,38 +5932,38 @@ msgstr ""
msgid "This discount code will be limited to be used in certain products? If set to true, you must define a list of allowed products."
msgstr ""
-#: inc/apis/schemas/domain-create.php:23
-#: inc/apis/schemas/domain-update.php:23
+#: inc/apis/schemas/domain-create.php:25
+#: inc/apis/schemas/domain-update.php:25
msgid "Your Domain name. You don't need to put http or https in front of your domain in this field. e.g: example.com."
msgstr ""
-#: inc/apis/schemas/domain-create.php:28
-#: inc/apis/schemas/domain-update.php:28
+#: inc/apis/schemas/domain-create.php:30
+#: inc/apis/schemas/domain-update.php:30
msgid "The blog ID attached to this domain."
msgstr ""
-#: inc/apis/schemas/domain-create.php:33
-#: inc/apis/schemas/domain-update.php:33
+#: inc/apis/schemas/domain-create.php:35
+#: inc/apis/schemas/domain-update.php:35
msgid "Set this domain as active (true), which means available to be used, or inactive (false)."
msgstr ""
-#: inc/apis/schemas/domain-create.php:38
-#: inc/apis/schemas/domain-update.php:38
+#: inc/apis/schemas/domain-create.php:40
+#: inc/apis/schemas/domain-update.php:40
msgid "Define true to set this as primary domain of a site, meaning it's the main url, or set false."
msgstr ""
-#: inc/apis/schemas/domain-create.php:43
-#: inc/apis/schemas/domain-update.php:43
+#: inc/apis/schemas/domain-create.php:45
+#: inc/apis/schemas/domain-update.php:45
msgid "If this domain has some SSL security or not."
msgstr ""
-#: inc/apis/schemas/domain-create.php:48
-#: inc/apis/schemas/domain-update.php:48
+#: inc/apis/schemas/domain-create.php:50
+#: inc/apis/schemas/domain-update.php:50
msgid "The state of the domain model object. Can be one of this options: checking-dns, checking-ssl-cert, done-without-ssl, done and failed."
msgstr ""
-#: inc/apis/schemas/domain-create.php:60
-#: inc/apis/schemas/domain-update.php:60
+#: inc/apis/schemas/domain-create.php:63
+#: inc/apis/schemas/domain-update.php:63
msgid "Date when the domain was created. If no date is set, the current date and time will be used."
msgstr ""
@@ -6711,7 +6748,7 @@ msgstr ""
#: inc/apis/trait-rest-api.php:174
#: inc/apis/trait-rest-api.php:247
#: inc/apis/trait-rest-api.php:305
-#: inc/models/class-base-model.php:646
+#: inc/models/class-base-model.php:639
#: inc/models/class-site.php:1435
msgid "Item not found."
msgstr ""
@@ -6879,38 +6916,38 @@ msgstr ""
msgid "Error: The password you entered is incorrect."
msgstr ""
-#: inc/checkout/class-checkout-pages.php:218
-#: inc/integrations/host-providers/class-closte-host-provider.php:256
+#: inc/checkout/class-checkout-pages.php:220
+#: inc/integrations/host-providers/class-closte-host-provider.php:292
msgid "Something went wrong"
msgstr ""
#. translators: %1$s and %2$s are HTML tags
-#: inc/checkout/class-checkout-pages.php:422
+#: inc/checkout/class-checkout-pages.php:424
#, php-format
msgid "Your email address is not yet verified. Your site %1$s will only be activated %2$s after your email address is verified. Check your inbox and verify your email address."
msgstr ""
-#: inc/checkout/class-checkout-pages.php:426
+#: inc/checkout/class-checkout-pages.php:428
msgid "Resend verification email →"
msgstr ""
-#: inc/checkout/class-checkout-pages.php:631
+#: inc/checkout/class-checkout-pages.php:633
msgid "Ultimate Multisite - Register Page"
msgstr ""
-#: inc/checkout/class-checkout-pages.php:632
+#: inc/checkout/class-checkout-pages.php:634
msgid "Ultimate Multisite - Login Page"
msgstr ""
-#: inc/checkout/class-checkout-pages.php:633
+#: inc/checkout/class-checkout-pages.php:635
msgid "Ultimate Multisite - Site Blocked Page"
msgstr ""
-#: inc/checkout/class-checkout-pages.php:634
+#: inc/checkout/class-checkout-pages.php:636
msgid "Ultimate Multisite - Membership Update Page"
msgstr ""
-#: inc/checkout/class-checkout-pages.php:635
+#: inc/checkout/class-checkout-pages.php:637
msgid "Ultimate Multisite - New Site Page"
msgstr ""
@@ -7959,24 +7996,24 @@ msgid "Minimal"
msgstr ""
#. translators: %s the url for login.
-#: inc/class-addon-repository.php:164
+#: inc/class-addon-repository.php:166
#, php-format
msgid "You must Connect to UltimateMultisite.com first."
msgstr ""
-#: inc/class-addon-repository.php:183
+#: inc/class-addon-repository.php:185
msgid "403 Access Denied returned from server. Ensure you have an active subscription for this addon."
msgstr ""
-#: inc/class-addon-repository.php:187
+#: inc/class-addon-repository.php:189
msgid "Failed to connect to the update server. Please try again later."
msgstr ""
-#: inc/class-addon-repository.php:233
+#: inc/class-addon-repository.php:235
msgid "Successfully connected your site to UltimateMultisite.com."
msgstr ""
-#: inc/class-addon-repository.php:242
+#: inc/class-addon-repository.php:244
msgid "Failed to authenticate with UltimateMultisite.com."
msgstr ""
@@ -8359,6 +8396,7 @@ msgstr ""
#: inc/class-orphaned-tables-manager.php:140
#: inc/class-orphaned-users-manager.php:133
+#: inc/class-settings.php:1645
msgid "Warning:"
msgstr ""
@@ -8460,19 +8498,51 @@ msgstr ""
msgid "here"
msgstr ""
+#: inc/class-scripts.php:170
+#: inc/functions/legacy.php:273
+#: views/admin-pages/fields/field-password.php:49
+msgid "Strength indicator"
+msgstr ""
+
+#: inc/class-scripts.php:171
+msgid "Super Strong"
+msgstr ""
+
+#: inc/class-scripts.php:172
+msgid "Required:"
+msgstr ""
+
+#. translators: %d is the minimum number of characters required
+#: inc/class-scripts.php:174
+#, php-format
+msgid "at least %d characters"
+msgstr ""
+
+#: inc/class-scripts.php:175
+msgid "uppercase letter"
+msgstr ""
+
+#: inc/class-scripts.php:176
+msgid "lowercase letter"
+msgstr ""
+
+#: inc/class-scripts.php:178
+msgid "special character"
+msgstr ""
+
#. translators: the day/month/year date format used by Ultimate Multisite. You can changed it to localize this date format to your language. the default value is d/m/Y, which is the format 31/12/2021.
-#: inc/class-scripts.php:317
+#: inc/class-scripts.php:348
msgid "d/m/Y"
msgstr ""
#. translators: %s is a relative future date.
-#: inc/class-scripts.php:327
+#: inc/class-scripts.php:358
#, php-format
msgid "in %s"
msgstr ""
#. translators: %s is a relative past date.
-#: inc/class-scripts.php:329
+#: inc/class-scripts.php:360
#: inc/functions/date.php:156
#: inc/list-tables/class-base-list-table.php:851
#: views/admin-pages/fields/field-text-display.php:43
@@ -8481,72 +8551,72 @@ msgstr ""
msgid "%s ago"
msgstr ""
-#: inc/class-scripts.php:330
+#: inc/class-scripts.php:361
msgid "a few seconds"
msgstr ""
#. translators: %s is the number of seconds.
-#: inc/class-scripts.php:332
+#: inc/class-scripts.php:363
#, php-format
msgid "%d seconds"
msgstr ""
-#: inc/class-scripts.php:333
+#: inc/class-scripts.php:364
msgid "a minute"
msgstr ""
#. translators: %s is the number of minutes.
-#: inc/class-scripts.php:335
+#: inc/class-scripts.php:366
#, php-format
msgid "%d minutes"
msgstr ""
-#: inc/class-scripts.php:336
+#: inc/class-scripts.php:367
msgid "an hour"
msgstr ""
#. translators: %s is the number of hours.
-#: inc/class-scripts.php:338
+#: inc/class-scripts.php:369
#, php-format
msgid "%d hours"
msgstr ""
-#: inc/class-scripts.php:339
+#: inc/class-scripts.php:370
msgid "a day"
msgstr ""
#. translators: %s is the number of days.
-#: inc/class-scripts.php:341
+#: inc/class-scripts.php:372
#, php-format
msgid "%d days"
msgstr ""
-#: inc/class-scripts.php:342
+#: inc/class-scripts.php:373
msgid "a week"
msgstr ""
#. translators: %s is the number of weeks.
-#: inc/class-scripts.php:344
+#: inc/class-scripts.php:375
#, php-format
msgid "%d weeks"
msgstr ""
-#: inc/class-scripts.php:345
+#: inc/class-scripts.php:376
msgid "a month"
msgstr ""
#. translators: %s is the number of months.
-#: inc/class-scripts.php:347
+#: inc/class-scripts.php:378
#, php-format
msgid "%d months"
msgstr ""
-#: inc/class-scripts.php:348
+#: inc/class-scripts.php:379
msgid "a year"
msgstr ""
#. translators: %s is the number of years.
-#: inc/class-scripts.php:350
+#: inc/class-scripts.php:381
#, php-format
msgid "%d years"
msgstr ""
@@ -8610,7 +8680,7 @@ msgid "Currency Options"
msgstr ""
#: inc/class-settings.php:636
-#: inc/class-settings.php:1342
+#: inc/class-settings.php:1368
msgid "The following options affect how prices are displayed on the frontend, the backend and in reports."
msgstr ""
@@ -8708,14 +8778,14 @@ msgstr ""
#: inc/class-settings.php:778
#: inc/class-settings.php:810
-#: inc/class-settings.php:936
-#: inc/class-settings.php:1193
+#: inc/class-settings.php:962
+#: inc/class-settings.php:1219
msgid "Search pages on the main site..."
msgstr ""
#: inc/class-settings.php:779
-#: inc/class-settings.php:937
-#: inc/class-settings.php:1194
+#: inc/class-settings.php:963
+#: inc/class-settings.php:1220
msgid "Only published pages on the main site are available for selection, and you need to make sure they contain a [wu_checkout] shortcode."
msgstr ""
@@ -8760,473 +8830,534 @@ msgid "By default, when a new pending site needs to be converted into a real net
msgstr ""
#: inc/class-settings.php:870
-#: inc/class-settings.php:1527
-#: inc/class-settings.php:1528
-msgid "Other Options"
+msgid "Password Strength"
msgstr ""
#: inc/class-settings.php:871
-msgid "Other registration-related options."
+msgid "Configure password strength requirements for user registration."
msgstr ""
#: inc/class-settings.php:880
-msgid "Default Role"
+msgid "Minimum Password Strength"
msgstr ""
#: inc/class-settings.php:881
+msgid "Set the minimum password strength required during registration and password reset. \"Super Strong\" requires at least 12 characters, including uppercase, lowercase, numbers, and special characters."
+msgstr ""
+
+#: inc/class-settings.php:885
+msgid "Medium"
+msgstr ""
+
+#: inc/class-settings.php:886
+msgid "Strong"
+msgstr ""
+
+#: inc/class-settings.php:887
+msgid "Super Strong (12+ chars, mixed case, numbers, symbols)"
+msgstr ""
+
+#: inc/class-settings.php:896
+#: inc/class-settings.php:1663
+#: inc/class-settings.php:1664
+msgid "Other Options"
+msgstr ""
+
+#: inc/class-settings.php:897
+msgid "Other registration-related options."
+msgstr ""
+
+#: inc/class-settings.php:906
+msgid "Default Role"
+msgstr ""
+
+#: inc/class-settings.php:907
msgid "Set the role to be applied to the user during the signup process."
msgstr ""
-#: inc/class-settings.php:892
+#: inc/class-settings.php:918
msgid "Add Users to the Main Site as well?"
msgstr ""
-#: inc/class-settings.php:893
+#: inc/class-settings.php:919
msgid "Enabling this option will also add the user to the main site of your network."
msgstr ""
-#: inc/class-settings.php:903
+#: inc/class-settings.php:929
msgid "Add to Main Site with Role..."
msgstr ""
-#: inc/class-settings.php:904
+#: inc/class-settings.php:930
msgid "Select the role Ultimate Multisite should use when adding the user to the main site of your network. Be careful."
msgstr ""
-#: inc/class-settings.php:935
+#: inc/class-settings.php:961
msgid "Default Membership Update Page"
msgstr ""
-#: inc/class-settings.php:955
+#: inc/class-settings.php:981
msgid "Block Frontend Access"
msgstr ""
-#: inc/class-settings.php:956
+#: inc/class-settings.php:982
msgid "Block the frontend access of network sites after a membership is no longer active."
msgstr ""
-#: inc/class-settings.php:957
+#: inc/class-settings.php:983
msgid "By default, if a user does not pay and the account goes inactive, only the admin panel will be blocked, but the user's site will still be accessible on the frontend. If enabled, this option will also block frontend access in those cases."
msgstr ""
-#: inc/class-settings.php:967
+#: inc/class-settings.php:993
msgid "Frontend Block Grace Period"
msgstr ""
-#: inc/class-settings.php:968
+#: inc/class-settings.php:994
msgid "Select the number of days Ultimate Multisite should wait after the membership goes inactive before blocking the frontend access. Leave 0 to block immediately after the membership becomes inactive."
msgstr ""
-#: inc/class-settings.php:982
+#: inc/class-settings.php:1008
msgid "Frontend Block Page"
msgstr ""
-#: inc/class-settings.php:983
+#: inc/class-settings.php:1009
msgid "Select a page on the main site to redirect user if access is blocked"
msgstr ""
-#: inc/class-settings.php:1003
+#: inc/class-settings.php:1029
msgid "Enable Multiple Memberships per Customer"
msgstr ""
-#: inc/class-settings.php:1004
+#: inc/class-settings.php:1030
msgid "Enabling this option will allow your users to create more than one membership."
msgstr ""
-#: inc/class-settings.php:1014
+#: inc/class-settings.php:1040
msgid "Enable Multiple Sites per Membership"
msgstr ""
-#: inc/class-settings.php:1015
+#: inc/class-settings.php:1041
msgid "Enabling this option will allow your customers to create more than one site. You can limit how many sites your users can create in a per plan basis."
msgstr ""
-#: inc/class-settings.php:1025
+#: inc/class-settings.php:1051
msgid "Block Sites on Downgrade"
msgstr ""
-#: inc/class-settings.php:1026
+#: inc/class-settings.php:1052
msgid "Choose how Ultimate Multisite should handle client sites above their plan quota on downgrade."
msgstr ""
-#: inc/class-settings.php:1030
+#: inc/class-settings.php:1056
msgid "Keep sites as is (do nothing)"
msgstr ""
-#: inc/class-settings.php:1031
+#: inc/class-settings.php:1057
msgid "Block only frontend access"
msgstr ""
-#: inc/class-settings.php:1032
+#: inc/class-settings.php:1058
msgid "Block only backend access"
msgstr ""
-#: inc/class-settings.php:1033
+#: inc/class-settings.php:1059
msgid "Block both frontend and backend access"
msgstr ""
-#: inc/class-settings.php:1045
+#: inc/class-settings.php:1071
msgid "Move Posts on Downgrade"
msgstr ""
-#: inc/class-settings.php:1046
+#: inc/class-settings.php:1072
msgid "Select how you want to handle the posts above the quota on downgrade. This will apply to all post types with quotas set."
msgstr ""
-#: inc/class-settings.php:1050
+#: inc/class-settings.php:1076
msgid "Keep posts as is (do nothing)"
msgstr ""
-#: inc/class-settings.php:1051
+#: inc/class-settings.php:1077
msgid "Move posts above the new quota to the Trash"
msgstr ""
-#: inc/class-settings.php:1052
+#: inc/class-settings.php:1078
msgid "Mark posts above the new quota as Drafts"
msgstr ""
-#: inc/class-settings.php:1062
+#: inc/class-settings.php:1088
msgid "Emulated Post Types"
msgstr ""
-#: inc/class-settings.php:1063
+#: inc/class-settings.php:1089
msgid "Emulates the registering of a custom post type to be able to create limits for it without having to activate plugins on the main site."
msgstr ""
-#: inc/class-settings.php:1072
+#: inc/class-settings.php:1098
msgid "By default, Ultimate Multisite only allows super admins to limit post types that are registered on the main site. This makes sense from a technical stand-point but it also forces you to have plugins network-activated in order to be able to set limitations for their custom post types. Using this option, you can emulate the registering of a post type. This will register them on the main site and allow you to create limits for them on your products."
msgstr ""
-#: inc/class-settings.php:1083
+#: inc/class-settings.php:1109
msgid "Add the first post type using the button below."
msgstr ""
-#: inc/class-settings.php:1117
+#: inc/class-settings.php:1143
msgid "Post Type Slug"
msgstr ""
-#: inc/class-settings.php:1118
+#: inc/class-settings.php:1144
msgid "e.g. product"
msgstr ""
-#: inc/class-settings.php:1127
+#: inc/class-settings.php:1153
msgid "Post Type Label"
msgstr ""
-#: inc/class-settings.php:1128
+#: inc/class-settings.php:1154
msgid "e.g. Products"
msgstr ""
-#: inc/class-settings.php:1144
+#: inc/class-settings.php:1170
msgid "+ Add Post Type"
msgstr ""
-#: inc/class-settings.php:1182
+#: inc/class-settings.php:1208
msgid "Configure certain aspects of how network Sites behave."
msgstr ""
-#: inc/class-settings.php:1192
+#: inc/class-settings.php:1218
msgid "Default New Site Page"
msgstr ""
-#: inc/class-settings.php:1212
+#: inc/class-settings.php:1238
msgid "Enable Visits Limitation & Counting"
msgstr ""
-#: inc/class-settings.php:1213
+#: inc/class-settings.php:1239
msgid "Enabling this option will add visits limitation settings to the plans and add the functionality necessary to count site visits on the front-end."
msgstr ""
-#: inc/class-settings.php:1223
+#: inc/class-settings.php:1249
msgid "Enable Screenshot Generator"
msgstr ""
-#: inc/class-settings.php:1224
+#: inc/class-settings.php:1250
msgid "With this option is enabled, Ultimate Multisite will take a screenshot for every newly created site on your network and set the resulting image as that site's featured image. This features requires a valid license key to work and it is not supported for local sites."
msgstr ""
-#: inc/class-settings.php:1234
+#: inc/class-settings.php:1260
msgid "WordPress Features"
msgstr ""
-#: inc/class-settings.php:1235
+#: inc/class-settings.php:1261
msgid "Override default WordPress settings for network Sites."
msgstr ""
-#: inc/class-settings.php:1244
+#: inc/class-settings.php:1270
msgid "Enable Plugins Menu"
msgstr ""
-#: inc/class-settings.php:1245
+#: inc/class-settings.php:1271
msgid "Do you want to let users on the network to have access to the Plugins page, activating plugins for their sites? If this option is disabled, the customer will not be able to manage the site plugins."
msgstr ""
-#: inc/class-settings.php:1246
+#: inc/class-settings.php:1272
msgid "You can select which plugins the user will be able to use for each plan."
msgstr ""
-#: inc/class-settings.php:1256
+#: inc/class-settings.php:1282
msgid "Add New Users"
msgstr ""
-#: inc/class-settings.php:1257
+#: inc/class-settings.php:1283
msgid "Allow site administrators to add new users to their site via the \"Users → Add New\" page."
msgstr ""
-#: inc/class-settings.php:1258
+#: inc/class-settings.php:1284
msgid "You can limit the number of users allowed for each plan."
msgstr ""
-#: inc/class-settings.php:1268
+#: inc/class-settings.php:1294
msgid "Site Template Options"
msgstr ""
-#: inc/class-settings.php:1269
+#: inc/class-settings.php:1295
msgid "Configure certain aspects of how Site Templates behave."
msgstr ""
-#: inc/class-settings.php:1278
+#: inc/class-settings.php:1304
msgid "Allow Template Switching"
msgstr ""
-#: inc/class-settings.php:1279
+#: inc/class-settings.php:1305
msgid "Enabling this option will add an option on your client's dashboard to switch their site template to another one available on the catalog of available templates. The data is lost after a switch as the data from the new template is copied over."
msgstr ""
-#: inc/class-settings.php:1289
+#: inc/class-settings.php:1315
msgid "Allow Users to use their own Sites as Templates"
msgstr ""
-#: inc/class-settings.php:1290
+#: inc/class-settings.php:1316
msgid "Enabling this option will add the user own sites to the template screen, allowing them to create a new site based on the content and customizations they made previously."
msgstr ""
-#: inc/class-settings.php:1303
+#: inc/class-settings.php:1329
msgid "Copy Media on Template Duplication?"
msgstr ""
-#: inc/class-settings.php:1304
+#: inc/class-settings.php:1330
msgid "Checking this option will copy the media uploaded on the template site to the newly created site. This can be overridden on each of the plans."
msgstr ""
-#: inc/class-settings.php:1314
+#: inc/class-settings.php:1340
msgid "Prevent Search Engines from indexing Site Templates"
msgstr ""
-#: inc/class-settings.php:1315
+#: inc/class-settings.php:1341
msgid "Checking this option will discourage search engines from indexing all the Site Templates on your network."
msgstr ""
-#: inc/class-settings.php:1341
+#: inc/class-settings.php:1367
msgid "Payment Settings"
msgstr ""
-#: inc/class-settings.php:1352
+#: inc/class-settings.php:1378
msgid "Force Auto-Renew"
msgstr ""
-#: inc/class-settings.php:1353
+#: inc/class-settings.php:1379
msgid "Enable this option if you want to make sure memberships are created with auto-renew activated whenever the selected gateway supports it. Disabling this option will show an auto-renew option during checkout."
msgstr ""
-#: inc/class-settings.php:1364
+#: inc/class-settings.php:1390
msgid "Allow Trials without Payment Method"
msgstr ""
-#: inc/class-settings.php:1365
+#: inc/class-settings.php:1391
msgid "By default, Ultimate Multisite asks customers to add a payment method on sign-up even if a trial period is present. Enable this option to only ask for a payment method when the trial period is over."
msgstr ""
-#: inc/class-settings.php:1376
+#: inc/class-settings.php:1402
msgid "Send Invoice on Payment Confirmation"
msgstr ""
-#: inc/class-settings.php:1377
+#: inc/class-settings.php:1403
msgid "Enabling this option will attach a PDF invoice (marked paid) with the payment confirmation email. This option does not apply to the Manual Gateway, which sends invoices regardless of this option."
msgstr ""
-#: inc/class-settings.php:1378
+#: inc/class-settings.php:1404
msgid "The invoice files will be saved on the wp-content/uploads/wu-invoices folder."
msgstr ""
-#: inc/class-settings.php:1388
+#: inc/class-settings.php:1414
msgid "Invoice Numbering Scheme"
msgstr ""
-#: inc/class-settings.php:1389
+#: inc/class-settings.php:1415
msgid "What should Ultimate Multisite use as the invoice number?"
msgstr ""
-#: inc/class-settings.php:1394
+#: inc/class-settings.php:1420
msgid "Payment Reference Code"
msgstr ""
-#: inc/class-settings.php:1395
+#: inc/class-settings.php:1421
msgid "Sequential Number"
msgstr ""
-#: inc/class-settings.php:1404
+#: inc/class-settings.php:1430
msgid "Next Invoice Number"
msgstr ""
-#: inc/class-settings.php:1405
+#: inc/class-settings.php:1431
msgid "This number will be used as the invoice number for the next invoice generated on the system. It is incremented by one every time a new invoice is created. You can change it and save it to reset the invoice sequential number to a specific value."
msgstr ""
-#: inc/class-settings.php:1419
+#: inc/class-settings.php:1445
msgid "Invoice Number Prefix"
msgstr ""
-#: inc/class-settings.php:1420
+#: inc/class-settings.php:1446
msgid "INV00"
msgstr ""
#. translators: %%YEAR%%, %%MONTH%%, and %%DAY%% are placeholders but are replaced before shown to the user but are used as examples.
-#: inc/class-settings.php:1422
+#: inc/class-settings.php:1448
#, php-format
msgid "Use %%YEAR%%, %%MONTH%%, and %%DAY%% to create a dynamic placeholder. E.g. %%YEAR%%-%%MONTH%%-INV will become %s."
msgstr ""
-#: inc/class-settings.php:1436
+#: inc/class-settings.php:1462
#: inc/ui/class-jumper.php:209
msgid "Payment Gateways"
msgstr ""
-#: inc/class-settings.php:1437
+#: inc/class-settings.php:1463
msgid "Activate and configure the installed payment gateways in this section."
msgstr ""
-#: inc/class-settings.php:1452
-#: inc/class-settings.php:1453
+#: inc/class-settings.php:1478
+#: inc/class-settings.php:1479
#: inc/list-tables/class-broadcast-list-table.php:481
#: inc/list-tables/class-email-list-table.php:40
#: inc/ui/class-jumper.php:211
msgid "Emails"
msgstr ""
-#: inc/class-settings.php:1468
-#: inc/class-settings.php:1469
+#: inc/class-settings.php:1494
+#: inc/class-settings.php:1495
msgid "Domain Mapping"
msgstr ""
-#: inc/class-settings.php:1484
-#: inc/class-settings.php:1485
+#: inc/class-settings.php:1510
+#: inc/class-settings.php:1511
msgid "Single Sign-On"
msgstr ""
-#: inc/class-settings.php:1510
+#: inc/class-settings.php:1536
msgid "Hosting or Panel Providers"
msgstr ""
-#: inc/class-settings.php:1511
+#: inc/class-settings.php:1537
msgid "Configure and manage the integration with your Hosting or Panel Provider."
msgstr ""
-#: inc/class-settings.php:1538
+#: inc/class-settings.php:1553
+msgid "Import/Export"
+msgstr ""
+
+#: inc/class-settings.php:1554
+msgid "Export your settings to a JSON file or import settings from a previously exported file."
+msgstr ""
+
+#: inc/class-settings.php:1565
+#: inc/class-settings.php:1590
+msgid "Export Settings"
+msgstr ""
+
+#: inc/class-settings.php:1566
+msgid "Download all your Ultimate Multisite settings as a JSON file for backup or migration purposes."
+msgstr ""
+
+#: inc/class-settings.php:1578
+msgid "The exported file will contain all ultimate multisite settings defined on this page. This includes general settings, payment gateway configurations, email settings, domain mapping settings, and all other plugin configurations. It does not include products, sites, domains, customers and other entities."
+msgstr ""
+
+#: inc/class-settings.php:1609
+msgid "Upload a previously exported JSON file to restore settings."
+msgstr ""
+
+#: inc/class-settings.php:1622
+msgid "Import and Replace All Settings"
+msgstr ""
+
+#: inc/class-settings.php:1646
+msgid "Importing settings will replace ALL current settings with the values from the uploaded file. This action cannot be undone. We recommend exporting your current settings as a backup before importing."
+msgstr ""
+
+#: inc/class-settings.php:1674
msgid "Miscellaneous"
msgstr ""
-#: inc/class-settings.php:1539
+#: inc/class-settings.php:1675
msgid "Other options that do not fit anywhere else."
msgstr ""
-#: inc/class-settings.php:1550
+#: inc/class-settings.php:1686
msgid "Hide UI Tours"
msgstr ""
-#: inc/class-settings.php:1551
+#: inc/class-settings.php:1687
msgid "The UI tours showed by Ultimate Multisite should permanently hide themselves after being seen but if they persist for whatever reason, toggle this option to force them into their viewed state - which will prevent them from showing up again."
msgstr ""
-#: inc/class-settings.php:1563
+#: inc/class-settings.php:1699
msgid "Disable \"Hover to Zoom\""
msgstr ""
-#: inc/class-settings.php:1564
+#: inc/class-settings.php:1700
msgid "By default, Ultimate Multisite adds a \"hover to zoom\" feature, allowing network admins to see larger version of site screenshots and other images across the UI in full-size when hovering over them. You can disable that feature here. Preview tags like the above are not affected."
msgstr ""
-#: inc/class-settings.php:1574
+#: inc/class-settings.php:1710
msgid "Logging"
msgstr ""
-#: inc/class-settings.php:1575
+#: inc/class-settings.php:1711
msgid "Log Ultimate Multisite data. This is useful for debugging purposes."
msgstr ""
-#: inc/class-settings.php:1584
+#: inc/class-settings.php:1720
msgid "Logging Level"
msgstr ""
-#: inc/class-settings.php:1585
+#: inc/class-settings.php:1721
msgid "Select the level of logging you want to use."
msgstr ""
-#: inc/class-settings.php:1589
+#: inc/class-settings.php:1725
msgid "PHP Default"
msgstr ""
-#: inc/class-settings.php:1591
+#: inc/class-settings.php:1727
msgid "Errors Only"
msgstr ""
-#: inc/class-settings.php:1592
+#: inc/class-settings.php:1728
msgid "Everything"
msgstr ""
-#: inc/class-settings.php:1601
+#: inc/class-settings.php:1737
msgid "Send Error Data to Ultimate Multisite Developers"
msgstr ""
-#: inc/class-settings.php:1602
+#: inc/class-settings.php:1738
msgid "With this option enabled, every time your installation runs into an error related to Ultimate Multisite, that error data will be sent to us. No sensitive data gets collected, only environmental stuff (e.g. if this is this is a subdomain network, etc)."
msgstr ""
-#: inc/class-settings.php:1613
+#: inc/class-settings.php:1749
msgid "Change the plugin and wordpress behavior."
msgstr ""
-#: inc/class-settings.php:1628
+#: inc/class-settings.php:1764
msgid "Run Migration Again"
msgstr ""
-#: inc/class-settings.php:1630
+#: inc/class-settings.php:1766
msgid "Rerun the Migration Wizard if you experience data-loss after migrate."
msgstr ""
-#: inc/class-settings.php:1633
+#: inc/class-settings.php:1769
msgid "Important: This process can have unexpected behavior with your current Ultimo models.%2$s%NETWORK_DOMAIN% and %NETWORK_IP%. HTML is allowed."
msgstr ""
-#: inc/managers/class-domain-manager.php:417
+#: inc/managers/class-domain-manager.php:418
msgid "DNS Check Interval"
msgstr ""
-#: inc/managers/class-domain-manager.php:418
+#: inc/managers/class-domain-manager.php:419
msgid "Set the interval in seconds between DNS and SSL certificate checks for domains."
msgstr ""
-#: inc/managers/class-domain-manager.php:419
+#: inc/managers/class-domain-manager.php:420
msgid "Minimum: 10 seconds, Maximum: 300 seconds (5 minutes). Default: 300 seconds."
msgstr ""
-#: inc/managers/class-domain-manager.php:437
+#: inc/managers/class-domain-manager.php:438
msgid "Create www Subdomain Automatically?"
msgstr ""
-#: inc/managers/class-domain-manager.php:438
+#: inc/managers/class-domain-manager.php:439
msgid "Control when www subdomains should be automatically created for mapped domains."
msgstr ""
-#: inc/managers/class-domain-manager.php:439
+#: inc/managers/class-domain-manager.php:440
msgid "This setting applies to all hosting integrations and determines when a www version of the domain should be automatically created."
msgstr ""
-#: inc/managers/class-domain-manager.php:443
+#: inc/managers/class-domain-manager.php:444
msgid "Always - Create www subdomain for all domains"
msgstr ""
-#: inc/managers/class-domain-manager.php:444
+#: inc/managers/class-domain-manager.php:445
msgid "Only for main domains (e.g., example.com but not subdomain.example.com)"
msgstr ""
-#: inc/managers/class-domain-manager.php:445
+#: inc/managers/class-domain-manager.php:446
msgid "Never - Do not automatically create www subdomains"
msgstr ""
-#: inc/managers/class-domain-manager.php:498
+#: inc/managers/class-domain-manager.php:499
msgid "Single Sign-On Settings"
msgstr ""
-#: inc/managers/class-domain-manager.php:499
+#: inc/managers/class-domain-manager.php:500
msgid "Settings to configure the Single Sign-On functionality of Ultimate Multisite, responsible for keeping customers and admins logged in across all network domains."
msgstr ""
-#: inc/managers/class-domain-manager.php:508
+#: inc/managers/class-domain-manager.php:509
msgid "Enable Single Sign-On"
msgstr ""
-#: inc/managers/class-domain-manager.php:509
+#: inc/managers/class-domain-manager.php:510
msgid "Enables the Single Sign-on functionality."
msgstr ""
-#: inc/managers/class-domain-manager.php:519
+#: inc/managers/class-domain-manager.php:520
msgid "Restrict SSO Checks to Login Pages"
msgstr ""
-#: inc/managers/class-domain-manager.php:520
+#: inc/managers/class-domain-manager.php:521
msgid "The Single Sign-on feature adds one extra ajax calls to every page load on sites with custom domains active to check if it should perform an auth loopback. You can restrict these extra calls to the login pages of sub-sites using this option. If enabled, SSO will only work on login pages."
msgstr ""
-#: inc/managers/class-domain-manager.php:533
+#: inc/managers/class-domain-manager.php:534
msgid "Enable SSO Loading Overlay"
msgstr ""
-#: inc/managers/class-domain-manager.php:534
+#: inc/managers/class-domain-manager.php:535
msgid "When active, a loading overlay will be added on-top of the site currently being viewed while the SSO auth loopback is performed on the background."
msgstr ""
-#: inc/managers/class-domain-manager.php:547
+#: inc/managers/class-domain-manager.php:548
msgid "Enable Magic Links"
msgstr ""
-#: inc/managers/class-domain-manager.php:548
+#: inc/managers/class-domain-manager.php:549
msgid "Enables magic link authentication for custom domains. Magic links provide a fallback authentication method for browsers that don't support third-party cookies. When enabled, dashboard and site links will automatically log users in when accessing sites with custom domains. Tokens are cryptographically secure, one-time use, and expire after 10 minutes."
msgstr ""
-#: inc/managers/class-domain-manager.php:564
+#: inc/managers/class-domain-manager.php:565
msgid "Cool! You're about to make this site accessible using your own domain name!"
msgstr ""
-#: inc/managers/class-domain-manager.php:566
+#: inc/managers/class-domain-manager.php:567
msgid "For that to work, you'll need to create a new CNAME record pointing to %NETWORK_DOMAIN% on your DNS manager."
msgstr ""
-#: inc/managers/class-domain-manager.php:568
+#: inc/managers/class-domain-manager.php:569
msgid "After you finish that step, come back to this screen and click the button below."
msgstr ""
#. translators: %s is the domain name
-#: inc/managers/class-domain-manager.php:665
+#: inc/managers/class-domain-manager.php:666
#, php-format
msgid "Starting Check for %s"
msgstr ""
-#: inc/managers/class-domain-manager.php:675
+#: inc/managers/class-domain-manager.php:676
msgid "- DNS propagation finished, advancing domain to next step..."
msgstr ""
#. translators: %d is the number of minutes to try again.
-#: inc/managers/class-domain-manager.php:702
+#: inc/managers/class-domain-manager.php:703
#, php-format
msgid "- DNS propagation checks tried for the max amount of times (5 times, one every %d minutes). Marking as failed."
msgstr ""
#. translators: %d is the number of minutes before trying again.
-#: inc/managers/class-domain-manager.php:711
+#: inc/managers/class-domain-manager.php:712
#, php-format
msgid "- DNS propagation not finished, retrying in %d minutes..."
msgstr ""
-#: inc/managers/class-domain-manager.php:736
+#: inc/managers/class-domain-manager.php:737
msgid "- Valid SSL cert found. Marking domain as done."
msgstr ""
#. translators: %d is the number of minutes to try again.
-#: inc/managers/class-domain-manager.php:752
+#: inc/managers/class-domain-manager.php:754
#, php-format
msgid "- SSL checks tried for the max amount of times (5 times, one every %d minutes). Marking as ready without SSL."
msgstr ""
#. translators: %d is the number of minutes before trying again.
-#: inc/managers/class-domain-manager.php:761
+#: inc/managers/class-domain-manager.php:763
#, php-format
msgid "- SSL Cert not found, retrying in %d minute(s)..."
msgstr ""
-#: inc/managers/class-domain-manager.php:850
+#: inc/managers/class-domain-manager.php:852
msgid "A valid domain was not passed."
msgstr ""
-#: inc/managers/class-domain-manager.php:863
-#: inc/managers/class-domain-manager.php:872
+#: inc/managers/class-domain-manager.php:865
+#: inc/managers/class-domain-manager.php:874
msgid "Not able to fetch DNS entries."
msgstr ""
-#: inc/managers/class-domain-manager.php:923
+#: inc/managers/class-domain-manager.php:925
msgid "Invalid Integration ID"
msgstr ""
#. translators: %s is the name of the missing constant
-#: inc/managers/class-domain-manager.php:936
+#: inc/managers/class-domain-manager.php:938
#, php-format
msgid "The necessary constants were not found on your wp-config.php file: %s"
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: URL being tested
-#: inc/managers/class-domain-manager.php:1053
+#: inc/managers/class-domain-manager.php:1060
#, php-format
msgid "Testing domain verification via Loopback using %1$s: %2$s"
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: Error Message
-#: inc/managers/class-domain-manager.php:1076
+#: inc/managers/class-domain-manager.php:1083
#, php-format
msgid "Failed to connect via %1$s: %2$s"
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: HTTP Response Code
-#: inc/managers/class-domain-manager.php:1094
+#: inc/managers/class-domain-manager.php:1101
#, php-format
msgid "Loopback request via %1$s returned HTTP %2$d"
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: Json error, %3$s part of the response
-#: inc/managers/class-domain-manager.php:1111
+#: inc/managers/class-domain-manager.php:1118
#, php-format
msgid "Loopback response via %1$s is not valid JSON: %2$s : %3$s"
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: Domain ID number
-#: inc/managers/class-domain-manager.php:1127
+#: inc/managers/class-domain-manager.php:1134
#, php-format
msgid "Domain verification successful via Loopback using %1$s. Domain ID %2$d confirmed."
msgstr ""
#. translators: %1$s: Protocol label (HTTPS with SSL verification, HTTPS without SSL verification, HTTP), %2$s: Domain ID number, %3$s Domain ID number
-#: inc/managers/class-domain-manager.php:1140
+#: inc/managers/class-domain-manager.php:1147
#, php-format
msgid "Loopback response via %1$s did not contain expected domain ID. Expected: %2$d, Got: %3$s"
msgstr ""
-#: inc/managers/class-domain-manager.php:1151
+#: inc/managers/class-domain-manager.php:1158
msgid "Domain verification failed via loopback on all protocols (HTTPS with SSL, HTTPS without SSL, HTTP)."
msgstr ""
@@ -18066,12 +18285,12 @@ msgstr ""
msgid "This is the number of sites the customer will be able to create under this membership."
msgstr ""
-#: inc/managers/class-membership-manager.php:134
-#: inc/managers/class-membership-manager.php:157
-#: inc/managers/class-membership-manager.php:191
-#: inc/managers/class-membership-manager.php:198
-#: inc/managers/class-membership-manager.php:310
-#: inc/managers/class-membership-manager.php:381
+#: inc/managers/class-membership-manager.php:137
+#: inc/managers/class-membership-manager.php:160
+#: inc/managers/class-membership-manager.php:194
+#: inc/managers/class-membership-manager.php:201
+#: inc/managers/class-membership-manager.php:313
+#: inc/managers/class-membership-manager.php:384
#: inc/managers/class-payment-manager.php:336
#: inc/managers/class-payment-manager.php:381
#: inc/ui/class-site-actions-element.php:594
@@ -18229,7 +18448,7 @@ msgstr ""
msgid "You can only use numeric fields to generate hashes."
msgstr ""
-#: inc/models/class-base-model.php:769
+#: inc/models/class-base-model.php:762
msgid "This method expects an array as argument."
msgstr ""
@@ -18321,7 +18540,7 @@ msgstr ""
msgid "%1$s OFF on Setup Fees"
msgstr ""
-#: inc/models/class-domain.php:581
+#: inc/models/class-domain.php:582
msgid "Domain deleted and logs cleared..."
msgstr ""
@@ -19262,7 +19481,7 @@ msgid "Remember Me Description"
msgstr ""
#: inc/ui/class-login-form-element.php:271
-#: inc/ui/class-login-form-element.php:370
+#: inc/ui/class-login-form-element.php:393
msgid "Keep me logged in for two weeks."
msgstr ""
@@ -19676,6 +19895,12 @@ msgstr ""
msgid "Upload Image"
msgstr ""
+#: views/admin-pages/fields/field-password.php:41
+#: views/checkout/fields/field-password.php:37
+#: assets/js/wu-password-toggle.js:48
+msgid "Show password"
+msgstr ""
+
#: views/admin-pages/fields/field-repeater.php:143
msgid "Add new Line"
msgstr ""
@@ -20089,14 +20314,10 @@ msgstr ""
msgid "No Targets"
msgstr ""
-#: views/checkout/fields/field-password.php:36
+#: views/checkout/fields/field-password.php:45
msgid "Strength Meter"
msgstr ""
-#: views/checkout/partials/inline-login-prompt.php:38
-msgid "Enter your password"
-msgstr ""
-
#: views/checkout/partials/pricing-table-list.php:19
#: views/checkout/templates/pricing-table/legacy.php:106
msgid "No Products Found."
@@ -20123,6 +20344,7 @@ msgid "PayPal Status:"
msgstr ""
#: views/checkout/paypal/confirm.php:85
+#: views/wizards/host-integrations/rocket-instructions.php:39
msgid "Email:"
msgstr ""
@@ -21329,6 +21551,171 @@ msgstr ""
msgid "Finish!"
msgstr ""
+#: views/wizards/host-integrations/rocket-instructions.php:12
+msgid "You'll need to get your"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:12
+msgid "Rocket.net account credentials"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:12
+msgid "from your Rocket.net control panel."
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:16
+msgid "About the Rocket.net API"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:20
+msgid "Rocket.net is one of the few Managed WordPress platforms that is 100% API-driven. The same API that powers their control panel is available to you for managing domains, SSL certificates, and more."
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:25
+msgid "Note:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:26
+msgid "The Rocket.net API uses JWT authentication. Your credentials are only used to generate secure access tokens and are never stored by Ultimate Multisite."
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:31
+msgid "Step 1: Prepare Your Account Credentials"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:35
+msgid "You will need:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:39
+msgid "Your Rocket.net account email address"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:40
+msgid "Password:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:40
+#: views/wizards/host-integrations/rocket-instructions.php:76
+msgid "Your Rocket.net account password"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:41
+msgid "Site ID:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:41
+msgid "The numeric ID of your WordPress site on Rocket.net"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:46
+msgid "Security Tip:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:47
+msgid "Consider creating a dedicated Rocket.net user account specifically for API access with appropriate permissions. This follows security best practices for API integrations."
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:52
+msgid "Step 2: Finding Your Site ID"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:56
+msgid "To find your Site ID:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:60
+msgid "Log in to your Rocket.net control panel"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:61
+msgid "Navigate to your WordPress site"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:62
+msgid "Look at the URL in your browser - the Site ID is the numeric value in the URL path"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:63
+msgid "For example, if the URL is"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:63
+msgid "your Site ID is"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:67
+msgid "Step 3: Configure the Integration"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:71
+msgid "In the next step, you will enter:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:75
+msgid "WU_ROCKET_EMAIL:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:75
+msgid "Your Rocket.net account email"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:76
+msgid "WU_ROCKET_PASSWORD:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:77
+msgid "WU_ROCKET_SITE_ID:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:77
+msgid "The Site ID you found in the previous step"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:81
+msgid "These values will be added to your wp-config.php file as PHP constants for secure storage."
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:85
+msgid "What This Integration Does"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:89
+msgid "Once configured, this integration will:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:93
+msgid "Automatically add custom domains to your Rocket.net site when mapped in Ultimate Multisite"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:94
+msgid "Automatically remove domains from Rocket.net when unmapped"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:95
+msgid "Enable automatic SSL certificate provisioning for all mapped domains"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:96
+msgid "Keep your Rocket.net configuration in sync with your WordPress multisite network"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:100
+msgid "Additional Resources"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:104
+msgid "For more information about the Rocket.net API:"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:110
+msgid "Rocket.net API Guide"
+msgstr ""
+
+#: views/wizards/host-integrations/rocket-instructions.php:115
+msgid "Rocket.net API Documentation"
+msgstr ""
+
#: views/wizards/host-integrations/runcloud-instructions.php:12
#: views/wizards/host-integrations/runcloud-instructions.php:35
msgid "API Token"
@@ -21338,6 +21725,10 @@ msgstr ""
msgid "as well as find the"
msgstr ""
+#: views/wizards/host-integrations/runcloud-instructions.php:12
+msgid "Server ID"
+msgstr ""
+
#: views/wizards/host-integrations/runcloud-instructions.php:12
msgid "APP ID"
msgstr ""
@@ -21578,3 +21969,7 @@ msgstr ""
#: views/wizards/setup/support_terms.php:40
msgid "Support for 3rd party plugins (i.e. plugins you install yourself later on)"
msgstr ""
+
+#: assets/js/wu-password-toggle.js:41
+msgid "Hide password"
+msgstr ""
diff --git a/package-lock.json b/package-lock.json
index a1213f86..12692f4f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ultimate-multisite",
- "version": "2.4.8",
+ "version": "2.4.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ultimate-multisite",
- "version": "2.4.8",
+ "version": "2.4.9",
"dependencies": {
"apexcharts": "^5.2.0",
"shepherd.js": "^14.5.0"
@@ -21,6 +21,7 @@
"cypress-mailpit": "^1.4.0",
"eslint": "^8.57.1",
"globals": "^16.5.0",
+ "lint-staged": "^16.2.7",
"npm-run-all": "^4.1.5",
"stylelint": "^16.26.1",
"stylelint-config-standard": "^39.0.1",
@@ -5815,6 +5816,19 @@
"node": ">=6"
}
},
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -6879,6 +6893,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/execa": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
@@ -7322,6 +7343,19 @@
"node": "6.* || 8.* || >= 10.*"
}
},
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -8788,6 +8822,309 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lint-staged": {
+ "version": "16.2.7",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
+ "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^14.0.2",
+ "listr2": "^9.0.5",
+ "micromatch": "^4.0.8",
+ "nano-spawn": "^2.0.0",
+ "pidtree": "^0.6.0",
+ "string-argv": "^0.3.2",
+ "yaml": "^2.8.1"
+ },
+ "bin": {
+ "lint-staged": "bin/lint-staged.js"
+ },
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/lint-staged"
+ }
+ },
+ "node_modules/lint-staged/node_modules/ansi-escapes": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
+ "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/cli-truncate": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
+ "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "^7.1.0",
+ "string-width": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/commander": {
+ "version": "14.0.2",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
+ "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/lint-staged/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lint-staged/node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/listr2": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
+ "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cli-truncate": "^5.0.0",
+ "colorette": "^2.0.20",
+ "eventemitter3": "^5.0.1",
+ "log-update": "^6.1.0",
+ "rfdc": "^1.4.1",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/lint-staged/node_modules/log-update": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/pidtree": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
+ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/lint-staged/node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/slice-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/string-width": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
+ "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lint-staged/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/lint-staged/node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/listr2": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz",
@@ -9109,6 +9446,19 @@
"node": ">=6"
}
},
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -9182,6 +9532,19 @@
"node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/nano-spawn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
+ "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -11187,6 +11550,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string-argv": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.19"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
diff --git a/package.json b/package.json
index ae706a60..427f9706 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"cypress-mailpit": "^1.4.0",
"eslint": "^8.57.1",
"globals": "^16.5.0",
+ "lint-staged": "^16.2.7",
"npm-run-all": "^4.1.5",
"stylelint": "^16.26.1",
"stylelint-config-standard": "^39.0.1",
@@ -92,5 +93,13 @@
"dependencies": {
"apexcharts": "^5.2.0",
"shepherd.js": "^14.5.0"
+ },
+ "lint-staged": {
+ "assets/js/**/*.js": [
+ "eslint --fix --ignore-pattern '*.min.js'"
+ ],
+ "assets/css/**/*.css": [
+ "stylelint --fix --ignore-pattern '*.min.css'"
+ ]
}
}
diff --git a/readme.txt b/readme.txt
index 03520dba..edb06191 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,7 +5,7 @@ Tags: multisite, waas, membership, domain-mapping, subscription
Requires at least: 5.3
Requires PHP: 7.4.30
Tested up to: 6.9
-Stable tag: 2.4.9
+Stable tag: 2.4.10
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
The Complete Network Solution for transforming your WordPress Multisite into a Website as a Service (WaaS) platform.
@@ -211,6 +211,18 @@ This plugin connects to several external services to provide its functionality.
All external service connections are clearly disclosed to users during setup, and most services are optional or can be configured based on your chosen hosting provider and payment methods.
+= Usage Tracking (Opt-In) =
+
+**Ultimate Multisite Anonymous Telemetry**
+- Service: Anonymous usage data collection to improve the plugin
+- Data sent: PHP version, WordPress version, MySQL version, server type, plugin version, active addon slugs, network type (subdomain/subdirectory), anonymized usage counts (ranges only, e.g., "11-50 sites"), active payment gateways, and sanitized error logs
+- Data NOT sent: Domain names, URLs, customer information, personal data, payment amounts, API keys, IP addresses, or exact counts
+- When: Weekly (if enabled) and when errors occur (if enabled)
+- This feature is DISABLED by default and requires explicit opt-in
+- You can enable or disable this at any time in Settings > Other > Help Improve Ultimate Multisite
+- Service URL: https://ultimatemultisite.com/wp-json/wu-telemetry/v1/track
+- Privacy Policy: https://developer.ultimatemultisite.com/privacy-policy/
+
== Screenshots ==
1. One of many settings pages.
@@ -228,6 +240,16 @@ We recommend running this in a staging environment before updating your producti
== Changelog ==
+Version [2.4.10] - Released on 2026-01-XX
+- New: Configurable minimum password strength setting with Medium, Strong, and Super Strong options.
+- New: Super Strong password requirements include 12+ characters, uppercase, lowercase, numbers, and special characters - compatible with WPMU DEV Defender Pro rules.
+- New: Real-time password requirement hints during checkout with translatable strings.
+- New: Themed password field styling with visibility toggle and color fallbacks for page builders (Elementor, Kadence, Beaver Builder).
+- New: Opt-in anonymous usage tracking to help improve the plugin.
+- New: Rating reminder notice after 30 days of installation.
+- New: WooCommerce Subscriptions compatibility layer for site duplication.
+- Improved: JSON response handling for pending site creation in non-FastCGI environments.
+
Version [2.4.9] - Released on 2025-12-23
- New: Inline login prompt at checkout for existing users - returning customers can sign in directly without leaving the checkout flow.
- New: GitHub Actions workflow for PR builds with WordPress Playground testing - enables one-click browser-based testing of pull requests.
diff --git a/ultimate-multisite.php b/ultimate-multisite.php
index 071a0453..e5f1a508 100644
--- a/ultimate-multisite.php
+++ b/ultimate-multisite.php
@@ -4,7 +4,7 @@
* Description: Transform your WordPress Multisite into a Website as a Service (WaaS) platform supporting site cloning, re-selling, and domain mapping integrations with many hosting providers.
* Plugin URI: https://ultimatemultisite.com
* Text Domain: ultimate-multisite
- * Version: 2.4.9
+ * Version: 2.4.10
* Author: Ultimate Multisite Community
* Author URI: https://github.com/Multisite-Ultimate/ultimate-multisite
* GitHub Plugin URI: https://github.com/Multisite-Ultimate/ultimate-multisite
diff --git a/views/admin-pages/fields/field-password.php b/views/admin-pages/fields/field-password.php
index a107a5ca..eb7dcaee 100644
--- a/views/admin-pages/fields/field-password.php
+++ b/views/admin-pages/fields/field-password.php
@@ -27,27 +27,25 @@
?>
- - With this option enabled, every time your installation runs into an error related to Ultimate Multisite, - that - error data will be sent to us. That way we can review, debug, and fix issues without you having to - manually report anything. No sensitive data gets collected, only environmental stuff (e.g. if this is - this is a subdomain network, etc). + + .