Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 12 Aug 2013 */ namespace IPS\Output; /* To prevent PHP errors (extending class does not exist) revealing path */ use DirectoryIterator; use Exception; use Garfix\JsMinify\Minifier; use InvalidArgumentException; use IPS\Application; use IPS\Data\Store; use IPS\Db; use IPS\Db\Select; use IPS\Dispatcher; use IPS\File; use IPS\Http\Url; use IPS\IPS; use IPS\Lang; use IPS\Output; use IPS\Patterns\ActiveRecord; use IPS\Settings; use IPS\Theme; use OutOfRangeException; use RuntimeException; use UnderflowException; use XMLReader; use XMLWriter; use function array_merge; use function chmod; use function count; use function defined; use function explode; use function file; use function file_exists; use function file_get_contents; use function file_put_contents; use function implode; use function in_array; use function intval; use function is_array; use function is_dir; use function is_file; use function is_string; use function json_encode; use function mb_strtolower; use function md5; use function mkdir; use function preg_match; use function rtrim; use function str_ends_with; use function substr; use function unlink; use function urlencode; use const IPS\CACHEBUST_KEY; use const IPS\IPS_FILE_PERMISSION; use const IPS\IPS_FOLDER_PERMISSION; use const IPS\ROOT_PATH; use const LOCK_EX; if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Javascript: Javascript handler */ class Javascript extends ActiveRecord { /** * @brief [Javascript] Array of found javascript keys and objects */ protected static array $foundJsObjects = array(); /** * @brief [Javascript] Position index for writing javascript to core_javascript */ protected static array $positions = array(); /** * @brief [ActiveRecord] Multiton Store */ protected static array $multitons; /** * @brief [ActiveRecord] Database Table */ public static ?string $databaseTable = 'core_javascript'; /** * @brief [ActiveRecord] Database Prefix */ public static string $databasePrefix = 'javascript_'; /** * @brief Javascript map of file object URLs */ protected static ?array $javascriptObjects = null; /** * Find JavaScript file * * @param string $app Application key or Plugin key * @param string $location Location (front, admin, etc) * @param string $path Path * @param string $name Filename * @return Javascript * @throws OutOfRangeException */ public static function find( string $app, string $location, string $path, string $name ): Javascript { $key = md5( $app . '-' . $location . '-' . $path . '-' . $name ); if ( !in_array( $key, static::$foundJsObjects ) ) { $where = array( 'javascript_app=?', 'javascript_location=?', 'javascript_path=?', 'javascript_name=?' ); $bindings = array( $app, $location, $path, $name ); try { $js = Db::i()->select( '*', 'core_javascript', array_merge( array( implode( ' AND ', $where ) ), $bindings ) )->first(); static::$foundJsObjects[ $key ] = parent::constructFromData( $js ); } catch ( UnderflowException $e ) { throw new OutOfRangeException; } } return static::$foundJsObjects[ $key ]; } /** * Set class properties if this object belongs to an application or a plugin * * @return void */ protected function setAppOrPluginProperties() : void { if ( ( $this->app AND !is_string( $this->app ) ) OR ( $this->plugin ) ) { $this->app = 'core'; $this->location = 'plugins'; $this->path = '/'; $this->type = 'plugin'; $this->plugin = ( !is_string( $this->app ) ) ? $this->app : $this->plugin; } } /** * Create a javascript file. This overwrites any existing JS that matches the same parameters. * If a $this->app is not a string, then it will assume plugin and automatically determine the correct 'app', 'location' and 'path' so these do not need to * be defined. * * @return void *@throws RuntimeException * @throws InvalidArgumentException */ public function save(): void { $this->setAppOrPluginProperties(); if ( ! isset( $this->path ) OR empty( $this->path ) ) { $this->path = '/'; } if ( ! $this->app OR ! $this->location OR ! $this->name ) { throw new InvalidArgumentException; } if ( ! $this->type ) { $this->type = static::_getType( $this->path, $this->name ); } $key = ''; Db::i()->insert( 'core_javascript', array( 'javascript_app' => $this->app, 'javascript_location' => $this->location, 'javascript_path' => $this->path, 'javascript_name' => $this->name, 'javascript_type' => $this->type, 'javascript_content' => $this->content, 'javascript_version' => $this->version, 'javascript_position' => ( $this->position ) ? $this->position : 2000000, 'javascript_key' => $key ) ); } /** * Delete a javascript file * be defined. * * @return void *@throws InvalidArgumentException */ public function delete(): void { $this->setAppOrPluginProperties(); if ( ! isset( $this->path ) OR empty( $this->path ) ) { $this->path = '/'; } if ( ! $this->app OR ! $this->location OR ! $this->name ) { throw new InvalidArgumentException; } if ( ! $this->type ) { $this->type = static::_getType( $this->path, $this->name ); } $_where = "javascript_app=? AND javascript_location=? AND javascript_path=? AND javascript_name=?"; $where = array( $this->app, $this->location, $this->path, $this->name ); array_unshift( $where, $_where ); Db::i()->delete( 'core_javascript', $where ); } /** * Create an XML document * * @param string $app Application * @param array $current Details about current javascript.xml file. Used if $changes is desired to be tracked * @param array|null $changes If set, will set details of any changes by reference * @return XMLWriter */ public static function createXml( string $app, array $current = array(), array &$changes = NULL ): XMLWriter { static::importDev($app); if ( $app === 'core' ) { static::importDev('global'); } /* Build XML and write to app directory */ $xml = new XMLWriter; $xml->openMemory(); $xml->setIndent( TRUE ); $xml->startDocument( '1.0', 'UTF-8' ); /* Root tag */ $xml->startElement('javascript'); $xml->startAttribute('app'); $xml->text( $app ); $xml->endAttribute(); /* Loop */ foreach ( Db::i()->select( '*', 'core_javascript', ( $app === 'core' ) ? Db::i()->in( 'javascript_app', array('core', 'global') ) : array( 'javascript_app=?', $app ), 'javascript_path, javascript_location, javascript_name' ) as $js ) { /* Initiate the