Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Controller
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage
|
||||
* @since 1/31/2024
|
||||
*/
|
||||
|
||||
namespace IPS\Developer;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\Application\Module;
|
||||
use IPS\Db;
|
||||
use IPS\Dispatcher\Controller as DispatcherController;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use OutOfRangeException;
|
||||
use RuntimeException;
|
||||
use const IPS\IPS_FOLDER_PERMISSION;
|
||||
use const IPS\NO_WRITES;
|
||||
use const IPS\ROOT_PATH;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
class Controller extends DispatcherController
|
||||
{
|
||||
/**
|
||||
* @brief Application object
|
||||
*/
|
||||
protected ?Application $application = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $header = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $breadcrumbs = [];
|
||||
|
||||
/**
|
||||
* Execute
|
||||
*
|
||||
* @param string $command The part of the query string which will be used to get the method
|
||||
* @return void
|
||||
*/
|
||||
public function execute( string $command='do' ) : void
|
||||
{
|
||||
/* This controller can only be accessed in developer mode, so we can bypass the check global */
|
||||
|
||||
Output::i()->bypassCsrfKeyCheck = true;
|
||||
|
||||
/* Are we in developer mode? */
|
||||
if( !\IPS\IN_DEV )
|
||||
{
|
||||
Output::i()->error( 'not_in_dev', '2C103/1', 403, '' );
|
||||
}
|
||||
if ( NO_WRITES )
|
||||
{
|
||||
Output::i()->error( 'no_writes', '1C103/M', 403, '' );
|
||||
}
|
||||
|
||||
/* Load application */
|
||||
try
|
||||
{
|
||||
$this->application = Application::load( Request::i()->appKey );
|
||||
}
|
||||
catch ( OutOfRangeException $e )
|
||||
{
|
||||
Output::i()->redirect( Url::internal( 'app=core&module=applications&controller=applications' ) );
|
||||
}
|
||||
|
||||
/* Default header */
|
||||
$this->url = Url::internal( "app=core&module=developer&controller=" . ( Request::i()->controller ?? 'details' ) . "&appKey=" . $this->application->directory );
|
||||
$this->header = 'dev_' . ( Request::i()->controller ?? 'details' );
|
||||
|
||||
/* Hand off to dispatcher */
|
||||
parent::execute();
|
||||
|
||||
Output::i()->jsFiles = array_merge(Output::i()->jsFiles, Output::i()->js('admin_system.js', 'core', 'admin'));
|
||||
Output::i()->jsFiles = array_merge( Output::i()->jsFiles, Output::i()->js( 'admin_developer.js', 'core', 'admin' ) );
|
||||
Output::i()->jsVars['devAppKey'] = $this->application->directory;
|
||||
|
||||
/* Generate a dropdown to easily switch between applications */
|
||||
$applicationMenu = array(
|
||||
'new' => array(
|
||||
'icon' => 'plus-circle',
|
||||
'title' => 'add',
|
||||
'link' => Url::internal( "app=core&module=applications&controller=applications&do=form" ),
|
||||
'data' => [ 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'add' ) ]
|
||||
),
|
||||
'sep' => array(
|
||||
'hr' => true
|
||||
)
|
||||
);
|
||||
foreach( Application::applications() as $app )
|
||||
{
|
||||
$applicationMenu[ $app->directory ] = array(
|
||||
'icon' => $app->_icon,
|
||||
'title' => $app->_title,
|
||||
'link' => Url::internal( "app=core&module=developer&controller=" . ( Request::i()->controller ?? 'details' ) . "&appKey=" . $app->directory )
|
||||
);
|
||||
}
|
||||
|
||||
Output::i()->sidebar['actions']['apps'] = array(
|
||||
'icon' => 'cubes',
|
||||
'title' => 'developer_change_apps',
|
||||
'menu' => $applicationMenu
|
||||
);
|
||||
|
||||
/* Add the build and download button */
|
||||
Output::i()->sidebar['actions']['tools'] = array(
|
||||
'icon' => 'cogs',
|
||||
'title' => 'developer_tools',
|
||||
'menu' => array(
|
||||
'compilejs' => array(
|
||||
'icon' => 'cog',
|
||||
'title' => 'app_compile_js',
|
||||
'link' => Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=compilejs" )->csrf()
|
||||
),
|
||||
'build' => array(
|
||||
'icon' => 'cog',
|
||||
'title' => 'app_build',
|
||||
'link' => Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=build"),
|
||||
'data' => array('ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('app_build'))
|
||||
),
|
||||
'export' => array(
|
||||
'icon' => 'download',
|
||||
'title' => 'download',
|
||||
'link' => Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download"),
|
||||
'data' => array(
|
||||
'controller' => 'system.buildApp',
|
||||
'downloadURL' => Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download"),
|
||||
'buildURL' => Url::internal("app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build"),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if( empty( Output::i()->title ) )
|
||||
{
|
||||
Output::i()->title = $this->application->_title;
|
||||
}
|
||||
|
||||
Output::i()->breadcrumb[] = [ Url::internal( "app=core&module=developer&appKey=" . $this->application->directory ), $this->application->_title ];
|
||||
Output::i()->breadcrumb[] = [ $this->url, $this->header ];
|
||||
foreach( $this->breadcrumbs as $crumb )
|
||||
{
|
||||
Output::i()->breadcrumb[] = $crumb;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ACP Menu Tabs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _getAcpMenuTabs() : array
|
||||
{
|
||||
return array_map( function( $val )
|
||||
{
|
||||
return mb_substr( $val, 9 );
|
||||
}, array_filter( array_keys( Member::loggedIn()->language()->words ), function( $val )
|
||||
{
|
||||
return preg_match( '/^menutab__[a-z]*$/i', $val );
|
||||
} ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available ACP restrictions
|
||||
*
|
||||
* @param Module|null $module The module to get restrictions for
|
||||
* @return array
|
||||
*/
|
||||
protected function _getRestrictions( ?Module $module ) : array
|
||||
{
|
||||
$restrictions = array();
|
||||
$_restrictions = $this->_getJson( ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
||||
|
||||
if ( $module !== null and isset( $_restrictions[ $module->key ] ) )
|
||||
{
|
||||
foreach ( $_restrictions[ $module->key ] as $groupKey => $rows )
|
||||
{
|
||||
foreach ( $rows as $key )
|
||||
{
|
||||
$restrictions[ 'r__'.$groupKey ][ $key ] = 'r__'.$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( $_restrictions as $moduleKey => $groups )
|
||||
{
|
||||
foreach( $groups as $groupKey => $rows )
|
||||
{
|
||||
foreach( $rows as $key )
|
||||
{
|
||||
$restrictions[ 'r__'.$groupKey ][ $key ] = 'r__'.$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $restrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON file
|
||||
*
|
||||
* @param string $file Filepath
|
||||
* @return array Decoded JSON data
|
||||
*/
|
||||
protected function _getJson( string $file ) : array
|
||||
{
|
||||
if( !file_exists( $file ) )
|
||||
{
|
||||
$json = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$json = json_decode( file_get_contents( $file ), TRUE );
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write JSON file
|
||||
*
|
||||
* @param string $file Filepath
|
||||
* @param array $data Data to write
|
||||
* @return void
|
||||
*/
|
||||
protected function _writeJson( string $file, array $data ) : void
|
||||
{
|
||||
try
|
||||
{
|
||||
Application::writeJson( $file, $data );
|
||||
}
|
||||
catch ( RuntimeException $e )
|
||||
{
|
||||
Output::i()->error( 'dev_could_not_write_data', '1C103/4', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Module
|
||||
*
|
||||
* @param bool $returnOnError
|
||||
* @return Module|null
|
||||
*/
|
||||
protected function _loadModule( bool $returnOnError=false ) : ?Module
|
||||
{
|
||||
try
|
||||
{
|
||||
$module = Module::get( $this->application->directory, Request::i()->module_key, Request::i()->location );
|
||||
}
|
||||
catch ( Exception $e )
|
||||
{
|
||||
if( $returnOnError )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Output::i()->error( 'node_error', '2C103/L', 404, '' );
|
||||
}
|
||||
|
||||
return $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modules.json
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function _getModules() : array
|
||||
{
|
||||
$file = ROOT_PATH . "/applications/{$this->application->directory}/data/modules.json";
|
||||
$json = $this->_getJson( $file );
|
||||
$modules = array();
|
||||
$extra = array();
|
||||
$db = array();
|
||||
|
||||
foreach ( Db::i()->select( '*', 'core_modules', array( 'sys_module_application=?', $this->application->directory ) ) as $row )
|
||||
{
|
||||
$db[] = $row;
|
||||
$extra[ $row['sys_module_area'] ][ $row['sys_module_key'] ] = array( 'default' => $row['sys_module_default'], 'id' => $row['sys_module_id'], 'default_controller' => $row['sys_module_default_controller'], 'protected' => $row['sys_module_protected'] );
|
||||
}
|
||||
|
||||
if ( count( $json ) )
|
||||
{
|
||||
$modules = $json;
|
||||
|
||||
foreach( $db as $row )
|
||||
{
|
||||
if( $row['sys_module_default'] )
|
||||
{
|
||||
$modules[ $row['sys_module_area'] ][ $row['sys_module_key'] ]['default'] = true;
|
||||
}
|
||||
elseif( isset( $modules[ $row['sys_module_area'] ][ $row['sys_module_key'] ]['default'] ) )
|
||||
{
|
||||
$modules[ $row['sys_module_area'] ][ $row['sys_module_key'] ]['default'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( $db as $row )
|
||||
{
|
||||
$modules[ $row['sys_module_area'] ][ $row['sys_module_key'] ] = array(
|
||||
'default_controller' => $row['sys_module_default_controller'],
|
||||
'protected' => $row['sys_module_protected'],
|
||||
'default' => $row['sys_module_default']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_file( $file ) )
|
||||
{
|
||||
$this->_writeJson( $file, $modules );
|
||||
}
|
||||
|
||||
/* We get the ID and default flag from the local DB to prevent devs syncing defaults */
|
||||
return array_replace_recursive( $modules, $extra );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queries for a version
|
||||
*
|
||||
* @param int|string $long Version ID
|
||||
* @return array
|
||||
*/
|
||||
protected function _getQueries( int|string $long ) : array
|
||||
{
|
||||
return $this->_getJson( ROOT_PATH . "/applications/{$this->application->directory}/setup/" . ( $long === 'install' ? $long : "upg_{$long}" ) . '/queries.json' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write queries.json file
|
||||
*
|
||||
* @param int|string $long Version ID
|
||||
* @param array $json Data
|
||||
* @return void
|
||||
*/
|
||||
protected function _writeQueries( int|string $long, array $json ) : void
|
||||
{
|
||||
/* Create a directory if we don't already have one */
|
||||
$path = ROOT_PATH . "/applications/{$this->application->directory}/setup/" . ( $long === 'install' ? $long : "upg_{$long}" );
|
||||
if ( ! is_dir( $path ) )
|
||||
{
|
||||
mkdir( $path );
|
||||
chmod( $path, IPS_FOLDER_PERMISSION );
|
||||
}
|
||||
|
||||
/* We need to make sure the array is 1-indexed otherwise the upgrader gets confused - unless this is the "working" version
|
||||
since that causes conflicts if two branches try to add queries - for the "working" version, this same thing is done
|
||||
by \IPS\Application::assignNewVersion() */
|
||||
|
||||
if ( $long === 'working' )
|
||||
{
|
||||
$write = array_values( $json );
|
||||
}
|
||||
else
|
||||
{
|
||||
$write = array();
|
||||
$i = 0;
|
||||
foreach ( $json as $query )
|
||||
{
|
||||
$write[ ++$i ] = $query;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write */
|
||||
$this->_writeJson( $path . '/queries.json', $write );
|
||||
|
||||
/* Update core_dev */
|
||||
Db::i()->update( 'core_dev', array(
|
||||
'last_sync' => time(),
|
||||
'ran' => json_encode( $write ),
|
||||
), array( 'app_key=? AND working_version=?', $this->application->directory, $long ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return array
|
||||
*/
|
||||
public function parseFurlsFile( string $file ) : array
|
||||
{
|
||||
if( !file_exists( $file ) )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$contents = file_get_contents( $file );
|
||||
preg_match( '/\/\*(.+?)\*\/(.+?)\{(.+?)$/is', $contents, $match );
|
||||
if( is_array( $match ) and count( $match ) )
|
||||
{
|
||||
$comments = '/*' . $match[1] . '*/';
|
||||
$json = '{' . trim( $match[3] );
|
||||
$json = preg_replace( '/\/\*(.+?)\*\//is', '', $json );
|
||||
}
|
||||
else
|
||||
{
|
||||
$json = $contents;
|
||||
}
|
||||
|
||||
return json_decode( $json, true );
|
||||
}
|
||||
}
|
||||
+108
-175
@@ -11,38 +11,47 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DirectoryIterator;
|
||||
use IPS\Data\Cache;
|
||||
use IPS\Data\Store;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Developer class used for IN_DEV management
|
||||
*/
|
||||
class _Developer
|
||||
class Developer
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Array of directories that should always be present inside /dev
|
||||
*/
|
||||
protected static $devDirs = array( 'css', 'email', 'html', 'img', 'js' );
|
||||
protected static array $devDirs = array( 'css', 'email', 'html', 'img', 'js' );
|
||||
|
||||
/**
|
||||
* @brief Array of multitons
|
||||
*/
|
||||
protected static $multitons = array();
|
||||
protected static array $multitons = array();
|
||||
|
||||
/**
|
||||
* Synchronises development data between installations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sync()
|
||||
public static function sync() : void
|
||||
{
|
||||
$updated = FALSE;
|
||||
|
||||
foreach ( \IPS\Application::applications() as $app )
|
||||
foreach (Application::applications() as $app )
|
||||
{
|
||||
$thisAppUpdated = static::load( $app->directory )->synchronize();
|
||||
$updated = $updated ?: $thisAppUpdated;
|
||||
@@ -50,24 +59,22 @@ class _Developer
|
||||
|
||||
if( $updated )
|
||||
{
|
||||
\IPS\Plugin\Hook::writeDataFile();
|
||||
|
||||
/* Update JS cache bust */
|
||||
\IPS\Settings::i()->changeValues( array( 'javascript_updated' => time() ) );
|
||||
Settings::i()->changeValues( array( 'javascript_updated' => time() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores objects
|
||||
*
|
||||
* @param string $app Application key
|
||||
* @return object \IPS\Developer
|
||||
* @param string $app Application key
|
||||
* @return Developer \IPS\Developer
|
||||
*/
|
||||
public static function load( $app )
|
||||
public static function load( string $app ): Developer
|
||||
{
|
||||
if ( ! isset( static::$multitons[ $app ] ) )
|
||||
{
|
||||
static::$multitons[ $app ] = new \IPS\Developer( \IPS\Application::load( $app ) );
|
||||
static::$multitons[ $app ] = new Developer( Application::load( $app ) );
|
||||
}
|
||||
|
||||
return static::$multitons[ $app ];
|
||||
@@ -76,15 +83,15 @@ class _Developer
|
||||
/**
|
||||
* @brief Application
|
||||
*/
|
||||
protected $app;
|
||||
protected ?Application $app;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Application $app The application the notification belongs to
|
||||
* @param Application $app The application the notification belongs to
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Application $app )
|
||||
public function __construct( Application $app )
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
@@ -92,7 +99,7 @@ class _Developer
|
||||
/**
|
||||
* @brief Last updates
|
||||
*/
|
||||
protected static $lastUpdates = NULL;
|
||||
protected static ?array $lastUpdates = NULL;
|
||||
|
||||
/**
|
||||
* Sync development data for an application
|
||||
@@ -103,11 +110,11 @@ class _Developer
|
||||
{
|
||||
if ( static::$lastUpdates === NULL )
|
||||
{
|
||||
static::$lastUpdates = iterator_to_array( \IPS\Db::i()->select( '*', 'core_dev' )->setKeyField('app_key') );
|
||||
static::$lastUpdates = iterator_to_array( Db::i()->select( '*', 'core_dev' )->setKeyField('app_key') );
|
||||
}
|
||||
|
||||
/* Get versions */
|
||||
$versions = array_keys( json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/versions.json" ), TRUE ) );
|
||||
$versions = array_keys( json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/data/versions.json" ), TRUE ) );
|
||||
$latestVersion = array_pop( $versions );
|
||||
|
||||
$updated = FALSE;
|
||||
@@ -120,12 +127,12 @@ class _Developer
|
||||
{
|
||||
$content = NULL;
|
||||
|
||||
if( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
{
|
||||
$content = file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" );
|
||||
$content = file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->insert( 'core_dev', array(
|
||||
Db::i()->insert( 'core_dev', array(
|
||||
'app_key' => $this->app->directory,
|
||||
'working_version' => $latestVersion,
|
||||
'last_sync' => time(),
|
||||
@@ -136,19 +143,19 @@ class _Developer
|
||||
else
|
||||
{
|
||||
/* Database schema */
|
||||
if( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
{
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ) )
|
||||
{
|
||||
/* Get schema file */
|
||||
$schema = json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/schema.json" ), TRUE );
|
||||
$schema = json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/data/schema.json" ), TRUE );
|
||||
|
||||
/* Run queries for previous versions */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['working_version'] != $latestVersion )
|
||||
{
|
||||
/* Get all versions past the working version */
|
||||
$dirMatches = [];
|
||||
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/" ) as $dir )
|
||||
foreach (new DirectoryIterator( ROOT_PATH . "/applications/{$this->app->directory}/setup/" ) as $dir )
|
||||
{
|
||||
if ( $dir->isDir() and !$dir->isDot() and preg_match( '/^upg_(\d+)$/', $dir, $matches ) )
|
||||
{
|
||||
@@ -165,38 +172,38 @@ class _Developer
|
||||
{
|
||||
if ( $match == static::$lastUpdates[ $this->app->directory ]['working_version'] )
|
||||
{
|
||||
if( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ) )
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ) )
|
||||
{
|
||||
$queries = json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ), TRUE );
|
||||
$queries = json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ), TRUE );
|
||||
$localQueries = json_decode( static::$lastUpdates[ $this->app->directory ]['ran'], TRUE );
|
||||
foreach ( $queries as $q )
|
||||
{
|
||||
if ( \is_array( $localQueries ) AND !\in_array( $q, $localQueries ) )
|
||||
if ( is_array( $localQueries ) AND !in_array( $q, $localQueries ) )
|
||||
{
|
||||
$method = $q['method'];
|
||||
$params = $q['params'];
|
||||
\IPS\Db::i()->$method( ...$params );
|
||||
Db::i()->$method( ...$params );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ) )
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ) )
|
||||
{
|
||||
$queries = json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ), TRUE );
|
||||
$queries = json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$match}/queries.json" ), TRUE );
|
||||
foreach ( $queries as $q )
|
||||
{
|
||||
try
|
||||
{
|
||||
$method = $q['method'];
|
||||
$params = $q['params'];
|
||||
\IPS\Db::i()->$method( ...$params );
|
||||
Db::i()->$method( ...$params );
|
||||
}
|
||||
catch( \IPS\Db\Exception $e )
|
||||
catch(Db\Exception $e )
|
||||
{
|
||||
/* If the issue is with a create table other than exists, we should just throw it */
|
||||
if ( $q['method'] == 'createTable' and ! \in_array( $e->getCode(), array( 1007, 1050 ) ) )
|
||||
if ( $q['method'] == 'createTable' and ! in_array( $e->getCode(), array( 1007, 1050 ) ) )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
@@ -206,19 +213,19 @@ class _Developer
|
||||
{
|
||||
if ( $q['method'] == 'changeColumn' )
|
||||
{
|
||||
if ( \IPS\Db::i()->checkForTable( $q['params'][0] ) )
|
||||
if ( Db::i()->checkForTable( $q['params'][0] ) )
|
||||
{
|
||||
/* Does the column exist already? */
|
||||
if ( \IPS\Db::i()->checkForColumn( $q['params'][0], $q['params'][2]['name'] ) )
|
||||
if ( Db::i()->checkForColumn( $q['params'][0], $q['params'][2]['name'] ) )
|
||||
{
|
||||
/* Just make sure it's up to date */
|
||||
\IPS\Db::i()->changeColumn( $q['params'][0], $q['params'][2]['name'], $q['params'][2] );
|
||||
Db::i()->changeColumn( $q['params'][0], $q['params'][2]['name'], $q['params'][2] );
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The table exists, so lets just add the column */
|
||||
\IPS\Db::i()->addColumn( $q['params'][0], $q['params'][2] );
|
||||
Db::i()->addColumn( $q['params'][0], $q['params'][2] );
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -232,7 +239,7 @@ class _Developer
|
||||
{
|
||||
if ( $q['method'] == 'renameTable' )
|
||||
{
|
||||
if ( \IPS\Db::i()->checkForTable( $q['params'][1] ) )
|
||||
if ( Db::i()->checkForTable( $q['params'][1] ) )
|
||||
{
|
||||
/* The table we are renaming to *does* exist */
|
||||
continue;
|
||||
@@ -242,7 +249,7 @@ class _Developer
|
||||
throw $e;
|
||||
}
|
||||
/* If the error isn't important we should ignore it */
|
||||
else if( !\in_array( $e->getCode(), array( 1007, 1008, 1050, 1051, 1060, 1061, 1062, 1091 ) ) )
|
||||
else if( !in_array( $e->getCode(), array( 1007, 1008, 1050, 1051, 1060, 1061, 1062, 1091, 1146 ) ) )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
@@ -256,25 +263,25 @@ class _Developer
|
||||
}
|
||||
|
||||
/* Run queries for this version */
|
||||
$queries = json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ), TRUE );
|
||||
$queries = json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ), TRUE );
|
||||
$localQueries = json_decode( static::$lastUpdates[ $this->app->directory ]['ran'], TRUE );
|
||||
|
||||
if( \is_array($queries) )
|
||||
if( is_array($queries) )
|
||||
{
|
||||
foreach ( $queries as $q )
|
||||
{
|
||||
if ( !\is_array($localQueries) OR !\in_array( $q, $localQueries ) )
|
||||
if ( !is_array($localQueries) OR !in_array( $q, $localQueries ) )
|
||||
{
|
||||
/* Check if the table exists, as it may be an import */
|
||||
if ( $q['method'] === 'renameTable' and \IPS\Db::i()->checkForTable( $q['params'][0] ) === FALSE )
|
||||
if ( $q['method'] === 'renameTable' and Db::i()->checkForTable( $q['params'][0] ) === FALSE )
|
||||
{
|
||||
if ( isset( $schema[ $q['params'][1] ] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->createTable( $schema[ $q['params'][1] ] );
|
||||
Db::i()->createTable( $schema[ $q['params'][1] ] );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ) { }
|
||||
catch (Db\Exception $e ) { }
|
||||
}
|
||||
}
|
||||
/* Run */
|
||||
@@ -284,9 +291,9 @@ class _Developer
|
||||
{
|
||||
$method = $q['method'];
|
||||
$params = $q['params'];
|
||||
\IPS\Db::i()->$method( ...$params );
|
||||
Db::i()->$method( ...$params );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ) { }
|
||||
catch (Db\Exception $e ) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,12 +303,12 @@ class _Developer
|
||||
}
|
||||
else
|
||||
{
|
||||
$queries = json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ), TRUE );
|
||||
$queries = json_decode( file_get_contents( ROOT_PATH . "/applications/{$this->app->directory}/setup/upg_{$latestVersion}/queries.json" ), TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for missing tables or columns */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/schema.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/schema.json" ) )
|
||||
{
|
||||
$this->app->installDatabaseSchema( TRUE );
|
||||
|
||||
@@ -309,7 +316,7 @@ class _Developer
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/settings.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/settings.json" ) )
|
||||
{
|
||||
$this->app->installSettings();
|
||||
|
||||
@@ -317,7 +324,7 @@ class _Developer
|
||||
}
|
||||
|
||||
/* Modules */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/modules.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/modules.json" ) )
|
||||
{
|
||||
$this->app->installModules();
|
||||
|
||||
@@ -325,7 +332,7 @@ class _Developer
|
||||
}
|
||||
|
||||
/* Tasks */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/tasks.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/tasks.json" ) )
|
||||
{
|
||||
$this->app->installTasks();
|
||||
|
||||
@@ -333,138 +340,36 @@ class _Developer
|
||||
}
|
||||
|
||||
/* Widgets */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/widgets.json" ) )
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/widgets.json" ) )
|
||||
{
|
||||
$this->app->installWidgets();
|
||||
|
||||
$updated = TRUE;
|
||||
}
|
||||
|
||||
/* Skin Settings */
|
||||
if ( static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/themesettings.json" ) )
|
||||
{
|
||||
if ( $this->app->directory == 'core' )
|
||||
{
|
||||
/* Make sure we've got a skin set ID 1 (constant DEFAULT_THEME_ID) */
|
||||
try
|
||||
{
|
||||
$skinSetOne = \IPS\Db::i()->select( '*', 'core_themes', array( 'set_id=?', \IPS\DEFAULT_THEME_ID ) )->first();
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
$skinSetOne = array();
|
||||
}
|
||||
|
||||
if ( ! isset( $skinSetOne['set_id'] ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_themes', array(
|
||||
'set_id' => \IPS\DEFAULT_THEME_ID,
|
||||
'set_name' => 'Default',
|
||||
'set_key' => 'master',
|
||||
'set_parent_id' => 0,
|
||||
'set_parent_array' => '[]',
|
||||
'set_child_array' => '[]',
|
||||
'set_permissions' => '*',
|
||||
'set_author_name' => "Invision Power Services, Inc",
|
||||
'set_author_url' => 'https://www.invisioncommunity.com',
|
||||
'set_added' => time(),
|
||||
'set_updated' => time(),
|
||||
'set_template_settings' => '[]',
|
||||
'set_version' => \IPS\Application::load( $this->app->directory )->version,
|
||||
'set_long_version' => \IPS\Application::load( $this->app->directory )->long_version,
|
||||
) );
|
||||
|
||||
\IPS\Lang::saveCustom( 'core', "core_theme_set_title_" . \IPS\DEFAULT_THEME_ID, "Default" );
|
||||
}
|
||||
else if ( $skinSetOne['set_name'] != 'Default' )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_themes', array( 'set_name' => 'Default' ), array( 'set_id=?', \IPS\DEFAULT_THEME_ID ) );
|
||||
\IPS\Lang::saveCustom( 'core', "core_theme_set_title_" . \IPS\DEFAULT_THEME_ID, "Default" );
|
||||
}
|
||||
|
||||
unset( \IPS\Data\Store::i()->themes );
|
||||
}
|
||||
|
||||
$currentSettings = iterator_to_array( \IPS\Db::i()->select( '*', 'core_theme_settings_fields', array( 'sc_set_id=? AND sc_app=?', \IPS\DEFAULT_THEME_ID, $this->app->directory ) )->setKeyField('sc_key') );
|
||||
|
||||
$json = ( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/themesettings.json" ) ) ?
|
||||
json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/themesettings.json" ), TRUE ) :
|
||||
array();
|
||||
$jsonKeys = array();
|
||||
|
||||
/* Add */
|
||||
foreach( $json as $key => $data)
|
||||
{
|
||||
$jsonKeys[] = $data['sc_key'];
|
||||
|
||||
if ( ! isset( $currentSettings[ $data['sc_key'] ] ) )
|
||||
{
|
||||
$currentId = \IPS\Db::i()->insert( 'core_theme_settings_fields', array(
|
||||
'sc_set_id' => \IPS\DEFAULT_THEME_ID,
|
||||
'sc_key' => $data['sc_key'],
|
||||
'sc_tab_key' => $data['sc_tab_key'],
|
||||
'sc_type' => $data['sc_type'],
|
||||
'sc_multiple' => $data['sc_multiple'],
|
||||
'sc_default' => $data['sc_default'],
|
||||
'sc_content' => $data['sc_content'],
|
||||
'sc_show_in_vse' => ( isset( $data['sc_show_in_vse'] ) ) ? $data['sc_show_in_vse'] : 0,
|
||||
'sc_updated' => time(),
|
||||
'sc_app' => $this->app->directory,
|
||||
'sc_title' => $data['sc_title'],
|
||||
'sc_order' => $data['sc_order'],
|
||||
'sc_condition' => $data['sc_condition'],
|
||||
) );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update */
|
||||
\IPS\Db::i()->update( 'core_theme_settings_fields', array(
|
||||
'sc_tab_key' => $data['sc_tab_key'],
|
||||
'sc_type' => $data['sc_type'],
|
||||
'sc_multiple' => $data['sc_multiple'],
|
||||
'sc_default' => $data['sc_default'],
|
||||
'sc_show_in_vse' => ( isset( $data['sc_show_in_vse'] ) ) ? $data['sc_show_in_vse'] : 0,
|
||||
'sc_content' => $data['sc_content'],
|
||||
'sc_title' => $data['sc_title'],
|
||||
'sc_order' => $data['sc_order'],
|
||||
'sc_condition' => $data['sc_condition'],
|
||||
), array( 'sc_set_id=? AND sc_key=? AND sc_app=?', \IPS\DEFAULT_THEME_ID, $data['sc_key'], $this->app->directory ) );
|
||||
|
||||
$currentId = $currentSettings[ $data['sc_key'] ]['sc_id'];
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete('core_theme_settings_values', array('sv_id=?', $currentId ) );
|
||||
\IPS\Db::i()->insert('core_theme_settings_values', array( 'sv_id' => $currentId, 'sv_value' => (string) $data['sc_default'] ) );
|
||||
}
|
||||
|
||||
/* Remove items not in the JSON file */
|
||||
foreach( $currentSettings as $key => $data )
|
||||
{
|
||||
if ( ! \in_array( $data['sc_key'], $jsonKeys ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_theme_settings_fields', array( 'sc_set_id=? AND sc_key=? AND sc_app=?', \IPS\DEFAULT_THEME_ID, $data['sc_key'], $this->app->directory ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme Editor Settings */
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/data/themeeditor.json" ) and static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/themeeditor.json" ) )
|
||||
{
|
||||
$this->app->installThemeEditorSettings();
|
||||
|
||||
$updated = TRUE;
|
||||
}
|
||||
|
||||
/* Custom Templates */
|
||||
if( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/data/customtemplates.json" ) and static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/customtemplates.json" ) )
|
||||
{
|
||||
$this->app->installCustomTemplates();
|
||||
$updated = true;
|
||||
}
|
||||
|
||||
/* ACP Search Keywords */
|
||||
if ( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/acpsearch.json" ) AND static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/acpsearch.json" ) )
|
||||
if ( file_exists( ROOT_PATH . "/applications/{$this->app->directory}/data/acpsearch.json" ) AND static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( ROOT_PATH . "/applications/{$this->app->directory}/data/acpsearch.json" ) )
|
||||
{
|
||||
$this->app->installSearchKeywords();
|
||||
|
||||
$updated = TRUE;
|
||||
}
|
||||
|
||||
/* Hooks */
|
||||
if ( file_exists( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/hooks.json" ) AND static::$lastUpdates[ $this->app->directory ]['last_sync'] < filemtime( \IPS\ROOT_PATH . "/applications/{$this->app->directory}/data/hooks.json" ) )
|
||||
{
|
||||
$this->app->installHooks();
|
||||
|
||||
$updated = TRUE;
|
||||
}
|
||||
|
||||
if ( method_exists( $this->app, 'developerSync' ) )
|
||||
{
|
||||
$devUpdated = $this->app->developerSync( static::$lastUpdates[ $this->app->directory ]['last_sync'] );
|
||||
@@ -474,20 +379,48 @@ class _Developer
|
||||
/* Update record */
|
||||
if ( $updated === TRUE )
|
||||
{
|
||||
\IPS\Theme::load( \IPS\DEFAULT_THEME_ID )->saveSet();
|
||||
Theme::load(DEFAULT_THEME_ID)->saveSet();
|
||||
|
||||
\IPS\Db::i()->update( 'core_dev', array(
|
||||
Db::i()->update( 'core_dev', array(
|
||||
'working_version' => $latestVersion,
|
||||
'last_sync' => time(),
|
||||
'ran' => isset( $queries ) ? json_encode( $queries ) : array(),
|
||||
), array( 'app_key=?', $this->app->directory ) );
|
||||
|
||||
\IPS\Data\Store::i()->clearAll();
|
||||
\IPS\Data\Cache::i()->clearAll();
|
||||
Store::i()->clearAll();
|
||||
Cache::i()->clearAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a link which will open the file in the IDE
|
||||
*
|
||||
* @param string $filePath
|
||||
* @param int $line
|
||||
* @return string|bool|null
|
||||
*/
|
||||
public static function getIdeHref( string $filePath, int $line = 0): string|bool|null
|
||||
{
|
||||
return static::getWhoopsHandler()?->getEditorHref( $filePath, $line );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Whoops handler
|
||||
*
|
||||
* @return PrettyPageHandler|null
|
||||
*/
|
||||
protected static function getWhoopsHandler(): PrettyPageHandler|null
|
||||
{
|
||||
static $handler = NULL;
|
||||
if( !$handler and DEV_WHOOPS_EDITOR )
|
||||
{
|
||||
$handler = new PrettyPageHandler();
|
||||
$handler->setEditor(DEV_WHOOPS_EDITOR);
|
||||
}
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user