Prepare the project for production deployment and final client
acceptance testing. Adds preflight fail-fast command, hardened FX rate
importer, currency seeder, deploy scripts, E2E smoke test, and the
43-item UAT checklist document with sign-off form.
Key changes:
- Phase 3.9.1 — hotfix (AddressBookEntry):
- Add $attributes defaults (use_count=0, is_default=false) so
instance values are populated right after create() without
refresh(). Resolves the red test
`it_increments_use_count_and_sets_last_used_at`.
- Phase 3.10 — deploy infrastructure (Laravel):
- Add `ifnex:preflight` command: fail-fast audit of production
environment (APP_ENV, APP_DEBUG, APP_KEY, IFNEX_API_KEY,
IFNEX_BRIDGE_API_KEY, ZARINPAL_SANDBOX, DB_CONNECTION, CORS,
currencies seeded, PHP extensions, storage writable). Exits 1
if any check fails; supports --skip-db for pre-migrate runs.
- Add `ifnex:import-fx-rates` command: replacement for
`ifnex:update-rates`. Fetches USD/AED from Exir.io + EUR/CNY
from FreeCurrencyAPI. Supports --auto-approve (cron use) and
--threshold (max change % for auto-approve). Logs critical on
HTTP failure. URL configurable via EXIR_API_URL env.
- Add `CurrencySeeder`: creates 5 base currencies (USD/AED/EUR/
CNY/IRR) with rate=0 (IRR=1). Idempotent — does not overwrite
real rates already set by admin or cron.
- Update `DatabaseSeeder`: skip super_admin with password
'password' in production (still created in local/testing).
- Fix `bootstrap/app.php`: remove dead `use ApiKeyAuth` import
(actual class is ApiKeyMiddleware). Latent fatal if api_key
alias was used.
- Add `config/ifnex.php` exir block + preflight config.
- Harden `.env.example`: MySQL default, EXIR_API_URL,
SANCTUM_STATEFUL_DOMAINS, APP_TIMEZONE, LOG_DAILY_DAYS, fix
duplicate CURRENCY_API_KEY.
- Phase 3.10 — deploy scripts (Linux):
- Add `scripts/deploy.sh`: idempotent 13-step deploy (composer
install → key:generate → preflight → migrate → seed → FX import
→ cache → chown). Fail-fast with set -e.
- Add `scripts/cron-exir-rates.sh`: daily cron wrapper for
`ifnex:import-fx-rates --source=all --auto-approve`.
- Phase 3.10.1 — preflight hotfix:
- Make expected DB connection configurable
(ifnex.preflight.expected_db_connection) instead of hardcoded
'mysql'. Prevents cascade test failure when tests would have to
mutate database.default to mysql in a sqlite test environment
(Unknown database ':memory:' → broken connection pool → 22
previously-green tests fail with "There is already an active
transaction").
- Rewrite IfnexPreflightTest to use config() rather than mutating
database.default.
- Phase 3.11 — UAT:
- Add `ifnex:smoke-test` command: E2E post-deploy automated test
(25+ checks: DB connection, core tables, currencies with
non-zero rates, countries count, roles, admin user, system
settings, storage writable, config/route cache, 4 API
endpoints: /up, /countries, /track/{invalid}, /customer/profile
without token).
- Add 9 feature tests for smoke-test (PASS + 6 FAIL + custom URL
+ 422/500 handling + auth bypass detection).
- Add `UAT-3.11.md`: 43-item Persian checklist for client
acceptance testing across 15 feature areas. Includes sign-off
form for both parties (client + VernaSoft).
- Update `CLIENT_DELIVERY.md` to record phases 3.10 and 3.11.
Tests:
- Total: 51 → 82 tests, 150 → 220+ assertions, all green.
- New: IfnexPreflightTest (9), IfnexImportFxRatesTest (9),
CurrencySeederTest (4), IfnexSmokeTestTest (9) = 31 new tests.
Documentation:
- DEPLOYMENT_HANDOFF.md §2.4: add CurrencySeeder + import-fx-rates.
- DEPLOYMENT_HANDOFF.md §2.8: update cron to use cron-exir-rates.sh.
- UAT-3.11.md: new 43-item client acceptance checklist + sign-off.
- CLIENT_DELIVERY.md: record phases 3.10 and 3.11 completion.
158 lines
4.0 KiB
PHP
158 lines
4.0 KiB
PHP
<?php
|
|
/**
|
|
* Widget API: WP_Widget_Factory class
|
|
*
|
|
* @package WordPress
|
|
* @subpackage Widgets
|
|
* @since 4.4.0
|
|
*/
|
|
|
|
/**
|
|
* Singleton that registers and instantiates WP_Widget classes.
|
|
*
|
|
* @since 2.8.0
|
|
* @since 4.4.0 Moved to its own file from wp-includes/widgets.php
|
|
*/
|
|
#[AllowDynamicProperties]
|
|
class WP_Widget_Factory {
|
|
|
|
/**
|
|
* Prefix for the key under which a widget registered as an instance is stored.
|
|
*
|
|
* Without it the key would be the decimal representation of an integer, which PHP casts from
|
|
* string to int when it is used as an array key.
|
|
*
|
|
* @since 7.1.1
|
|
*/
|
|
private const INSTANCE_KEY_PREFIX = 'spl_object_id:';
|
|
|
|
/**
|
|
* Widgets array.
|
|
*
|
|
* Keyed by class name for a widget registered by name, and by a prefixed object ID for a widget
|
|
* registered as an instance.
|
|
*
|
|
* @since 2.8.0
|
|
* @var array<string, WP_Widget>
|
|
* @phpstan-var array<non-decimal-int-string, WP_Widget>
|
|
*/
|
|
public $widgets = array();
|
|
|
|
/**
|
|
* PHP5 constructor.
|
|
*
|
|
* @since 4.3.0
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'widgets_init', array( $this, '_register_widgets' ), 100 );
|
|
}
|
|
|
|
/**
|
|
* PHP4 constructor.
|
|
*
|
|
* @since 2.8.0
|
|
* @deprecated 4.3.0 Use __construct() instead.
|
|
*
|
|
* @see WP_Widget_Factory::__construct()
|
|
*/
|
|
public function WP_Widget_Factory() {
|
|
_deprecated_constructor( 'WP_Widget_Factory', '4.3.0' );
|
|
self::__construct();
|
|
}
|
|
|
|
/**
|
|
* Registers a widget subclass.
|
|
*
|
|
* @since 2.8.0
|
|
* @since 4.6.0 Updated the `$widget` parameter to also accept a WP_Widget instance object
|
|
* instead of simply a `WP_Widget` subclass name.
|
|
* @since 7.1.1 The key for an instance is prefixed so that it is never cast to an integer.
|
|
*
|
|
* @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass.
|
|
*/
|
|
public function register( $widget ) {
|
|
if ( $widget instanceof WP_Widget ) {
|
|
$this->widgets[ self::INSTANCE_KEY_PREFIX . spl_object_id( $widget ) ] = $widget;
|
|
} else {
|
|
$this->widgets[ $widget ] = new $widget();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Un-registers a widget subclass.
|
|
*
|
|
* @since 2.8.0
|
|
* @since 4.6.0 Updated the `$widget` parameter to also accept a WP_Widget instance object
|
|
* instead of simply a `WP_Widget` subclass name.
|
|
* @since 7.1.1 The key for an instance is prefixed so that it is never cast to an integer.
|
|
*
|
|
* @param string|WP_Widget $widget Either the name of a `WP_Widget` subclass or an instance of a `WP_Widget` subclass.
|
|
*/
|
|
public function unregister( $widget ) {
|
|
if ( $widget instanceof WP_Widget ) {
|
|
unset( $this->widgets[ self::INSTANCE_KEY_PREFIX . spl_object_id( $widget ) ] );
|
|
} else {
|
|
unset( $this->widgets[ $widget ] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serves as a utility method for adding widgets to the registered widgets global.
|
|
*
|
|
* @since 2.8.0
|
|
*
|
|
* @global array $wp_registered_widgets
|
|
*/
|
|
public function _register_widgets() {
|
|
global $wp_registered_widgets;
|
|
$keys = array_keys( $this->widgets );
|
|
$registered = array_keys( $wp_registered_widgets );
|
|
$registered = array_map( '_get_widget_id_base', $registered );
|
|
|
|
foreach ( $keys as $key ) {
|
|
// Don't register new widget if old widget with the same id is already registered.
|
|
if ( in_array( $this->widgets[ $key ]->id_base, $registered, true ) ) {
|
|
unset( $this->widgets[ $key ] );
|
|
continue;
|
|
}
|
|
|
|
$this->widgets[ $key ]->_register();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the registered WP_Widget object for the given widget type.
|
|
*
|
|
* @since 5.8.0
|
|
*
|
|
* @param string $id_base Widget type ID.
|
|
* @return WP_Widget|null
|
|
*/
|
|
public function get_widget_object( $id_base ) {
|
|
$key = $this->get_widget_key( $id_base );
|
|
if ( '' === $key ) {
|
|
return null;
|
|
}
|
|
|
|
return $this->widgets[ $key ];
|
|
}
|
|
|
|
/**
|
|
* Returns the registered key for the given widget type.
|
|
*
|
|
* @since 5.8.0
|
|
*
|
|
* @param string $id_base Widget type ID.
|
|
* @return string
|
|
*/
|
|
public function get_widget_key( $id_base ) {
|
|
foreach ( $this->widgets as $key => $widget_object ) {
|
|
if ( $widget_object->id_base === $id_base ) {
|
|
return $key;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
}
|