// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
// phpcs:ignore Universal.Namespaces.DisallowCurlyBraceSyntax.Forbidden
use AllowDynamicProperties;
use WPForms_Form_Handler;
#[AllowDynamicProperties]
* List of screen IDs where heartbeat requests are allowed.
private const HEARTBEAT_ALLOWED_SCREEN_IDS = [
'wpforms_page_wpforms-entries',
* One is the loneliest number that you'll ever do.
private static $instance;
* Plugin version for enqueueing, etc.
* The value is got from WPFORMS_VERSION constant.
* List of legacy public properties.
private $legacy_properties = [
* Paid returns true, free (Lite) returns false.
* @since 1.7.3 changed to private.
* Backward compatibility method for accessing the class registry in an old way,
* e.g. 'wpforms()->form' or 'wpforms()->entry'.
* @param string $name Name of the object to get.
* @noinspection MagicMethodsValidityInspection
* @noinspection PhpDeprecationInspection
public function __get( $name ) {
if ( $name === 'smart_tags' ) {
'1.6.7 of the WPForms plugin',
"Please use `wpforms()->obj( 'smart_tags' )` instead."
'1.8.2.2 of the WPForms plugin',
'Please use `wpforms()->is_pro()` instead.'
return wpforms()->is_pro();
return $this->get( $name );
* Only one instance of WPForms exists in memory at any one time.
* Also, prevent the need to define globals all over the place.
public static function instance(): WPForms {
if ( self::$instance === null || ! self::$instance instanceof self ) {
self::$instance = new self();
* @noinspection UsingInclusionOnceReturnValueInspection
private function init(): void {
if ( self::is_restricted_heartbeat() ) {
// Load Pro or Lite specific files.
$this->registry['pro'] = require_once WPFORMS_PLUGIN_DIR . 'pro/wpforms-pro.php';
require_once WPFORMS_PLUGIN_DIR . 'lite/wpforms-lite.php';
* Setup plugin constants.
* All the path/URL-related constants are defined in the main plugin file.
private function constants(): void {
$this->version = WPFORMS_VERSION;
// Plugin Slug - Determine a plugin type and set slug accordingly.
// This filter is documented in \WPForms\WPForms::is_pro.
if ( apply_filters( 'wpforms_allow_pro_version', file_exists( WPFORMS_PLUGIN_DIR . 'pro/wpforms-pro.php' ) ) ) {
define( 'WPFORMS_PLUGIN_SLUG', 'wpforms' );
define( 'WPFORMS_PLUGIN_SLUG', 'wpforms-lite' );
private function includes(): void {
// Action Scheduler requires a special loading procedure.
require_once WPFORMS_PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
// Autoload Composer packages.
require_once WPFORMS_PLUGIN_DIR . 'vendor/autoload.php';
// Base class and functions.
require_once WPFORMS_PLUGIN_DIR . 'includes/class-db.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/functions.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/fields/class-base.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/class-install.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/class-form.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/class-fields.php';
// TODO: class-templates.php should be loaded in admin area only.
require_once WPFORMS_PLUGIN_DIR . 'includes/class-templates.php';
// TODO: class-providers.php should be loaded in admin area only.
require_once WPFORMS_PLUGIN_DIR . 'includes/class-providers.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/class-process.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/class-widget.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/emails/class-emails.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/integrations.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/deprecated.php';
// Admin/Dashboard only includes, also in ajax.
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/admin.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-notices.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-menu.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/class-builder.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/builder/functions.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-settings.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-welcome.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-editor.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-review.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/class-about.php';
require_once WPFORMS_PLUGIN_DIR . 'includes/admin/ajax-actions.php';
* @since 1.9.3 No longer static.
private function hooks(): void {
add_action( 'plugins_loaded', [ self::$instance, 'objects' ] );
add_action( 'wpforms_settings_init', [ self::$instance, 'reinstall_custom_tables' ] );
* Include the error handler to suppress deprecated messages from vendor folders.
private function error_handler(): void {
require_once WPFORMS_PLUGIN_DIR . 'src/ErrorHandler.php';
( new ErrorHandler() )->init();
* Including the new files with PHP 5.3 style.
private function includes_magic(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Load the class loader.
'name' => 'Integrations\SolidCentral\SolidCentral',
'hook' => 'plugins_loaded',
'condition' => ! empty( $_GET['ithemes-sync-request'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
* Load admin components. Exclude from the frontend.
add_action( 'wpforms_loaded', [ '\WPForms\Admin\Loader', 'get_instance' ] );
* Properly init the providers' loader that will handle all the related logic and further loading.
add_action( 'wpforms_loaded', [ '\WPForms\Providers\Providers', 'get_instance' ] );
* Properly init the integration loader that will handle all the related logic and further loading.
add_action( 'wpforms_loaded', [ '\WPForms\Integrations\Loader', 'get_instance' ] );
public function objects(): void {
$this->registry['form'] = new WPForms_Form_Handler();
$this->registry['process'] = new WPForms_Process();
* Executes when all the WPForms stuff was loaded.
do_action( 'wpforms_loaded' );
* Re-create plugin custom tables if they don't exist.
* @param WPForms_Settings $wpforms_settings WPForms settings object.
public function reinstall_custom_tables( WPForms_Settings $wpforms_settings ): void {
if ( empty( $wpforms_settings->view ) ) {
// Proceed on the Settings plugin admin area page only.
if ( $wpforms_settings->view !== 'general' ) {
// Install on the current site only.
if ( ! DB::custom_tables_exist() ) {
DB::create_custom_tables();
* @param array $class_data Class registration info.
* $class_data array accepts these params: name, id, hook, run, condition.
* - name: required -- class name to register.
* - id: optional -- class ID to register.
* - hook: optional -- hook to register the class on -- default wpforms_loaded.
* - run: optional -- method to run on class instantiation -- default init.
* - condition: optional -- condition to check before registering the class.
* @noinspection OnlyWritesOnParameterInspection
public function register( $class_data ): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
if ( empty( $class_data['name'] ) || ! is_string( $class_data['name'] ) ) {
if ( isset( $class_data['condition'] ) && empty( $class_data['condition'] ) ) {
$full_name = $this->is_pro() ? '\WPForms\Pro\\' . $class_data['name'] : '\WPForms\Lite\\' . $class_data['name'];
$full_name = class_exists( $full_name ) ? $full_name : '\WPForms\\' . $class_data['name'];
// Register an addon class.
if ( ! empty( $class_data['addon_class'] ) && ! empty( $class_data['addon_slug'] ) ) {
$is_initialized = wpforms_is_addon_initialized( $class_data['addon_slug'] ) && $this->is_pro();
$full_name = $is_initialized ? $class_data['addon_class'] : $full_name;
$full_name = strpos( $full_name, '\\' ) !== 0 ? '\\' . $full_name : $full_name;
// The core plugin classes have priority 10.
// Addon classes should be initialized after the core.
$class_data['priority'] = 100;
// Bail if the class doesn't exist AND it is not an addon class.
if ( ! class_exists( $full_name ) && empty( $class_data['addon_class'] ) ) {
$id = $class_data['id'] ?? '';
$id = $id ? preg_replace( '/[^a-z_]/', '', (string) $id ) : $id;
$hook = isset( $class_data['hook'] ) ? (string) $class_data['hook'] : 'wpforms_loaded';
$run = $class_data['run'] ?? 'init';
$priority = isset( $class_data['priority'] ) && is_int( $class_data['priority'] ) ? $class_data['priority'] : 10;
$callback = function () use ( $full_name, $id, $run, $hook ) {
if ( ! class_exists( $full_name ) ) {
$instance = new $full_name();
$this->register_instance( $id, $instance );
if ( $run && method_exists( $instance, $run ) ) {
add_action( $hook, $callback, $priority );
* Register any class instance.
* @param string $id Class ID.
* @param object $instance Any class instance (object).
public function register_instance( $id, $instance ): void {
if ( $id && is_object( $instance ) && ! array_key_exists( $id, $this->registry ) ) {
$this->registry[ $id ] = $instance;
* Register classes in bulk.
* @param array $classes Classes to register.
public function register_bulk( $classes ): void {
if ( ! is_array( $classes ) ) {
foreach ( $classes as $class ) {
$this->register( $class );
* Get a class instance from a registry.
* Use \WPForms\WPForms::obj() instead.
* @param string $name Class name or an alias.
* @return mixed|stdClass|null
public function get( $name ) {
if ( ! empty( $this->registry[ $name ] ) ) {
return $this->registry[ $name ];
// Backward compatibility for old public properties.
// Return null to save old condition for these properties.
if ( in_array( $name, $this->legacy_properties, true ) ) {
return $this->{$name} ?? null;
* Get a class instance from a registry.