🌌 SpectraShell

🌌 SpectraShell

Current path: home/a1642gum/public_html/wp-includes/



⬆️ Go up: public_html

📄 Viewing: class-wp-script-modules.php

<?php
/**
 * Script Modules API: WP_Script_Modules class.
 *
 * Native support for ES Modules and Import Maps.
 *
 * @package WordPress
 * @subpackage Script Modules
 */

/**
 * Core class used to register script modules.
 *
 * @since 6.5.0
 */
class WP_Script_Modules {
	/**
	 * Holds the registered script modules, keyed by script module identifier.
	 *
	 * @since 6.5.0
	 * @var array<string, array<string, mixed>>
	 */
	private $registered = array();

	/**
	 * An array of IDs for queued script modules.
	 *
	 * @since 6.9.0
	 * @var string[]
	 */
	private $queue = array();

	/**
	 * Holds the script module identifiers that have been printed.
	 *
	 * @since 6.9.0
	 * @var string[]
	 */
	private $done = array();

	/**
	 * Tracks whether the @wordpress/a11y script module is available.
	 *
	 * Some additional HTML is required on the page for the module to work. Track
	 * whether it's available to print at the appropriate time.
	 *
	 * @since 6.7.0
	 * @var bool
	 */
	private $a11y_available = false;

	/**
	 * Holds a mapping of dependents (as IDs) for a given script ID.
	 * Used to optimize recursive dependency tree checks.
	 *
	 * @since 6.9.0
	 * @var array<string, string[]>
	 */
	private $dependents_map = array();

	/**
	 * Holds the valid values for fetchpriority.
	 *
	 * @since 6.9.0
	 * @var string[]
	 */
	private $priorities = array(
		'low',
		'auto',
		'high',
	);

	/**
	 * List of IDs for script modules encountered which have missing dependencies.
	 *
	 * An ID is added to this list when it is discovered to have missing dependencies. At this time, a warning is
	 * emitted with {@see _doing_it_wrong()}. The ID is then added to this list, so that duplicate warnings don't occur.
	 *
	 * @since 6.9.1
	 * @var string[]
	 */
	private $modules_with_missing_dependencies = array();

	/**
	 * Registers the script module if no script module with that script module
	 * identifier has already been registered.
	 *
	 * @since 6.5.0
	 * @since 6.9.0 Added the $args parameter.
	 *
	 * @param string            $id       The identifier of the script module. Should be unique. It will be used in the
	 *                                    final import map.
	 * @param string            $src      Optional. Full URL of the script module, or path of the script module relative
	 *                                    to the WordPress root directory. If it is provided and the script module has
	 *                                    not been registered yet, it will be registered.
	 * @param array             $deps     {
	 *                                        Optional. List of dependencies.
	 *
	 *                                        @type string|array ...$0 {
	 *                                            An array of script module identifiers of the dependencies of this script
	 *                                            module. The dependencies can be strings or arrays. If they are arrays,
	 *                                            they need an `id` key with the script module identifier, and can contain
	 *                                            an `import` key with either `static` or `dynamic`. By default,
	 *                                            dependencies that don't contain an `import` key are considered static.
	 *
	 *                                            @type string $id     The script module identifier.
	 *                                            @type string $import Optional. Import type. May be either `static` or
	 *                                                                 `dynamic`. Defaults to `static`.
	 *                                        }
	 *                                    }
	 * @param string|false|null $version  Optional. String specifying the script module version number. Defaults to false.
	 *                                    It is added to the URL as a query string for cache busting purposes. If $version
	 *                                    is set to false, the version number is the currently installed WordPress version.
	 *                                    If $version is set to null, no version is added.
	 * @param array             $args     {
	 *     Optional. An array of additional args. Default empty array.
	 *
	 *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
	 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
	 * }
	 */
	public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) {
		if ( '' === $id ) {
			_doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' );
			return;
		}

		if ( ! isset( $this->registered[ $id ] ) ) {
			$dependencies = array();
			foreach ( $deps as $dependency ) {
				if ( is_array( $dependency ) ) {
					if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) {
						_doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
						continue;
					}
					$dependencies[] = array(
						'id'     => $dependency['id'],
						'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static',
					);
				} elseif ( is_string( $dependency ) ) {
					$dependencies[] = array(
						'id'     => $dependency,
						'import' => 'static',
					);
				} else {
					_doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' );
				}
			}

			$in_footer = isset( $args['in_footer'] ) && (bool) $args['in_footer'];

			$fetchpriority = 'auto';
			if ( isset( $args['fetchpriority'] ) ) {
				if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) {
					$fetchpriority = $args['fetchpriority'];
				} else {
					_doing_it_wrong(
						__METHOD__,
						sprintf(
							/* translators: 1: $fetchpriority, 2: $id */
							__( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
							is_string( $args['fetchpriority'] ) ? $args['fetchpriority'] : gettype( $args['fetchpriority'] ),
							$id
						),
						'6.9.0'
					);
				}
			}

			$this->registered[ $id ] = array(
				'src'           => $src,
				'version'       => $version,
				'dependencies'  => $dependencies,
				'in_footer'     => $in_footer,
				'fetchpriority' => $fetchpriority,
			);
		}
	}

	/**
	 * Gets IDs for queued script modules.
	 *
	 * @since 6.9.0
	 *
	 * @return string[] Script module IDs.
	 */
	public function get_queue(): array {
		return $this->queue;
	}

	/**
	 * Checks if the provided fetchpriority is valid.
	 *
	 * @since 6.9.0
	 *
	 * @param string|mixed $priority Fetch priority.
	 * @return bool Whether valid fetchpriority.
	 */
	private function is_valid_fetchpriority( $priority ): bool {
		return in_array( $priority, $this->priorities, true );
	}

	/**
	 * Sets the fetch priority for a script module.
	 *
	 * @since 6.9.0
	 *
	 * @param string              $id       Script module identifier.
	 * @param 'auto'|'low'|'high' $priority Fetch priority for the script module.
	 * @return bool Whether setting the fetchpriority was successful.
	 */
	public function set_fetchpriority( string $id, string $priority ): bool {
		if ( ! isset( $this->registered[ $id ] ) ) {
			return false;
		}

		if ( '' === $priority ) {
			$priority = 'auto';
		}

		if ( ! $this->is_valid_fetchpriority( $priority ) ) {
			_doing_it_wrong(
				__METHOD__,
				/* translators: %s: Invalid fetchpriority. */
				sprintf( __( 'Invalid fetchpriority: %s' ), $priority ),
				'6.9.0'
			);
			return false;
		}

		$this->registered[ $id ]['fetchpriority'] = $priority;
		return true;
	}

	/**
	 * Sets whether a script module should be printed in the footer.
	 *
	 * This is only relevant in block themes.
	 *
	 * @since 6.9.0
	 *
	 * @param string           $id        Script module identifier.
	 * @param bool             $in_footer Whether to print in the footer.
	 * @return bool Whether setting the printing location was successful.
	 */
	public function set_in_footer( string $id, bool $in_footer ): bool {
		if ( ! isset( $this->registered[ $id ] ) ) {
			return false;
		}
		$this->registered[ $id ]['in_footer'] = $in_footer;
		return true;
	}

	/**
	 * Marks the script module to be enqueued in the page.
	 *
	 * If a src is provided and the script module has not been registered yet, it
	 * will be registered.
	 *
	 * @since 6.5.0
	 * @since 6.9.0 Added the $args parameter.
	 *
	 * @param string            $id       The identifier of the script module. Should be unique. It will be used in the
	 *                                    final import map.
	 * @param string            $src      Optional. Full URL of the script module, or path of the script module relative
	 *                                    to the WordPress root directory. If it is provided and the script module has
	 *                                    not been registered yet, it will be registered.
	 * @param array             $deps     {
	 *                                        Optional. List of dependencies.
	 *
	 *                                        @type string|array ...$0 {
	 *                                            An array of script module identifiers of the dependencies of this script
	 *                                            module. The dependencies can be strings or arrays. If they are arrays,
	 *                                            they need an `id` key with the script module identifier, and can contain
	 *                                            an `import` key with either `static` or `dynamic`. By default,
	 *                                            dependencies that don't contain an `import` key are considered static.
	 *
	 *                                            @type string $id     The script module identifier.
	 *                                            @type string $import Optional. Import type. May be either `static` or
	 *                                                                 `dynamic`. Defaults to `static`.
	 *                                        }
	 *                                    }
	 * @param string|false|null $version  Optional. String specifying the script module version number. Defaults to false.
	 *                                    It is added to the URL as a query string for cache busting purposes. If $version
	 *                                    is set to false, the version number is the currently installed WordPress version.
	 *                                    If $version is set to null, no version is added.
	 * @param array             $args     {
	 *     Optional. An array of additional args. Default empty array.
	 *
	 *     @type bool                $in_footer     Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional.
	 *     @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional.
	 * }
	 */
	public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) {
		if ( '' === $id ) {
			_doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' );
			return;
		}

		if ( ! in_array( $id, $this->queue, true ) ) {
			$this->queue[] = $id;
		}
		if ( ! isset( $this->registered[ $id ] ) && $src ) {
			$this->register( $id, $src, $deps, $version, $args );
		}
	}

	/**
	 * Unmarks the script module so it will no longer be enqueued in the page.
	 *
	 * @since 6.5.0
	 *
	 * @param string $id The identifier of the script module.
	 */
	public function dequeue( string $id ) {
		$this->queue = array_values( array_diff( $this->queue, array( $id ) ) );
	}

	/**
	 * Removes a registered script module.
	 *
	 * @since 6.5.0
	 *
	 * @param string $id The identifier of the script module.
	 */
	public function deregister( string $id ) {
		$this->dequeue( $id );
		unset( $this->registered[ $id ] );
	}

	/**
	 * Adds the hooks to print the import map, enqueued script modules and script
	 * module preloads.
	 *
	 * In classic themes, the script modules used by the blocks are not yet known
	 * when the `wp_head` actions is fired, so it needs to print everything in the
	 * footer.
	 *
	 * @since 6.5.0
	 */
	public function add_hooks() {
		$is_block_theme = wp_is_block_theme();
		$position       = $is_block_theme ? 'wp_head' : 'wp_footer';
		add_action( $position, array( $this, 'print_import_map' ) );
		if ( $is_block_theme ) {
			/*
			 * Modules can only be printed in the head for block themes because only with
			 * block themes will import map be fully populated by modules discovered by
			 * rendering the block template. In classic themes, modules are enqueued during
			 * template rendering, thus the import map must be printed in the footer,
			 * followed by all enqueued modules.
			 */
			add_action( 'wp_head', array( $this, 'print_head_enqueued_script_modules' ) );
		}
		add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) );
		add_action( $position, array( $this, 'print_script_module_preloads' ) );

		add_action( 'admin_print_footer_scripts', array( $this, 'print_import_map' ) );
		add_action( 'admin_print_footer_scripts', array( $this, 'print_enqueued_script_modules' ) );
		add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_preloads' ) );

		add_action( 'wp_footer', array( $this, 'print_script_module_data' ) );
		add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_data' ) );
		add_action( 'wp_footer', array( $this, 'print_a11y_script_module_html' ), 20 );
		add_action( 'admin_print_footer_scripts', array( $this, 'print_a11y_script_module_html' ), 20 );
	}

	/**
	 * Gets the highest fetch priority for the provided script IDs.
	 *
	 * @since 6.9.0
	 *
	 * @param string[] $ids Script module IDs.
	 * @return 'auto'|'low'|'high' Highest fetch priority for the provided script module IDs.
	 */
	private function get_highest_fetchpriority( array $ids ): string {
		static $high_priority_index = null;
		if ( null === $high_priority_index ) {
			$high_priority_index = count( $this->priorities ) - 1;
		}

		$highest_priority_index = 0;
		foreach ( $ids as $id ) {
			if ( isset( $this->registered[ $id ] ) ) {
				$highest_priority_index = (int) max(
					$highest_priority_index,
					(int) array_search( $this->registered[ $id ]['fetchpriority'], $this->priorities, true )
				);
				if ( $high_priority_index === $highest_priority_index ) {
					break;
				}
			}
		}

		return $this->priorities[ $highest_priority_index ];
	}

	/**
	 * Prints the enqueued script modules in head.
	 *
	 * This is only used in block themes.
	 *
	 * @since 6.9.0
	 */
	public function print_head_enqueued_script_modules() {
		foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) {
			if (
				isset( $this->registered[ $id ] ) &&
				! $this->registered[ $id ]['in_footer']
			) {
				// If any dependency is set to be printed in footer, skip printing this module in head.
				$dependencies = array_keys( $this->get_dependencies( array( $id ) ) );
				foreach ( $dependencies as $dependency_id ) {
					if (
						in_array( $dependency_id, $this->queue, true ) &&
						isset( $this->registered[ $dependency_id ] ) &&
						$this->registered[ $dependency_id ]['in_footer']
					) {
						continue 2;
					}
				}
				$this->print_script_module( $id );
			}
		}
	}

	/**
	 * Prints the enqueued script modules in footer.
	 *
	 * @since 6.5.0
	 */
	public function print_enqueued_script_modules() {
		foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) {
			$this->print_script_module( $id );
		}
	}

	/**
	 * Prints the enqueued script module using script tags with type="module"
	 * attributes.
	 *
	 * @since 6.9.0
	 *
	 * @param string $id The script module identifier.
	 */
	private function print_script_module( string $id ) {
		if ( in_array( $id, $this->done, true ) || ! in_array( $id, $this->queue, true ) ) {
			return;
		}

		$this->done[] = $id;

		$src = $this->get_src( $id );
		if ( '' === $src ) {
			return;
		}

		$attributes = array(
			'type' => 'module',
			'src'  => $src,
			'id'   => $id . '-js-module',
		);

		$script_module     = $this->registered[ $id ];
		$queued_dependents = array_intersect( $this->queue, $this->get_recursive_dependents( $id ) );
		$fetchpriority     = $this->get_highest_fetchpriority( array_merge( array( $id ), $queued_dependents ) );
		if ( 'auto' !== $fetchpriority ) {
			$attributes['fetchpriority'] = $fetchpriority;
		}
		if ( $fetchpriority !== $script_module['fetchpriority'] ) {
			$attributes['data-wp-fetchpriority'] = $script_module['fetchpriority'];
		}
		wp_print_script_tag( $attributes );
	}

	/**
	 * Prints the static dependencies of the enqueued script modules using
	 * link tags with rel="modulepreload" attributes.
	 *
	 * If a script module is marked for enqueue, it will not be preloaded.
	 *
	 * @since 6.5.0
	 */
	public function print_script_module_preloads() {
		$dependency_ids = $this->get_sorted_dependencies( $this->queue, array( 'static' ) );
		foreach ( $dependency_ids as $id ) {
			// Don't preload if it's marked for enqueue.
			if ( in_array( $id, $this->queue, true ) ) {
				continue;
			}

			$src = $this->get_src( $id );
			if ( '' === $src ) {
				continue;
			}

			$enqueued_dependents   = array_intersect( $this->get_recursive_dependents( $id ), $this->queue );
			$highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents );
			printf(
				'<link rel="modulepreload" href="%s" id="%s"',
				esc_url( $src ),
				esc_attr( $id . '-js-modulepreload' )
			);
			if ( 'auto' !== $highest_fetchpriority ) {
				printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) );
			}
			if ( $highest_fetchpriority !== $this->registered[ $id ]['fetchpriority'] && 'auto' !== $this->registered[ $id ]['fetchpriority'] ) {
				printf( ' data-wp-fetchpriority="%s"', esc_attr( $this->registered[ $id ]['fetchpriority'] ) );
			}
			echo ">\n";
		}
	}

	/**
	 * Prints the import map using a script tag with a type="importmap" attribute.
	 *
	 * @since 6.5.0
	 */
	public function print_import_map() {
		$import_map = $this->get_import_map();
		if ( ! empty( $import_map['imports'] ) ) {
			wp_print_inline_script_tag(
				(string) wp_json_encode( $import_map, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
				array(
					'type' => 'importmap',
					'id'   => 'wp-importmap',
				)
			);
		}
	}

	/**
	 * Returns the import map array.
	 *
	 * @since 6.5.0
	 *
	 * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective
	 *               URLs, including the version query.
	 */
	private function get_import_map(): array {
		$imports = array();
		foreach ( array_keys( $this->get_dependencies( $this->queue ) ) as $id ) {
			$src = $this->get_src( $id );
			if ( '' !== $src ) {
				$imports[ $id ] = $src;
			}
		}
		return array( 'imports' => $imports );
	}

	/**
	 * Retrieves the list of script modules marked for enqueue.
	 *
	 * Even though this is a private method and is unused in core, there are ecosystem plugins accessing it via the
	 * Reflection API. The ecosystem should rather use {@see self::get_queue()}.
	 *
	 * @since 6.5.0
	 *
	 * @return array<string, array> Script modules marked for enqueue, keyed by script module identifier.
	 */
	private function get_marked_for_enqueue(): array {
		return wp_array_slice_assoc(
			$this->registered,
			$this->queue
		);
	}

	/**
	 * Retrieves all the dependencies for the given script module identifiers, filtered by import types.
	 *
	 * It will consolidate an array containing a set of unique dependencies based
	 * on the requested import types: 'static', 'dynamic', or both. This method is
	 * recursive and also retrieves dependencies of the dependencies.
	 *
	 * @since 6.5.0
	 *
	 * @param string[] $ids          The identifiers of the script modules for which to gather dependencies.
	 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
	 *                                         Default is both.
	 * @return array<string, array> List of dependencies, keyed by script module identifier.
	 */
	private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array {
		$all_dependencies = array();
		$id_queue         = $ids;

		while ( ! empty( $id_queue ) ) {
			$id = array_shift( $id_queue );
			if ( ! isset( $this->registered[ $id ] ) ) {
				continue;
			}

			foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
				if (
					! isset( $all_dependencies[ $dependency['id'] ] ) &&
					in_array( $dependency['import'], $import_types, true ) &&
					isset( $this->registered[ $dependency['id'] ] )
				) {
					$all_dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ];

					// Add this dependency to the list to get dependencies for.
					$id_queue[] = $dependency['id'];
				}
			}
		}

		return $all_dependencies;
	}

	/**
	 * Gets all dependents of a script module.
	 *
	 * This is not recursive.
	 *
	 * @since 6.9.0
	 *
	 * @see WP_Scripts::get_dependents()
	 *
	 * @param string $id The script ID.
	 * @return string[] Script module IDs.
	 */
	private function get_dependents( string $id ): array {
		// Check if dependents map for the handle in question is present. If so, use it.
		if ( isset( $this->dependents_map[ $id ] ) ) {
			return $this->dependents_map[ $id ];
		}

		$dependents = array();

		// Iterate over all registered scripts, finding dependents of the script passed to this method.
		foreach ( $this->registered as $registered_id => $args ) {
			if ( in_array( $id, wp_list_pluck( $args['dependencies'], 'id' ), true ) ) {
				$dependents[] = $registered_id;
			}
		}

		// Add the module's dependents to the map to ease future lookups.
		$this->dependents_map[ $id ] = $dependents;

		return $dependents;
	}

	/**
	 * Gets all recursive dependents of a script module.
	 *
	 * @since 6.9.0
	 *
	 * @see WP_Scripts::get_dependents()
	 *
	 * @param string $id The script ID.
	 * @return string[] Script module IDs.
	 */
	private function get_recursive_dependents( string $id ): array {
		$dependents = array();
		$id_queue   = array( $id );
		$processed  = array();

		while ( ! empty( $id_queue ) ) {
			$current_id = array_shift( $id_queue );

			// Skip unregistered or already-processed script modules.
			if ( ! isset( $this->registered[ $current_id ] ) || isset( $processed[ $current_id ] ) ) {
				continue;
			}

			// Mark as processed to guard against infinite loops from circular dependencies.
			$processed[ $current_id ] = true;

			// Find the direct dependents of the current script.
			foreach ( $this->get_dependents( $current_id ) as $dependent_id ) {
				// Only add the dependent if we haven't found it before.
				if ( ! isset( $dependents[ $dependent_id ] ) ) {
					$dependents[ $dependent_id ] = true;

					// Add dependency to the queue.
					$id_queue[] = $dependent_id;
				}
			}
		}

		return array_keys( $dependents );
	}

	/**
	 * Sorts the given script module identifiers based on their dependencies.
	 *
	 * It will return a list of script module identifiers sorted in the order
	 * they should be printed, so that dependencies are printed before the script
	 * modules that depend on them.
	 *
	 * @since 6.9.0
	 *
	 * @param string[] $ids          The identifiers of the script modules to sort.
	 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
	 *                                         Default is both.
	 * @return string[] Sorted list of script module identifiers.
	 */
	private function get_sorted_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array {
		$sorted = array();

		foreach ( $ids as $id ) {
			$this->sort_item_dependencies( $id, $import_types, $sorted );
		}

		return array_unique( $sorted );
	}

	/**
	 * Recursively sorts the dependencies for a single script module identifier.
	 *
	 * @since 6.9.0
	 *
	 * @param string   $id           The identifier of the script module to sort.
	 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
	 * @param string[] &$sorted      The array of sorted identifiers, passed by reference.
	 * @return bool True on success, false on failure (e.g., missing dependency).
	 */
	private function sort_item_dependencies( string $id, array $import_types, array &$sorted ): bool {
		// If already processed, don't do it again.
		if ( in_array( $id, $sorted, true ) ) {
			return true;
		}

		// If the item doesn't exist, fail.
		if ( ! isset( $this->registered[ $id ] ) ) {
			return false;
		}

		$dependency_ids = array();
		foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) {
			if ( in_array( $dependency['import'], $import_types, true ) ) {
				$dependency_ids[] = $dependency['id'];
			}
		}

		// If the item requires dependencies that do not exist, fail.
		$missing_dependencies = array_diff( $dependency_ids, array_keys( $this->registered ) );
		if ( count( $missing_dependencies ) > 0 ) {
			if ( ! in_array( $id, $this->modules_with_missing_dependencies, true ) ) {
				_doing_it_wrong(
					get_class( $this ) . '::register',
					sprintf(
						/* translators: 1: Script module ID, 2: List of missing dependency IDs. */
						__( 'The script module with the ID "%1$s" was enqueued with dependencies that are not registered: %2$s.' ),
						$id,
						implode( wp_get_list_item_separator(), $missing_dependencies )
					),
					'6.9.1'
				);
				$this->modules_with_missing_dependencies[] = $id;
			}

			return false;
		}

		// Recursively process dependencies.
		foreach ( $dependency_ids as $dependency_id ) {
			if ( ! $this->sort_item_dependencies( $dependency_id, $import_types, $sorted ) ) {
				// A dependency failed to resolve, so this branch fails.
				return false;
			}
		}

		// All dependencies are sorted, so we can now add the current item.
		$sorted[] = $id;

		return true;
	}

	/**
	 * Gets the versioned URL for a script module src.
	 *
	 * If $version is set to false, the version number is the currently installed
	 * WordPress version. If $version is set to null, no version is added.
	 * Otherwise, the string passed in $version is used.
	 *
	 * @since 6.5.0
	 *
	 * @param string $id The script module identifier.
	 * @return string The script module src with a version if relevant.
	 */
	private function get_src( string $id ): string {
		if ( ! isset( $this->registered[ $id ] ) ) {
			return '';
		}

		$script_module = $this->registered[ $id ];
		$src           = $script_module['src'];

		if ( '' !== $src ) {
			if ( false === $script_module['version'] ) {
				$src = add_query_arg( 'ver', get_bloginfo( 'version' ), $src );
			} elseif ( null !== $script_module['version'] ) {
				$src = add_query_arg( 'ver', $script_module['version'], $src );
			}
		}

		/**
		 * Filters the script module source.
		 *
		 * @since 6.5.0
		 *
		 * @param string $src Module source URL.
		 * @param string $id  Module identifier.
		 */
		$src = apply_filters( 'script_module_loader_src', $src, $id );
		if ( ! is_string( $src ) ) {
			$src = '';
		}

		return $src;
	}

	/**
	 * Print data associated with Script Modules.
	 *
	 * The data will be embedded in the page HTML and can be read by Script Modules on page load.
	 *
	 * @since 6.7.0
	 *
	 * Data can be associated with a Script Module via the
	 * {@see "script_module_data_{$module_id}"} filter.
	 *
	 * The data for a Script Module will be serialized as JSON in a script tag with an ID of the
	 * form `wp-script-module-data-{$module_id}`.
	 */
	public function print_script_module_data(): void {
		$modules = array();
		foreach ( array_unique( $this->queue ) as $id ) {
			if ( '@wordpress/a11y' === $id ) {
				$this->a11y_available = true;
			}
			$modules[ $id ] = true;
		}
		foreach ( array_keys( $this->get_import_map()['imports'] ) as $id ) {
			if ( '@wordpress/a11y' === $id ) {
				$this->a11y_available = true;
			}
			$modules[ $id ] = true;
		}

		foreach ( array_keys( $modules ) as $module_id ) {
			/**
			 * Filters data associated with a given Script Module.
			 *
			 * Script Modules may require data that is required for initialization or is essential
			 * to have immediately available on page load. These are suitable use cases for
			 * this data.
			 *
			 * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID
			 * that the data is associated with.
			 *
			 * This is best suited to pass essential data that must be available to the module for
			 * initialization or immediately on page load. It does not replace the REST API or
			 * fetching data from the client.
			 *
			 * Example:
			 *
			 *     add_filter(
			 *         'script_module_data_MyScriptModuleID',
			 *         function ( array $data ): array {
			 *             $data['dataForClient'] = 'ok';
			 *             return $data;
			 *         }
			 *     );
			 *
			 * If the filter returns no data (an empty array), nothing will be embedded in the page.
			 *
			 * The data for a given Script Module, if provided, will be JSON serialized in a script
			 * tag with an ID of the form `wp-script-module-data-{$module_id}`.
			 *
			 * The data can be read on the client with a pattern like this:
			 *
			 * Example:
			 *
			 *     const dataContainer = document.getElementById( 'wp-script-module-data-MyScriptModuleID' );
			 *     let data = {};
			 *     if ( dataContainer ) {
			 *         try {
			 *             data = JSON.parse( dataContainer.textContent );
			 *         } catch {}
			 *     }
			 *     // data.dataForClient === 'ok';
			 *     initMyScriptModuleWithData( data );
			 *
			 * @since 6.7.0
			 *
			 * @param array $data The data associated with the Script Module.
			 */
			$data = apply_filters( "script_module_data_{$module_id}", array() );

			if ( is_array( $data ) && array() !== $data ) {
				/*
				 * This data will be printed as JSON inside a script tag like this:
				 *   <script type="application/json"></script>
				 *
				 * A script tag must be closed by a sequence beginning with `</`. It's impossible to
				 * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
				 * remain unescaped, so `</script>` will be printed as `\u003C/script\u00E3`.
				 *
				 *   - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
				 *   - JSON_UNESCAPED_SLASHES: Don't escape /.
				 *
				 * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
				 *
				 *   - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
				 *   - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
				 *     JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
				 *     before PHP 7.1 without this constant. Available as of PHP 7.1.0.
				 *
				 * The JSON specification requires encoding in UTF-8, so if the generated HTML page
				 * is not encoded in UTF-8 then it's not safe to include those literals. They must
				 * be escaped to avoid encoding issues.
				 *
				 * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
				 * @see https://www.php.net/manual/en/json.constants.php for details on these constants.
				 * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
				 */
				$json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
				if ( ! is_utf8_charset() ) {
					$json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
				}

				wp_print_inline_script_tag(
					(string) wp_json_encode(
						$data,
						$json_encode_flags
					),
					array(
						'type' => 'application/json',
						'id'   => "wp-script-module-data-{$module_id}",
					)
				);
			}
		}
	}

	/**
	 * @access private This is only intended to be called by the registered actions.
	 *
	 * @since 6.7.0
	 */
	public function print_a11y_script_module_html() {
		if ( ! $this->a11y_available ) {
			return;
		}
		echo '<div style="position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip-path:inset(50%);border:0;word-wrap:normal !important;">'
			. '<p id="a11y-speak-intro-text" class="a11y-speak-intro-text" hidden>' . esc_html__( 'Notifications' ) . '</p>'
			. '<div id="a11y-speak-assertive" class="a11y-speak-region" aria-live="assertive" aria-relevant="additions text" aria-atomic="true"></div>'
			. '<div id="a11y-speak-polite" class="a11y-speak-region" aria-live="polite" aria-relevant="additions text" aria-atomic="true"></div>'
			. '</div>';
	}
}



📁

Why Autism Will Never Interfere with Our Happiness

Here we are with another inspiring story about how a family beat the odds (and stereotype) of autism and emerged victorious. Through this series of Autism Success Stories, we want to celebrate the developments and achievements of our young stars and their families, and through them, want to let you know that there is always hope and support for you (as a parent and a professional).

 

This month, we feature 20-year-old Vishal Srinivas, and his parents – Viji and Srinivas. Srinivas works for a multinational corporation and Viji has been a teacher at SAI for 7 years. Vishal was a student at SAI for the duration too. Here is Viji sharing her’s and Vishal’s remarkable story of improvement and happiness.

 

On September 5th, 2000, I took Vishal for his first session with a Speech Language Pathologist (SLP) at Anushakti Nagar, Chembur. As soon as we stepped out of the building after the session, Vishal freed himself from my hold and took off like a bullet. He kept running (on the footpath, thankfully) and didn’t turn back even once. It was a new place and I was terrified. While chasing him, I kept thinking, “God, where am I? What is happening? How will I get through this?”

 

Fast forward to September 5th, 2015, and Vishal stepped out of home with his father to go to the bank. We have a ritual: whenever a person leaves from home, someone from the family stands in the balcony and waves. If I leave, my mother-in-law stands in the balcony. That day, Vishal took his long, self-assured strides (he’s 6’ 2”), stopped, turned around to look at the balcony and waved “Bye ma! Bye patima (grandma)!” It’s a norm for him to wave out to us in the balcony now. Then he takes his long strides, reaches the end of the road and waits for the person accompanying him to catch up – whether his dad or me. My son has come such a long way.

 

Vishal was initially admitted to Sevadaan Special School in Chembur. He was placed in the group of most heavily affected children (probably because the staff found it hard to handle children with autism). I was in awe of children there who were adept at cooking and were paid for it. Today, my son, the masterchef, strides into our kitchen like a lion strides into his territory and takes charge. He inspects what is being cooked, stirs simmering dishes and is keen on helping. It’s like he’s the supervisor and I’m the assistant. Not without good reason. He is immensely fond of cooking – in fact, it’s his passion. The other day, he was preparing his favorite potato sabzi and it turned out spicy. I told him so. Rather than getting anxious, he calmly added more tomatoes to reduce the spiciness. He has remarkable acumen when it comes to altering the taste of dishes, whether they have to be sweetened or spiced up. And when I tell him to turn off the gas or add salt to a dish, I am confident that he will do it. No need for me to double check. All this from a boy who could not sit in one place due to hyperactivity.

How my son conquered autism

Vishal is sensitive to sound. When he was about 8 years old, he would bang his head hard against my arm whenever he heard a loud honk. It would hurt. But with time, he has learned to put his long fingers into his ears and partly insulate himself against loud noises. I laugh to myself when teenagers passing by wonder aloud which device to talk to someone or listen to music is so small that it is not visible… During the initial years, I simply took him along (more out of compulsion) and hoped that he would take care of himself in public. But after I joined the family consultation program, I started giving him responsibilities during our monthly provision shopping. And he loved it. Now I tell him what he has to buy orally (he’s an auditory learner) and simply have to linger around. He thinks and buys what he has been entrusted with. And once we check out, bags containing the items he has bought are his responsibility.

 

It’s age appropriate too. How many neuro-typical 20-year-olds help their parents? In fact, parents have to be more careful with 20-year-olds today. Who are they hanging out with? Are they doing something wrong? Are they lying? Not with Vishal, or any other child with autism, I believe. Living with someone with autism is a totally different experience. Our kids are loving, helpful and honest. If Vishal opens the fridge and eats a chocolate (which he rarely does, because he knows he has to follow a strict diet), he promptly walks up to me and says “I ate a chocolate.” My son’s honesty and love are reassuring.

 

Living with someone with autism can be both frustrating and gratifying. Vishal’s OCD can cause both these emotions in me. Frustration, well, if you know someone with OCD, you know what I mean. Gratifying, because once he returns home from somewhere, he puts his footwear in place, changes his clothes, washes his hands and feet and only then gets on the bed. His and my awareness and understanding have increased each day after I joined the family consultation program. I have stopped giving instructions to Vishal – it has been two years now – and the relation between him and me has blossomed. I have gained immense confidence in understanding my child, and this has helped me address and reduce one of his biggest challenges – his anxiety.

 

I want to share an incident with you. Vishal’s anxiety had reached disturbing levels in 2010. He would indulge in self-injurious behavior often. Years of purely following instructions can do that to you. He would use self-injurious behavior as a means of communication whenever he was anxious, not just on himself, but on others too. He would pull or push anyone close by, and this was impeding all his progress. It made many people around him anxious too (including yours truly). But a few weeks ago, something different occurred. We have a yoga instructor who comes home and helps Vishal do yoga for an hour (Vishal suffers from asthma. So though he loves the outdoors, he can’t spend much time there). During yoga, Vishal sweats profusely, like the cricketers we see on TV. During one session, he was tired and said something to the instructor. The instructor assumed that he had spoken in Tamil and said “Say it in English.” That was where Vishal had a bout of anxiety. Two years ago, at such times, he would have started hitting himself after saying “Adchiko” – a warning that he was about to cause harm. But today, he sat on the sofa and within a few seconds I was there. “What happened Vishal?”, I asked. The look on his face gave me the feeling that every parent yearns for. He looked at me through eyes with tears almost ready to roll out of them, as if saying “Thank God you are here! I am in so much trouble!” He told me what was troubling him and himself said “No adchiko.” Vishal feels reassured in my presence now. He knows that I am there for him and looks up to me. We share many beautiful moments together. These wonderful improvements in his life are not only because of his effort – they have come because I have worked hard on myself too. The family consultation program has taught me to stay calm when Vishal feels anxious. That gave me the confidence to take care of him which, in turn, gave him the reassurance that he could rely on me for anything. He and I trust each other implicitly now.

my son has conquered autism and does not let it come in his way of enjoying life

I could go on and on… about his love for music from Jagjit Singh to Honey Singh, about how he interacts with my relatives like a mature individual, how he likes to join conversations, how responsibly he conducted himself during the last rites of my grandmother, how he smiled when my relative’s 5-year-old daughter kept harrowing him to speak to her… and on and on… about how impressed people who interact with him are, how he thoroughly enjoyed himself sitting with folded legs on the floor and eating from a banana leaf during a family function. It’s wonderful to see the once hyperactive boy riddled with anxiety now conduct himself with such dignity in public, and become the master chef of my kitchen, and our lives.

 

I believe that we cannot expect professionals ‘cure our children’ magically. Whether a child with autism, or a neuro-typical one, we parents have to spend time with them and help them develop. Not just spend time, we have to encourage them to think for themselves, and treat them with dignity and respect – like grown-ups. This is the biggest lesson that I have learned from Mrs. Kamini Lakhani and my son. I have met many professionals who are also parents of children on the spectrum. But I have never seen anyone give as much respect and build a bond with the children like Mrs. Lakhani. Through our interactions which have spanned more than 7 years, I have learned to respect my son and treat him with dignity, and to expect others to do the same for Vishal and all other children on the autism spectrum.

 

Kamini Lakhani’s thoughts:

 

It is truly heartening to read this wonderful account about Vishal’s emergence. I am privileged to witness this emergence from close quarters.

 

In the past, I have watched the agonizing effects of his anxiety, on himself and others around him. Today, this self assured, competent young man, brings joy to my heart, as he strides through life. What I see today is a beautiful, meaningful relationship between mother and son. A relationship that is based on trust. Hats off to Viji’s hard work and diligence.

 

I look forward with hope, for Vishal to find his place in the sun.

 

If you are the parent or guardian of someone with autism or a learning disability, your family can achieve these remarkable results too. Our parent training program will offer you all the support and guidance that you need for these landmark improvements.

  •  
    1
    Share
  •  
  •  
  •  
  •  
  • 1
  •  

Kamini Lakhani

Kamini Lakhani is the founder and director of SAI Connections. She has been providing services in the field of autism for more than 25 years and is the authorized director of Professional Training for RDI in India and the Middle East. She is also the mother of a young adult with autism.

25 COMMENTS

Leave a Reply

Your email address will not be published. Required fields are marked *