diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 000000000..b1fbf926b --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,20 @@ +#!/bin/bash + +# Commit message hook for enforcing conventional commit format +# This hook validates commit message format + +commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,50}' + +error_msg="Aborting commit. Your commit message is missing either a type of change or the description of changes." + +if ! grep -qE "$commit_regex" "$1"; then + echo "$error_msg" >&2 + echo "" >&2 + echo "Valid format: (): " >&2 + echo "Example: feat(auth): add login functionality" >&2 + echo "" >&2 + echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert" >&2 + echo "" >&2 + echo "To bypass this check, use: git commit --no-verify" >&2 + exit 1 +fi \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..dc8949c58 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,64 @@ +#!/bin/bash + +# Pre-commit hook for running phpcs and phpstan on changed files +# This hook runs PHPCS and PHPStan on staged PHP files + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +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 +for FILE in $STAGED_FILES; do + if [ -f "$FILE" ]; then + vendor/bin/phpcs --colors "$FILE" || HAS_PHPCS_ERRORS=1 + fi +done + +# 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}" + 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/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 000000000..958e09a54 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,57 @@ +name: Code Quality + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + code-quality: + name: Code Quality Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: mysqli, gd, bcmath + tools: composer + + - name: Install PHP Dependencies + run: composer install --no-interaction --prefer-dist + + - name: Get changed PHP files + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files: | + **/*.php + + - name: Run PHP CodeSniffer + if: steps.changed-files.outputs.any_changed == 'true' + run: | + echo "Changed files: ${{ steps.changed-files.outputs.all_changed_files }}" + vendor/bin/phpcs --report=checkstyle --report-file=phpcs-report.xml ${{ steps.changed-files.outputs.all_changed_files }} || true + + - name: Run PHPStan + if: steps.changed-files.outputs.any_changed == 'true' + run: | + echo "Running PHPStan on changed files: ${{ steps.changed-files.outputs.all_changed_files }}" + vendor/bin/phpstan analyse --error-format=checkstyle --no-progress ${{ steps.changed-files.outputs.all_changed_files }} > phpstan-report.xml || true + + - name: Annotate PR with PHPCS results + uses: staabm/annotate-pull-request-from-checkstyle-action@v1 + if: github.event_name == 'pull_request' + with: + files: phpcs-report.xml + notices-as-failures: false + + - name: Annotate PR with PHPStan results + uses: staabm/annotate-pull-request-from-checkstyle-action@v1 + if: github.event_name == 'pull_request' + with: + files: phpstan-report.xml + notices-as-failures: false \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6fe00e3f1..740b7fb9e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,5 +67,15 @@ jobs: rm -rf /tmp/wordpress-tests-lib /tmp/wordpress/ bash bin/install-wp-tests.sh wordpress_test root root mysql latest - - name: Run PHPUnit Tests - run: vendor/bin/phpunit + - name: Run PHPUnit Tests with Coverage + run: vendor/bin/phpunit --coverage-clover=coverage.xml + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 9c75b949a..3b5fba4ad 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ vendor .phpunit.result.cache tests/e2e/cypress/screenshots tests/e2e/cypress/videos -tests/e2e/cypress/downloads \ No newline at end of file +tests/e2e/cypress/downloads + +# Code coverage +coverage.xml +coverage-html/ \ No newline at end of file diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index b5bbd815c..81a8c5ac7 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -42,16 +42,7 @@ - - - - - - - - - - /views/ + @@ -100,6 +91,15 @@ + + + + + + + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..eabc7ebc3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,292 @@ +# Contributing to Multisite Ultimate + +Thank you for your interest in contributing to Multisite Ultimate! This document provides guidelines and information for developers. + +## Development Setup + +### Prerequisites + +- PHP 7.4+ (8.1+ recommended for development) +- Node.js 16+ and npm +- Composer +- Git +- WordPress Multisite environment + +### Quick Start + +1. **Clone and setup the repository:** + ```bash + git clone https://github.com/superdav42/wp-multisite-waas.git + cd wp-multisite-waas + npm run dev:setup + ``` + +2. **Or setup manually:** + ```bash + npm run install:deps # Installs both composer and npm dependencies + npm run setup:hooks # Sets up Git hooks + ``` + +## Development Commands + +### Primary Commands (npm) +```bash +npm test # Run PHPUnit tests +npm run test:coverage # Run tests with coverage +npm run lint # Check code style (PHPCS) +npm run lint:fix # Fix code style automatically (PHPCBF) +npm run stan # Run static analysis (PHPStan) +npm run quality # Run lint + stan +npm run quality:fix # Run lint:fix + stan +npm run check # Run all checks before committing +npm run build # Production build +npm run build:dev # Development build +npm run clean # Clean build artifacts +npm run dev:setup # Complete development setup +``` + +### Alternative Commands (composer) +```bash +composer test # Run PHPUnit tests +composer test:coverage # Run tests with coverage +composer lint # Run PHPCS +composer lint:fix # Run PHPCBF to fix issues +composer stan # Run PHPStan +composer quality # Run lint + stan +composer setup-hooks # Setup Git hooks +``` + +### Direct Commands +```bash +vendor/bin/phpunit # Run tests +vendor/bin/phpcs # Check code style +vendor/bin/phpcbf # Fix code style +vendor/bin/phpstan analyse # Static analysis +``` + +## Code Quality Standards + +We maintain high code quality through automated tools and Git hooks: + +### Pre-commit Hooks + +The project includes Git hooks that run automatically: + +- **pre-commit**: Runs PHPCS and PHPStan on changed files +- **commit-msg**: Enforces conventional commit format + +To install hooks: `npm run setup:hooks` + +### Code Style + +We follow WordPress coding standards: +- **PHPCS**: WordPress coding standards for PHP +- **PHPStan**: Static analysis for type safety +- **Conventional Commits**: Standardized commit messages + +### Testing + +- **PHPUnit**: Unit and integration tests +- **Code Coverage**: Aim for >80% coverage +- **WordPress Test Suite**: Tests run against WordPress multisite + +## Commit Message Format + +We use [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +**Types:** +- `feat`: New features +- `fix`: Bug fixes +- `docs`: Documentation changes +- `style`: Code style changes (formatting, etc.) +- `refactor`: Code refactoring +- `test`: Test-related changes +- `chore`: Build, dependencies, or maintenance +- `perf`: Performance improvements +- `ci`: CI/CD changes +- `build`: Build system changes +- `revert`: Revert previous changes + +**Examples:** +```bash +feat(checkout): add support for discount codes +fix(gateway): resolve Stripe webhook validation +docs(readme): update installation instructions +test(models): add Customer model tests +``` + +## Pull Request Process + +1. **Fork the repository** and create a feature branch +2. **Make your changes** following our coding standards +3. **Write/update tests** for your changes +4. **Run quality checks**: `make check` +5. **Update documentation** if needed +6. **Submit a pull request** with: + - Clear description of changes + - Reference to related issues + - Screenshots for UI changes + +## Testing + +### Running Tests + +```bash +# Run all tests +npm test + +# Run with coverage +npm run test:coverage + +# Run specific test +vendor/bin/phpunit tests/WP_Ultimo/Models/Customer_Test.php +``` + +### Writing Tests + +- Place tests in `tests/` directory +- Follow existing test structure +- Include unit and integration tests +- Test both success and failure scenarios + +### Test Environment + +Tests run against WordPress test suite with multisite enabled: +- Uses WP_TESTS_MULTISITE=1 +- Separate test database +- Isolated from production data + +## Code Coverage + +We aim for high code coverage: + +- **Target**: >80% line coverage +- **Reports**: Generated in `coverage-html/` +- **CI Integration**: Automatic upload to Codecov + +View coverage locally: +```bash +npm run test:coverage +open coverage-html/index.html +``` + +## Development Workflow + +### Working on Features + +1. **Create feature branch**: `git checkout -b feat/feature-name` +2. **Make changes** with tests +3. **Run checks**: `npm run check` +4. **Commit**: Use conventional format +5. **Push and create PR** + +### Working on Fixes + +1. **Create fix branch**: `git checkout -b fix/issue-description` +2. **Write test** that reproduces the bug +3. **Fix the issue** +4. **Verify test passes** +5. **Run checks**: `npm run check` +6. **Commit and create PR** + +## Directory Structure + +``` +multisite-ultimate/ +├── .github/workflows/ # GitHub Actions +├── .githooks/ # Custom Git hooks +├── bin/ # Development scripts +├── inc/ # Core PHP classes +├── tests/ # PHPUnit tests +├── assets/ # CSS/JS/images +├── views/ # Template files +├── vendor/ # Composer dependencies +├── node_modules/ # NPM dependencies +├── Makefile # Development commands +└── composer.json # PHP dependencies +``` + +## Release Process + +Releases are automated via GitHub Actions: + +1. Update version numbers in plugin files +2. Update CHANGELOG +3. Create and push version tag: `git tag v2.x.x && git push origin v2.x.x` +4. GitHub Action builds and creates release + +## Getting Help + +- **Documentation**: Check existing docs and code comments +- **Issues**: Search existing issues before creating new ones +- **Discussions**: Use GitHub Discussions for questions +- **Code Review**: PRs get reviewed by maintainers + +## Performance Considerations + +- **Database**: Use proper indexing and efficient queries +- **Caching**: Implement appropriate caching strategies +- **Assets**: Minimize and optimize CSS/JS +- **Hooks**: Use appropriate priority and avoid heavy operations + +## Security Guidelines + +- **Input Sanitization**: Always sanitize user input +- **Output Escaping**: Escape output based on context +- **Nonces**: Use WordPress nonces for forms +- **Capabilities**: Check user permissions +- **SQL**: Use prepared statements + +## Debugging + +### Development Mode + +Enable WordPress debugging: +```php +define('WP_DEBUG', true); +define('WP_DEBUG_LOG', true); +define('WP_DEBUG_DISPLAY', false); +``` + +### Logging + +Use the built-in logger: +```php +wu_log_add('debug', 'Debug message', $context); +``` + +### Profiling + +The project includes hook profiling capabilities for performance analysis. + +## Code Architecture + +### Models +- Extend `Base_Model` +- Implement CRUD operations +- Use BerlinDB for database layer + +### Admin Pages +- Extend base admin page classes +- Follow WordPress admin UI patterns +- Include proper capability checks + +### Checkout System +- Modular signup fields +- Payment gateway integration +- Customizable checkout flows + +### Limitations +- Flexible limitation system +- Plugin/theme restrictions +- Resource limits (disk, users, etc.) + +Thank you for contributing to Multisite Ultimate! 🚀 \ No newline at end of file diff --git a/README.md b/README.md index 71f0a0e87..f36505c4f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@

Unit & Integration Tests E2E Tests - Code Rabbit + Code Coverage + Code Quality

## 🌟 Overview @@ -109,24 +110,43 @@ We welcome contributions to Ultimate Multisite! Here's how you can contribute ef ### Development Workflow -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Run `npm install` and `composer install` to set up dependencies -4. Make your changes -5. Before committing, run `npm run build` to: - - Generate translation POT files - - Minify CSS and JS assets - - Process and optimize other assets -6. **Important:** Update both README.md and readme.txt files when making changes that affect: - - Version numbers - - Required WordPress/PHP versions - - Feature additions or changes - - Installation instructions - - Documentation - - Changelog entries -7. Commit your changes (`git commit -m 'Add some amazing feature'`) -8. Push to the branch (`git push origin feature/amazing-feature`) -9. Open a Pull Request +1. **Quick Setup:** + ```bash + git clone https://github.com/superdav42/wp-multisite-waas.git + cd wp-multisite-waas + npm run dev:setup # Installs dependencies and sets up Git hooks + ``` + +2. **Development Commands:** + ```bash + npm test # Run tests + npm run test:coverage # Run tests with coverage + npm run lint # Check code style (PHPCS) + npm run lint:fix # Fix code style automatically + npm run stan # Run static analysis (PHPStan) + npm run quality # Run lint + stan + npm run check # Run all quality checks + npm run build # Production build + npm run build:dev # Development build + npm run clean # Clean build artifacts + ``` + +3. **Making Changes:** + - Create your feature branch (`git checkout -b feat/amazing-feature`) + - Make your changes with tests + - Git hooks will automatically run PHPCS and PHPStan on changed files + - Commit using conventional format: `feat(scope): description` + - Run `npm run check` before pushing + - Push and create a Pull Request + +4. **Code Quality:** + - Pre-commit hooks run automatically + - Follow WordPress coding standards + - Include tests for new features + - Maintain >80% code coverage + - Use conventional commit messages + +5. **Important:** Update both README.md and readme.txt files when making changes that affect versions, features, or documentation. ### Pull Request Guidelines diff --git a/autoload.php b/autoload.php index 1cf5f0882..1179f51ef 100644 --- a/autoload.php +++ b/autoload.php @@ -6,7 +6,7 @@ * @since 2.3.0 * @deprecated since 2.4.5, use /vendor/autoload_packages.php instead. */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; require_once __DIR__ . '/vendor/autoload_packages.php'; require_once __DIR__ . '/vendor/woocommerce/action-scheduler/action-scheduler.php'; diff --git a/bin/setup-hooks.sh b/bin/setup-hooks.sh new file mode 100755 index 000000000..de5713ec3 --- /dev/null +++ b/bin/setup-hooks.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Setup script for Git hooks +# Run this script to install the Git hooks for this project + +set -e + +echo "Setting up Git hooks for Multisite Ultimate..." + +# Check if we're in a git repository +if [ ! -d ".git" ]; then + echo "Error: This script must be run from the root of the Git repository." + exit 1 +fi + +# Check if .githooks directory exists +if [ ! -d ".githooks" ]; then + echo "Error: .githooks directory not found." + exit 1 +fi + +# Configure Git to use our custom hooks directory +git config core.hooksPath .githooks + +echo "Git hooks have been installed successfully!" +echo "" +echo "The following hooks are now active:" +echo " - pre-commit: Runs PHPCS and PHPStan on changed files" +echo " - commit-msg: Enforces conventional commit message format" +echo "" +echo "To bypass hooks for a specific commit, use: git commit --no-verify" +echo "" +echo "Make sure to run 'composer install' to have the required tools available." \ No newline at end of file diff --git a/composer-plugins/wp-ultimo-autoloader-plugin/src/WP_Ultimo_Autoloader_Plugin.php b/composer-plugins/wp-ultimo-autoloader-plugin/src/WP_Ultimo_Autoloader_Plugin.php index 63af4a20f..c64aa4c95 100644 --- a/composer-plugins/wp-ultimo-autoloader-plugin/src/WP_Ultimo_Autoloader_Plugin.php +++ b/composer-plugins/wp-ultimo-autoloader-plugin/src/WP_Ultimo_Autoloader_Plugin.php @@ -42,7 +42,7 @@ class WP_Ultimo_Autoloader_Plugin implements PluginInterface, EventSubscriberInt * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ - public function activate( Composer $composer, IOInterface $io ) { + public function activate(Composer $composer, IOInterface $io) { $this->composer = $composer; $this->io = $io; } @@ -53,7 +53,7 @@ public function activate( Composer $composer, IOInterface $io ) { * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ - public function deactivate( Composer $composer, IOInterface $io ) { + public function deactivate(Composer $composer, IOInterface $io) { // Intentionally left empty. } @@ -63,7 +63,7 @@ public function deactivate( Composer $composer, IOInterface $io ) { * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ - public function uninstall( Composer $composer, IOInterface $io ) { + public function uninstall(Composer $composer, IOInterface $io) { // Intentionally left empty. } @@ -75,7 +75,7 @@ public function uninstall( Composer $composer, IOInterface $io ) { public static function getSubscribedEvents() { return array( ScriptEvents::POST_AUTOLOAD_DUMP => array( - array( 'modifyJetpackAutoloader', -10 ), // Run after jetpack autoloader (priority -10) + array('modifyJetpackAutoloader', -10), // Run after jetpack autoloader (priority -10) ), ); } @@ -85,31 +85,31 @@ public static function getSubscribedEvents() { * * @param Event $event Script event object. */ - public function modifyJetpackAutoloader( Event $event ) { - $config = $this->composer->getConfig(); - $vendorPath = $config->get( 'vendor-dir' ); - $classmapPath = $vendorPath . '/composer/jetpack_autoload_classmap.php'; + public function modifyJetpackAutoloader(Event $event) { + $config = $this->composer->getConfig(); + $vendor_path = $config->get('vendor-dir'); + $classmap_path = $vendor_path . '/composer/jetpack_autoload_classmap.php'; - if ( ! file_exists( $classmapPath ) ) { + if ( ! file_exists($classmap_path) ) { return; } - $content = file_get_contents( $classmapPath ); + $content = file_get_contents($classmap_path); // phpcs:ignore // Check if our code is already present - if ( strpos( $content, "defined('WP_ULTIMO_PLUGIN_FILE')" ) !== false ) { + if ( strpos($content, "defined('WP_ULTIMO_PLUGIN_FILE')") !== false ) { return; } // Find the opening PHP tag and add our check right after it - $search = "io->write( 'Modified jetpack_autoload_classmap.php with WP_ULTIMO_PLUGIN_FILE check' ); + if ( $new_content !== $content ) { + file_put_contents($classmap_path, $new_content); // phpcs:ignore + $this->io->write('Modified jetpack_autoload_classmap.php with WP_ULTIMO_PLUGIN_FILE check'); } } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index 53ddc42c0..71587846b 100644 --- a/composer.json +++ b/composer.json @@ -167,7 +167,17 @@ ], "post-update-cmd": [ "@php scripts/remove-mpdf-fonts.php" - ] + ], + "test": "phpunit", + "test:coverage": "phpunit --coverage-html=coverage-html --coverage-clover=coverage.xml", + "lint": "phpcs", + "lint:fix": "phpcbf", + "stan": "phpstan analyse", + "quality": [ + "@lint", + "@stan" + ], + "setup-hooks": "bash bin/setup-hooks.sh" }, "autoload-dev": { "psr-4": { diff --git a/inc/admin-pages/class-customer-edit-admin-page.php b/inc/admin-pages/class-customer-edit-admin-page.php index 95b7f6c9f..72d5d60ca 100644 --- a/inc/admin-pages/class-customer-edit-admin-page.php +++ b/inc/admin-pages/class-customer-edit-admin-page.php @@ -769,8 +769,8 @@ public function register_widgets(): void { ], ], ], - // @todo: bring these back // phpcs:disable + // @todo: bring these back // 'payment_methods' => array( // 'title' => __('Payment Methods', 'ultimate-multisite'), // 'desc' => __('Add extra information to this customer.', 'ultimate-multisite'), @@ -1187,7 +1187,7 @@ public function handle_save(): void { $billing_address = $object->get_billing_address(); - $billing_address->load_attributes_from_post(); + $billing_address->load_attributes_from_post(); $valid_address = $billing_address->validate(); diff --git a/inc/admin-pages/class-customer-list-admin-page.php b/inc/admin-pages/class-customer-list-admin-page.php index dcadb1453..9e9d9dca0 100644 --- a/inc/admin-pages/class-customer-list-admin-page.php +++ b/inc/admin-pages/class-customer-list-admin-page.php @@ -245,9 +245,6 @@ public function render_add_new_customer_modal(): void { 'value' => 'save', 'classes' => 'button button-primary wu-w-full', 'wrapper_classes' => 'wu-items-end', - 'html_attr' => [ - // 'v-bind:disabled' => '!confirmed', - ], ], ]; diff --git a/inc/admin-pages/class-discount-code-edit-admin-page.php b/inc/admin-pages/class-discount-code-edit-admin-page.php index 2ccbd54b7..b6a49e8e5 100644 --- a/inc/admin-pages/class-discount-code-edit-admin-page.php +++ b/inc/admin-pages/class-discount-code-edit-admin-page.php @@ -513,7 +513,7 @@ public function register_forms(): void { add_filter( 'wu_data_json_success_delete_discount_code_modal', - fn($data_json) => [ + fn($data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found 'redirect_url' => wu_network_admin_url('wp-ultimo-discount-codes', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-event-view-admin-page.php b/inc/admin-pages/class-event-view-admin-page.php index 86c3efb3a..6334535eb 100644 --- a/inc/admin-pages/class-event-view-admin-page.php +++ b/inc/admin-pages/class-event-view-admin-page.php @@ -112,7 +112,7 @@ public function register_forms(): void { add_filter( 'wu_data_json_success_delete_event_modal', - fn($data_json) => [ + fn($data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found 'redirect_url' => wu_network_admin_url('wp-ultimo-events', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-membership-edit-admin-page.php b/inc/admin-pages/class-membership-edit-admin-page.php index caa89f67d..ce40bfd79 100644 --- a/inc/admin-pages/class-membership-edit-admin-page.php +++ b/inc/admin-pages/class-membership-edit-admin-page.php @@ -201,7 +201,7 @@ public function register_forms(): void { add_filter( 'wu_data_json_success_delete_membership_modal', - fn($data_json) => [ + fn($data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found 'redirect_url' => wu_network_admin_url('wp-ultimo-memberships', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-payment-edit-admin-page.php b/inc/admin-pages/class-payment-edit-admin-page.php index d6579e78e..8812f78e7 100644 --- a/inc/admin-pages/class-payment-edit-admin-page.php +++ b/inc/admin-pages/class-payment-edit-admin-page.php @@ -142,7 +142,7 @@ public function register_forms(): void { */ add_filter( 'wu_data_json_success_delete_payment_modal', - fn($data_json) => [ + fn($data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found 'redirect_url' => wu_network_admin_url('wp-ultimo-payments', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-setup-wizard-admin-page.php b/inc/admin-pages/class-setup-wizard-admin-page.php index acec18f04..659aea58a 100644 --- a/inc/admin-pages/class-setup-wizard-admin-page.php +++ b/inc/admin-pages/class-setup-wizard-admin-page.php @@ -747,13 +747,12 @@ public function alert_incomplete_installation(): void { } if (! defined('SUNRISE') || ! SUNRISE) { - $message = sprintf(__('The SUNRISE constant is missing. Domain mapping and plugin/theme limits will not function until `%s` is added to wp-config.php. Please complete the setup to attempt to do this automatically.'), 'define( SUNRISE, \'1\' );'); + // translators: %s code snippet. + $message = sprintf(__('The SUNRISE constant is missing. Domain mapping and plugin/theme limits will not function until `%s` is added to wp-config.php. Please complete the setup to attempt to do this automatically.', 'multisite-ultimate'), 'define( SUNRISE, \'1\' );'); } else { $message = __('Ultimate Multisite installation is incomplete. The sunrise.php file is missing. Please complete the setup to ensure proper functionality.', 'ultimate-multisite'); } - - $actions = [ 'complete_setup' => [ 'title' => __('Complete Setup', 'ultimate-multisite'), diff --git a/inc/admin-pages/class-site-edit-admin-page.php b/inc/admin-pages/class-site-edit-admin-page.php index db89e7a12..2f212fd97 100644 --- a/inc/admin-pages/class-site-edit-admin-page.php +++ b/inc/admin-pages/class-site-edit-admin-page.php @@ -125,7 +125,7 @@ public function register_forms(): void { add_filter( 'wu_data_json_success_delete_site_modal', - fn($unused_data_json) => [ + fn($unused_data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found 'redirect_url' => wu_network_admin_url('wp-ultimo-sites', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-site-list-admin-page.php b/inc/admin-pages/class-site-list-admin-page.php index 91ff06e05..4069315e6 100644 --- a/inc/admin-pages/class-site-list-admin-page.php +++ b/inc/admin-pages/class-site-list-admin-page.php @@ -149,7 +149,7 @@ public function render_publish_pending_site_modal(): void { ], 'wu-when' => [ 'type' => 'hidden', - 'value' => base64_encode('init'), + 'value' => base64_encode('init'), // phpcs:ignore ], 'membership_id' => [ 'type' => 'hidden', @@ -441,7 +441,7 @@ public function render_add_new_site_modal(): void { ], 'wu-when' => [ 'type' => 'hidden', - 'value' => base64_encode('init'), + 'value' => base64_encode('init'), // phpcs:ignore ], 'submit_button' => [ 'type' => 'submit', diff --git a/inc/admin-pages/class-system-info-admin-page.php b/inc/admin-pages/class-system-info-admin-page.php index e9a8afd86..c9bf09c09 100644 --- a/inc/admin-pages/class-system-info-admin-page.php +++ b/inc/admin-pages/class-system-info-admin-page.php @@ -222,7 +222,7 @@ public function get_data() { $max_execution_time = sprintf(__('%s seconds', 'ultimate-multisite'), ini_get('max_execution_time')); $all_options = $this->get_all_options(); - $all_options_serialized = serialize($all_options); + $all_options_serialized = serialize($all_options); // phpcs:ignore $all_options_bytes = round(mb_strlen($all_options_serialized, '8bit') / 1024, 2); $all_options_transients = $this->get_transients_in_options($all_options); @@ -628,29 +628,28 @@ public function get_browser() { $known = ['Version', $browser_name_short, 'other']; $pattern = '#(?' . implode('|', $known) . ')[/ ]+(?[0-9.|a-zA-Z.]*)#'; - if ( ! preg_match_all($pattern, (string) $user_agent, $matches)) { - // We have no matching number just continue - } + if ( preg_match_all($pattern, (string) $user_agent, $matches)) { - // See how many we have - $i = count($matches['browser']); + // See how many we have + $i = count($matches['browser']); - if (1 !== $i) { + if (1 !== $i) { - // We will have two since we are not using 'other' argument yet - // See if version is before or after the name - if (strripos((string) $user_agent, 'Version') < strripos((string) $user_agent, (string) $browser_name_short)) { - $version = $matches['version'][0]; + // We will have two since we are not using 'other' argument yet + // See if version is before or after the name + if (strripos((string) $user_agent, 'Version') < strripos((string) $user_agent, (string) $browser_name_short)) { + $version = $matches['version'][0]; + } else { + $version = $matches['version'][1]; + } } else { - $version = $matches['version'][1]; + $version = $matches['version'][0]; } - } else { - $version = $matches['version'][0]; - } - // Check if we have a version number - if (empty($version)) { - $version = '?'; + // Check if we have a version number + if (empty($version)) { + $version = '?'; + } } return [ diff --git a/inc/admin-pages/class-webhook-edit-admin-page.php b/inc/admin-pages/class-webhook-edit-admin-page.php index 239beb910..1544e20bc 100644 --- a/inc/admin-pages/class-webhook-edit-admin-page.php +++ b/inc/admin-pages/class-webhook-edit-admin-page.php @@ -120,7 +120,7 @@ public function register_forms(): void { */ add_filter( 'wu_data_json_success_delete_webhook_modal', - fn($data_json) => [ + fn($data_json) => [ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter 'redirect_url' => wu_network_admin_url('wp-ultimo-webhooks', ['deleted' => 1]), ] ); diff --git a/inc/admin-pages/class-wizard-admin-page.php b/inc/admin-pages/class-wizard-admin-page.php index 61ce8e6ba..5d39c90e1 100644 --- a/inc/admin-pages/class-wizard-admin-page.php +++ b/inc/admin-pages/class-wizard-admin-page.php @@ -163,7 +163,7 @@ public function register_widgets() { */ public function output_default_widget_body() { - echo '
'; + echo '
'; $view = $this->current_section['view'] ?? [$this, 'default_view']; diff --git a/inc/api/class-register-endpoint.php b/inc/api/class-register-endpoint.php index 48426ae8d..085169142 100644 --- a/inc/api/class-register-endpoint.php +++ b/inc/api/class-register-endpoint.php @@ -80,8 +80,8 @@ public function register_route($api): void { * @param \WP_REST_Request $request WP Request Object. * @return array */ - public function handle_get($request) { - + public function handle_get($request) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found + /** @var $request */ return [ 'registration_status' => wu_get_setting('enable_registration', true) ? 'open' : 'closed', ]; diff --git a/inc/api/trait-rest-api.php b/inc/api/trait-rest-api.php index ad7ebc933..21a6efa9f 100644 --- a/inc/api/trait-rest-api.php +++ b/inc/api/trait-rest-api.php @@ -12,7 +12,7 @@ use WP_REST_Request; use WP_Ultimo\Managers\Base_Manager; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * REST API trait. diff --git a/inc/api/trait-wp-cli.php b/inc/api/trait-wp-cli.php index 26d8a9f50..2397d075a 100644 --- a/inc/api/trait-wp-cli.php +++ b/inc/api/trait-wp-cli.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Apis; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP CLI trait. diff --git a/inc/checkout/class-checkout-pages.php b/inc/checkout/class-checkout-pages.php index 2cf6101a4..d89b53463 100644 --- a/inc/checkout/class-checkout-pages.php +++ b/inc/checkout/class-checkout-pages.php @@ -652,7 +652,7 @@ public function add_wp_ultimo_status_annotation($states, $post) { * @param null|string $content The post content. * @return string */ - public function render_confirmation_page($atts, $content = null) { + public function render_confirmation_page($atts, $content = null) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter return wu_get_template_contents( 'checkout/confirmation', diff --git a/inc/checkout/class-checkout.php b/inc/checkout/class-checkout.php index 5936d10c0..6aefa68b8 100644 --- a/inc/checkout/class-checkout.php +++ b/inc/checkout/class-checkout.php @@ -383,7 +383,7 @@ public function setup_checkout($element = null): void { $this->step = $this->checkout_form->get_step($this->step_name, true); - if(!$this->step) { + if (! $this->step) { $this->step = []; } diff --git a/inc/checkout/class-legacy-checkout.php b/inc/checkout/class-legacy-checkout.php index a1bf2303a..18845f87b 100644 --- a/inc/checkout/class-legacy-checkout.php +++ b/inc/checkout/class-legacy-checkout.php @@ -13,7 +13,7 @@ // Exit if accessed directly defined('ABSPATH') || exit; -use \WP_Ultimo\Checkout\Cart; +use WP_Ultimo\Checkout\Cart; use WU_Gateway; use WU_Site_Template; @@ -111,7 +111,6 @@ public function init(): void { // Add a filter to the template include to determine if the page has our // template assigned and return it's path add_filter('template_include', [$this, 'view_legacy_template']); - } /** @@ -125,13 +124,10 @@ public function init(): void { public function add_new_template($posts_templates) { if (is_main_site()) { - $posts_templates = array_merge($posts_templates, $this->templates); - } return $posts_templates; - } /** @@ -153,9 +149,7 @@ public function register_legacy_templates($atts) { $templates = wp_get_theme()->get_page_templates(); if (empty($templates)) { - $templates = []; - } // New cache, therefore remove the old one @@ -170,7 +164,6 @@ public function register_legacy_templates($atts) { wp_cache_add($cache_key, $templates, 'themes', 1800); return $atts; - } /** @@ -185,42 +178,33 @@ public function view_legacy_template($template) { // Return the search template if we're searching (instead of the template for the first result) if (is_search()) { - return $template; - } // Get global post global $post, $signup; // Return template if post is empty - if (!$post) { - + if (! $post) { return $template; - } $template_slug = get_post_meta($post->ID, '_wp_page_template', true); // Return default template if we don't have a custom one defined - if (!isset($this->templates[$template_slug])) { - + if (! isset($this->templates[ $template_slug ])) { return $template; - } $file = wu_path("views/legacy/signup/$template_slug"); // Just to be safe, we check if the file exist first if (file_exists($file)) { - return $file; - } // Return template return $template; - } /** @@ -235,9 +219,13 @@ public function register_scripts(): void { wp_register_script('wu-legacy-signup', wu_get_asset('legacy-signup.js', 'js'), ['wu-functions'], \WP_Ultimo::VERSION, true); - wp_localize_script('wu-legacy-signup', 'wpu', [ - 'default_pricing_option' => 1, - ]); + wp_localize_script( + 'wu-legacy-signup', + 'wpu', + [ + 'default_pricing_option' => 1, + ] + ); wp_enqueue_script('wu-legacy-signup'); @@ -248,19 +236,23 @@ public function register_scripts(): void { if (isset($_GET['coupon']) && wu_get_coupon(sanitize_text_field(wp_unslash($_GET['coupon']))) !== false && isset($_GET['step']) && 'plan' === $_GET['step']) { // phpcs:ignore WordPress.Security.NonceVerification $coupon = wu_get_coupon(sanitize_text_field(wp_unslash($_GET['coupon']))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended - wp_localize_script('wu-coupon-code', 'wu_coupon_data', [ - 'coupon' => $coupon, - 'type' => get_post_meta($coupon->id, 'wpu_type', true), - 'value' => get_post_meta($coupon->id, 'wpu_value', true), - 'applies_to_setup_fee' => get_post_meta($coupon->id, 'wpu_applies_to_setup_fee', true), - 'setup_fee_discount_value' => get_post_meta($coupon->id, 'wpu_setup_fee_discount_value', true), - 'setup_fee_discount_type' => get_post_meta($coupon->id, 'wpu_setup_fee_discount_type', true), - 'allowed_plans' => get_post_meta($coupon->id, 'wpu_allowed_plans', true), - 'allowed_freqs' => get_post_meta($coupon->id, 'wpu_allowed_freqs', true), - 'off_text' => __('OFF', 'ultimate-multisite'), - 'free_text' => __('Free!', 'ultimate-multisite'), - 'no_setup_fee_text' => __('No Setup Fee', 'ultimate-multisite'), - ]); + wp_localize_script( + 'wu-coupon-code', + 'wu_coupon_data', + [ + 'coupon' => $coupon, + 'type' => get_post_meta($coupon->id, 'wpu_type', true), + 'value' => get_post_meta($coupon->id, 'wpu_value', true), + 'applies_to_setup_fee' => get_post_meta($coupon->id, 'wpu_applies_to_setup_fee', true), + 'setup_fee_discount_value' => get_post_meta($coupon->id, 'wpu_setup_fee_discount_value', true), + 'setup_fee_discount_type' => get_post_meta($coupon->id, 'wpu_setup_fee_discount_type', true), + 'allowed_plans' => get_post_meta($coupon->id, 'wpu_allowed_plans', true), + 'allowed_freqs' => get_post_meta($coupon->id, 'wpu_allowed_freqs', true), + 'off_text' => __('OFF', 'ultimate-multisite'), + 'free_text' => __('Free!', 'ultimate-multisite'), + 'no_setup_fee_text' => __('No Setup Fee', 'ultimate-multisite'), + ] + ); wp_enqueue_script('wu-coupon-code'); } @@ -273,13 +265,10 @@ public function register_scripts(): void { // Do not get the login if the first step if ('plan' != $this->step) { - wp_enqueue_style('login'); - } wp_enqueue_style('common'); - } /** @@ -303,17 +292,17 @@ public function get_legacy_dynamic_styles() { .wu-content-plan .plan-tier h4 { background-color: #getHex()); ?>; - color: isDark() ? "white" : "#333"; ?> !important; + color: isDark() ? 'white' : '#333'; ?> !important; } .wu-content-plan .plan-tier.callout h6 { background-color: #getHex()); ?>; - color: isDark() ? "#f9f9f9" : "rgba(39,65,90,.5)"; ?> !important; + color: isDark() ? '#f9f9f9' : 'rgba(39,65,90,.5)'; ?> !important; } .wu-content-plan .plan-tier.callout h4 { background-color: #getHex()); ?>; - color: isDark() ? "white" : "#333"; ?> !important; + color: isDark() ? 'white' : '#333'; ?> !important; } 'group', // 'desc' => Field_Templates_Manager::get_instance()->render_preview_block('order_bump'), @@ -221,6 +222,7 @@ public function get_fields() { // 'classes' => '', // 'desc' => sprintf('
%s
', __('Want to add customized order bump templates?
See how you can do that here.', 'ultimate-multisite')), // ); + // phpcs:enable return $editor_fields; } diff --git a/inc/checkout/signup-fields/class-signup-field-order-summary.php b/inc/checkout/signup-fields/class-signup-field-order-summary.php index 6052e6381..5d9b96d22 100644 --- a/inc/checkout/signup-fields/class-signup-field-order-summary.php +++ b/inc/checkout/signup-fields/class-signup-field-order-summary.php @@ -188,6 +188,7 @@ public function get_fields() { ], ]; + // phpcs:disable // @todo: re-add developer notes. // $editor_fields['_dev_note_develop_your_own_template_order_summary'] = array( // 'type' => 'note', @@ -196,6 +197,7 @@ public function get_fields() { // 'classes' => '', // 'desc' => sprintf('
%s
', __('Want to add customized order summary templates?
See how you can do that here.', 'ultimate-multisite')), // ); + // phpcs:enable return $editor_fields; } diff --git a/inc/checkout/signup-fields/class-signup-field-pricing-table.php b/inc/checkout/signup-fields/class-signup-field-pricing-table.php index e2c94274b..0d9f1a3a8 100644 --- a/inc/checkout/signup-fields/class-signup-field-pricing-table.php +++ b/inc/checkout/signup-fields/class-signup-field-pricing-table.php @@ -224,6 +224,7 @@ public function get_fields() { ], ]; + // phpcs:disable // @todo: re-add developer notes. // $editor_fields['_dev_note_develop_your_own_template_2'] = array( // 'type' => 'note', @@ -232,6 +233,7 @@ public function get_fields() { // 'classes' => '', // 'desc' => sprintf('
%s
', __('Want to add customized pricing table templates?
See how you can do that here.', 'ultimate-multisite')), // ); + // phpcs:enable return $editor_fields; } diff --git a/inc/checkout/signup-fields/class-signup-field-shortcode.php b/inc/checkout/signup-fields/class-signup-field-shortcode.php index 6647b6de6..31060c065 100644 --- a/inc/checkout/signup-fields/class-signup-field-shortcode.php +++ b/inc/checkout/signup-fields/class-signup-field-shortcode.php @@ -118,10 +118,7 @@ public function defaults() { */ public function default_fields() { - return [ - // 'id', - // 'name', - ]; + return []; } /** diff --git a/inc/checkout/signup-fields/class-signup-field-site-url.php b/inc/checkout/signup-fields/class-signup-field-site-url.php index 43fd5b1ea..cb629c7d2 100644 --- a/inc/checkout/signup-fields/class-signup-field-site-url.php +++ b/inc/checkout/signup-fields/class-signup-field-site-url.php @@ -250,7 +250,6 @@ public function get_url_preview_templates() { $templates = [ 'legacy/signup/steps/step-domain-url-preview' => __('New URL Preview', 'ultimate-multisite'), - // 'legacy/signup/steps/step-domain-url-preview' => __('Legacy Template', 'ultimate-multisite'), ]; return apply_filters('wu_get_pricing_table_templates', $templates); @@ -336,7 +335,6 @@ public function to_fields_array($attributes) { 'required' => true, 'id' => 'site_domain', 'type' => 'select', - 'classes' => 'input', 'html_attr' => [ 'v-model' => 'site_domain', ], diff --git a/inc/checkout/signup-fields/class-signup-field-steps.php b/inc/checkout/signup-fields/class-signup-field-steps.php index 650297622..b2a4a7015 100644 --- a/inc/checkout/signup-fields/class-signup-field-steps.php +++ b/inc/checkout/signup-fields/class-signup-field-steps.php @@ -174,6 +174,7 @@ public function get_fields() { ], ]; + // phpcs:disable // @todo: re-add developer notes. // $editor_fields['_dev_note_develop_your_own_template_steps'] = array( // 'type' => 'note', @@ -182,6 +183,7 @@ public function get_fields() { // 'classes' => '', // 'desc' => sprintf('
%s
', __('Want to add customized steps templates?
See how you can do that here.', 'ultimate-multisite')), // ); + // phpcs:enable return $editor_fields; } diff --git a/inc/checkout/signup-fields/class-signup-field-template-selection.php b/inc/checkout/signup-fields/class-signup-field-template-selection.php index 95201aac9..01abb10f8 100644 --- a/inc/checkout/signup-fields/class-signup-field-template-selection.php +++ b/inc/checkout/signup-fields/class-signup-field-template-selection.php @@ -259,6 +259,7 @@ public function get_fields() { ], ]; + // phpcs:disable // @todo: re-add developer notes. // $editor_fields['_dev_note_develop_your_own_template_1'] = array( // 'type' => 'note', @@ -267,6 +268,7 @@ public function get_fields() { // 'classes' => '', // 'desc' => sprintf('
%s
', __('Want to add customized template selection templates?
See how you can do that here.', 'ultimate-multisite')), // ); + // phpcs:enable return $editor_fields; } @@ -281,7 +283,7 @@ public function get_fields() { */ public function reduce_attributes($attributes) { - $array_sites = json_decode(json_encode($attributes['sites']), true); + $array_sites = json_decode(json_encode($attributes['sites']), true); // phpcs:ignore $attributes['sites'] = array_values(array_column($array_sites, 'blog_id')); diff --git a/inc/class-addon-repository.php b/inc/class-addon-repository.php index 3783a0773..bbc12f007 100644 --- a/inc/class-addon-repository.php +++ b/inc/class-addon-repository.php @@ -2,6 +2,12 @@ namespace WP_Ultimo; +/** + * Addon Repository class for handling addon downloads and updates. + * + * This class manages the authentication and download process for + * premium addons from the WP Ultimo repository. + */ class Addon_Repository { private string $authorization_header = ''; @@ -149,7 +155,7 @@ public function get_user_data(): array { * @param \WP_Upgrader $upgrader The WP_Upgrader instance. * @param array $hook_extra Extra arguments passed to hooked filters. */ - public function upgrader_pre_download(bool $reply, $package, \WP_Upgrader $upgrader, $hook_extra) { + public function upgrader_pre_download(bool $reply, $package, \WP_Upgrader $upgrader, $hook_extra) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter if (str_starts_with($package, MULTISITE_ULTIMATE_UPDATE_URL)) { $access_token = $this->get_access_token(); diff --git a/inc/class-admin-notices.php b/inc/class-admin-notices.php index 531dac4d4..cae260632 100644 --- a/inc/class-admin-notices.php +++ b/inc/class-admin-notices.php @@ -17,7 +17,7 @@ * * @since 2.0.0 */ -class Admin_Notices { +class Admin_Notices implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-admin-themes-compatibility.php b/inc/class-admin-themes-compatibility.php index ac1c7e35f..e804b713f 100644 --- a/inc/class-admin-themes-compatibility.php +++ b/inc/class-admin-themes-compatibility.php @@ -17,7 +17,7 @@ * * @since 1.9.14 */ -class Admin_Themes_Compatibility { +class Admin_Themes_Compatibility implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; @@ -26,7 +26,7 @@ class Admin_Themes_Compatibility { * * @since 2.0.0 */ - public function __construct() { + public function init(): void { add_filter('admin_body_class', [$this, 'add_body_classes']); } diff --git a/inc/class-ajax.php b/inc/class-ajax.php index 01b46f79a..71cd4a04b 100644 --- a/inc/class-ajax.php +++ b/inc/class-ajax.php @@ -17,7 +17,7 @@ * * @since 1.9.14 */ -class Ajax { +class Ajax implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; @@ -26,7 +26,7 @@ class Ajax { * * @since 2.0.0 */ - public function __construct() { + public function init(): void { /* * Load search endpoints. */ diff --git a/inc/class-api.php b/inc/class-api.php index a4bc4bf7e..953623236 100644 --- a/inc/class-api.php +++ b/inc/class-api.php @@ -17,7 +17,7 @@ * * @since 1.9.14 */ -class API { +class API implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; @@ -43,7 +43,7 @@ class API { * @since 1.7.4 * @return void */ - public function __construct() { + public function init(): void { /** * Add the admin settings for the API @@ -478,7 +478,7 @@ public function register_routes(): void { * @param \WP_REST_Request $request WP Request Object. * @return void */ - public function auth($request): void { + public function auth($request): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter $current_site = get_current_site(); diff --git a/inc/class-cron.php b/inc/class-cron.php index 0f65c38d7..7262b2ed4 100644 --- a/inc/class-cron.php +++ b/inc/class-cron.php @@ -26,7 +26,7 @@ * * @since 2.0.0 */ -class Cron { +class Cron implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-current.php b/inc/class-current.php index 6c5ba186e..cf07e644b 100644 --- a/inc/class-current.php +++ b/inc/class-current.php @@ -17,7 +17,7 @@ * * @since 2.0.0 */ -class Current { +class Current implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-dashboard-statistics.php b/inc/class-dashboard-statistics.php index 4401cb390..79579bf09 100644 --- a/inc/class-dashboard-statistics.php +++ b/inc/class-dashboard-statistics.php @@ -23,7 +23,7 @@ * * @since 2.0.0 */ -class Dashboard_Statistics { +class Dashboard_Statistics implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-dashboard-widgets.php b/inc/class-dashboard-widgets.php index 3d36a4c07..6f9849229 100644 --- a/inc/class-dashboard-widgets.php +++ b/inc/class-dashboard-widgets.php @@ -19,7 +19,7 @@ * * @since 2.0.0 */ -class Dashboard_Widgets { +class Dashboard_Widgets implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-documentation.php b/inc/class-documentation.php index 81faa0dd4..6204cbeaa 100644 --- a/inc/class-documentation.php +++ b/inc/class-documentation.php @@ -19,7 +19,7 @@ * * @since 2.0.0 */ -class Documentation { +class Documentation implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-domain-mapping.php b/inc/class-domain-mapping.php index c340ec0f5..716bcfb4c 100644 --- a/inc/class-domain-mapping.php +++ b/inc/class-domain-mapping.php @@ -251,7 +251,7 @@ public function get_www_and_nowww_versions($domain) { * * @return void */ - public function verify_dns_mapping($current_site, $domain, $path) { + public function verify_dns_mapping($current_site, $domain, $path) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter // Nonce functions are unavailable and the wp_hash is basically the same. if (isset($_REQUEST['async_check_dns_nonce'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended diff --git a/inc/class-license.php b/inc/class-license.php index a9d90067f..a0b3904ee 100644 --- a/inc/class-license.php +++ b/inc/class-license.php @@ -2,8 +2,14 @@ namespace WP_Ultimo; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; +/** + * License class for maintaining compatibility with addons. + * + * This class provides license-related functionality primarily for + * backward compatibility with existing addons. + */ class License { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/class-newsletter.php b/inc/class-newsletter.php index 6bdbef8da..1cd70de65 100644 --- a/inc/class-newsletter.php +++ b/inc/class-newsletter.php @@ -2,18 +2,41 @@ namespace WP_Ultimo; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; +/** + * Newsletter subscription management class. + * + * Handles newsletter opt-in settings and subscription management + * for the WP Ultimo plugin. + */ class Newsletter { use \WP_Ultimo\Traits\Singleton; const SETTING_FIELD_SLUG = 'newsletter_optin'; + /** + * Initializes the newsletter functionality. + * + * Sets up hooks for adding settings and handling newsletter subscription updates. + * + * @since 2.0.0 + * @return void + */ public function init(): void { add_action('wu_settings_login', [$this, 'add_settings'], 20); add_filter('wu_pre_save_settings', [$this, 'maybe_update_newsletter_subscription'], 10, 3); } + /** + * Adds the newsletter subscription setting field. + * + * Registers a toggle setting that allows users to opt-in to the + * Multisite Ultimate newsletter for updates and information. + * + * @since 2.0.0 + * @return void + */ public function add_settings(): void { wu_register_settings_field( 'general', diff --git a/inc/class-orphaned-tables-manager.php b/inc/class-orphaned-tables-manager.php index 0c845e0c6..f85164dca 100644 --- a/inc/class-orphaned-tables-manager.php +++ b/inc/class-orphaned-tables-manager.php @@ -59,6 +59,15 @@ public function register_forms(): void { ); } + /** + * Registers the cleanup orphaned tables settings field. + * + * Adds a settings field to the other settings tab that allows administrators + * to scan and cleanup database tables from deleted sites. + * + * @since 2.0.0 + * @return void + */ public function register_settings_field(): void { wu_register_settings_field( 'other', diff --git a/inc/class-settings.php b/inc/class-settings.php index 7d389d7a7..712c3c206 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -20,7 +20,7 @@ * * @since 2.0.0 */ -class Settings { +class Settings implements \WP_Ultimo\Interfaces\Singleton { use \WP_Ultimo\Traits\Singleton; use \WP_Ultimo\Traits\WP_Ultimo_Settings_Deprecated; @@ -239,7 +239,7 @@ public function save_setting($setting, $value) { * @param boolean $reset If true, Ultimate Multisite will override the saved settings with the default values. * @return array */ - public function save_settings($settings_to_save = [], $reset = false) { + public function save_settings($settings_to_save = [], $reset = false) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter $settings = []; diff --git a/inc/class-wp-ultimo.php b/inc/class-wp-ultimo.php index cecd811d1..b1c7b5069 100644 --- a/inc/class-wp-ultimo.php +++ b/inc/class-wp-ultimo.php @@ -902,6 +902,15 @@ protected function load_managers(): void { WP_Ultimo\Views::get_instance(); } + /** + * Gets the addon repository instance. + * + * Returns a singleton instance of the Addon_Repository class that manages + * addon installations and updates for WP Ultimo. + * + * @since 2.0.0 + * @return Addon_Repository The addon repository instance. + */ public function get_addon_repository(): Addon_Repository { if (! isset($this->addon_repository)) { $this->addon_repository = new Addon_Repository(); diff --git a/inc/compat/class-edit-users-compat.php b/inc/compat/class-edit-users-compat.php index a497ad8d7..b8023897d 100644 --- a/inc/compat/class-edit-users-compat.php +++ b/inc/compat/class-edit-users-compat.php @@ -16,10 +16,24 @@ namespace WP_Ultimo\Compat; +/** + * Edit Users compatibility class. + * + * Allows site administrators to edit user accounts for users on their site + * in multisite environments where normally only super admins can edit users. + */ class Edit_Users_Compat { use \WP_Ultimo\Traits\Singleton; + /** + * Initialize the Edit Users compatibility functionality. + * + * Sets up hooks and actions to enable site administrators to edit users + * and adds settings to control this feature. + * + * @since 2.4.4 + */ public function init(): void { // Add the settings to enable or disable this feature. add_action('wu_settings_login', [$this, 'add_settings'], 10); @@ -177,7 +191,7 @@ public function handle_adduser_action() { * @param array $args Arguments that accompany the requested capability check. * @return array Modified capabilities. */ - public function grant_temp_network_capability($allcaps, $caps, $args) { + public function grant_temp_network_capability($allcaps, $caps, $args) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found // Only grant during adduser action and if user can create users if (! empty($allcaps['create_users']) && ! isset($allcaps['manage_network_users'])) { $allcaps['manage_network_users'] = true; diff --git a/inc/compat/class-general-compat.php b/inc/compat/class-general-compat.php index 647c08750..a768ead30 100644 --- a/inc/compat/class-general-compat.php +++ b/inc/compat/class-general-compat.php @@ -420,6 +420,7 @@ public function run_wp_on_template_previewer(): void { * images not loading due lazy loading functionality * * @since 2.0.11 + * @param array $data Data containing blog_id to switch context. */ public function clear_avada_cache($data): void { diff --git a/inc/compat/class-honeypot-compat.php b/inc/compat/class-honeypot-compat.php index 7e78a0d50..ce2864c21 100644 --- a/inc/compat/class-honeypot-compat.php +++ b/inc/compat/class-honeypot-compat.php @@ -16,6 +16,12 @@ defined('ABSPATH') || exit; +/** + * Honeypot/WP Armor compatibility class. + * + * Provides compatibility fixes for Honeypot and WP Armor plugins + * to ensure proper functionality with WP Ultimo forms. + */ class Honeypot_Compat { use \WP_Ultimo\Traits\Singleton; diff --git a/inc/compat/class-login-wp-compat.php b/inc/compat/class-login-wp-compat.php index 6568b2257..b3a557487 100644 --- a/inc/compat/class-login-wp-compat.php +++ b/inc/compat/class-login-wp-compat.php @@ -11,12 +11,17 @@ namespace WP_Ultimo\Compat; - // Exit if accessed directly use WP_Ultimo\Checkout\Checkout_Pages; defined('ABSPATH') || exit; +/** + * LoginWP compatibility class. + * + * Provides compatibility fixes for LoginWP plugins to ensure + * proper login error handling and form display. + */ class Login_WP_Compat { use \WP_Ultimo\Traits\Singleton; @@ -28,9 +33,19 @@ class Login_WP_Compat { * @return void */ public function init(): void { - add_filter("wu_wp-ultimo/login-form_form_fields", [$this, 'add_error_field']); + add_filter('wu_wp-ultimo/login-form_form_fields', [$this, 'add_error_field']); } + /** + * Add error field to login form when login fails. + * + * Checks for login failure and adds an error message field + * to the top of the login form fields array. + * + * @since 2.0.0 + * @param array $fields The current form fields. + * @return array The modified form fields with error field if needed. + */ public function add_error_field(array $fields): array { /* * Check for error messages @@ -50,4 +65,4 @@ public function add_error_field(array $fields): array { } return $fields; } -} \ No newline at end of file +} diff --git a/inc/contracts/Session.php b/inc/contracts/Session.php index 7a488d331..581ccd35b 100644 --- a/inc/contracts/Session.php +++ b/inc/contracts/Session.php @@ -8,7 +8,7 @@ namespace WP_Ultimo\Contracts; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; interface Session { diff --git a/inc/database/checkout-forms/class-checkout-forms-table.php b/inc/database/checkout-forms/class-checkout-forms-table.php index 1b63a33ec..f03cbb18e 100644 --- a/inc/database/checkout-forms/class-checkout-forms-table.php +++ b/inc/database/checkout-forms/class-checkout-forms-table.php @@ -80,7 +80,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/customers/class-customers-table.php b/inc/database/customers/class-customers-table.php index c1fe701ac..c2ff3b7fd 100644 --- a/inc/database/customers/class-customers-table.php +++ b/inc/database/customers/class-customers-table.php @@ -150,7 +150,7 @@ protected function __20210607() { // phpcs:ignore * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_modified', diff --git a/inc/database/discount-codes/class-discount-codes-table.php b/inc/database/discount-codes/class-discount-codes-table.php index dc97d3994..6a3f3b648 100644 --- a/inc/database/discount-codes/class-discount-codes-table.php +++ b/inc/database/discount-codes/class-discount-codes-table.php @@ -87,7 +87,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/domains/class-domains-table.php b/inc/database/domains/class-domains-table.php index 684abd190..ee98e22c0 100644 --- a/inc/database/domains/class-domains-table.php +++ b/inc/database/domains/class-domains-table.php @@ -94,7 +94,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/engine/class-enum.php b/inc/database/engine/class-enum.php index d78e9ef78..7cca69d05 100644 --- a/inc/database/engine/class-enum.php +++ b/inc/database/engine/class-enum.php @@ -96,6 +96,15 @@ public static function get_options() { return static::$options[ $hook ]; } + /** + * Gets the allowed list of enum values. + * + * Returns the unique list of allowed enum values, optionally as a comma-separated string. + * + * @since 2.0.0 + * @param bool $str Whether to return as a comma-separated string. Default false. + * @return array|string Array of allowed values or comma-separated string if $str is true. + */ public static function get_allowed_list($str = false) { $options = array_unique(self::get_options()); diff --git a/inc/database/events/class-events-table.php b/inc/database/events/class-events-table.php index 5fce48e9e..c8586e3cd 100644 --- a/inc/database/events/class-events-table.php +++ b/inc/database/events/class-events-table.php @@ -83,7 +83,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/memberships/class-memberships-table.php b/inc/database/memberships/class-memberships-table.php index ae55898c9..c0559c50a 100644 --- a/inc/database/memberships/class-memberships-table.php +++ b/inc/database/memberships/class-memberships-table.php @@ -106,7 +106,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/payments/class-payments-table.php b/inc/database/payments/class-payments-table.php index fc9857879..f195bb68f 100644 --- a/inc/database/payments/class-payments-table.php +++ b/inc/database/payments/class-payments-table.php @@ -149,7 +149,7 @@ protected function __20210607() { // phpcs:ignore * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/posts/class-posts-table.php b/inc/database/posts/class-posts-table.php index 0d4fa16c0..802271481 100644 --- a/inc/database/posts/class-posts-table.php +++ b/inc/database/posts/class-posts-table.php @@ -82,7 +82,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/products/class-products-table.php b/inc/database/products/class-products-table.php index 21f75ff4f..cef999b42 100644 --- a/inc/database/products/class-products-table.php +++ b/inc/database/products/class-products-table.php @@ -148,7 +148,7 @@ protected function __20210607() { // phpcs:ignore * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/database/webhooks/class-webhooks-table.php b/inc/database/webhooks/class-webhooks-table.php index 4bc5723fb..c4b4fb613 100644 --- a/inc/database/webhooks/class-webhooks-table.php +++ b/inc/database/webhooks/class-webhooks-table.php @@ -88,7 +88,7 @@ protected function set_schema(): void { * * @since 2.1.2 */ - protected function __20230601(): bool { + protected function __20230601(): bool { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.MethodDoubleUnderscore $null_columns = [ 'date_created', diff --git a/inc/deprecated/deprecated.php b/inc/deprecated/deprecated.php index 0c81d43c9..388f6d4c9 100644 --- a/inc/deprecated/deprecated.php +++ b/inc/deprecated/deprecated.php @@ -1032,7 +1032,8 @@ class WU_Signup extends \WP_Ultimo\Checkout\Legacy_Checkout { * * @deprecated 2.0.0 */ - public function __construct() { + public function init(): void { + parent::init(); _deprecated_function(self::class, '2.0.0', esc_html(\WP_Ultimo\Checkout\Legacy_Checkout::class)); } diff --git a/inc/development/class-toolkit.php b/inc/development/class-toolkit.php index eca25201c..50892a8ed 100644 --- a/inc/development/class-toolkit.php +++ b/inc/development/class-toolkit.php @@ -321,7 +321,7 @@ public function setup_query_monitor(): void { * @param \QueryMonitor $qm The Query Monitor instance. * @return array */ - public function register_collector_overview(array $collectors, \QueryMonitor $qm) { + public function register_collector_overview(array $collectors, \QueryMonitor $qm) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter $collectors['wp-ultimo'] = new Query_Monitor\Collectors\Collector_Overview(); diff --git a/inc/development/query-monitor/collectors/class-collector-overview.php b/inc/development/query-monitor/collectors/class-collector-overview.php index 108417b74..777a22056 100644 --- a/inc/development/query-monitor/collectors/class-collector-overview.php +++ b/inc/development/query-monitor/collectors/class-collector-overview.php @@ -26,28 +26,6 @@ class Collector_Overview extends \QM_Collector { */ public $id = 'wp-ultimo'; - /** - * Set-up routines. - * - * @since 2.0.11 - * @return void - */ - public function set_up(): void { - - parent::set_up(); - } - - /** - * Tear down routines. - * - * @since 2.0.11 - * @return void - */ - public function tear_down(): void { - - parent::tear_down(); - } - /** * Process the collection. * diff --git a/inc/duplication/data.php b/inc/duplication/data.php index 0c433a3b2..71cee09fd 100644 --- a/inc/duplication/data.php +++ b/inc/duplication/data.php @@ -2,10 +2,16 @@ use Psr\Log\LogLevel; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; if ( ! class_exists('MUCD_Data') ) { + /** + * Multisite Ultimate Clone Duplicator Data class. + * + * Handles database operations for site duplication, including copying + * and updating table data between sites. + */ class MUCD_Data { private static $to_site_id; @@ -14,8 +20,8 @@ class MUCD_Data { * Copy and Update tables from a site to another * * @since 0.2.0 - * @param int $from_site_id duplicated site id - * @param int $to_site_id new site id + * @param int $from_site_id Duplicated site id. + * @param int $to_site_id New site id. */ public static function copy_data($from_site_id, $to_site_id): void { self::$to_site_id = $to_site_id; @@ -27,6 +33,16 @@ public static function copy_data($from_site_id, $to_site_id): void { self::db_update_data($from_site_id, $to_site_id, $saved_options); } + /** + * Copy blog meta data from source site to target site. + * + * Deletes existing meta data for the target site and copies all + * meta data from the source site. + * + * @since 0.2.0 + * @param int $from_site_id Source site ID. + * @param int $to_site_id Target site ID. + */ public static function db_copy_blog_meta($from_site_id, $to_site_id): void { global $wpdb; @@ -52,8 +68,8 @@ public static function db_copy_blog_meta($from_site_id, $to_site_id): void { * Copy tables from a site to another * * @since 0.2.0 - * @param int $from_site_id duplicated site id - * @param int $to_site_id new site id + * @param int $from_site_id Duplicated site id. + * @param int $to_site_id New site id. */ public static function db_copy_tables($from_site_id, $to_site_id) { global $wpdb; @@ -133,9 +149,8 @@ public static function db_copy_tables($from_site_id, $to_site_id) { * Get tables to copy if duplicated site is primary site * * @since 0.2.0 - * @param array of string $from_site_tables all tables of duplicated site - * @param string $from_site_prefix db prefix of duplicated site - * @return array of strings : the tables + * @param string $from_site_prefix DB prefix of duplicated site. + * @return array Array of table names to copy. */ public static function get_primary_tables($from_site_prefix) { @@ -153,8 +168,9 @@ public static function get_primary_tables($from_site_prefix) { * Updated tables from a site to another * * @since 0.2.0 - * @param int $from_site_id duplicated site id - * @param int $to_site_id new site id + * @param int $from_site_id Duplicated site id. + * @param int $to_site_id New site id. + * @param array $saved_options Saved options from source site. */ public static function db_update_data($from_site_id, $to_site_id, $saved_options): void { @@ -232,8 +248,8 @@ public static function db_update_data($from_site_id, $to_site_id, $saved_options * Restore options that should be preserved in the new blog * * @since 0.2.0 - * @param int $from_site_id duplicated site id - * @param int $to_site_id new site id + * @param int $to_site_id Target site id. + * @param array $saved_options Saved options to restore. */ public static function db_restore_data($to_site_id, $saved_options): void { @@ -254,10 +270,10 @@ public static function db_restore_data($to_site_id, $saved_options): void { * Updates a table * * @since 0.2.0 - * @param string $table to update - * @param array of string $fields to update - * @param string $from_string original string to replace - * @param string $to_string new string + * @param string $table Table to update. + * @param array $fields Fields to update. + * @param string $from_string Original string to replace. + * @param string $to_string New string. */ public static function update($table, $fields, $from_string, $to_string): void { if (is_array($fields) || ! empty($fields)) { @@ -297,10 +313,10 @@ public static function update($table, $fields, $from_string, $to_string): void { * Warning : if $to_string already in $val, no replacement is made * * @since 0.2.0 - * @param string $val - * @param string $from_string - * @param string $to_string - * @return string the new string + * @param string $val Original value to modify. + * @param string $from_string String to replace. + * @param string $to_string Replacement string. + * @return string The new string. */ public static function replace($val, $from_string, $to_string) { $new = $val; @@ -318,10 +334,10 @@ public static function replace($val, $from_string, $to_string) { * Replace recursively $from_string with $to_string in $val * * @since 0.2.0 - * @param mixte (string|array) $val - * @param string $from_string - * @param string $to_string - * @return string the new string + * @param mixed $val Original value (string or array) to modify. + * @param string $from_string String to replace. + * @param string $to_string Replacement string. + * @return mixed The modified value. */ public static function replace_recursive($val, $from_string, $to_string) { $unset = []; @@ -344,11 +360,11 @@ public static function replace_recursive($val, $from_string, $to_string) { * Try to replace $from_string with $to_string in a row * * @since 0.2.0 - * @param array $row the row - * @param array $field the field - * @param string $from_string - * @param string $to_string - * @return the new data + * @param array $row The row data. + * @param string $field The field name. + * @param string $from_string String to replace. + * @param string $to_string Replacement string. + * @return mixed The modified data. */ public static function try_replace($row, $field, $from_string, $to_string) { if (is_serialized($row[ $field ])) { @@ -394,10 +410,10 @@ public static function try_replace($row, $field, $from_string, $to_string) { * Runs a WPDB query * * @since 0.2.0 - * @param string $sql_query the query - * @param string $type type of result - * @param boolean $log log the query, or not - * @return $results of the query + * @param string $sql_query The SQL query to execute. + * @param string $type Type of result to return. + * @param bool $log Whether to log the query. + * @return mixed Results of the query. */ public static function do_sql_query($sql_query, $type = '', $log = true) { global $wpdb; @@ -443,8 +459,8 @@ public static function do_sql_query($sql_query, $type = '', $log = true) { * Stop process on SQL Error, print and log error, removes the new blog * * @since 0.2.0 - * @param string $sql_query the query - * @param string $sql_error the error + * @param string $sql_query The SQL query that failed. + * @param string $sql_error The error message. */ public static function sql_error($sql_query, $sql_error): void { wu_log_add('site-duplication-errors', sprintf('Got error "%s" while running: %s', $sql_error, $sql_query), LogLevel::ERROR); diff --git a/inc/duplication/duplicate.php b/inc/duplication/duplicate.php index 5c33200b4..8410dc4ce 100644 --- a/inc/duplication/duplicate.php +++ b/inc/duplication/duplicate.php @@ -1,5 +1,5 @@ =')) { $defaults = ['number' => MUCD_MAX_NUMBER_OF_SITE]; diff --git a/inc/duplication/log.php b/inc/duplication/log.php index 473cda2fc..b5e14d353 100644 --- a/inc/duplication/log.php +++ b/inc/duplication/log.php @@ -1,8 +1,14 @@ mod = $mod; @@ -136,8 +142,8 @@ private function init_file(): bool { * Writes a message in log file * * @since 0.2.0 - * @param string $message the message to write - * @return boolean True on success, False on failure + * @param string $message The message to write to the log. + * @return bool True on success, False on failure. */ public function write_log($message): bool { if (false !== $this->mod && $this->can_write() ) { diff --git a/inc/duplication/option.php b/inc/duplication/option.php index 26c55d442..c2cbbe896 100644 --- a/inc/duplication/option.php +++ b/inc/duplication/option.php @@ -1,18 +1,24 @@ get_gateway($id); diff --git a/inc/functions/helper.php b/inc/functions/helper.php index 35e351fbf..f5b6cfcc8 100644 --- a/inc/functions/helper.php +++ b/inc/functions/helper.php @@ -290,7 +290,7 @@ function wu_cli_is_plugin_skipped($plugin = null): bool { * * @return void */ -function wu_ignore_errors($func, $log = false) { +function wu_ignore_errors($func, $log = false) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter try { call_user_func($func); diff --git a/inc/functions/settings.php b/inc/functions/settings.php index 9df16daad..7dd0edcc5 100644 --- a/inc/functions/settings.php +++ b/inc/functions/settings.php @@ -84,6 +84,7 @@ function wu_register_settings_section($section_slug, $atts) { * @param string $section_slug Section to which this field will be added to. * @param string $field_slug ID of the field. This is used to later retrieve the value saved on this setting. * @param array $atts Field attributes such as title, description, tooltip, default value, etc. + * @param int $priority Priority of the field. Lower numbers correspond with earlier execution. Default 10. * @return void */ function wu_register_settings_field($section_slug, $field_slug, $atts, $priority = 10) { diff --git a/inc/functions/site.php b/inc/functions/site.php index 09c90aba1..7f7d235e7 100644 --- a/inc/functions/site.php +++ b/inc/functions/site.php @@ -20,7 +20,7 @@ function wu_get_current_site() { static $sites = array(); $blog_id = get_current_blog_id(); - if ( ! isset( $sites[ $blog_id ] ) ) { + if ( ! isset($sites[ $blog_id ]) ) { $sites[ $blog_id ] = new \WP_Ultimo\Models\Site(get_blog_details($blog_id)); } return $sites[ $blog_id ]; diff --git a/inc/gateways/class-base-stripe-gateway.php b/inc/gateways/class-base-stripe-gateway.php index 2b7f2b3d5..7cf81a0b8 100644 --- a/inc/gateways/class-base-stripe-gateway.php +++ b/inc/gateways/class-base-stripe-gateway.php @@ -1937,7 +1937,8 @@ public function before_backwards_compatible_webhook(): void { * Process webhooks * * @since 2.0.0 - * @throws Ignorable_Exception|Stripe\Exception\ApiErrorException + * @throws Ignorable_Exception When the webhook should be ignored (duplicate payments, wrong gateway, etc.). + * @throws Stripe\Exception\ApiErrorException When Stripe API calls fail. * @return bool */ public function process_webhooks() { diff --git a/inc/gateways/class-ignorable-exception.php b/inc/gateways/class-ignorable-exception.php index 47c178514..2bb90d29b 100644 --- a/inc/gateways/class-ignorable-exception.php +++ b/inc/gateways/class-ignorable-exception.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Gateways; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * This exception will be caught but will not trigger a 500. diff --git a/inc/helpers/validation-rules/class-city.php b/inc/helpers/validation-rules/class-city.php index 77977d4cf..ef8618ed8 100644 --- a/inc/helpers/validation-rules/class-city.php +++ b/inc/helpers/validation-rules/class-city.php @@ -27,7 +27,7 @@ class City extends Rule { * @since 2.0.4 * @var array */ - protected $fillableParams = ['country', 'state']; + protected $fillableParams = ['country', 'state']; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase /** * Performs the actual check. diff --git a/inc/installers/class-base-installer.php b/inc/installers/class-base-installer.php index e6c957dc5..ad8863a00 100644 --- a/inc/installers/class-base-installer.php +++ b/inc/installers/class-base-installer.php @@ -71,7 +71,7 @@ public function all_done() { * @param object $wizard Wizard class. * @return bool|\WP_Error */ - public function handle($status, $installer, $wizard) { + public function handle($status, $installer, $wizard) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter global $wpdb; diff --git a/inc/integrations/host-providers/class-base-host-provider.php b/inc/integrations/host-providers/class-base-host-provider.php index a62dc7bb2..300f1ff4f 100644 --- a/inc/integrations/host-providers/class-base-host-provider.php +++ b/inc/integrations/host-providers/class-base-host-provider.php @@ -76,18 +76,6 @@ abstract class Base_Host_Provider { */ public function init(): void { - add_filter('wu_domain_manager_get_integrations', [$this, 'self_register']); - - add_action('init', [$this, 'add_to_integration_list']); - } - - /** - * Loads the hooks and dependencies, but only if the hosting is enabled via is_enabled(). - * - * @since 2.0.0 - */ - final public function __construct() { - if ($this->detect() && ! $this->is_enabled()) { /* * Adds an admin notice telling the admin that they should probably enable this integration. @@ -121,6 +109,10 @@ final public function __construct() { */ $this->register_hooks(); } + + add_filter('wu_domain_manager_get_integrations', [$this, 'self_register']); + + add_action('init', [$this, 'add_to_integration_list']); } /** diff --git a/inc/integrations/host-providers/class-cloudflare-host-provider.php b/inc/integrations/host-providers/class-cloudflare-host-provider.php index ed8ec4191..4daa43973 100644 --- a/inc/integrations/host-providers/class-cloudflare-host-provider.php +++ b/inc/integrations/host-providers/class-cloudflare-host-provider.php @@ -252,14 +252,14 @@ public function on_add_subdomain($subdomain, $site_id): void { return; } - // Build FQDN so Domain_Manager can classify main vs. subdomain correctly. - $full_domain = $subdomain . '.' . $current_site->domain; - $should_add_www = apply_filters( - 'wu_cloudflare_should_add_www', - \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($full_domain), - $subdomain, - $site_id - ); + // Build FQDN so Domain_Manager can classify main vs. subdomain correctly. + $full_domain = $subdomain . '.' . $current_site->domain; + $should_add_www = apply_filters( + 'wu_cloudflare_should_add_www', + \WP_Ultimo\Managers\Domain_Manager::get_instance()->should_create_www_subdomain($full_domain), + $subdomain, + $site_id + ); $domains_to_send = [$subdomain]; diff --git a/inc/integrations/host-providers/class-cloudways-host-provider.php b/inc/integrations/host-providers/class-cloudways-host-provider.php index d1dff6363..6251c087f 100644 --- a/inc/integrations/host-providers/class-cloudways-host-provider.php +++ b/inc/integrations/host-providers/class-cloudways-host-provider.php @@ -12,7 +12,7 @@ use Psr\Log\LogLevel; use WP_Ultimo\Domain_Mapping\Helper; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * This base class should be extended to implement new host integrations for SSL and domains. diff --git a/inc/integrations/host-providers/cpanel-api/class-cpanel-api.php b/inc/integrations/host-providers/cpanel-api/class-cpanel-api.php index 3da4baaad..bf9dcd8da 100644 --- a/inc/integrations/host-providers/cpanel-api/class-cpanel-api.php +++ b/inc/integrations/host-providers/cpanel-api/class-cpanel-api.php @@ -9,7 +9,6 @@ namespace WP_Ultimo\Integrations\Host_Providers\CPanel_API; - defined('ABSPATH') || exit; /** diff --git a/inc/interfaces/interface-singleton.php b/inc/interfaces/interface-singleton.php new file mode 100644 index 000000000..62f41edc8 --- /dev/null +++ b/inc/interfaces/interface-singleton.php @@ -0,0 +1,21 @@ +printer = new Mpdf( + $this->printer = new Mpdf( [ 'mode' => '+aCJK', 'autoScriptToLang' => true, diff --git a/inc/managers/class-notes-manager.php b/inc/managers/class-notes-manager.php index d7f5f219a..0f230eea5 100644 --- a/inc/managers/class-notes-manager.php +++ b/inc/managers/class-notes-manager.php @@ -109,7 +109,7 @@ public function register_forms(): void { * * @since 2.0.0 * - * @param array $sections Array sections. + * @param array $sections Array sections. * @param \WP_Ultimo\Models\Interfaces\Notable $obj The object. * * @return array diff --git a/inc/managers/class-notification-manager.php b/inc/managers/class-notification-manager.php index 561ee2073..bd777905e 100644 --- a/inc/managers/class-notification-manager.php +++ b/inc/managers/class-notification-manager.php @@ -73,11 +73,11 @@ public function hide_notifications_subsites(): void { $cleaner = [$this, 'clear_callback_list']; if (wu_get_isset($wp_filter, 'admin_notices')) { - $wp_filter['admin_notices']->callbacks = array_filter($wp_filter['admin_notices']->callbacks, $cleaner ?? fn($v, $k): bool => ! empty($v), null === $cleaner ? ARRAY_FILTER_USE_BOTH : 0); + $wp_filter['admin_notices']->callbacks = array_filter($wp_filter['admin_notices']->callbacks, $cleaner ?? fn($v, $k): bool => ! empty($v), null === $cleaner ? ARRAY_FILTER_USE_BOTH : 0); // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter } if (wu_get_isset($wp_filter, 'all_admin_notices')) { - $wp_filter['all_admin_notices']->callbacks = array_filter($wp_filter['all_admin_notices']->callbacks, $cleaner ?? fn($v, $k): bool => ! empty($v), null === $cleaner ? ARRAY_FILTER_USE_BOTH : 0); + $wp_filter['all_admin_notices']->callbacks = array_filter($wp_filter['all_admin_notices']->callbacks, $cleaner ?? fn($v, $k): bool => ! empty($v), null === $cleaner ? ARRAY_FILTER_USE_BOTH : 0); // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter } } diff --git a/inc/models/class-base-model.php b/inc/models/class-base-model.php index 761eff498..b8f6150e9 100644 --- a/inc/models/class-base-model.php +++ b/inc/models/class-base-model.php @@ -292,7 +292,7 @@ public function load_attributes_from_post() { * * @since 2.0.0 * @return Schema - * @throws \ReflectionException + * @throws \ReflectionException When reflection operations fail on the query class. */ public static function get_schema() { diff --git a/inc/models/class-product.php b/inc/models/class-product.php index 534025b89..abeb5dff0 100644 --- a/inc/models/class-product.php +++ b/inc/models/class-product.php @@ -1386,8 +1386,11 @@ public function duplicate() { public function get_available_addons() { if (null === $this->available_addons) { - $this->available_addons = $this->get_meta('wu_ - available_addons', []); + $this->available_addons = $this->get_meta( + 'wu_ + available_addons', + [] + ); if (is_string($this->available_addons)) { $this->available_addons = explode(',', $this->available_addons); diff --git a/inc/models/class-site.php b/inc/models/class-site.php index 3f35d19c1..204ee4e8f 100644 --- a/inc/models/class-site.php +++ b/inc/models/class-site.php @@ -1480,7 +1480,7 @@ protected function handles_existing_search_and_replace() { if ($transient) { add_filter( 'wu_search_and_replace_on_duplication', - function ($replace_list, $from_site_id, $to_site_id) use ($transient) { + function ($replace_list, $from_site_id, $to_site_id) use ($transient) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter foreach ($transient as $transient_key => $transient_value) { $key = sprintf('{{%s}}', $transient_key); diff --git a/inc/models/interfaces/interface-billable.php b/inc/models/interfaces/interface-billable.php index 2bd134c7f..2d1044a9a 100644 --- a/inc/models/interfaces/interface-billable.php +++ b/inc/models/interfaces/interface-billable.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Models\Interfaces; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Billable interface. @@ -44,4 +44,4 @@ public function get_billing_address(); * @return void */ public function set_billing_address($billing_address): void; -} \ No newline at end of file +} diff --git a/inc/models/interfaces/interface-limitable.php b/inc/models/interfaces/interface-limitable.php index cf20a822d..70746c1d9 100644 --- a/inc/models/interfaces/interface-limitable.php +++ b/inc/models/interfaces/interface-limitable.php @@ -8,7 +8,7 @@ namespace WP_Ultimo\Models\Interfaces; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; interface Limitable { /** diff --git a/inc/models/interfaces/interface-notable.php b/inc/models/interfaces/interface-notable.php index 68041fa55..31f7c3b92 100644 --- a/inc/models/interfaces/interface-notable.php +++ b/inc/models/interfaces/interface-notable.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Models\Interfaces; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Notable interface. @@ -52,4 +52,4 @@ public function clear_notes(); * @return bool */ public function delete_note($note_id); -} \ No newline at end of file +} diff --git a/inc/models/traits/trait-billable.php b/inc/models/traits/trait-billable.php index b6bdf3409..9add42c63 100644 --- a/inc/models/traits/trait-billable.php +++ b/inc/models/traits/trait-billable.php @@ -11,7 +11,7 @@ use WP_Ultimo\Objects\Billing_Address; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Singleton trait. diff --git a/inc/models/traits/trait-limitable.php b/inc/models/traits/trait-limitable.php index 170870062..f545b02e0 100644 --- a/inc/models/traits/trait-limitable.php +++ b/inc/models/traits/trait-limitable.php @@ -12,7 +12,7 @@ use WP_Ultimo\Database\Sites\Site_Type; use WP_Ultimo\Objects\Limitations; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Singleton trait. @@ -25,7 +25,7 @@ trait Limitable { * @since 2.0.0 * @var array */ - protected $_limitations = []; + protected array $limitations = []; /** * @inheritDoc @@ -33,7 +33,16 @@ trait Limitable { abstract public function limitations_to_merge(); /** - * @inheritdoc + * Gets the limitations for this model. + * + * Returns a Limitations object containing the limitations for this model. + * Can optionally merge limitations from parent models in a waterfall manner, + * and can exclude this model's own limitations for comparison purposes. + * + * @since 2.0.0 + * @param bool $waterfall Whether to merge limitations from parent models. Default true. + * @param bool $skip_self Whether to skip this model's own limitations. Default false. + * @return \WP_Ultimo\Objects\Limitations The limitations object. */ public function get_limitations($waterfall = true, $skip_self = false) { @@ -51,7 +60,7 @@ public function get_limitations($waterfall = true, $skip_self = false) { $cache_key = $this->get_id() . $cache_key . $this->model; - $cached_version = wu_get_isset($this->_limitations, $cache_key); + $cached_version = wu_get_isset($this->limitations, $cache_key); if ( ! empty($cached_version)) { return $cached_version; @@ -86,7 +95,7 @@ public function get_limitations($waterfall = true, $skip_self = false) { $limitations = $limitations->merge($modules_data); } - $this->_limitations[ $cache_key ] = $limitations; + $this->limitations[ $cache_key ] = $limitations; return $limitations; } diff --git a/inc/models/traits/trait-notable.php b/inc/models/traits/trait-notable.php index 097b438aa..6a3b80b75 100644 --- a/inc/models/traits/trait-notable.php +++ b/inc/models/traits/trait-notable.php @@ -11,7 +11,7 @@ use WP_Ultimo\Objects\Note; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Singleton trait. diff --git a/inc/objects/class-limitations.php b/inc/objects/class-limitations.php index 7bcf4bbd1..c6085b9f6 100644 --- a/inc/objects/class-limitations.php +++ b/inc/objects/class-limitations.php @@ -299,7 +299,7 @@ protected function merge_recursive(array &$array1, array &$array2, $should_sum = $original_value = wu_get_isset($array1, $key); // If the value is 0 or '' it can be an unlimited value - $is_unlimited = (is_numeric($value) || '' === $value) && (int) $value === 0; + $is_unlimited = (is_numeric($value) || '' === $value) && 0 === (int) $value; if ($should_sum && ('' === $original_value || 0 === $original_value)) { /** diff --git a/inc/sso/auth-functions.php b/inc/sso/auth-functions.php index 6b87cb156..4cf3c7157 100644 --- a/inc/sso/auth-functions.php +++ b/inc/sso/auth-functions.php @@ -18,7 +18,7 @@ use Delight\Cookie\Cookie; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; if ( ! function_exists('wp_set_auth_cookie') ) : @@ -67,7 +67,7 @@ function wp_set_auth_cookie($user_id, $remember = false, $secure = '', $token = } // Front-end cookie is secure when the auth cookie is secure and the site's home URL uses HTTPS. - $secure_logged_in_cookie = $secure && 'https' === parse_url((string) get_option('home'), PHP_URL_SCHEME); + $secure_logged_in_cookie = $secure && 'https' === wp_parse_url((string) get_option('home'), PHP_URL_SCHEME); /** * Filters whether the auth cookie should only be sent over HTTPS. diff --git a/inc/sso/class-sso-broker.php b/inc/sso/class-sso-broker.php index 01e5ba939..c8d45fc0a 100644 --- a/inc/sso/class-sso-broker.php +++ b/inc/sso/class-sso-broker.php @@ -14,7 +14,7 @@ use Jasny\SSO\Broker\Broker; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * The SSO Broker implementation. diff --git a/inc/sso/class-sso-session-handler.php b/inc/sso/class-sso-session-handler.php index 60671f9af..fb7691110 100644 --- a/inc/sso/class-sso-session-handler.php +++ b/inc/sso/class-sso-session-handler.php @@ -14,8 +14,9 @@ namespace WP_Ultimo\SSO; +use Jasny\SSO\Server\BrokerException; use Jasny\SSO\Server\SessionInterface; -use Jasny\SSO\ServerException; +use Jasny\SSO\Server\ServerException; use WP_Ultimo\SSO\Exception\SSO_Session_Exception; // Exit if accessed directly @@ -32,18 +33,18 @@ class SSO_Session_Handler implements SessionInterface { * The SSO manager instance. * * @since 2.0.11 - * @var \WP_Ultimo\SSO + * @var SSO */ public $sso_manager; /** * Build the handler with the SSO manager. * - * @since 2.0.11 + * @param SSO|null $sso_manager The sso manager. * - * @param \WP_Ultimo\SSO\SSO|null $sso_manager The sso manager. + * @since 2.0.11 */ - public function __construct(\WP_Ultimo\SSO\SSO $sso_manager = null) { + public function __construct(SSO $sso_manager = null) { $this->sso_manager = $sso_manager; } @@ -52,7 +53,7 @@ public function __construct(\WP_Ultimo\SSO\SSO $sso_manager = null) { * * @since 2.0.11 */ - public function getId(): string { // phpcs:ignore + public function getId(): string { return $this->sso_manager->input('broker'); } @@ -61,10 +62,9 @@ public function getId(): string { // phpcs:ignore * * @since 2.0.11 * - * @throws ServerException If session can't be started. * @throws SSO_Session_Exception If session can't be started. */ - public function start(): void { // phpcs:ignore + public function start(): void { $site_hash = $this->sso_manager->input('broker'); @@ -87,7 +87,7 @@ public function start(): void { // phpcs:ignore * * @param string $id The session id. */ - public function resume(string $id): void { // phpcs:ignore + public function resume(string $id): void { $decoded_id = $this->sso_manager->decode($id, $this->sso_manager->salt()); @@ -105,7 +105,7 @@ public function resume(string $id): void { // phpcs:ignore * * @since 2.0.11 */ - public function isActive(): bool { // phpcs:ignore + public function isActive(): bool { return false; } } diff --git a/inc/sso/class-sso.php b/inc/sso/class-sso.php index cb3ddecd3..2a0a37d35 100644 --- a/inc/sso/class-sso.php +++ b/inc/sso/class-sso.php @@ -326,9 +326,6 @@ public function handle_auth_redirect() { $broker = $this->get_broker(); - if ( ! $broker) { - } - if ($broker->is_must_redirect_call()) { return false; } @@ -656,7 +653,7 @@ public function determine_current_user($current_user_id) { * on if we are not able to validate the customer. * * @throws ServerException - * @throws SsoException + * @throws SSO_Exception * @throws BrokerException * @throws NotAttachedException */ diff --git a/inc/stuff.php b/inc/stuff.php index de7f965c6..a81c4f423 100644 --- a/inc/stuff.php +++ b/inc/stuff.php @@ -1,5 +1,5 @@ 'ZGbiJ3FBm0xe7rkIo8IkEGxuakV6dncrRXVBRWlUY3BzZ1JMbXRuWStZcklxR0FHVTFTRGIvQ2NNU3ViSlpXUHM1L29SbEQ1V1FJWjBOSjc=', - 1 => 'dOStiyLlVC5Q0/pwA3vfym5jTjRqREpFQVVBakJsNXJLVGFnRjVEU0VkTk04R1MvdjgvVnE0YjkydVhHdnU3YzhzQ3Nhb010N3Ryb05rcEE=', -); \ No newline at end of file +return array( + 0 => 'A3mbUD7luXjl6AuqHEQwdThkR0JsTjdQSTdWeGxUenpzRGExVDFoT0lSZlNadERUU0sxSUtjRlU3OHhnZmVVY2hJNTR5amlBaThXUytOTmE=', + 1 => 'DYz6uNb8q+kH+xXPS0A0VzJTb2g1UWd1QUVIZlBFOXRuVHVyNEhrclBVL0tFcG8vaUM3Q2RCNlAxQVdtZkpuWVdOdnBUTWdTQkVsVk5ZU1U=', +); diff --git a/inc/traits/trait-singleton.php b/inc/traits/trait-singleton.php index 5a9b907c3..47b7a23f3 100644 --- a/inc/traits/trait-singleton.php +++ b/inc/traits/trait-singleton.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Singleton trait. @@ -60,4 +60,10 @@ public function has_parents(): bool { return (bool) class_parents($this); } + + /** + * Private constructor so get_instance() must be used and init() will always be called. + */ + final private function __construct() { + } } diff --git a/inc/traits/trait-wp-ultimo-coupon-deprecated.php b/inc/traits/trait-wp-ultimo-coupon-deprecated.php index 160966b0c..2c7956cf0 100644 --- a/inc/traits/trait-wp-ultimo-coupon-deprecated.php +++ b/inc/traits/trait-wp-ultimo-coupon-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Coupon_Deprecated trait. @@ -59,8 +59,6 @@ public function __get($key) { // translators: the placeholder is the key. $message = sprintf(__('Discount Codes do not have a %s parameter', 'ultimate-multisite'), $key); - // throw new \Exception($message); - return false; } diff --git a/inc/traits/trait-wp-ultimo-deprecated.php b/inc/traits/trait-wp-ultimo-deprecated.php index 08fbe6a57..b4c0ca105 100644 --- a/inc/traits/trait-wp-ultimo-deprecated.php +++ b/inc/traits/trait-wp-ultimo-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Deprecated trait. diff --git a/inc/traits/trait-wp-ultimo-plan-deprecated.php b/inc/traits/trait-wp-ultimo-plan-deprecated.php index e76941d00..6b3d5e361 100644 --- a/inc/traits/trait-wp-ultimo-plan-deprecated.php +++ b/inc/traits/trait-wp-ultimo-plan-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Plan_Deprecated trait. @@ -61,7 +61,6 @@ public function __get($key) { break; case 'quotas': $value = [ - // 'sites' => 300, 'upload' => 1024 * 1024 * 1024, 'visits' => 300, ]; @@ -294,10 +293,10 @@ public function get_quota($quota_name) { * * @since 1.5.4 * @param string $quota_type Post type to check. - * @param string $default Default value. + * @param string $default_value Default value. * @return bool */ - public function should_display_quota_on_pricing_tables($quota_type, $default = false) { + public function should_display_quota_on_pricing_tables($quota_type, $default_value = false) { /* * @since 1.3.3 Only Show elements allowed on the plan settings */ @@ -307,7 +306,7 @@ public function should_display_quota_on_pricing_tables($quota_type, $default = f return true; } - if ( ! isset($elements[ $quota_type ]) && $default) { + if ( ! isset($elements[ $quota_type ]) && $default_value) { return true; } diff --git a/inc/traits/trait-wp-ultimo-settings-deprecated.php b/inc/traits/trait-wp-ultimo-settings-deprecated.php index cceb0236a..ce90c1a48 100644 --- a/inc/traits/trait-wp-ultimo-settings-deprecated.php +++ b/inc/traits/trait-wp-ultimo-settings-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Settings_Deprecated trait. diff --git a/inc/traits/trait-wp-ultimo-site-deprecated.php b/inc/traits/trait-wp-ultimo-site-deprecated.php index 554430f4c..3488126c7 100644 --- a/inc/traits/trait-wp-ultimo-site-deprecated.php +++ b/inc/traits/trait-wp-ultimo-site-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Site_Deprecated trait. diff --git a/inc/traits/trait-wp-ultimo-subscription-deprecated.php b/inc/traits/trait-wp-ultimo-subscription-deprecated.php index 7edc2c5f9..57abd0503 100644 --- a/inc/traits/trait-wp-ultimo-subscription-deprecated.php +++ b/inc/traits/trait-wp-ultimo-subscription-deprecated.php @@ -9,7 +9,7 @@ namespace WP_Ultimo\Traits; -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * WP_Ultimo_Subscription_Deprecated trait. diff --git a/inc/ui/class-base-element.php b/inc/ui/class-base-element.php index 6e4e7fe88..c29a1fa85 100644 --- a/inc/ui/class-base-element.php +++ b/inc/ui/class-base-element.php @@ -444,12 +444,12 @@ public function setup_preview() {} /** * Ensures setup is called before output to prevent errors. - * + * * @since 2.4.3 * @return void */ protected function ensure_setup() { - if (!$this->loaded) { + if (! $this->loaded) { $this->is_preview() ? $this->setup_preview() : $this->setup(); $this->loaded = true; } diff --git a/mu-plugins/email-smtp-dev/email-smtp-dev.php b/mu-plugins/email-smtp-dev/email-smtp-dev.php index 0067b3175..6abb115c6 100644 --- a/mu-plugins/email-smtp-dev/email-smtp-dev.php +++ b/mu-plugins/email-smtp-dev/email-smtp-dev.php @@ -7,21 +7,21 @@ */ function DevConfigMailpit($phpmailer) { - error_log('📬 PHPMailer init hook triggered'); + error_log('📬 PHPMailer init hook triggered'); - $phpmailer->isSMTP(); - $phpmailer->Host = 'wp-multisite-waas-mailpit'; // Mailpit SMTP host - $phpmailer->Port = 1025; // Mailpit SMTP port - $phpmailer->SMTPAuth = false; // No auth for Mailpit by default - $phpmailer->SMTPSecure = false; // No encryption for local SMTP - $phpmailer->Username = null; // Leave empty - $phpmailer->Password = null; // Leave empty + $phpmailer->isSMTP(); + $phpmailer->Host = 'wp-multisite-waas-mailpit'; // Mailpit SMTP host + $phpmailer->Port = 1025; // Mailpit SMTP port + $phpmailer->SMTPAuth = false; // No auth for Mailpit by default + $phpmailer->SMTPSecure = false; // No encryption for local SMTP + $phpmailer->Username = null; // Leave empty + $phpmailer->Password = null; // Leave empty - // Optional: set the default From address and name for all outgoing emails - $phpmailer->From = 'dev@example.local'; - $phpmailer->FromName = 'Dev Site'; + // Optional: set the default From address and name for all outgoing emails + $phpmailer->From = 'dev@example.local'; + $phpmailer->FromName = 'Dev Site'; - // Uncomment to enable SMTP debug output (helpful for troubleshooting) - $phpmailer->SMTPDebug = 2; + // Uncomment to enable SMTP debug output (helpful for troubleshooting) + $phpmailer->SMTPDebug = 2; } add_action('phpmailer_init', 'DevConfigMailpit', 10, 1); diff --git a/mu-plugins/email-smtp-test/email-smtp-test.php b/mu-plugins/email-smtp-test/email-smtp-test.php index bd29d02ac..041c421e5 100644 --- a/mu-plugins/email-smtp-test/email-smtp-test.php +++ b/mu-plugins/email-smtp-test/email-smtp-test.php @@ -7,21 +7,21 @@ */ function TestConfigMailpit($phpmailer) { - error_log('📬 PHPMailer init hook triggered'); - - $phpmailer->isSMTP(); - $phpmailer->Host = 'wp-multisite-waas-mailpit'; // Mailpit SMTP host - $phpmailer->Port = 1025; // Mailpit SMTP port - $phpmailer->SMTPAuth = false; // No auth for Mailpit by default - $phpmailer->SMTPSecure = false; // No encryption for local SMTP - $phpmailer->Username = null; // Leave empty - $phpmailer->Password = null; // Leave empty + error_log('📬 PHPMailer init hook triggered'); - // Optional: set the default From address and name for all outgoing emails - $phpmailer->From = 'test@example.local'; - $phpmailer->FromName = 'Test Site'; + $phpmailer->isSMTP(); + $phpmailer->Host = 'wp-multisite-waas-mailpit'; // Mailpit SMTP host + $phpmailer->Port = 1025; // Mailpit SMTP port + $phpmailer->SMTPAuth = false; // No auth for Mailpit by default + $phpmailer->SMTPSecure = false; // No encryption for local SMTP + $phpmailer->Username = null; // Leave empty + $phpmailer->Password = null; // Leave empty - // Uncomment to enable SMTP debug output (helpful for troubleshooting) - $phpmailer->SMTPDebug = 2; + // Optional: set the default From address and name for all outgoing emails + $phpmailer->From = 'test@example.local'; + $phpmailer->FromName = 'Test Site'; + + // Uncomment to enable SMTP debug output (helpful for troubleshooting) + $phpmailer->SMTPDebug = 2; } add_action('phpmailer_init', 'TestConfigMailpit', 10, 1); diff --git a/package.json b/package.json index a7a6f4a32..6378e924f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,24 @@ "precleancss": "node scripts/clean-css.js", "cleancss": "node scripts/cleancss.js", "makepot": "node scripts/makepot.js", + "test": "vendor/bin/phpunit", + "test:coverage": "vendor/bin/phpunit --coverage-html=coverage-html --coverage-clover=coverage.xml", + "test:watch": "vendor/bin/phpunit --watch", + "lint": "vendor/bin/phpcs", + "lint:fix": "vendor/bin/phpcbf", + "stan": "vendor/bin/phpstan analyse", + "quality": "run-s lint stan", + "quality:fix": "run-s lint:fix stan", + "check": "run-s lint stan test", + "dev:install": "run-s install:deps setup:hooks", + "install:deps": "run-p install:composer install:npm", + "install:composer": "composer install", + "install:npm": "npm install", + "setup:hooks": "bash bin/setup-hooks.sh", + "dev:setup": "run-s install:deps setup:hooks", + "clean": "run-p clean:coverage clean:cache", + "clean:coverage": "rm -rf coverage-html coverage.xml", + "clean:cache": "rm -f .phpunit.result.cache && rm -rf node_modules/.cache", "translate": "php scripts/translate.php", "translate:force": "php scripts/translate.php --force", "env:start": "wp-env start", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2f08435fa..0efc63883 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,4 +8,22 @@ ./tests/ + + + ./inc + ./multisite-ultimate.php + + + ./inc/ui + ./inc/views + ./inc/assets + ./vendor + ./inc/stuff.php + + + + + + + diff --git a/scripts/remove-mpdf-fonts.php b/scripts/remove-mpdf-fonts.php index dd69a8c1b..a9e574dec 100644 --- a/scripts/remove-mpdf-fonts.php +++ b/scripts/remove-mpdf-fonts.php @@ -1,29 +1,81 @@ get_user_id(), $customer->get_username()); // Create a high-tier product with unlimited domains - $high_tier_product = wu_create_product( + $high_tier_product = wu_create_product( [ 'name' => 'High Tier Product', 'slug' => 'high-tier-product', @@ -234,8 +239,8 @@ public function test_domain_mapping_downgrade_validation_over_limit() { 'active' => true, ] ); - - // Set limitations manually + + // Set limitations manually $low_tier_product->meta['wu_limitations'] = [ 'domain_mapping' => [ 'enabled' => true, @@ -317,7 +322,6 @@ public function test_domain_mapping_downgrade_validation_over_limit() { $is_valid = $cart->is_valid(); $validation_errors = $cart->get_errors(); - // Cart should have validation errors due to domain limit $this->assertFalse($is_valid); $this->assertInstanceOf(\WP_Error::class, $validation_errors); @@ -334,13 +338,25 @@ public function test_domain_mapping_downgrade_validation_over_limit() { $this->assertTrue($domain_error_found, 'Domain mapping validation error not found'); // Clean up - skip site deletion to avoid core table corruption - if ($domain1) $domain1->delete(); - if ($domain2) $domain2->delete(); - if ($domain3) $domain3->delete(); + if ($domain1) { + $domain1->delete(); + } + if ($domain2) { + $domain2->delete(); + } + if ($domain3) { + $domain3->delete(); + } // Skip: $site->delete(); - causes core table deletion issues - if ($membership) $membership->delete(); - if ($high_tier_product) $high_tier_product->delete(); - if ($low_tier_product) $low_tier_product->delete(); + if ($membership) { + $membership->delete(); + } + if ($high_tier_product) { + $high_tier_product->delete(); + } + if ($low_tier_product) { + $low_tier_product->delete(); + } } /** @@ -351,7 +367,7 @@ public function test_domain_mapping_downgrade_validation_within_limit() { wp_set_current_user($customer->get_user_id(), $customer->get_username()); // Create a high-tier product with unlimited domains - $high_tier_product = wu_create_product( + $high_tier_product = wu_create_product( [ 'name' => 'High Tier Product 2', 'slug' => 'high-tier-product-2', @@ -373,7 +389,7 @@ public function test_domain_mapping_downgrade_validation_within_limit() { $high_tier_product->save(); // Create a mid-tier product with 3 domain limit - $mid_tier_product = wu_create_product( + $mid_tier_product = wu_create_product( [ 'name' => 'Mid Tier Product', 'slug' => 'mid-tier-product', @@ -471,12 +487,22 @@ public function test_domain_mapping_downgrade_validation_within_limit() { } // Clean up - skip site deletion to avoid core table corruption - if ($domain1) $domain1->delete(); - if ($domain2) $domain2->delete(); + if ($domain1) { + $domain1->delete(); + } + if ($domain2) { + $domain2->delete(); + } // Skip: $site->delete(); - causes core table deletion issues - if ($membership) $membership->delete(); - if ($high_tier_product) $high_tier_product->delete(); - if ($mid_tier_product) $mid_tier_product->delete(); + if ($membership) { + $membership->delete(); + } + if ($high_tier_product) { + $high_tier_product->delete(); + } + if ($mid_tier_product) { + $mid_tier_product->delete(); + } } /** @@ -487,7 +513,7 @@ public function test_domain_mapping_downgrade_validation_disabled_in_new_plan() wp_set_current_user($customer->get_user_id(), $customer->get_username()); // Create a high-tier product with unlimited domains - $high_tier_product = wu_create_product( + $high_tier_product = wu_create_product( [ 'name' => 'High Tier Product 3', 'slug' => 'high-tier-product-3', @@ -509,7 +535,7 @@ public function test_domain_mapping_downgrade_validation_disabled_in_new_plan() $high_tier_product->save(); // Create a basic product with no domains allowed - $basic_product = wu_create_product( + $basic_product = wu_create_product( [ 'name' => 'Basic Product', 'slug' => 'basic-product', @@ -598,11 +624,19 @@ public function test_domain_mapping_downgrade_validation_disabled_in_new_plan() $this->assertTrue($domain_error_found, 'Domain mapping validation error not found for disabled domains'); // Clean up - skip site deletion to avoid core table corruption - if ($domain1) $domain1->delete(); + if ($domain1) { + $domain1->delete(); + } // Skip: $site->delete(); - causes core table deletion issues - if ($membership) $membership->delete(); - if ($high_tier_product) $high_tier_product->delete(); - if ($basic_product) $basic_product->delete(); + if ($membership) { + $membership->delete(); + } + if ($high_tier_product) { + $high_tier_product->delete(); + } + if ($basic_product) { + $basic_product->delete(); + } } public static function tear_down_after_class() { diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Base_Signup_Field_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Base_Signup_Field_Test.php new file mode 100644 index 000000000..aab5eb5e7 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Base_Signup_Field_Test.php @@ -0,0 +1,312 @@ + [ + 'type' => 'text', + 'title' => 'Test Option', + ], + ]; + } + + /** + * Returns the field/element actual field array. + * + * @since 2.0.0 + * @return array + */ + public function to_array() { + return [ + 'type' => $this->get_type(), + 'title' => $this->get_title(), + 'description' => $this->get_description(), + 'required' => $this->is_required(), + ]; + } + + /** + * Returns the field/element actual field array to be used on the checkout form. + * + * @since 2.0.0 + * @param array $attributes Field attributes. + * @return array + */ + public function to_fields_array($attributes = []) { + return [ + 'test_field' => [ + 'type' => 'text', + 'title' => $this->get_title(), + 'placeholder' => 'Enter test value', + 'required' => $this->is_required(), + ], + ]; + } + + /** + * Outputs the contents of the field. + * + * @since 2.0.0 + * @return void + */ + public function output() { + echo ''; + } +} + +/** + * Test Base Signup Field functionality. + */ +class Base_Signup_Field_Test extends WP_UnitTestCase { + + /** + * Test field instance. + * + * @var Test_Signup_Field + */ + private $field; + + /** + * Set up test. + */ + public function setUp(): void { + parent::setUp(); + + $this->field = new Test_Signup_Field(); + } + + /** + * Test field type. + */ + public function test_get_type() { + $this->assertEquals('test_field', $this->field->get_type()); + } + + /** + * Test field required status. + */ + public function test_is_required() { + $this->assertTrue($this->field->is_required()); + } + + /** + * Test field title. + */ + public function test_get_title() { + $this->assertEquals('Test Field', $this->field->get_title()); + } + + /** + * Test field description. + */ + public function test_get_description() { + $this->assertEquals('A test field for testing purposes', $this->field->get_description()); + } + + /** + * Test field tooltip. + */ + public function test_get_tooltip() { + $this->assertEquals('This is a test field tooltip', $this->field->get_tooltip()); + } + + /** + * Test field icon. + */ + public function test_get_icon() { + $this->assertEquals('dashicons-admin-generic', $this->field->get_icon()); + } + + /** + * Test field configuration fields. + */ + public function test_get_fields() { + $fields = $this->field->get_fields(); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('test_option', $fields); + $this->assertEquals('text', $fields['test_option']['type']); + $this->assertEquals('Test Option', $fields['test_option']['title']); + } + + /** + * Test field to array conversion. + */ + public function test_to_array() { + $array = $this->field->to_array(); + + $this->assertIsArray($array); + $this->assertEquals('test_field', $array['type']); + $this->assertEquals('Test Field', $array['title']); + $this->assertEquals('A test field for testing purposes', $array['description']); + $this->assertTrue($array['required']); + } + + /** + * Test field output. + */ + public function test_output() { + ob_start(); + $this->field->output(); + $output = ob_get_clean(); + + $this->assertStringContainsString('input', $output); + $this->assertStringContainsString('test_field', $output); + } + + /** + * Test field attribute handling. + */ + public function test_attributes() { + // Test that attributes property exists + $reflection = new \ReflectionClass($this->field); + $this->assertTrue($reflection->hasProperty('attributes')); + + $property = $reflection->getProperty('attributes'); + $property->setAccessible(true); + + // Initially should be null or empty + $attributes = $property->getValue($this->field); + $this->assertTrue(is_null($attributes) || empty($attributes)); + } + + /** + * Test abstract class enforcement. + */ + public function test_abstract_methods() { + $reflection = new \ReflectionClass(Base_Signup_Field::class); + + $this->assertTrue($reflection->isAbstract()); + + // Check that all required abstract methods exist + $abstract_methods = [ + 'get_type', + 'is_required', + 'get_title', + 'get_description', + 'get_tooltip', + 'get_icon', + 'get_fields', + ]; + + foreach ($abstract_methods as $method) { + $this->assertTrue($reflection->hasMethod($method)); + } + } + + /** + * Test field inheritance. + */ + public function test_inheritance() { + $this->assertInstanceOf(Base_Signup_Field::class, $this->field); + } + + /** + * Test field method return types. + */ + public function test_return_types() { + $this->assertIsString($this->field->get_type()); + $this->assertIsBool($this->field->is_required()); + $this->assertIsString($this->field->get_title()); + $this->assertIsString($this->field->get_description()); + $this->assertIsString($this->field->get_tooltip()); + $this->assertIsString($this->field->get_icon()); + $this->assertIsArray($this->field->get_fields()); + $this->assertIsArray($this->field->to_array()); + } + + /** + * Test field validation methods exist. + */ + public function test_has_validation_methods() { + $reflection = new \ReflectionClass($this->field); + + // These are common methods that signup fields often have + $common_methods = ['get_type', 'get_title', 'is_required']; + + foreach ($common_methods as $method) { + $this->assertTrue($reflection->hasMethod($method), "Method {$method} should exist"); + } + } +} diff --git a/tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php b/tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php index 8c7ed8e53..eb9b1c821 100644 --- a/tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php +++ b/tests/WP_Ultimo/Gateways/Stripe_Gateway_Process_Checkout_Test.php @@ -143,7 +143,7 @@ public function setUp(): void { $this->arrayHasKey('idempotency_key') ) ->willReturnCallback( - function ($params, $options) { + function ($params, $options) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter return \Stripe\Subscription::constructFrom( [ 'id' => 'sub_123', // Dynamic ID based on idempotency key diff --git a/tests/WP_Ultimo/Helpers/Hash_Test.php b/tests/WP_Ultimo/Helpers/Hash_Test.php new file mode 100644 index 000000000..c525c133e --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Hash_Test.php @@ -0,0 +1,229 @@ +assertEquals($original_id, $decoded_id); + $this->assertIsString($hash); + $this->assertNotEmpty($hash); + } + + /** + * Test hash encoding produces consistent results. + */ + public function test_consistent_encoding() { + $id = 999; + + $hash1 = Hash::encode($id); + $hash2 = Hash::encode($id); + + $this->assertEquals($hash1, $hash2); + } + + /** + * Test hash encoding with different groups. + */ + public function test_different_groups_produce_different_hashes() { + $id = 123; + + $hash1 = Hash::encode($id, 'group1'); + $hash2 = Hash::encode($id, 'group2'); + + $this->assertNotEquals($hash1, $hash2); + } + + /** + * Test decode with matching groups. + */ + public function test_decode_with_matching_groups() { + $id = 456; + $group = 'test-group'; + + $hash = Hash::encode($id, $group); + $decoded_id = Hash::decode($hash, $group); + + $this->assertEquals($id, $decoded_id); + } + + /** + * Test decode with mismatched groups returns different result. + */ + public function test_decode_with_mismatched_groups() { + $id = 789; + + $hash = Hash::encode($id, 'group1'); + $decoded_id = Hash::decode($hash, 'group2'); + + $this->assertNotEquals($id, $decoded_id); + } + + /** + * Test encoding zero. + */ + public function test_encode_zero() { + $id = 0; + + $hash = Hash::encode($id); + $decoded_id = Hash::decode($hash); + + $this->assertEquals($id, $decoded_id); + $this->assertIsString($hash); + } + + /** + * Test encoding large numbers. + */ + public function test_encode_large_numbers() { + $id = 999999999; + + $hash = Hash::encode($id); + $decoded_id = Hash::decode($hash); + + $this->assertEquals($id, $decoded_id); + } + + /** + * Test hash length constant. + */ + public function test_hash_length_constant() { + $this->assertEquals(10, Hash::LENGTH); + } + + /** + * Test default group encoding. + */ + public function test_default_group_encoding() { + $id = 555; + + $hash1 = Hash::encode($id); + $hash2 = Hash::encode($id, 'wp-ultimo'); + + $this->assertEquals($hash1, $hash2); + } + + /** + * Test multiple consecutive IDs produce different hashes. + */ + public function test_consecutive_ids_different_hashes() { + $id1 = 100; + $id2 = 101; + + $hash1 = Hash::encode($id1); + $hash2 = Hash::encode($id2); + + $this->assertNotEquals($hash1, $hash2); + } + + /** + * Test hash contains only allowed characters. + */ + public function test_hash_character_set() { + $id = 12345; + $hash = Hash::encode($id); + + // Should only contain uppercase letters and numbers + $this->assertMatchesRegularExpression('/^[A-Z0-9]+$/', $hash); + } + + /** + * Test decoding invalid hash. + */ + public function test_decode_invalid_hash() { + $invalid_hash = 'invalid-hash-string'; + $result = Hash::decode($invalid_hash); + + // Should return false or empty when decoding fails + $this->assertFalse($result); + } + + /** + * Test encoding negative numbers. + */ + public function test_encode_negative_numbers() { + // Hashids typically doesn't handle negative numbers well + $id = -123; + + $hash = Hash::encode($id); + $decoded_id = Hash::decode($hash); + + // The behavior may vary, but it should at least not crash + $this->assertIsString($hash); + } + + /** + * Test round trip with various ID ranges. + */ + public function test_round_trip_various_ranges() { + $test_ids = [1, 10, 100, 1000, 10000, 99999]; + + foreach ($test_ids as $id) { + $hash = Hash::encode($id); + $decoded_id = Hash::decode($hash); + + $this->assertEquals($id, $decoded_id, "Failed for ID: {$id}"); + } + } + + /** + * Test hash uniqueness across range of IDs. + */ + public function test_hash_uniqueness() { + $hashes = []; + + for ($i = 1; $i <= 100; $i++) { + $hash = Hash::encode($i); + $this->assertNotContains($hash, $hashes, "Duplicate hash found for ID: {$i}"); + $hashes[] = $hash; + } + + // Ensure we generated 100 unique hashes + $this->assertEquals(100, count(array_unique($hashes))); + } + + /** + * Test encoding with empty string group. + */ + public function test_encode_empty_group() { + $id = 123; + + $hash = Hash::encode($id, ''); + $decoded_id = Hash::decode($hash, ''); + + $this->assertEquals($id, $decoded_id); + } + + /** + * Test encoding with very long group name. + */ + public function test_encode_long_group_name() { + $id = 456; + $long_group = str_repeat('very-long-group-name-', 10); + + $hash = Hash::encode($id, $long_group); + $decoded_id = Hash::decode($hash, $long_group); + + $this->assertEquals($id, $decoded_id); + } +} diff --git a/tests/WP_Ultimo/Helpers/Site_Duplicator_Test.php b/tests/WP_Ultimo/Helpers/Site_Duplicator_Test.php new file mode 100644 index 000000000..03ac35262 --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Site_Duplicator_Test.php @@ -0,0 +1,316 @@ +markTestSkipped('Site duplication tests require multisite'); + } + + // Create test customer + $this->customer = wu_create_customer( + [ + 'username' => 'testuser', + 'email_address' => 'test@example.com', + 'password' => 'password123', + ] + ); + + if (is_wp_error($this->customer)) { + $this->markTestSkipped('Could not create test customer: ' . $this->customer->get_error_message()); + } + + // Create template site + $this->template_site_id = self::factory()->blog->create( + [ + 'domain' => 'template.example.com', + 'path' => '/', + 'title' => 'Template Site', + ] + ); + + // Switch to template site and add some content + switch_to_blog($this->template_site_id); + + // Create a test post + wp_insert_post( + [ + 'post_title' => 'Template Post', + 'post_content' => 'This is template content', + 'post_status' => 'publish', + ] + ); + + // Create a test page + wp_insert_post( + [ + 'post_title' => 'Template Page', + 'post_type' => 'page', + 'post_content' => 'This is a template page', + 'post_status' => 'publish', + ] + ); + + restore_current_blog(); + } + + /** + * Test successful site duplication. + */ + public function test_successful_site_duplication() { + $args = [ + 'domain' => 'newsite.example.com', + 'path' => '/', + 'title' => 'New Site', + ]; + + $result = Site_Duplicator::duplicate_site($this->template_site_id, 'New Site', $args); + + $this->assertIsInt($result); + $this->assertGreaterThan(0, $result); + + // Verify the new site exists + $new_site = get_site($result); + $this->assertNotNull($new_site); + $this->assertEquals('New Site', $new_site->blogname); + + // Clean up + wpmu_delete_blog($result, true); + } + + /** + * Test duplication with invalid source site. + */ + public function test_duplicate_invalid_source_site() { + $invalid_site_id = 99999; + + $args = [ + 'domain' => 'newsite.example.com', + 'path' => '/', + 'title' => 'New Site', + ]; + + $result = Site_Duplicator::duplicate_site($invalid_site_id, 'New Site', $args); + + // The result should be either a WP_Error or a failure case + $this->assertTrue(is_wp_error($result) || ! $result || is_int($result)); + } + + /** + * Test duplication with conflicting domain. + */ + public function test_duplicate_conflicting_domain() { + // Use the same domain as template site + $args = [ + 'domain' => 'template.example.com', + 'path' => '/', + 'title' => 'Conflicting Site', + ]; + + $result = Site_Duplicator::duplicate_site($this->template_site_id, 'Conflicting Site', $args); + + $this->assertInstanceOf(\WP_Error::class, $result); + } + + /** + * Test site override functionality. + */ + public function test_site_override() { + // Create target site to override + $target_site_id = self::factory()->blog->create( + [ + 'domain' => 'target.example.com', + 'path' => '/', + 'title' => 'Target Site', + ] + ); + + // Create wu_site record for target + $target_wu_site = wu_create_site( + [ + 'blog_id' => $target_site_id, + 'customer_id' => $this->customer->get_id(), + 'type' => Site_Type::REGULAR, + ] + ); + + if (is_wp_error($target_wu_site)) { + $this->markTestSkipped('Could not create wu_site record: ' . $target_wu_site->get_error_message()); + } + + $args = []; + + $result = Site_Duplicator::override_site($this->template_site_id, $target_site_id, $args); + + // Method should return the target site ID or false + $this->assertTrue($result === $target_site_id || $result === false); + + // Clean up + wpmu_delete_blog($target_site_id, true); + if ($target_wu_site && ! is_wp_error($target_wu_site)) { + $target_wu_site->delete(); + } + } + + /** + * Test override with invalid target site. + */ + public function test_override_invalid_target_site() { + $invalid_target_id = 99999; + + $args = []; + + $result = Site_Duplicator::override_site($this->template_site_id, $invalid_target_id, $args); + + // Should handle gracefully + $this->assertFalse($result); + } + + /** + * Test duplication with custom arguments. + */ + public function test_duplication_with_custom_args() { + $args = [ + 'domain' => 'custom.example.com', + 'path' => '/', + 'title' => 'Custom Site', + 'copy_files' => true, + 'copy_users' => false, + 'keep_users' => true, + ]; + + $result = Site_Duplicator::duplicate_site($this->template_site_id, 'Custom Site', $args); + + if (! is_wp_error($result)) { + $this->assertIsInt($result); + $this->assertGreaterThan(0, $result); + + // Clean up + wpmu_delete_blog($result, true); + } else { + // Some configurations might fail, which is acceptable + $this->assertInstanceOf(\WP_Error::class, $result); + } + } + + /** + * Test duplication preserves site content. + */ + public function test_duplication_preserves_content() { + $args = [ + 'domain' => 'content.example.com', + 'path' => '/', + 'title' => 'Content Site', + ]; + + $result = Site_Duplicator::duplicate_site($this->template_site_id, 'Content Site', $args); + + if (! is_wp_error($result)) { + $this->assertIsInt($result); + + // Switch to new site and check content + switch_to_blog($result); + + $posts = get_posts(['post_type' => 'any']); + $this->assertNotEmpty($posts); + + // Look for our template content + $found_template_post = false; + foreach ($posts as $post) { + if ($post->post_title === 'Template Post') { + $found_template_post = true; + break; + } + } + + $this->assertTrue($found_template_post); + + restore_current_blog(); + + // Clean up + wpmu_delete_blog($result, true); + } else { + $this->markTestSkipped('Site duplication failed: ' . $result->get_error_message()); + } + } + + /** + * Test duplication with subdirectory path. + */ + public function test_duplication_with_subdirectory() { + $args = [ + 'domain' => get_current_site()->domain, + 'path' => '/subdir/', + 'title' => 'Subdirectory Site', + ]; + + $result = Site_Duplicator::duplicate_site($this->template_site_id, 'Subdirectory Site', $args); + + if (! is_wp_error($result)) { + $this->assertIsInt($result); + + $new_site = get_site($result); + $this->assertEquals('/subdir/', $new_site->path); + + // Clean up + wpmu_delete_blog($result, true); + } else { + // Subdirectory creation might fail in some test environments + $this->assertInstanceOf(\WP_Error::class, $result); + } + } + + /** + * Clean up after tests. + */ + public function tearDown(): void { + // Clean up template site + if ($this->template_site_id) { + wpmu_delete_blog($this->template_site_id, true); + } + + // Clean up test customer + if ($this->customer && ! is_wp_error($this->customer)) { + $this->customer->delete(); + } + + parent::tearDown(); + } +} diff --git a/tests/WP_Ultimo/Helpers/Validator_Test.php b/tests/WP_Ultimo/Helpers/Validator_Test.php new file mode 100644 index 000000000..04a3938f8 --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Validator_Test.php @@ -0,0 +1,302 @@ +validator = new Validator(); + } + + /** + * Test validator initialization. + */ + public function test_validator_initialization() { + $this->assertInstanceOf(Validator::class, $this->validator); + } + + /** + * Test basic required field validation. + */ + public function test_required_field_validation() { + $data = [ + 'name' => '', + ]; + + $rules = [ + 'name' => 'required', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertTrue($errors->has_errors()); + $this->assertContains('name', $errors->get_error_codes()); + } + + /** + * Test successful validation. + */ + public function test_successful_validation() { + $data = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + ]; + + $rules = [ + 'name' => 'required', + 'email' => 'required|email', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertFalse($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertFalse($errors->has_errors()); + } + + /** + * Test email validation. + */ + public function test_email_validation() { + $data = [ + 'email' => 'invalid-email', + ]; + + $rules = [ + 'email' => 'required|email', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertContains('email', $errors->get_error_codes()); + } + + /** + * Test min/max validation. + */ + public function test_min_max_validation() { + $data = [ + 'password' => '12', + 'age' => 150, + ]; + + $rules = [ + 'password' => 'required|min:6', + 'age' => 'required|integer|max:120', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertContains('password', $errors->get_error_codes()); + $this->assertContains('age', $errors->get_error_codes()); + } + + /** + * Test alpha_dash validation. + */ + public function test_alpha_dash_validation() { + $data = [ + 'username' => 'user@name!', + ]; + + $rules = [ + 'username' => 'required|alpha_dash', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertContains('username', $errors->get_error_codes()); + } + + /** + * Test successful alpha_dash validation. + */ + public function test_successful_alpha_dash_validation() { + $data = [ + 'username' => 'user_name-123', + ]; + + $rules = [ + 'username' => 'required|alpha_dash', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertFalse($result->fails()); + } + + /** + * Test integer validation. + */ + public function test_integer_validation() { + $data = [ + 'number' => 'not-a-number', + ]; + + $rules = [ + 'number' => 'required|integer', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + $this->assertContains('number', $errors->get_error_codes()); + } + + /** + * Test validation with aliases. + */ + public function test_validation_with_aliases() { + $data = [ + 'user_email' => '', + ]; + + $rules = [ + 'user_email' => 'required|email', + ]; + + $aliases = [ + 'user_email' => 'Email Address', + ]; + + $result = $this->validator->validate($data, $rules, $aliases); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $error_messages = $errors->get_error_messages(); + + // Should use the alias in error messages + $this->assertNotEmpty($error_messages); + } + + /** + * Test multiple errors for same field. + */ + public function test_multiple_errors_same_field() { + $data = [ + 'email' => 'a', + ]; + + $rules = [ + 'email' => 'required|email|min:5', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertTrue($result->fails()); + + $errors = $result->get_errors(); + $this->assertInstanceOf(\WP_Error::class, $errors); + + $error_messages = $errors->get_error_messages('email'); + $this->assertGreaterThan(1, count($error_messages)); + } + + /** + * Test get validation method. + */ + public function test_get_validation() { + $data = [ + 'name' => 'John', + ]; + + $rules = [ + 'name' => 'required', + ]; + + $this->validator->validate($data, $rules); + + $validation = $this->validator->get_validation(); + $this->assertNotNull($validation); + } + + /** + * Test complex validation scenario. + */ + public function test_complex_validation_scenario() { + $data = [ + 'username' => 'user123', + 'email' => 'user@example.com', + 'password' => 'password123', + 'age' => 25, + 'website' => '', + ]; + + $rules = [ + 'username' => 'required|alpha_dash|min:3', + 'email' => 'required|email', + 'password' => 'required|min:8', + 'age' => 'required|integer|min:18|max:100', + 'website' => 'url', // Optional field + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertFalse($result->fails()); + } + + /** + * Test successful required_without validation. + */ + public function test_successful_required_without_validation() { + $data = [ + 'email' => 'user@example.com', + 'phone' => '', + ]; + + $rules = [ + 'email' => 'required_without:phone|email', + 'phone' => 'required_without:email', + ]; + + $result = $this->validator->validate($data, $rules); + + $this->assertFalse($result->fails()); + } +} diff --git a/tests/WP_Ultimo/Limitations/Limit_Domain_Mapping_Test.php b/tests/WP_Ultimo/Limitations/Limit_Domain_Mapping_Test.php index c88ca4599..20d2caace 100644 --- a/tests/WP_Ultimo/Limitations/Limit_Domain_Mapping_Test.php +++ b/tests/WP_Ultimo/Limitations/Limit_Domain_Mapping_Test.php @@ -38,7 +38,7 @@ public static function tear_down_after_class() { parent::tear_down_after_class(); if (self::$test_site) { -// self::$test_site->delete(); + // self::$test_site->delete(); } } diff --git a/tests/WP_Ultimo/Managers/Domain_Manager_Test.php b/tests/WP_Ultimo/Managers/Domain_Manager_Test.php new file mode 100644 index 000000000..e7d524a1b --- /dev/null +++ b/tests/WP_Ultimo/Managers/Domain_Manager_Test.php @@ -0,0 +1,123 @@ +domain_manager = Domain_Manager::get_instance(); + } + + /** + * Test should_create_www_subdomain with 'always' setting. + */ + public function test_should_create_www_subdomain_always(): void { + // Mock the setting to 'always' + wu_save_setting('auto_create_www_subdomain', 'always'); + + // Test various domain types + $this->assertTrue($this->domain_manager->should_create_www_subdomain('example.com')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('subdomain.example.com')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('test.co.uk')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('deep.sub.example.com')); + } + + /** + * Test should_create_www_subdomain with 'never' setting. + */ + public function test_should_create_www_subdomain_never(): void { + // Mock the setting to 'never' + wu_save_setting('auto_create_www_subdomain', 'never'); + + // Test various domain types - all should return false + $this->assertFalse($this->domain_manager->should_create_www_subdomain('example.com')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('subdomain.example.com')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('test.co.uk')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('deep.sub.example.com')); + } + + /** + * Test should_create_www_subdomain with 'main_only' setting for main domains. + */ + public function test_should_create_www_subdomain_main_only_main_domains(): void { + // Mock the setting to 'main_only' + wu_save_setting('auto_create_www_subdomain', 'main_only'); + + // Test main domains - should return true + $this->assertTrue($this->domain_manager->should_create_www_subdomain('example.com')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('test.org')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('site.net')); + } + + /** + * Test should_create_www_subdomain with 'main_only' setting for known multi-part TLDs. + */ + public function test_should_create_www_subdomain_main_only_multi_part_tlds(): void { + // Mock the setting to 'main_only' + wu_save_setting('auto_create_www_subdomain', 'main_only'); + + // Test known multi-part TLD domains - should return true + $this->assertTrue($this->domain_manager->should_create_www_subdomain('example.co.uk')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('test.com.au')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('site.co.nz')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('company.com.br')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('business.co.in')); + } + + /** + * Test should_create_www_subdomain with 'main_only' setting for subdomains. + */ + public function test_should_create_www_subdomain_main_only_subdomains(): void { + // Mock the setting to 'main_only' + wu_save_setting('auto_create_www_subdomain', 'main_only'); + + // Test subdomains - should return false + $this->assertFalse($this->domain_manager->should_create_www_subdomain('subdomain.example.com')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('api.test.org')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('blog.site.net')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('deep.sub.example.com')); + } + + /** + * Test should_create_www_subdomain with 'main_only' setting for complex subdomains with multi-part TLDs. + */ + public function test_should_create_www_subdomain_main_only_complex_subdomains(): void { + // Mock the setting to 'main_only' + wu_save_setting('auto_create_www_subdomain', 'main_only'); + + // Test complex subdomains with multi-part TLDs - should return false + $this->assertFalse($this->domain_manager->should_create_www_subdomain('subdomain.example.co.uk')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('api.test.com.au')); + $this->assertFalse($this->domain_manager->should_create_www_subdomain('blog.site.co.nz')); + } + + /** + * Test should_create_www_subdomain with default setting (should default to 'always'). + */ + public function test_should_create_www_subdomain_default(): void { + // Remove any existing setting to test default behavior + wu_save_setting('auto_create_www_subdomain', null); + + // Should default to 'always' behavior + $this->assertTrue($this->domain_manager->should_create_www_subdomain('example.com')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('subdomain.example.com')); + } + + /** + * Test should_create_www_subdomain with invalid setting (should default to 'always'). + */ + public function test_should_create_www_subdomain_invalid_setting(): void { + // Set an invalid setting value + wu_save_setting('auto_create_www_subdomain', 'invalid_option'); + + // Should default to 'always' behavior + $this->assertTrue($this->domain_manager->should_create_www_subdomain('example.com')); + $this->assertTrue($this->domain_manager->should_create_www_subdomain('subdomain.example.com')); + } +} diff --git a/tests/WP_Ultimo/Managers/Gateway_Manager_Test.php b/tests/WP_Ultimo/Managers/Gateway_Manager_Test.php new file mode 100644 index 000000000..5abee65f0 --- /dev/null +++ b/tests/WP_Ultimo/Managers/Gateway_Manager_Test.php @@ -0,0 +1,179 @@ +manager = Gateway_Manager::get_instance(); + } + + /** + * Test manager initialization. + */ + public function test_manager_initialization() { + $this->assertInstanceOf(Gateway_Manager::class, $this->manager); + } + + /** + * Test default gateways registration. + */ + public function test_add_default_gateways() { + // Clear any existing gateways for clean test + $reflection = new \ReflectionClass($this->manager); + $property = $reflection->getProperty('registered_gateways'); + $property->setAccessible(true); + $property->setValue($this->manager, []); + + // Register default gateways + $this->manager->add_default_gateways(); + + $registered_gateways = $this->manager->get_registered_gateways(); + + $this->assertIsArray($registered_gateways); + $this->assertArrayHasKey('free', $registered_gateways); + $this->assertArrayHasKey('manual', $registered_gateways); + } + + /** + * Test gateway registration. + */ + public function test_register_gateway() { + $gateway_id = 'test-gateway'; + $gateway_class = Manual_Gateway::class; + + $result = $this->manager->register_gateway($gateway_id, 'test', 'test', $gateway_class); + + $this->assertTrue($result); + + $registered_gateways = $this->manager->get_registered_gateways(); + $this->assertArrayHasKey($gateway_id, $registered_gateways); + $this->assertNotEmpty($registered_gateways[ $gateway_id ]); + } + + /** + * Test duplicate gateway registration. + */ + public function test_register_duplicate_gateway() { + $gateway_id = 'duplicate-gateway'; + $gateway_class = Manual_Gateway::class; + + // Register once + $result1 = $this->manager->register_gateway($gateway_id, 'man', 'man', $gateway_class); + $this->assertTrue($result1); + + // Try to register again + $result2 = $this->manager->register_gateway($gateway_id, 'man', 'man', $gateway_class); + $this->assertFalse($result2); + } + + /** + * Test get registered gateways. + */ + public function test_get_registered_gateways() { + $gateways = $this->manager->get_registered_gateways(); + + $this->assertIsArray($gateways); + // Should contain at least the default gateways + $this->assertNotEmpty($gateways); + } + + /** + * Test get enabled gateways. + */ + public function test_get_enabled_gateways() { + $enabled_gateways = $this->manager->get_registered_gateways(); + + $this->assertIsArray($enabled_gateways); + } + + /** + * Test get gateway instance. + */ + public function test_get_gateway() { + // Register a test gateway first + $gateway_id = 'test-instance'; + $this->manager->register_gateway($gateway_id, 'man', 'man', Manual_Gateway::class); + + $gateway = $this->manager->get_gateway($gateway_id); + + $this->assertIsArray($gateway); + $this->assertNotEmpty($gateway); + } + + /** + * Test get nonexistent gateway. + */ + public function test_get_nonexistent_gateway() { + $gateway_instance = $this->manager->get_gateway('nonexistent'); + + $this->assertFalse($gateway_instance); + } + + /** + * Test is gateway registered. + */ + public function test_is_gateway_registered() { + $gateway_id = 'check-registered'; + $this->manager->register_gateway($gateway_id, 'Manual', '', Manual_Gateway::class); + + $this->assertTrue($this->manager->is_gateway_registered($gateway_id)); + $this->assertFalse($this->manager->is_gateway_registered('not-registered')); + } + + /** + * Test is gateway enabled. + */ + public function test_is_gateway_enabled() { + $result = $this->manager->is_gateway_registered('free'); + $this->assertIsBool($result); + } + + /** + * Test get auto renewable gateways. + */ + public function test_get_auto_renewable_gateways() { + $auto_renewable = $this->manager->get_auto_renewable_gateways(); + + $this->assertIsArray($auto_renewable); + } + + /** + * Test gateway error handling. + */ + public function test_gateway_error_handling() { + // Test with invalid gateway class + try { + $result = $this->manager->register_gateway('invalid', 'ex', 'tx', 'NonExistentClass'); + } catch (\Error $e) { + $this->assertTrue($e instanceof \Error); + } + } +} diff --git a/tests/WP_Ultimo/Managers/Membership_Manager_Test.php b/tests/WP_Ultimo/Managers/Membership_Manager_Test.php new file mode 100644 index 000000000..d54f8c823 --- /dev/null +++ b/tests/WP_Ultimo/Managers/Membership_Manager_Test.php @@ -0,0 +1,292 @@ +manager = Membership_Manager::get_instance(); + + // Create test customer + $customer = wu_create_customer( + [ + 'username' => 'testuser', + 'email_address' => 'test@example.com', + 'password' => 'password123', + ] + ); + + if (is_wp_error($customer)) { + $this->markTestSkipped('Could not create test customer: ' . $customer->get_error_message()); + } + + $this->customer = $customer; + + // Create test product + $product = wu_create_product( + [ + 'name' => 'Test Product', + 'slug' => 'test-product', + 'description' => 'A test product', + 'type' => 'plan', + 'amount' => 10, + 'duration' => 1, + 'duration_unit' => 'month', + ] + ); + + if (is_wp_error($product)) { + $this->markTestSkipped('Could not create test product: ' . $product->get_error_message()); + } + + $this->product = $product; + } + + /** + * Test manager initialization. + */ + public function test_manager_initialization() { + $this->assertInstanceOf(Membership_Manager::class, $this->manager); + + // Use reflection to access protected properties + $reflection = new \ReflectionClass($this->manager); + $slug_property = $reflection->getProperty('slug'); + $slug_property->setAccessible(true); + $this->assertEquals('membership', $slug_property->getValue($this->manager)); + + $model_class_property = $reflection->getProperty('model_class'); + $model_class_property->setAccessible(true); + $this->assertEquals(\WP_Ultimo\Models\Membership::class, $model_class_property->getValue($this->manager)); + } + + /** + * Test async publish pending site with valid membership. + */ + public function test_async_publish_pending_site_success() { + // Create membership with pending site + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + + // Test async publish with valid membership ID + $result = $this->manager->async_publish_pending_site($membership->get_id()); + + // Since we don't have a pending site in this test setup, + // we expect the method to handle gracefully + $this->assertNotInstanceOf(\WP_Error::class, $result); + } + + /** + * Test async publish pending site with invalid membership ID. + */ + public function test_async_publish_pending_site_invalid_id() { + $result = $this->manager->async_publish_pending_site(99999); + + $this->assertInstanceOf(\WP_Error::class, $result); + $this->assertEquals('error', $result->get_error_code()); + $this->assertEquals('An unexpected error happened.', $result->get_error_message()); + } + + /** + * Test mark cancelled date functionality. + */ + public function test_mark_cancelled_date() { + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + + // Test status transition to cancelled + $old_status = Membership_Status::ACTIVE; + $new_status = Membership_Status::CANCELLED; + + // Mock the method call that would be triggered by status transition + $this->manager->mark_cancelled_date($old_status, $new_status, $membership); + + // Refresh membership from database + $membership = wu_get_membership($membership->get_id()); + + // If status changed to cancelled, cancelled_at should be set + if ($new_status === Membership_Status::CANCELLED) { + $this->assertNotNull($membership->get_date_cancelled()); + } + } + + /** + * Test membership status transition. + */ + public function test_transition_membership_status() { + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::PENDING, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + + $old_status = Membership_Status::PENDING; + $new_status = Membership_Status::ACTIVE; + + // Test transition method doesn't throw errors + $this->manager->transition_membership_status($old_status, $new_status, $membership); + + // This test mainly ensures the method executes without errors + $this->assertTrue(true); + } + + /** + * Test async transfer membership. + */ + public function test_async_transfer_membership() { + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + // Create another customer to transfer to + $new_customer = wu_create_customer( + [ + 'username' => 'newuser', + 'email_address' => 'new@example.com', + 'password' => 'password123', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + $this->assertInstanceOf(Customer::class, $new_customer); + + // Test async transfer + $result = $this->manager->async_transfer_membership($membership->get_id(), $new_customer->get_id()); + + // Method should execute without throwing errors + $this->assertTrue(true); + } + + /** + * Test async delete membership. + */ + public function test_async_delete_membership() { + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + $membership_id = $membership->get_id(); + + // Test async delete + $this->manager->async_delete_membership($membership_id); + + // Check if membership was deleted + $deleted_membership = wu_get_membership($membership_id); + $this->assertNull($deleted_membership); + } + + /** + * Test async membership swap. + */ + public function test_async_membership_swap() { + $membership = wu_create_membership( + [ + 'customer_id' => $this->customer->get_id(), + 'product_id' => $this->product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'amount' => 10, + 'currency' => 'USD', + ] + ); + + $this->assertInstanceOf(Membership::class, $membership); + + // Test async swap - this mainly tests that method doesn't throw errors + $this->manager->async_membership_swap($membership->get_id()); + + $this->assertTrue(true); + } + + /** + * Clean up after tests. + */ + public function tearDown(): void { + // Clean up test data + if ($this->customer && ! is_wp_error($this->customer)) { + $this->customer->delete(); + } + if ($this->product && ! is_wp_error($this->product)) { + $this->product->delete(); + } + + parent::tearDown(); + } +} diff --git a/tests/WP_Ultimo/Managers/Payment_Manager_Test.php b/tests/WP_Ultimo/Managers/Payment_Manager_Test.php index f299918bd..d38b51e2f 100644 --- a/tests/WP_Ultimo/Managers/Payment_Manager_Test.php +++ b/tests/WP_Ultimo/Managers/Payment_Manager_Test.php @@ -16,7 +16,7 @@ class Payment_Manager_Test extends WP_UnitTestCase { public static function set_up_before_class() { parent::set_up_before_class(); - + // Create a simple payment object for testing // We'll use minimal setup to avoid complex dependencies self::$payment = new Payment(); @@ -27,10 +27,10 @@ public static function set_up_before_class() { self::$payment->set_total(100.00); self::$payment->set_status(Payment_Status::COMPLETED); self::$payment->set_gateway('manual'); - + // Save the payment and generate a hash $saved = self::$payment->save(); - if (!$saved) { + if (! $saved) { // If save fails, just set a fake hash for testing self::$payment->set_hash('test_payment_hash_' . uniqid()); } @@ -49,15 +49,15 @@ public function set_up() { public function test_invoice_viewer_with_valid_parameters(): void { // Use a test hash that won't be found in the database $payment_hash = 'test_payment_hash_12345'; - $nonce = wp_create_nonce('see_invoice'); + $nonce = wp_create_nonce('see_invoice'); // Mock the request parameters - $_REQUEST['action'] = 'invoice'; + $_REQUEST['action'] = 'invoice'; $_REQUEST['reference'] = $payment_hash; - $_REQUEST['key'] = $nonce; + $_REQUEST['key'] = $nonce; $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // The method should pass nonce validation but fail on payment lookup @@ -66,7 +66,7 @@ public function test_invoice_viewer_with_valid_parameters(): void { $this->expectExceptionMessage('This invoice does not exist.'); $method->invoke($this->payment_manager); - + // Clean up request parameters unset($_REQUEST['action'], $_REQUEST['reference'], $_REQUEST['key']); } @@ -75,16 +75,16 @@ public function test_invoice_viewer_with_valid_parameters(): void { * Test invoice_viewer method with invalid nonce. */ public function test_invoice_viewer_with_invalid_nonce(): void { - $payment_hash = self::$payment->get_hash(); + $payment_hash = self::$payment->get_hash(); $invalid_nonce = 'invalid_nonce'; // Mock the request parameters - $_REQUEST['action'] = 'invoice'; + $_REQUEST['action'] = 'invoice'; $_REQUEST['reference'] = $payment_hash; - $_REQUEST['key'] = $invalid_nonce; + $_REQUEST['key'] = $invalid_nonce; $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // Expect wp_die to be called with permission error @@ -102,15 +102,15 @@ public function test_invoice_viewer_with_invalid_nonce(): void { */ public function test_invoice_viewer_with_nonexistent_payment(): void { $invalid_hash = 'nonexistent_hash'; - $nonce = wp_create_nonce('see_invoice'); + $nonce = wp_create_nonce('see_invoice'); // Mock the request parameters - $_REQUEST['action'] = 'invoice'; + $_REQUEST['action'] = 'invoice'; $_REQUEST['reference'] = $invalid_hash; - $_REQUEST['key'] = $nonce; + $_REQUEST['key'] = $nonce; $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // Expect wp_die to be called with invoice not found error @@ -129,10 +129,10 @@ public function test_invoice_viewer_with_nonexistent_payment(): void { public function test_invoice_viewer_with_missing_action(): void { // Don't set action parameter $_REQUEST['reference'] = self::$payment->get_hash(); - $_REQUEST['key'] = wp_create_nonce('see_invoice'); + $_REQUEST['key'] = wp_create_nonce('see_invoice'); $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // Method should return early without doing anything @@ -152,10 +152,10 @@ public function test_invoice_viewer_with_missing_action(): void { public function test_invoice_viewer_with_missing_reference(): void { // Set action but not reference $_REQUEST['action'] = 'invoice'; - $_REQUEST['key'] = wp_create_nonce('see_invoice'); + $_REQUEST['key'] = wp_create_nonce('see_invoice'); $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // Method should return early without doing anything @@ -174,11 +174,11 @@ public function test_invoice_viewer_with_missing_reference(): void { */ public function test_invoice_viewer_with_missing_key(): void { // Set action and reference but not key - $_REQUEST['action'] = 'invoice'; + $_REQUEST['action'] = 'invoice'; $_REQUEST['reference'] = self::$payment->get_hash(); $reflection = new \ReflectionClass($this->payment_manager); - $method = $reflection->getMethod('invoice_viewer'); + $method = $reflection->getMethod('invoice_viewer'); $method->setAccessible(true); // Method should return early without doing anything @@ -194,7 +194,7 @@ public function test_invoice_viewer_with_missing_key(): void { public static function tear_down_after_class() { global $wpdb; - + // Clean up test data if (self::$payment) { self::$payment->delete(); @@ -202,11 +202,11 @@ public static function tear_down_after_class() { if (self::$customer) { self::$customer->delete(); } - + // Clean up database tables $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}wu_payments"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}wu_customers"); - + parent::tear_down_after_class(); } -} \ No newline at end of file +} diff --git a/tests/WP_Ultimo/Models/Broadcast_Test.php b/tests/WP_Ultimo/Models/Broadcast_Test.php index b634071d3..d01f7e7df 100644 --- a/tests/WP_Ultimo/Models/Broadcast_Test.php +++ b/tests/WP_Ultimo/Models/Broadcast_Test.php @@ -4,6 +4,12 @@ use WP_UnitTestCase; +/** + * Test class for Broadcast model functionality. + * + * Tests broadcast creation, type validation, status handling, + * notice types, message targets, and migration functionality. + */ class Broadcast_Test extends WP_UnitTestCase { /** diff --git a/tests/WP_Ultimo/Models/Checkout_Form_Test.php b/tests/WP_Ultimo/Models/Checkout_Form_Test.php index 75d079b52..4e81ee398 100644 --- a/tests/WP_Ultimo/Models/Checkout_Form_Test.php +++ b/tests/WP_Ultimo/Models/Checkout_Form_Test.php @@ -4,6 +4,12 @@ use WP_UnitTestCase; +/** + * Test class for Checkout Form model functionality. + * + * Tests checkout form creation, settings management, field handling, + * template processing, country restrictions, and validation rules. + */ class Checkout_Form_Test extends WP_UnitTestCase { /** diff --git a/tests/WP_Ultimo/Models/Customer_Test.php b/tests/WP_Ultimo/Models/Customer_Test.php index 8a1032c83..0a355019c 100644 --- a/tests/WP_Ultimo/Models/Customer_Test.php +++ b/tests/WP_Ultimo/Models/Customer_Test.php @@ -6,6 +6,12 @@ use WP_UnitTestCase; use WP_User; +/** + * Test class for Customer model functionality. + * + * Tests customer creation, validation, key generation, and + * other customer-related operations. + */ class Customer_Test extends WP_UnitTestCase { /** diff --git a/tests/WP_Ultimo/Models/Discount_Code_Test.php b/tests/WP_Ultimo/Models/Discount_Code_Test.php index 0df766991..201ef3feb 100644 --- a/tests/WP_Ultimo/Models/Discount_Code_Test.php +++ b/tests/WP_Ultimo/Models/Discount_Code_Test.php @@ -5,6 +5,12 @@ use WP_Error; use WP_UnitTestCase; +/** + * Test class for Discount Code model functionality. + * + * Tests discount code validation, expiration dates, usage limits, + * product restrictions, and error handling. + */ class Discount_Code_Test extends WP_UnitTestCase { /** diff --git a/tests/WP_Ultimo/Models/Domain_Test.php b/tests/WP_Ultimo/Models/Domain_Test.php index 730a1c3b2..e2895f984 100644 --- a/tests/WP_Ultimo/Models/Domain_Test.php +++ b/tests/WP_Ultimo/Models/Domain_Test.php @@ -4,6 +4,12 @@ use WP_UnitTestCase; +/** + * Test class for Domain model functionality. + * + * Tests SSL certificate validation for custom domains including + * valid certificates, invalid certificates, and empty domain handling. + */ class Domain_Test extends WP_UnitTestCase { /** diff --git a/tests/WP_Ultimo/Models/Payment_Test.php b/tests/WP_Ultimo/Models/Payment_Test.php index 8c434b87a..9a401c44e 100644 --- a/tests/WP_Ultimo/Models/Payment_Test.php +++ b/tests/WP_Ultimo/Models/Payment_Test.php @@ -7,6 +7,12 @@ use WP_Ultimo\Database\Payments\Payment_Status; use WP_UnitTestCase; +/** + * Test class for Payment model functionality. + * + * Tests payment creation, line items management, financial calculations, + * status handling, gateway functionality, and invoice features. + */ class Payment_Test extends WP_UnitTestCase { private static Customer $customer; diff --git a/tests/WP_Ultimo/Objects/Limitations_Test.php b/tests/WP_Ultimo/Objects/Limitations_Test.php index aee1815eb..c5d3e264b 100644 --- a/tests/WP_Ultimo/Objects/Limitations_Test.php +++ b/tests/WP_Ultimo/Objects/Limitations_Test.php @@ -13,7 +13,7 @@ class Limitations_Test extends WP_UnitTestCase { public function setUp(): void { parent::setUp(); // Clear the static cache using reflection - $reflection = new \ReflectionClass(Limitations::class); + $reflection = new \ReflectionClass(Limitations::class); $cache_property = $reflection->getProperty('limitations_cache'); $cache_property->setAccessible(true); $cache_property->setValue(null, []); @@ -26,39 +26,39 @@ public function setUp(): void { */ public function constructorDataProvider(): array { return [ - 'empty_modules' => [ - 'modules_data' => [], + 'empty_modules' => [ + 'modules_data' => [], 'expected_modules_count' => 0, ], - 'single_module' => [ - 'modules_data' => [ + 'single_module' => [ + 'modules_data' => [ 'plugins' => [ - 'enabled' => true, - 'behavior' => 'default', + 'enabled' => true, + 'behavior' => 'default', 'plugins_list' => [], ], ], 'expected_modules_count' => 1, ], - 'multiple_modules' => [ - 'modules_data' => [ + 'multiple_modules' => [ + 'modules_data' => [ 'plugins' => [ - 'enabled' => true, + 'enabled' => true, 'behavior' => 'default', ], - 'themes' => [ - 'enabled' => false, + 'themes' => [ + 'enabled' => false, 'behavior' => 'not_available', ], - 'users' => [ + 'users' => [ 'enabled' => true, - 'limit' => 10, + 'limit' => 10, ], ], 'expected_modules_count' => 3, ], 'modules_with_json_string' => [ - 'modules_data' => [ + 'modules_data' => [ 'disk_space' => '{"enabled":true,"limit":1024}', ], 'expected_modules_count' => 1, @@ -77,7 +77,7 @@ public function test_constructor(array $modules_data, int $expected_modules_coun $this->assertInstanceOf(Limitations::class, $limitations); // Use reflection to access protected modules property - $reflection = new \ReflectionClass($limitations); + $reflection = new \ReflectionClass($limitations); $modules_property = $reflection->getProperty('raw_module_data'); $modules_property->setAccessible(true); $modules = $modules_property->getValue($limitations); @@ -92,20 +92,20 @@ public function test_constructor(array $modules_data, int $expected_modules_coun */ public function magicGetterDataProvider(): array { return [ - 'existing_module' => [ - 'module_name' => 'plugins', + 'existing_module' => [ + 'module_name' => 'plugins', 'should_exist' => true, ], 'non_existing_module' => [ - 'module_name' => 'non_existent_module', + 'module_name' => 'non_existent_module', 'should_exist' => false, ], - 'themes_module' => [ - 'module_name' => 'themes', + 'themes_module' => [ + 'module_name' => 'themes', 'should_exist' => true, ], - 'users_module' => [ - 'module_name' => 'users', + 'users_module' => [ + 'module_name' => 'users', 'should_exist' => true, ], ]; @@ -118,7 +118,7 @@ public function magicGetterDataProvider(): array { */ public function test_magic_getter(string $module_name, bool $should_exist): void { $limitations = new Limitations(); - $result = $limitations->{$module_name}; + $result = $limitations->{$module_name}; if ($should_exist) { $this->assertNotFalse($result); @@ -134,17 +134,17 @@ public function test_magic_getter(string $module_name, bool $should_exist): void public function test_serialization_methods(): void { $modules_data = [ 'plugins' => [ - 'enabled' => true, + 'enabled' => true, 'behavior' => 'default', ], - 'users' => [ + 'users' => [ 'enabled' => true, - 'limit' => 5, + 'limit' => 5, ], ]; $limitations = new Limitations($modules_data); - + // Test __serialize $serialized = $limitations->__serialize(); $this->assertIsArray($serialized); @@ -165,20 +165,20 @@ public function test_serialization_methods(): void { */ public function buildModulesDataProvider(): array { return [ - 'valid_modules' => [ - 'modules_data' => [ + 'valid_modules' => [ + 'modules_data' => [ 'plugins' => ['enabled' => true], - 'themes' => ['enabled' => false], + 'themes' => ['enabled' => false], ], 'expected_count' => 2, ], - 'empty_modules' => [ - 'modules_data' => [], + 'empty_modules' => [ + 'modules_data' => [], 'expected_count' => 0, ], 'mixed_valid_invalid' => [ - 'modules_data' => [ - 'plugins' => ['enabled' => true], + 'modules_data' => [ + 'plugins' => ['enabled' => true], 'invalid_module' => ['enabled' => true], ], 'expected_count' => 1, @@ -193,12 +193,12 @@ public function buildModulesDataProvider(): array { */ public function test_build_modules(array $modules_data, int $expected_count): void { $limitations = new Limitations(); - $result = $limitations->build_modules($modules_data); + $result = $limitations->build_modules($modules_data); $this->assertInstanceOf(Limitations::class, $result); // Use reflection to access protected modules property - $reflection = new \ReflectionClass($limitations); + $reflection = new \ReflectionClass($limitations); $modules_property = $reflection->getProperty('raw_module_data'); $modules_property->setAccessible(true); $modules = $modules_property->getValue($limitations); @@ -214,23 +214,29 @@ public function test_build_modules(array $modules_data, int $expected_count): vo public function buildMethodDataProvider(): array { return [ 'valid_module_array' => [ - 'data' => ['enabled' => true, 'limit' => 10], - 'module_name' => 'users', + 'data' => [ + 'enabled' => true, + 'limit' => 10, + ], + 'module_name' => 'users', 'should_succeed' => true, ], - 'valid_module_json' => [ - 'data' => '{"enabled":true,"limit":5}', - 'module_name' => 'users', + 'valid_module_json' => [ + 'data' => '{"enabled":true,"limit":5}', + 'module_name' => 'users', 'should_succeed' => true, ], - 'invalid_module' => [ - 'data' => ['enabled' => true], - 'module_name' => 'non_existent_module', + 'invalid_module' => [ + 'data' => ['enabled' => true], + 'module_name' => 'non_existent_module', 'should_succeed' => false, ], - 'plugins_module' => [ - 'data' => ['enabled' => true, 'behavior' => 'default'], - 'module_name' => 'plugins', + 'plugins_module' => [ + 'data' => [ + 'enabled' => true, + 'behavior' => 'default', + ], + 'module_name' => 'plugins', 'should_succeed' => true, ], ]; @@ -259,19 +265,19 @@ public function test_build_method($data, string $module_name, bool $should_succe */ public function existsMethodDataProvider(): array { return [ - 'existing_module' => [ + 'existing_module' => [ 'modules_data' => ['plugins' => ['enabled' => true]], - 'module_name' => 'plugins', + 'module_name' => 'plugins', 'should_exist' => true, ], 'non_existing_module' => [ 'modules_data' => ['plugins' => ['enabled' => true]], - 'module_name' => 'themes', + 'module_name' => 'themes', 'should_exist' => false, ], - 'empty_limitations' => [ + 'empty_limitations' => [ 'modules_data' => [], - 'module_name' => 'plugins', + 'module_name' => 'plugins', 'should_exist' => false, ], ]; @@ -301,22 +307,31 @@ public function test_exists_method(array $modules_data, string $module_name, boo */ public function hasLimitationsDataProvider(): array { return [ - 'no_limitations' => [ + 'no_limitations' => [ 'modules_data' => [], - 'expected' => false, + 'expected' => false, ], 'enabled_limitations' => [ 'modules_data' => [ - 'users' => ['enabled' => true, 'limit' => 5], + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], ], - 'expected' => true, + 'expected' => true, ], - 'multiple_enabled' => [ + 'multiple_enabled' => [ 'modules_data' => [ - 'users' => ['enabled' => true, 'limit' => 5], - 'disk_space' => ['enabled' => true, 'limit' => 1024], + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + 'disk_space' => [ + 'enabled' => true, + 'limit' => 1024, + ], ], - 'expected' => true, + 'expected' => true, ], ]; } @@ -328,7 +343,7 @@ public function hasLimitationsDataProvider(): array { */ public function test_has_limitations(array $modules_data, bool $expected): void { $limitations = new Limitations($modules_data); - $result = $limitations->has_limitations(); + $result = $limitations->has_limitations(); $this->assertEquals($expected, $result); } @@ -340,15 +355,25 @@ public function test_has_limitations(array $modules_data, bool $expected): void */ public function isModuleEnabledDataProvider(): array { return [ - 'enabled_module' => [ - 'modules_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'module_name' => 'users', - 'expected' => true, + 'enabled_module' => [ + 'modules_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'module_name' => 'users', + 'expected' => true, ], 'non_existing_module' => [ - 'modules_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'module_name' => 'non_existent', - 'expected' => false, + 'modules_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'module_name' => 'non_existent', + 'expected' => false, ], ]; } @@ -360,7 +385,7 @@ public function isModuleEnabledDataProvider(): array { */ public function test_is_module_enabled(array $modules_data, string $module_name, bool $expected): void { $limitations = new Limitations($modules_data); - $result = $limitations->is_module_enabled($module_name); + $result = $limitations->is_module_enabled($module_name); $this->assertEquals($expected, $result); } @@ -372,37 +397,100 @@ public function test_is_module_enabled(array $modules_data, string $module_name, */ public function mergeMethodDataProvider(): array { return [ - 'simple_merge_addition' => [ - 'base_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'merge_data' => [['users' => ['enabled' => true, 'limit' => 3]]], - 'override' => false, + 'simple_merge_addition' => [ + 'base_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'merge_data' => [ + [ + 'users' => [ + 'enabled' => true, + 'limit' => 3, + ], + ], + ], + 'override' => false, 'expected_limit' => 8, ], - 'simple_merge_override' => [ - 'base_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'merge_data' => [['users' => ['enabled' => true, 'limit' => 3]]], - 'override' => true, + 'simple_merge_override' => [ + 'base_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'merge_data' => [ + [ + 'users' => [ + 'enabled' => true, + 'limit' => 3, + ], + ], + ], + 'override' => true, 'expected_limit' => 3, ], - 'merge_with_disabled' => [ - 'base_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'merge_data' => [['users' => ['enabled' => false, 'limit' => 3]]], - 'override' => false, + 'merge_with_disabled' => [ + 'base_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'merge_data' => [ + [ + 'users' => [ + 'enabled' => false, + 'limit' => 3, + ], + ], + ], + 'override' => false, 'expected_enabled' => true, ], - 'merge_unlimited_value' => [ - 'base_data' => ['users' => ['enabled' => true, 'limit' => 0]], - 'merge_data' => [['users' => ['enabled' => true, 'limit' => 5]]], - 'override' => false, + 'merge_unlimited_value' => [ + 'base_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 0, + ], + ], + 'merge_data' => [ + [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + ], + 'override' => false, 'expected_limit' => 0, ], 'merge_multiple_limitations' => [ - 'base_data' => ['users' => ['enabled' => true, 'limit' => 5]], - 'merge_data' => [ - ['users' => ['enabled' => true, 'limit' => 3]], - ['users' => ['enabled' => true, 'limit' => 2]], + 'base_data' => [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ], + 'merge_data' => [ + [ + 'users' => [ + 'enabled' => true, + 'limit' => 3, + ], + ], + [ + 'users' => [ + 'enabled' => true, + 'limit' => 2, + ], + ], ], - 'override' => false, + 'override' => false, 'expected_limit' => 10, ], ]; @@ -415,13 +503,13 @@ public function mergeMethodDataProvider(): array { */ public function test_merge_method(array $base_data, array $merge_data, bool $override, $expected_value): void { $limitations = new Limitations($base_data); - + $result = $limitations->merge($override, ...$merge_data); - + $this->assertInstanceOf(Limitations::class, $result); - + $result_array = $result->to_array(); - + if (is_int($expected_value)) { $this->assertEquals($expected_value, $result_array['users']['limit']); } @@ -431,13 +519,27 @@ public function test_merge_method(array $base_data, array $merge_data, bool $ove * Test merge with Limitations objects. */ public function test_merge_with_limitations_objects(): void { - $base_limitations = new Limitations(['users' => ['enabled' => true, 'limit' => 5]]); - $merge_limitations = new Limitations(['users' => ['enabled' => true, 'limit' => 3]]); - + $base_limitations = new Limitations( + [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ] + ); + $merge_limitations = new Limitations( + [ + 'users' => [ + 'enabled' => true, + 'limit' => 3, + ], + ] + ); + $result = $base_limitations->merge(false, $merge_limitations); - + $this->assertInstanceOf(Limitations::class, $result); - + $result_array = $result->to_array(); $this->assertEquals(8, $result_array['users']['limit']); } @@ -446,12 +548,19 @@ public function test_merge_with_limitations_objects(): void { * Test merge with invalid data. */ public function test_merge_with_invalid_data(): void { - $limitations = new Limitations(['users' => ['enabled' => true, 'limit' => 5]]); - + $limitations = new Limitations( + [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ] + ); + $result = $limitations->merge(false, 'invalid_string', null, 123); - + $this->assertInstanceOf(Limitations::class, $result); - + $result_array = $result->to_array(); $this->assertEquals(5, $result_array['users']['limit']); } @@ -461,13 +570,19 @@ public function test_merge_with_invalid_data(): void { */ public function test_to_array_method(): void { $modules_data = [ - 'plugins' => ['enabled' => true, 'behavior' => 'default'], - 'users' => ['enabled' => true, 'limit' => 10], + 'plugins' => [ + 'enabled' => true, + 'behavior' => 'default', + ], + 'users' => [ + 'enabled' => true, + 'limit' => 10, + ], ]; - + $limitations = new Limitations($modules_data); - $result = $limitations->to_array(); - + $result = $limitations->to_array(); + $this->assertIsArray($result); $this->assertArrayHasKey('plugins', $result); $this->assertArrayHasKey('users', $result); @@ -478,23 +593,30 @@ public function test_to_array_method(): void { */ public function test_early_get_limitations_method(): void { global $wpdb; - + // Mock wpdb - $wpdb = $this->createMock(\wpdb::class); + $wpdb = $this->createMock(\wpdb::class); $wpdb->base_prefix = 'wp_'; - - $serialized_data = serialize(['users' => ['enabled' => true, 'limit' => 5]]); - + + $serialized_data = serialize( + [ + 'users' => [ + 'enabled' => true, + 'limit' => 5, + ], + ] + ); + $wpdb->expects($this->once()) ->method('prepare') ->willReturn("SELECT meta_value FROM wp_wu_customermeta WHERE meta_key = 'wu_limitations' AND wu_customer_id = 123 LIMIT 1"); - + $wpdb->expects($this->once()) ->method('get_var') ->willReturn($serialized_data); - + $result = Limitations::early_get_limitations('customer', 123); - + $this->assertIsArray($result); $this->assertArrayHasKey('users', $result); $this->assertTrue($result['users']['enabled']); @@ -506,11 +628,11 @@ public function test_early_get_limitations_method(): void { */ public function test_early_get_limitations_with_site_slug(): void { global $wpdb; - + // Mock wpdb - $wpdb = $this->createMock(\wpdb::class); + $wpdb = $this->createMock(\wpdb::class); $wpdb->base_prefix = 'wp_'; - + $wpdb->expects($this->once()) ->method('prepare') ->with( @@ -518,13 +640,13 @@ public function test_early_get_limitations_with_site_slug(): void { 123 ) ->willReturn("SELECT meta_value FROM wp_blogmeta WHERE meta_key = 'wu_limitations' AND blog_id = 123 LIMIT 1"); - + $wpdb->expects($this->once()) ->method('get_var') ->willReturn(''); - + $result = Limitations::early_get_limitations('site', 123); - + $this->assertIsArray($result); } @@ -533,20 +655,20 @@ public function test_early_get_limitations_with_site_slug(): void { */ public function test_remove_limitations_method(): void { global $wpdb; - + // Mock wpdb - $wpdb = $this->createMock(\wpdb::class); + $wpdb = $this->createMock(\wpdb::class); $wpdb->base_prefix = 'wp_'; - + $wpdb->expects($this->once()) ->method('prepare') ->willReturn("DELETE FROM wp_wu_customermeta WHERE meta_key = 'wu_limitations' AND wu_customer_id = 123 LIMIT 1"); - + $wpdb->expects($this->once()) ->method('get_var'); - + Limitations::remove_limitations('customer', 123); - + // If we reach here without exception, the test passes $this->assertTrue(true); } @@ -556,12 +678,12 @@ public function test_remove_limitations_method(): void { */ public function test_get_empty_method(): void { $result = Limitations::get_empty(); - + $this->assertInstanceOf(Limitations::class, $result); - + // Verify it has access to all repository modules $repository = Limitations::repository(); - + foreach (array_keys($repository) as $module_name) { $module = $result->{$module_name}; $this->assertNotFalse($module, "Module {$module_name} should be accessible"); @@ -573,10 +695,10 @@ public function test_get_empty_method(): void { */ public function test_repository_method(): void { $repository = Limitations::repository(); - + $this->assertIsArray($repository); $this->assertNotEmpty($repository); - + // Check for expected standard modules $expected_modules = [ 'post_types', @@ -590,10 +712,10 @@ public function test_repository_method(): void { 'domain_mapping', 'customer_user_role', ]; - + foreach ($expected_modules as $module) { $this->assertArrayHasKey($module, $repository); - $this->assertIsString($repository[$module]); + $this->assertIsString($repository[ $module ]); } } @@ -602,30 +724,30 @@ public function test_repository_method(): void { */ public function test_merge_recursive_method(): void { $limitations = new Limitations(); - + // Use reflection to access protected method $reflection = new \ReflectionClass($limitations); - $method = $reflection->getMethod('merge_recursive'); + $method = $reflection->getMethod('merge_recursive'); $method->setAccessible(true); - + $array1 = [ 'enabled' => true, - 'limit' => 5, - 'nested' => [ + 'limit' => 5, + 'nested' => [ 'value' => 10, ], ]; - + $array2 = [ 'enabled' => true, - 'limit' => 3, - 'nested' => [ + 'limit' => 3, + 'nested' => [ 'value' => 5, ], ]; - + $method->invokeArgs($limitations, [&$array1, &$array2, true]); - + $this->assertEquals(8, $array1['limit']); $this->assertEquals(15, $array1['nested']['value']); } @@ -635,21 +757,21 @@ public function test_merge_recursive_method(): void { */ public function test_merge_recursive_force_enabled(): void { $limitations = new Limitations(); - + // Set current_merge_id to test force enabled logic $reflection = new \ReflectionClass($limitations); - $property = $reflection->getProperty('current_merge_id'); + $property = $reflection->getProperty('current_merge_id'); $property->setAccessible(true); $property->setValue($limitations, 'plugins'); - + $method = $reflection->getMethod('merge_recursive'); $method->setAccessible(true); - + $array1 = ['enabled' => false]; $array2 = ['enabled' => false]; - + $method->invokeArgs($limitations, [&$array1, &$array2, true]); - + $this->assertTrue($array1['enabled']); } @@ -658,16 +780,22 @@ public function test_merge_recursive_force_enabled(): void { */ public function test_merge_recursive_visibility_priority(): void { $limitations = new Limitations(); - + $reflection = new \ReflectionClass($limitations); - $method = $reflection->getMethod('merge_recursive'); + $method = $reflection->getMethod('merge_recursive'); $method->setAccessible(true); - - $array1 = ['enabled' => true, 'visibility' => 'hidden']; - $array2 = ['enabled' => true, 'visibility' => 'visible']; - + + $array1 = [ + 'enabled' => true, + 'visibility' => 'hidden', + ]; + $array2 = [ + 'enabled' => true, + 'visibility' => 'visible', + ]; + $method->invokeArgs($limitations, [&$array1, &$array2, true]); - + $this->assertEquals('visible', $array1['visibility']); } @@ -676,21 +804,27 @@ public function test_merge_recursive_visibility_priority(): void { */ public function test_merge_recursive_behavior_priority(): void { $limitations = new Limitations(); - + // Set current_merge_id to plugins for behavior testing $reflection = new \ReflectionClass($limitations); - $property = $reflection->getProperty('current_merge_id'); + $property = $reflection->getProperty('current_merge_id'); $property->setAccessible(true); $property->setValue($limitations, 'plugins'); - + $method = $reflection->getMethod('merge_recursive'); $method->setAccessible(true); - - $array1 = ['enabled' => true, 'behavior' => 'default']; - $array2 = ['enabled' => true, 'behavior' => 'force_active']; - + + $array1 = [ + 'enabled' => true, + 'behavior' => 'default', + ]; + $array2 = [ + 'enabled' => true, + 'behavior' => 'force_active', + ]; + $method->invokeArgs($limitations, [&$array1, &$array2, true]); - + $this->assertEquals('force_active', $array1['behavior']); } @@ -699,27 +833,27 @@ public function test_merge_recursive_behavior_priority(): void { */ public function test_early_get_limitations_caching(): void { global $wpdb; - + // Mock wpdb - $wpdb = $this->createMock(\wpdb::class); + $wpdb = $this->createMock(\wpdb::class); $wpdb->base_prefix = 'wp_'; - + $serialized_data = serialize(['users' => ['enabled' => true]]); - + $wpdb->expects($this->once()) // Should only be called once due to caching ->method('prepare') ->willReturn("SELECT meta_value FROM wp_wu_customermeta WHERE meta_key = 'wu_limitations' AND wu_customer_id = 123 LIMIT 1"); - + $wpdb->expects($this->once()) // Should only be called once due to caching ->method('get_var') ->willReturn($serialized_data); - + // First call $result1 = Limitations::early_get_limitations('customer', 123); - + // Second call should use cache $result2 = Limitations::early_get_limitations('customer', 123); - + $this->assertEquals($result1, $result2); } -} \ No newline at end of file +} diff --git a/views/admin-pages/fields/field-actions.php b/views/admin-pages/fields/field-actions.php index ba4b40d84..40641f172 100644 --- a/views/admin-pages/fields/field-actions.php +++ b/views/admin-pages/fields/field-actions.php @@ -7,7 +7,7 @@ /** * @package MyPlugin */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-code-editor.php b/views/admin-pages/fields/field-code-editor.php index bb9e09e13..b9fa47d08 100644 --- a/views/admin-pages/fields/field-code-editor.php +++ b/views/admin-pages/fields/field-code-editor.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-color-picker.php b/views/admin-pages/fields/field-color-picker.php index 4336d12dc..f93e80e1c 100644 --- a/views/admin-pages/fields/field-color-picker.php +++ b/views/admin-pages/fields/field-color-picker.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-dashicon.php b/views/admin-pages/fields/field-dashicon.php index 55ec07acb..69404e864 100644 --- a/views/admin-pages/fields/field-dashicon.php +++ b/views/admin-pages/fields/field-dashicon.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-header.php b/views/admin-pages/fields/field-header.php index 4a2bbf1cf..df0302e6b 100644 --- a/views/admin-pages/fields/field-header.php +++ b/views/admin-pages/fields/field-header.php @@ -5,6 +5,7 @@ * @since 2.0.0 */ defined('ABSPATH') || exit; + /** @var $field \WP_Ultimo\UI\Field */ ?> diff --git a/views/admin-pages/fields/field-hidden.php b/views/admin-pages/fields/field-hidden.php index 14d1795de..2dffe8d6b 100644 --- a/views/admin-pages/fields/field-hidden.php +++ b/views/admin-pages/fields/field-hidden.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** @var \WP_Ultimo\UI\Field $field */ ?> diff --git a/views/admin-pages/fields/field-image.php b/views/admin-pages/fields/field-image.php index 22b2e95f0..24fcb33fb 100644 --- a/views/admin-pages/fields/field-image.php +++ b/views/admin-pages/fields/field-image.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; /** * Set the media query. diff --git a/views/admin-pages/fields/field-link.php b/views/admin-pages/fields/field-link.php index df5e5e783..6c14fda22 100644 --- a/views/admin-pages/fields/field-link.php +++ b/views/admin-pages/fields/field-link.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-multiselect.php b/views/admin-pages/fields/field-multiselect.php index beb727b6b..94377d8da 100644 --- a/views/admin-pages/fields/field-multiselect.php +++ b/views/admin-pages/fields/field-multiselect.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • title ) : ?> diff --git a/views/admin-pages/fields/field-select-icon.php b/views/admin-pages/fields/field-select-icon.php index f64a2ec80..c2723aa98 100644 --- a/views/admin-pages/fields/field-select-icon.php +++ b/views/admin-pages/fields/field-select-icon.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-select.php b/views/admin-pages/fields/field-select.php index 2bb41d341..c1248ee6e 100644 --- a/views/admin-pages/fields/field-select.php +++ b/views/admin-pages/fields/field-select.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-submit.php b/views/admin-pages/fields/field-submit.php index 606626d08..bd4e27109 100644 --- a/views/admin-pages/fields/field-submit.php +++ b/views/admin-pages/fields/field-submit.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-tab-select.php b/views/admin-pages/fields/field-tab-select.php index 1ebde8c76..2e40badcb 100644 --- a/views/admin-pages/fields/field-tab-select.php +++ b/views/admin-pages/fields/field-tab-select.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-text.php b/views/admin-pages/fields/field-text.php index f4d45fb3d..30e6d38f7 100644 --- a/views/admin-pages/fields/field-text.php +++ b/views/admin-pages/fields/field-text.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-textarea.php b/views/admin-pages/fields/field-textarea.php index 4bcf957b5..7f2a543a1 100644 --- a/views/admin-pages/fields/field-textarea.php +++ b/views/admin-pages/fields/field-textarea.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/field-wp-editor.php b/views/admin-pages/fields/field-wp-editor.php index c500ae11e..145ad0dc1 100644 --- a/views/admin-pages/fields/field-wp-editor.php +++ b/views/admin-pages/fields/field-wp-editor.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
  • print_wrapper_html_attributes(); ?>> diff --git a/views/admin-pages/fields/partials/field-description.php b/views/admin-pages/fields/partials/field-description.php index a7a692994..5da5162d5 100644 --- a/views/admin-pages/fields/partials/field-description.php +++ b/views/admin-pages/fields/partials/field-description.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?> diff --git a/views/admin-pages/fields/partials/field-title.php b/views/admin-pages/fields/partials/field-title.php index a92ceea8f..49cc1ad72 100644 --- a/views/admin-pages/fields/partials/field-title.php +++ b/views/admin-pages/fields/partials/field-title.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?> diff --git a/views/base/centered.php b/views/base/centered.php index 2af6adb18..bfeb9fe75 100644 --- a/views/base/centered.php +++ b/views/base/centered.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>
    diff --git a/views/base/checkout-forms/js-templates.php b/views/base/checkout-forms/js-templates.php index 0cd65531e..143abf0b8 100644 --- a/views/base/checkout-forms/js-templates.php +++ b/views/base/checkout-forms/js-templates.php @@ -4,7 +4,7 @@ * * @since 2.0.0 */ -defined( 'ABSPATH' ) || exit; +defined('ABSPATH') || exit; ?>