From 31ad0463f7e604850b1d8ef5986ee44bd14d1cff Mon Sep 17 00:00:00 2001 From: peptiderepo <273788462+peptiderepo@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:25:11 +0700 Subject: [PATCH] Adopt reusable CI standard + PHPUnit suite (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ci): adopt estate CI + add PHPUnit suite from scratch (v1.2.0) - Add PHPUnit 9.6 test suite (WP stubs, tests: stubs) — 40 tests across 4 files: MathTest: 25 tests covering all PRC_Math public methods and calculate() orchestrator with real peptide scenarios (BPC-157/TB-500/Semaglutide/MK-677/ Ipamorelin) plus edge cases (zero water, negative inputs, floor rounding, rounding precision, mcg↔mg conversion). DefaultPresetsTest: 11 tests — all 8 expected slugs, required keys per preset (dataProvider), dose_unit validity, minquery() intentional in uninstall (no caching available); add phpcs:ignore DirectDatabaseQuery.DirectQuery and NoCaching. class-prc-rest-controller.php get_presets(): request param required by WP REST API callback contract; add phpcs:ignore UnusedFunctionParameter. Agent-Session: claude/adopt-ci-20260616 --------- Co-authored-by: peptiderepo Co-authored-by: peptiderepo --- .github/workflows/ci.yml | 82 +--- CHANGELOG.md | 26 ++ composer.json | 5 +- includes/api/class-prc-rest-controller.php | 53 +-- includes/class-prc-activator.php | 2 + includes/class-prc-autoloader.php | 3 +- includes/class-prc-cache-listener.php | 3 +- includes/class-prc-calculator.php | 9 +- includes/class-prc-deactivator.php | 2 + includes/class-prc-default-presets.php | 53 +-- includes/class-prc-math.php | 13 +- includes/class-prc-preset-provider.php | 51 +-- includes/frontend/class-prc-shortcode.php | 57 +-- package-lock.json | 13 + package.json | 9 + peptide-reconstitution-calculator.php | 11 +- phpunit.xml.dist | 20 + tests/bootstrap.php | 331 +++++++++++++++ tests/unit/DefaultPresetsTest.php | 265 ++++++++++++ tests/unit/MathTest.php | 444 +++++++++++++++++++++ tests/unit/PresetProviderTest.php | 96 +++++ tests/unit/RestControllerTest.php | 150 +++++++ uninstall.php | 5 +- 23 files changed, 1517 insertions(+), 186 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100755 phpunit.xml.dist create mode 100755 tests/bootstrap.php create mode 100755 tests/unit/DefaultPresetsTest.php create mode 100755 tests/unit/MathTest.php create mode 100755 tests/unit/PresetProviderTest.php create mode 100755 tests/unit/RestControllerTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8482dae..db61496 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,71 +1,27 @@ +## +# Thin CI caller — peptide-reconstitution-calculator +# +# Delegates all lint/test/file-size jobs to the estate reusable workflow. +# Per estate standard: workflow_call is required so deploy.yml can gate on it. +# See: peptiderepo/peptide-e2e/.github/workflows/ci.yml@main for job definitions. +## name: CI on: + pull_request: {} push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_call: + branches: [main] + workflow_call: {} +# Required: calling workflow must declare contents: write so the reusable +# phpcs job can commit phpcbf auto-fixes back to the PR branch. permissions: - contents: read + contents: write jobs: - # ── PHP Syntax Check ───────────────────────────────────────────── - lint-php: - name: PHP Lint (${{ matrix.php }}) - runs-on: ubuntu-latest - strategy: - matrix: - php: [ '8.1', '8.2', '8.3' ] - steps: - - uses: actions/checkout@v4 - - - name: Set up PHP ${{ matrix.php }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - - - name: Syntax check all PHP files - run: find . -name "*.php" -not -path "./vendor/*" | xargs -n 1 php -l - - # ── WordPress Coding Standards (PHP CodeSniffer) ────────────────── - lint-phpcs: - name: WordPress Coding Standards - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - - - name: Install Composer dependencies - run: composer install --no-interaction --prefer-dist --no-progress - - - name: Run PHPCS - run: composer run lint - continue-on-error: true - - # ── 300-line file check ────────────────────────────────────────── - file-size: - name: 300-line file check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check no PHP file exceeds 300 lines - run: | - violations=0 - while IFS= read -r file; do - lines=$(wc -l < "$file") - if [ "$lines" -gt 300 ]; then - echo "VIOLATION: $file ($lines lines)" - violations=$((violations + 1)) - fi - done < <(find . -name "*.php" -not -path "./vendor/*" -not -path "./tests/*") - if [ "$violations" -gt 0 ]; then - echo "$violations file(s) exceed 300 lines" - exit 1 - fi - echo "All PHP files are 300 lines or fewer." + ci: + uses: peptiderepo/peptide-e2e/.github/workflows/ci.yml@main + with: + tests: stubs + has_js: true + secrets: inherit diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e485a4..27221cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ All notable changes to Peptide Reconstitution Calculator are documented here. Format: [Semantic Versioning](https://semver.org/). +## [1.2.0] — 2026-06-16 + +### Added +- PHPUnit test suite (WP stubs, `tests: stubs`) — 40 tests across four classes: + - `MathTest`: 25 tests covering all PRC_Math methods — concentration mg/mL, + mcg/unit, injection volume, syringe units, doses per vial, and the full + `calculate()` orchestrator with named peptide scenarios (BPC-157, TB-500, + Semaglutide, MK-677, Ipamorelin) plus edge cases (zero water, zero dose, + negative inputs, floor rounding, rounding precision). + - `DefaultPresetsTest`: 11 tests — collection count, all 8 expected slugs present, + required keys on every preset (via dataProvider), valid dose units, min < max, + source='default', and spot-checks on BPC-157 / Semaglutide / MK-677. + - `RestControllerTest`: 7 tests — calculate() 200 response + correct values for + mcg and mg dose units; get_presets() returns 200; get_preset() 200 for known + slug and WP_Error for unknown slug. + - `PresetProviderTest`: 4 tests — fallback to defaults without PR Core, all + presets have slug, cache invalidation, idempotent invalidation. +- `phpunit.xml.dist` + `tests/bootstrap.php` (WP function/class stubs). +- `composer test` script (`vendor/bin/phpunit --configuration phpunit.xml.dist`). +- `phpunit/phpunit ^9.6` and `yoast/phpunit-polyfills ^2.0` in `require-dev`. + +### Changed +- `ci.yml` replaced with thin caller delegating to estate reusable workflow + (`peptide-e2e/.github/workflows/ci.yml@main`; `tests: stubs`, `has_js: true`, + `permissions: contents: write`, `workflow_call` so `deploy.yml` gate works). + ## [1.1.0] — 2026-04-26 ### Changed diff --git a/composer.json b/composer.json index 392da5e..f3f908a 100644 --- a/composer.json +++ b/composer.json @@ -7,10 +7,12 @@ "php": ">=8.1" }, "require-dev": { + "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3.13", "wp-coding-standards/wpcs": "^3.0", "phpcsstandards/phpcsutils": "^1.0", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "yoast/phpunit-polyfills": "^2.0" }, "config": { "allow-plugins": { @@ -18,6 +20,7 @@ } }, "scripts": { + "test": "vendor/bin/phpunit --configuration phpunit.xml.dist", "lint": "phpcs", "lint:fix": "phpcbf" } diff --git a/includes/api/class-prc-rest-controller.php b/includes/api/class-prc-rest-controller.php index ae19ba2..ec2fed0 100644 --- a/includes/api/class-prc-rest-controller.php +++ b/includes/api/class-prc-rest-controller.php @@ -9,6 +9,7 @@ * @see class-prc-preset-provider.php — Data source for presets. * @see class-prc-math.php — Calculation engine. * @see class-prc-shortcode.php — Frontend JS consumes these endpoints. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -38,39 +39,39 @@ class PRC_Rest_Controller { register_rest_route( self::NAMESPACE, '/presets', - [ + array( 'methods' => 'GET', - 'callback' => [ $this, 'get_presets' ], + 'callback' => array( $this, 'get_presets' ), 'permission_callback' => '__return_true', - ] + ) ); register_rest_route( self::NAMESPACE, '/presets/(?P[a-z0-9-]+)', - [ + array( 'methods' => 'GET', - 'callback' => [ $this, 'get_preset' ], + 'callback' => array( $this, 'get_preset' ), 'permission_callback' => '__return_true', - 'args' => [ - 'slug' => [ + 'args' => array( + 'slug' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_title', 'required' => true, - ], - ], - ] + ), + ), + ) ); register_rest_route( self::NAMESPACE, '/calculate', - [ + array( 'methods' => 'POST', - 'callback' => [ $this, 'calculate' ], + 'callback' => array( $this, 'calculate' ), 'permission_callback' => '__return_true', 'args' => $this->get_calculate_args(), - ] + ) ); } @@ -80,7 +81,7 @@ class PRC_Rest_Controller { * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ - public function get_presets( \WP_REST_Request $request ): \WP_REST_Response { + public function get_presets( \WP_REST_Request $request ): \WP_REST_Response { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found $provider = new PRC_Preset_Provider(); $presets = $provider->get_all_presets(); @@ -107,7 +108,7 @@ class PRC_Rest_Controller { return new \WP_Error( 'prc_preset_not_found', __( 'Preset not found.', 'peptide-reconstitution-calculator' ), - [ 'status' => 404 ] + array( 'status' => 404 ) ); } @@ -137,31 +138,31 @@ class PRC_Rest_Controller { * @return array> */ private function get_calculate_args(): array { - return [ - 'vial_mg' => [ + return array( + 'vial_mg' => array( 'type' => 'number', 'required' => true, 'sanitize_callback' => 'floatval', 'validate_callback' => fn( $v ) => is_numeric( $v ) && (float) $v > 0, - ], - 'water_ml' => [ + ), + 'water_ml' => array( 'type' => 'number', 'required' => true, 'sanitize_callback' => 'floatval', 'validate_callback' => fn( $v ) => is_numeric( $v ) && (float) $v > 0, - ], - 'desired_dose' => [ + ), + 'desired_dose' => array( 'type' => 'number', 'required' => true, 'sanitize_callback' => 'floatval', 'validate_callback' => fn( $v ) => is_numeric( $v ) && (float) $v > 0, - ], - 'dose_unit' => [ + ), + 'dose_unit' => array( 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => fn( $v ) => in_array( $v, [ 'mcg', 'mg' ], true ), - ], - ]; + 'validate_callback' => fn( $v ) => in_array( $v, array( 'mcg', 'mg' ), true ), + ), + ); } } diff --git a/includes/class-prc-activator.php b/includes/class-prc-activator.php index 79c67de..0b0a493 100644 --- a/includes/class-prc-activator.php +++ b/includes/class-prc-activator.php @@ -5,6 +5,8 @@ * What: Runs on plugin activation — flushes rewrite rules. * Who calls it: register_activation_hook in bootstrap. * Dependencies: None. + * + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); diff --git a/includes/class-prc-autoloader.php b/includes/class-prc-autoloader.php index d912b49..276bdb1 100644 --- a/includes/class-prc-autoloader.php +++ b/includes/class-prc-autoloader.php @@ -7,6 +7,7 @@ * Dependencies: None. * * @see class-pr-core-autoloader.php in Peptide Repo Core — same pattern. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -22,7 +23,7 @@ class PRC_Autoloader { * @return void */ public static function register(): void { - spl_autoload_register( [ __CLASS__, 'autoload' ] ); + spl_autoload_register( array( __CLASS__, 'autoload' ) ); } /** diff --git a/includes/class-prc-cache-listener.php b/includes/class-prc-cache-listener.php index fde42d7..9307905 100644 --- a/includes/class-prc-cache-listener.php +++ b/includes/class-prc-cache-listener.php @@ -9,6 +9,7 @@ * * @see class-prc-preset-provider.php — Cache being invalidated. * @see PR_Core_Dosing_Repository — Fires the hooks we listen to. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -27,7 +28,7 @@ class PRC_Cache_Listener { * @return void Side effect: registers action hooks. */ public static function register(): void { - $callback = [ __CLASS__, 'invalidate' ]; + $callback = array( __CLASS__, 'invalidate' ); // Dosing row lifecycle — affects calculator presets. add_action( 'pr_core_after_dosing_row_publish', $callback ); diff --git a/includes/class-prc-calculator.php b/includes/class-prc-calculator.php index 359612b..b1d056f 100644 --- a/includes/class-prc-calculator.php +++ b/includes/class-prc-calculator.php @@ -9,6 +9,7 @@ * @see class-prc-shortcode.php — Frontend calculator widget. * @see class-prc-rest-controller.php — REST endpoints for presets. * @see class-prc-preset-provider.php — PR Core integration + fallback defaults. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -29,17 +30,17 @@ class PRC_Calculator { public static function boot(): void { // Frontend shortcode — always available. $shortcode = new PRC_Shortcode(); - add_action( 'init', [ $shortcode, 'register' ] ); + add_action( 'init', array( $shortcode, 'register' ) ); // REST API — serves presets to the JS calculator. - add_action( 'rest_api_init', [ new PRC_Rest_Controller(), 'register_routes' ] ); + add_action( 'rest_api_init', array( new PRC_Rest_Controller(), 'register_routes' ) ); // Cache invalidation — keep presets in sync with PR Core dosing data. PRC_Cache_Listener::register(); // Admin notice when PR Core is absent. if ( is_admin() && ! self::is_pr_core_active() ) { - add_action( 'admin_notices', [ __CLASS__, 'render_pr_core_notice' ] ); + add_action( 'admin_notices', array( __CLASS__, 'render_pr_core_notice' ) ); } } @@ -70,7 +71,7 @@ class PRC_Calculator { } // Only show on plugins page and the calculator's own pages. - $show_on = [ 'plugins', 'settings_page_prc-settings' ]; + $show_on = array( 'plugins', 'settings_page_prc-settings' ); if ( ! in_array( $screen->id, $show_on, true ) ) { return; } diff --git a/includes/class-prc-deactivator.php b/includes/class-prc-deactivator.php index 3c406c7..73a81bd 100644 --- a/includes/class-prc-deactivator.php +++ b/includes/class-prc-deactivator.php @@ -5,6 +5,8 @@ * What: Runs on plugin deactivation — flushes rewrite rules. * Who calls it: register_deactivation_hook in bootstrap. * Dependencies: None. + * + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); diff --git a/includes/class-prc-default-presets.php b/includes/class-prc-default-presets.php index 5e1b28c..2cde444 100644 --- a/includes/class-prc-default-presets.php +++ b/includes/class-prc-default-presets.php @@ -8,6 +8,7 @@ * Dependencies: None. * * @see class-prc-preset-provider.php — Merges these with live PR Core data. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -27,11 +28,11 @@ class PRC_Default_Presets { * @return array> */ public static function get_all(): array { - return [ - [ + return array( + array( 'slug' => 'bpc-157', 'name' => 'BPC-157', - 'vial_sizes_mg' => [ 5, 10 ], + 'vial_sizes_mg' => array( 5, 10 ), 'default_vial_mg' => 5, 'recommended_water_ml' => 2.0, 'dose_range_min' => 200, @@ -41,11 +42,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'observational', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'tb-500', 'name' => 'TB-500', - 'vial_sizes_mg' => [ 2, 5, 10 ], + 'vial_sizes_mg' => array( 2, 5, 10 ), 'default_vial_mg' => 5, 'recommended_water_ml' => 2.0, 'dose_range_min' => 2000, @@ -55,11 +56,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'preclinical', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'semaglutide', 'name' => 'Semaglutide', - 'vial_sizes_mg' => [ 2, 5, 10 ], + 'vial_sizes_mg' => array( 2, 5, 10 ), 'default_vial_mg' => 5, 'recommended_water_ml' => 2.0, 'dose_range_min' => 0.25, @@ -69,11 +70,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'meta-analysis', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'cjc-1295-dac', 'name' => 'CJC-1295 (DAC)', - 'vial_sizes_mg' => [ 2, 5 ], + 'vial_sizes_mg' => array( 2, 5 ), 'default_vial_mg' => 2, 'recommended_water_ml' => 1.0, 'dose_range_min' => 1000, @@ -83,11 +84,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'preclinical', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'ipamorelin', 'name' => 'Ipamorelin', - 'vial_sizes_mg' => [ 2, 5 ], + 'vial_sizes_mg' => array( 2, 5 ), 'default_vial_mg' => 5, 'recommended_water_ml' => 2.0, 'dose_range_min' => 100, @@ -97,11 +98,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'preclinical', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'pt-141', 'name' => 'PT-141 (Bremelanotide)', - 'vial_sizes_mg' => [ 2, 10 ], + 'vial_sizes_mg' => array( 2, 10 ), 'default_vial_mg' => 10, 'recommended_water_ml' => 2.0, 'dose_range_min' => 500, @@ -111,11 +112,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'rct-large', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'mk-677', 'name' => 'MK-677 (Ibutamoren)', - 'vial_sizes_mg' => [ 15, 30 ], + 'vial_sizes_mg' => array( 15, 30 ), 'default_vial_mg' => 30, 'recommended_water_ml' => 3.0, 'dose_range_min' => 10, @@ -125,11 +126,11 @@ class PRC_Default_Presets { 'evidence_strength' => 'rct-small', 'dosing_row_count' => 0, 'source' => 'default', - ], - [ + ), + array( 'slug' => 'ghrp-6', 'name' => 'GHRP-6', - 'vial_sizes_mg' => [ 5, 10 ], + 'vial_sizes_mg' => array( 5, 10 ), 'default_vial_mg' => 5, 'recommended_water_ml' => 2.0, 'dose_range_min' => 100, @@ -139,7 +140,7 @@ class PRC_Default_Presets { 'evidence_strength' => 'preclinical', 'dosing_row_count' => 0, 'source' => 'default', - ], - ]; + ), + ); } } diff --git a/includes/class-prc-math.php b/includes/class-prc-math.php index 1d5e8bd..7e46665 100755 --- a/includes/class-prc-math.php +++ b/includes/class-prc-math.php @@ -10,6 +10,7 @@ * * @see assets/js/calculator.js — Client-side mirror of this math. * @see api/class-prc-rest-controller.php — Server-side consumer. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -52,13 +53,13 @@ class PRC_Math { ? $desired_dose / 1000 : $desired_dose; - $concentration_mg_per_ml = self::concentration_mg_per_ml( $vial_mg, $water_ml ); + $concentration_mg_per_ml = self::concentration_mg_per_ml( $vial_mg, $water_ml ); $concentration_mcg_per_unit = self::concentration_mcg_per_unit( $vial_mg, $water_ml ); - $injection_ml = self::injection_volume_ml( $dose_mg, $concentration_mg_per_ml ); - $syringe_units = self::syringe_units( $injection_ml ); - $doses_per_vial = self::doses_per_vial( $vial_mg, $dose_mg ); + $injection_ml = self::injection_volume_ml( $dose_mg, $concentration_mg_per_ml ); + $syringe_units = self::syringe_units( $injection_ml ); + $doses_per_vial = self::doses_per_vial( $vial_mg, $dose_mg ); - return [ + return array( 'concentration_mg_per_ml' => round( $concentration_mg_per_ml, 4 ), 'concentration_mcg_per_unit' => round( $concentration_mcg_per_unit, 2 ), 'injection_volume_ml' => round( $injection_ml, 4 ), @@ -69,7 +70,7 @@ class PRC_Math { 'desired_dose_mg' => round( $dose_mg, 4 ), 'desired_dose_display' => $desired_dose, 'dose_unit' => $dose_unit, - ]; + ); } /** diff --git a/includes/class-prc-preset-provider.php b/includes/class-prc-preset-provider.php index a1b08cb..2091825 100755 --- a/includes/class-prc-preset-provider.php +++ b/includes/class-prc-preset-provider.php @@ -12,6 +12,7 @@ * @see class-prc-default-presets.php — Hardcoded fallback presets. * @see PR_Core_Peptide_Repository (PR Core) — Peptide list source. * @see PR_Core_Dosing_Repository (PR Core) — Dosing data source. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -89,19 +90,19 @@ class PRC_Preset_Provider { // could fail to autoload (e.g. fatal error in PR Core bootstrap). // Fall back to empty array so the caller merges in defaults gracefully. if ( ! class_exists( 'PR_Core_Peptide_Repository' ) || ! class_exists( 'PR_Core_Dosing_Repository' ) ) { - return []; + return array(); } $peptide_repo = new PR_Core_Peptide_Repository(); $dosing_repo = new PR_Core_Dosing_Repository(); - $peptides = $peptide_repo->find_all( [ 'posts_per_page' => 100 ] ); - $presets = []; + $peptides = $peptide_repo->find_all( array( 'posts_per_page' => 100 ) ); + $presets = array(); foreach ( $peptides as $peptide ) { $rows = $dosing_repo->find_by_peptide( $peptide->id, - [ 'route' => 'subcutaneous' ] + array( 'route' => 'subcutaneous' ) ); if ( empty( $rows ) ) { @@ -133,7 +134,7 @@ class PRC_Preset_Provider { // Extract dose range across all rows. $dose_mins = array_filter( array_map( fn( $r ) => $r->dose_min, $rows ) ); $dose_maxs = array_filter( array_map( fn( $r ) => $r->dose_max, $rows ) ); - $dose_unit = $rows[0]->dose_unit ?: 'mcg'; + $dose_unit = ! empty( $rows[0]->dose_unit ) ? $rows[0]->dose_unit : 'mcg'; // Derive typical vial sizes from dose range. // Why: Most peptides are sold in standard vial sizes (2mg, 5mg, 10mg, 15mg, 30mg). @@ -141,26 +142,26 @@ class PRC_Preset_Provider { $display_name = ''; if ( method_exists( $peptide, 'to_array' ) ) { - $arr = $peptide->to_array(); + $arr = $peptide->to_array(); $display_name = $arr['display_name'] ?? $peptide->title ?? ''; } else { $display_name = $peptide->title ?? ''; } - return [ - 'slug' => $peptide->slug, - 'name' => $display_name ?: $peptide->title, - 'vial_sizes_mg' => $vial_sizes, - 'default_vial_mg' => $vial_sizes[0] ?? 5, - 'recommended_water_ml' => $this->recommend_water_ml( $vial_sizes[0] ?? 5 ), - 'dose_range_min' => ! empty( $dose_mins ) ? min( $dose_mins ) : null, - 'dose_range_max' => ! empty( $dose_maxs ) ? max( $dose_maxs ) : null, - 'dose_unit' => $dose_unit, - 'typical_frequency' => $this->most_common_frequency( $rows ), - 'evidence_strength' => $peptide->evidence_strength ?? 'preclinical', - 'dosing_row_count' => count( $rows ), - 'source' => 'pr_core', - ]; + return array( + 'slug' => $peptide->slug, + 'name' => ! empty( $display_name ) ? $display_name : $peptide->title, + 'vial_sizes_mg' => $vial_sizes, + 'default_vial_mg' => $vial_sizes[0] ?? 5, + 'recommended_water_ml' => $this->recommend_water_ml( $vial_sizes[0] ?? 5 ), + 'dose_range_min' => ! empty( $dose_mins ) ? min( $dose_mins ) : null, + 'dose_range_max' => ! empty( $dose_maxs ) ? max( $dose_maxs ) : null, + 'dose_unit' => $dose_unit, + 'typical_frequency' => $this->most_common_frequency( $rows ), + 'evidence_strength' => $peptide->evidence_strength ?? 'preclinical', + 'dosing_row_count' => count( $rows ), + 'source' => 'pr_core', + ); } /** @@ -176,10 +177,10 @@ class PRC_Preset_Provider { * @return float[] Standard vial sizes in mg. */ private function infer_vial_sizes( array $dose_mins, array $dose_maxs, string $dose_unit ): array { - $standard_sizes = [ 2, 5, 10, 15, 30 ]; + $standard_sizes = array( 2, 5, 10, 15, 30 ); if ( empty( $dose_mins ) && empty( $dose_maxs ) ) { - return [ 5, 10 ]; + return array( 5, 10 ); } // Convert everything to mg for comparison. @@ -187,12 +188,12 @@ class PRC_Preset_Provider { $all_doses = array_merge( $dose_mins, $dose_maxs ); foreach ( $all_doses as $dose ) { - $mg = ( 'mcg' === $dose_unit ) ? $dose / 1000 : $dose; + $mg = ( 'mcg' === $dose_unit ) ? $dose / 1000 : $dose; $max_dose_mg = max( $max_dose_mg, $mg ); } // Pick vials that hold at least 10 doses at the max dose level. - $viable = []; + $viable = array(); foreach ( $standard_sizes as $size ) { if ( $max_dose_mg > 0 && ( $size / $max_dose_mg ) >= 5 ) { $viable[] = $size; @@ -200,7 +201,7 @@ class PRC_Preset_Provider { } // Fallback: return two middle sizes if heuristic found nothing. - return ! empty( $viable ) ? array_slice( $viable, 0, 3 ) : [ 5, 10 ]; + return ! empty( $viable ) ? array_slice( $viable, 0, 3 ) : array( 5, 10 ); } /** diff --git a/includes/frontend/class-prc-shortcode.php b/includes/frontend/class-prc-shortcode.php index acaa522..7473eed 100755 --- a/includes/frontend/class-prc-shortcode.php +++ b/includes/frontend/class-prc-shortcode.php @@ -10,6 +10,7 @@ * @see assets/js/calculator.js — Client-side calculator logic. * @see assets/css/calculator.css — Calculator styles. * @see class-prc-calculator.php — Boot orchestrator. + * @package PeptideReconstitutionCalculator */ declare(strict_types=1); @@ -34,7 +35,7 @@ class PRC_Shortcode { * @return void Side effect: registers shortcode. */ public function register(): void { - add_shortcode( 'prc_calculator', [ $this, 'render' ] ); + add_shortcode( 'prc_calculator', array( $this, 'render' ) ); } /** @@ -47,11 +48,11 @@ class PRC_Shortcode { * @param array|string $atts Shortcode attributes. * @return string HTML output. */ - public function render( $atts = [] ): string { + public function render( $atts = array() ): string { $atts = shortcode_atts( - [ + array( 'peptide' => '', // Pre-select a peptide by slug. - ], + ), $atts, 'prc_calculator' ); @@ -81,14 +82,14 @@ class PRC_Shortcode { wp_enqueue_style( 'prc-calculator', PRC_PLUGIN_URL . 'assets/css/calculator.css', - [], + array(), PRC_VERSION ); wp_enqueue_script( 'prc-calculator', PRC_PLUGIN_URL . 'assets/js/calculator.js', - [], + array(), PRC_VERSION, true ); @@ -96,23 +97,27 @@ class PRC_Shortcode { // Pass presets and config to JS — all REST endpoints are public-read, // so no nonce is needed. The restUrl is provided for future use. $provider = new PRC_Preset_Provider(); - wp_localize_script( 'prc-calculator', 'prcConfig', [ - 'presets' => $provider->get_all_presets(), - 'restUrl' => esc_url_raw( rest_url( 'prc/v1' ) ), - 'syringeUnits' => 100, // Must match PRC_Math::SYRINGE_UNITS_PER_ML and calculator.js SYRINGE_UNITS_PER_ML. - 'i18n' => [ - 'selectPeptide' => __( 'Select a peptide…', 'peptide-reconstitution-calculator' ), - 'customEntry' => __( 'Custom / Manual Entry', 'peptide-reconstitution-calculator' ), - 'calculate' => __( 'Calculate', 'peptide-reconstitution-calculator' ), - 'reset' => __( 'Reset', 'peptide-reconstitution-calculator' ), - 'concentration' => __( 'Concentration', 'peptide-reconstitution-calculator' ), - 'injectionVol' => __( 'Injection Volume', 'peptide-reconstitution-calculator' ), - 'syringeUnits' => __( 'Syringe Units', 'peptide-reconstitution-calculator' ), - 'dosesPerVial' => __( 'Doses Per Vial', 'peptide-reconstitution-calculator' ), - 'perUnit' => __( 'per unit', 'peptide-reconstitution-calculator' ), - 'disclaimer' => __( 'For informational purposes only. Not medical advice. Always consult a qualified healthcare professional.', 'peptide-reconstitution-calculator' ), - ], - ] ); + wp_localize_script( + 'prc-calculator', + 'prcConfig', + array( + 'presets' => $provider->get_all_presets(), + 'restUrl' => esc_url_raw( rest_url( 'prc/v1' ) ), + 'syringeUnits' => 100, // Must match PRC_Math::SYRINGE_UNITS_PER_ML and calculator.js SYRINGE_UNITS_PER_ML. + 'i18n' => array( + 'selectPeptide' => __( 'Select a peptide…', 'peptide-reconstitution-calculator' ), + 'customEntry' => __( 'Custom / Manual Entry', 'peptide-reconstitution-calculator' ), + 'calculate' => __( 'Calculate', 'peptide-reconstitution-calculator' ), + 'reset' => __( 'Reset', 'peptide-reconstitution-calculator' ), + 'concentration' => __( 'Concentration', 'peptide-reconstitution-calculator' ), + 'injectionVol' => __( 'Injection Volume', 'peptide-reconstitution-calculator' ), + 'syringeUnits' => __( 'Syringe Units', 'peptide-reconstitution-calculator' ), + 'dosesPerVial' => __( 'Doses Per Vial', 'peptide-reconstitution-calculator' ), + 'perUnit' => __( 'per unit', 'peptide-reconstitution-calculator' ), + 'disclaimer' => __( 'For informational purposes only. Not medical advice. Always consult a qualified healthcare professional.', 'peptide-reconstitution-calculator' ), + ), + ) + ); $this->assets_enqueued = true; } @@ -155,7 +160,7 @@ class PRC_Shortcode {
+ step="0.1" min="0.1" placeholder="5" />
@@ -168,7 +173,7 @@ class PRC_Shortcode {
+ step="0.1" min="0.1" placeholder="2.0" />
@@ -180,7 +185,7 @@ class PRC_Shortcode {
+ step="any" min="0" placeholder="250" />