3907 lines
128 KiB
PHP
3907 lines
128 KiB
PHP
<?php
|
|
/**
|
|
* @brief Developer Center 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
|
|
* @since 18 Feb 2013
|
|
*/
|
|
|
|
namespace IPS\core\modules\admin\applications;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Developer Center Controller
|
|
*/
|
|
class _developer extends \IPS\Dispatcher\Controller
|
|
{
|
|
/**
|
|
* Execute
|
|
*
|
|
* @param string $command The part of the query string which will be used to get the method
|
|
* @return void
|
|
*/
|
|
public function execute( $command='do' )
|
|
{
|
|
/* Are we in developer mode? */
|
|
if( !\IPS\IN_DEV )
|
|
{
|
|
\IPS\Output::i()->error( 'not_in_dev', '2C103/1', 403, '' );
|
|
}
|
|
if ( \IPS\NO_WRITES )
|
|
{
|
|
\IPS\Output::i()->error( 'no_writes', '1C103/M', 403, '' );
|
|
}
|
|
|
|
/* Load application */
|
|
try
|
|
{
|
|
$this->application = \IPS\Application::load( \IPS\Request::i()->appKey );
|
|
}
|
|
catch ( \OutOfRangeException $e )
|
|
{
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=applications&controller=applications' ) );
|
|
}
|
|
|
|
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}" ), $this->application->_title );
|
|
|
|
/* Get tab content */
|
|
$this->activeTab = \IPS\Request::i()->tab ?: 'modules-front';
|
|
|
|
/* Hand off to dispatcher */
|
|
return parent::execute( $command );
|
|
}
|
|
|
|
/**
|
|
* Tree sorting calls to do=reorder, but we want to let the individual tabs handle that in this case.
|
|
* This method just takes the request and passes it to manage(), which in turn passes it to the correct tab, which then finally handles the reordering.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function reorder()
|
|
{
|
|
return $this->manage();
|
|
}
|
|
|
|
/**
|
|
* Manage: Works out tab and fetches content
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function manage()
|
|
{
|
|
/* Work out output */
|
|
if ( $pos = mb_strpos( $this->activeTab, '-' ) )
|
|
{
|
|
$activeTabContents = call_user_func( array( $this, '_manage' . mb_ucfirst( mb_substr( $this->activeTab, 0, $pos ) ) ), mb_substr( $this->activeTab, $pos + 1 ) );
|
|
}
|
|
else
|
|
{
|
|
$activeTabContents = call_user_func( array( $this, '_manage' . mb_ucfirst( $this->activeTab ) ) );
|
|
}
|
|
|
|
/* If this is an AJAX request, just return it */
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
\IPS\Output::i()->output = $activeTabContents;
|
|
return;
|
|
}
|
|
|
|
/* Build tab list */
|
|
$tabs = array();
|
|
$tabs['acpmenu'] = 'dev_acpmenu';
|
|
$tabs['acprestrictions'] = 'dev_acprestrictions';
|
|
$tabs['schema'] = 'dev_schema';
|
|
$tabs['extensions'] = 'dev_extensions';
|
|
$tabs['hooks'] = 'plugin_hooks';
|
|
$tabs['modules-admin'] = 'dev_module_admin';
|
|
$tabs['modules-front'] = 'dev_module_front';
|
|
$tabs['settings'] = 'dev_settings';
|
|
$tabs['tasks'] = 'dev_tasks';
|
|
$tabs['versions'] = 'dev_versions';
|
|
$tabs['widgets'] = 'dev_widgets';
|
|
|
|
/* Display */
|
|
if ( $activeTabContents )
|
|
{
|
|
/* Add the build and download button */
|
|
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_system.js', 'core', 'admin' ) );
|
|
\IPS\Output::i()->sidebar['actions']['build'] = array(
|
|
'icon' => 'download',
|
|
'title' => 'download',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download" ),
|
|
'data' => array(
|
|
'controller' => 'system.buildApp',
|
|
'downloadURL' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=download" ),
|
|
'buildURL' => \IPS\Http\Url::internal( "app=core&module=applications&controller=applications&appKey={$this->application->directory}&do=download&type=build" ),
|
|
)
|
|
);
|
|
|
|
\IPS\Output::i()->title = $this->application->_title;
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $this->activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}" ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modules: Show Modules
|
|
*
|
|
* @param string $location Location (e.g. "admin" or "front")
|
|
* @return string HTML to display
|
|
*/
|
|
protected function _manageModules( $location )
|
|
{
|
|
/* Get modules */
|
|
$appKey = $this->application->directory;
|
|
$modules = $this->_getModules();
|
|
|
|
/* Are we setting a default? */
|
|
if ( isset( \IPS\Request::i()->default ) )
|
|
{
|
|
$modules[ $location ][ \IPS\Request::i()->root ]['default_controller'] = mb_substr( \IPS\Request::i()->default, 0, -4 );
|
|
$this->_writeModules( $modules );
|
|
|
|
$module = \IPS\Application\Module::get( $this->application->directory, \IPS\Request::i()->root, $location );
|
|
$module->default_controller = mb_substr( \IPS\Request::i()->default, 0, -4 );
|
|
$module->save();
|
|
}
|
|
|
|
/* Or deleting a controller? */
|
|
if ( isset( \IPS\Request::i()->delete ) )
|
|
{
|
|
if ( @unlink( \IPS\ROOT_PATH . "/applications/{$appKey}/modules/{$location}/" . \IPS\Request::i()->root . '/' . \IPS\Request::i()->delete ) === FALSE )
|
|
{
|
|
\IPS\Output::i()->error( 'dev_could_not_write_controller', '1C103/I', 403, '' );
|
|
}
|
|
|
|
/* delete all the other stuff */
|
|
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
if (isset ( $restrictions[ \IPS\Request::i()->root ][ mb_substr(\IPS\Request::i()->delete,0 ,-4 )] ) )
|
|
{
|
|
unset($restrictions[ \IPS\Request::i()->root ][ mb_substr(\IPS\Request::i()->delete,0 ,-4 )] );
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
}
|
|
|
|
$menu = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json" );
|
|
if (isset ( $menu[ \IPS\Request::i()->root ][ mb_substr(\IPS\Request::i()->delete,0 ,-4 )] ) )
|
|
{
|
|
unset($menu[ \IPS\Request::i()->root ][ mb_substr(\IPS\Request::i()->delete,0 ,-4 )] );
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json", $menu );
|
|
}
|
|
|
|
|
|
if ( \IPS\Request::i()->isAjax() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Show tree */
|
|
$url = \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&tab=modules-{$location}" );
|
|
return new \IPS\Helpers\Tree\Tree(
|
|
$url,
|
|
\IPS\Member::loggedIn()->language()->addToStack('dev_modules', FALSE, array( 'sprintf' => array( ucwords( $location ) ) ) ),
|
|
/* Get Roots */
|
|
function() use ( $appKey, $location, $modules, $url )
|
|
{
|
|
$rows = array();
|
|
|
|
if( !empty($modules[ $location ]) AND is_array($modules[ $location ]) )
|
|
{
|
|
foreach ( $modules[ $location ] as $k => $module )
|
|
{
|
|
$rows[ $k ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, $k, $k, TRUE, array(
|
|
'default'=> array(
|
|
'icon' => ( array_key_exists( 'default', $module ) ) ? ( $module['default'] ? 'star' : 'star-o' ) : 'star-o',
|
|
'title' => 'make_default_module',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=setDefaultModule&id={$module['id']}&location={$location}" ),
|
|
),
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'modules_add_controller',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=addController&module_key={$k}&location={$location}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('modules_add_controller') )
|
|
),
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=moduleForm&key={$k}&location={$location}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('edit') ),
|
|
'hotkey' => 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=deleteModule&key={$k}&location={$location}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), "", NULL, NULL );
|
|
}
|
|
}
|
|
return $rows;
|
|
},
|
|
/* Get Row */
|
|
function( $key, $root=FALSE ) use ( $url, $appKey, $location )
|
|
{
|
|
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, $key, $key, TRUE, array(
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'modules_add_controller',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=addController&module_key={$key}&location={$location}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('modules_add_controller') )
|
|
),
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=moduleForm&key={$key}&location={$location}" ),
|
|
'data' => array( 'ipDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('edit') ),
|
|
'hotkey' => 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=deleteModule&key={$key}&location={$location}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), '', NULL, NULL, $root );
|
|
},
|
|
/* Get Row's Parent ID */
|
|
function( $id )
|
|
{
|
|
return NULL;
|
|
},
|
|
/* Get Children */
|
|
function( $key ) use ( $appKey, $location, $modules, $url )
|
|
{
|
|
$rows = array();
|
|
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$appKey}/modules/{$location}/{$key}" ) as $controller )
|
|
{
|
|
if ( $controller->isFile() and \substr( $controller, 0, 1 ) !== '.' and $controller->getFilename() !== 'index.html' )
|
|
{
|
|
$rows[] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, $controller, $controller, FALSE, array(
|
|
'default' => array(
|
|
'icon' => ( str_replace( '.php', '', $controller ) == $modules[ $location ][ $key ]['default_controller'] ) ? 'star' : 'star-o',
|
|
'title' => 'modules_make_default',
|
|
'link' => $url->setQueryString( array( 'root' => $key, 'default' => (string) $controller ) ),
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => $url->setQueryString( array( 'root' => $key, 'delete' => (string) $controller ) ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), '', NULL, NULL, FALSE, NULL, NULL, ( $modules[ $location ][ $key ]['default_controller'] == $controller ? array( 'green', 'modules_default' ) : NULL ) );
|
|
}
|
|
}
|
|
return $rows;
|
|
},
|
|
/* Get Root Buttons */
|
|
function() use ( $appKey, $location )
|
|
{
|
|
return array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'modules_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=moduleForm&key=&location={$location}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('modules_add') )
|
|
),
|
|
);
|
|
},
|
|
FALSE,
|
|
TRUE,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Make this the default module
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setDefaultModule()
|
|
{
|
|
try
|
|
{
|
|
$module = \IPS\Application\Module::load( \IPS\Request::i()->id );
|
|
$module->setAsDefault();
|
|
}
|
|
catch ( \OutOfRangeException $e )
|
|
{
|
|
\IPS\Output::i()->error( 'no_module_for_default', '2C133/A', 403, '' );
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$module->application}&tab=modules-{$module->area}" ), 'saved' );
|
|
}
|
|
|
|
|
|
/**
|
|
* Add/Edit Module
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function moduleForm()
|
|
{
|
|
/* Get JSON */
|
|
$modules = $this->_getModules();
|
|
$application = $this->application;
|
|
$location = \IPS\Request::i()->location;
|
|
|
|
/* Load existing module if we're editing */
|
|
if ( \IPS\Request::i()->key )
|
|
{
|
|
if ( !isset( $modules[ $location ][ \IPS\Request::i()->key ] ) )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/J', 404, '' );
|
|
}
|
|
$current = array( 'module_key' => \IPS\Request::i()->key, 'protected' => $modules[ $location ][ \IPS\Request::i()->key ]['protected'], 'default_controller' => $modules[ $location ][ \IPS\Request::i()->key ]['default_controller'] );
|
|
}
|
|
else
|
|
{
|
|
$current = array( 'module_key' => NULL, 'protected' => FALSE, 'default_controller' => NULL );
|
|
}
|
|
|
|
/* Build the form */
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Text( 'module_key', $current['module_key'], TRUE, array( 'maxLength' => 32 ), function( $val ) use ( $application, $modules, $location, $current )
|
|
{
|
|
if ( !preg_match( '/^[a-z]*$/', $val ) )
|
|
{
|
|
throw new \DomainException( 'module_key_bad' );
|
|
}
|
|
|
|
try
|
|
{
|
|
$module = ( !$current['module_key'] OR $current['module_key'] != $val ) ? \IPS\Application\Module::load( $val, 'sys_module_key', array( 'sys_module_application=? and sys_module_area=?', $application->directory, $location ) ) : NULL;
|
|
if( $module === NULL )
|
|
{
|
|
throw new \OutOfRangeException;
|
|
}
|
|
|
|
throw new \DomainException( 'module_key_exists' );
|
|
}
|
|
catch ( \OutOfRangeException $e ) {}
|
|
} ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'module_protected', $current['protected'], TRUE ) );
|
|
if ( \IPS\Request::i()->key )
|
|
{
|
|
$form->add( new \IPS\Helpers\Form\Text( 'module_default_controller', $current['default_controller'] ) );
|
|
}
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'modules_add', $form, FALSE );
|
|
if ( \IPS\Request::i()->isAjax() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
if( $current['module_key'] )
|
|
{
|
|
$module = \IPS\Application\Module::get( $this->application->directory, $current['module_key'], $location );
|
|
}
|
|
else
|
|
{
|
|
$module = new \IPS\Application\Module;
|
|
$module->application = $this->application->directory;
|
|
$module->area = $location;
|
|
}
|
|
|
|
$module->key = $values['module_key'];
|
|
$module->protected = $values['module_protected'];
|
|
$module->default_controller = isset( $values['module_default_controller'] ) ? $values['module_default_controller'] : '';
|
|
$module->save();
|
|
|
|
$modules[ $location ][ $module->key ] = array(
|
|
'default_controller' => $module->default_controller,
|
|
'protected' => $module->protected,
|
|
);
|
|
|
|
if( $current['module_key'] AND $current['module_key'] != $module->key )
|
|
{
|
|
unset( $modules[ $location ][ $current['module_key'] ] );
|
|
}
|
|
|
|
$this->_writeModules( $modules );
|
|
|
|
if( $current['module_key'] )
|
|
{
|
|
$oldDir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/modules/{$location}/{$current['module_key']}";
|
|
$newDir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/modules/{$location}/{$module->key}";
|
|
@rename( $oldDir, $newDir );
|
|
chmod( $newDir, \IPS\IPS_FOLDER_PERMISSION );
|
|
}
|
|
else
|
|
{
|
|
$dir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/modules/{$location}/{$module->key}";
|
|
@mkdir( $dir );
|
|
chmod( $dir, \IPS\IPS_FOLDER_PERMISSION );
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=modules-{$location}" ), 'saved' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete Module
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function deleteModule()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
/* Load the module */
|
|
try
|
|
{
|
|
$module = \IPS\Application\Module::get( $this->application->directory, \IPS\Request::i()->key, \IPS\Request::i()->location );
|
|
}
|
|
catch ( \OutOfRangeException $e )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/K', 404, '' );
|
|
}
|
|
|
|
/* Remove it from the JSON */
|
|
$modules = $this->_getModules();
|
|
unset( $modules[ $module->area ][ $module->key ] );
|
|
$this->_writeModules( $modules );
|
|
|
|
/* Delete it */
|
|
$location = $module->area;
|
|
$module->delete();
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=modules-{$location}" ) );
|
|
}
|
|
|
|
/**
|
|
* Manage ACP Menu
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _manageAcpmenu()
|
|
{
|
|
$modules = $this->_getModules();
|
|
$url = \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acpmenu" );
|
|
$menu = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json" );
|
|
$appKey = $this->application->directory;
|
|
|
|
/* Handle reordering */
|
|
if ( \IPS\Request::i()->do === 'reorder' )
|
|
{
|
|
/* Normalise AJAX vs non-AJAX */
|
|
if( isset( \IPS\Request::i()->ajax_order ) )
|
|
{
|
|
$order = array();
|
|
$position = array();
|
|
foreach( \IPS\Request::i()->ajax_order as $id => $parent )
|
|
{
|
|
/* We have to fudge children "ids" to prevent conflicts in the array */
|
|
$id = str_replace( 's@', '', $id );
|
|
|
|
if ( !isset( $order[ $parent ] ) )
|
|
{
|
|
$order[ $parent ] = array();
|
|
$position[ $parent ] = 1;
|
|
}
|
|
$order[ $parent ][ $id ] = $position[ $parent ]++;
|
|
}
|
|
}
|
|
/* Non-AJAX way */
|
|
else
|
|
{
|
|
$order = array( \IPS\Request::i()->root ?: 'null' => \IPS\Request::i()->order );
|
|
}
|
|
|
|
/* Sort */
|
|
$_menu = $menu;
|
|
$menu = array();
|
|
|
|
if( isset( $order['null'] ) )
|
|
{
|
|
foreach( $order['null'] as $key => $position )
|
|
{
|
|
foreach ( $_menu as $_parent => $_items )
|
|
{
|
|
if ( $key == $_parent )
|
|
{
|
|
$menu[ $_parent ] = $_menu[ $_parent ];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
foreach ( $order as $parent => $items )
|
|
{
|
|
if ( $parent === NULL OR array_key_exists( $parent, $_menu ) )
|
|
{
|
|
$menu[ $parent ] = array();
|
|
foreach ( $items as $key => $position )
|
|
{
|
|
foreach ( $_menu as $_parent => $_items )
|
|
{
|
|
if ( array_key_exists( $key, $_items ) )
|
|
{
|
|
$menu[ $parent ][ $key ] = $_menu[ $_parent ][ $key ];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Write */
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json", $menu );
|
|
|
|
if ( \IPS\Request::i()->isAjax() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Display the table */
|
|
return new \IPS\Helpers\Tree\Tree( $url, 'dev_acpmenu',
|
|
/* Get Roots */
|
|
function () use ( $url, $appKey, $menu )
|
|
{
|
|
$rows = array();
|
|
$order = 1;
|
|
foreach ( array_keys($menu) as $k )
|
|
{
|
|
$lang = "menu__{$appKey}_{$k}";
|
|
$rows[ $k ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, $k, \IPS\Member::loggedIn()->language()->addToStack( $lang ), isset( $menu[ $k ] ), array(
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'acpmenu_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=menuForm&module_key={$k}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acpmenu_add') )
|
|
)
|
|
), "app={$appKey}&module={$k}", NULL, $order );
|
|
$order++;
|
|
}
|
|
return $rows;
|
|
},
|
|
/* Get Row */
|
|
function ( $k, $root ) use ( $url, $menu, $appKey )
|
|
{
|
|
$lang = "menu__{$appKey}_{$k}";
|
|
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, $k, \IPS\Member::loggedIn()->language()->addToStack( $lang ), isset( $menu[ $k ] ), array(
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'acpmenu_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=menuForm&module_key={$k}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acpmenu_add') )
|
|
)
|
|
), "app={$appKey}&module={$k}", NULL, NULL, $root );
|
|
},
|
|
/* Get Row Parent */
|
|
function ()
|
|
{
|
|
return NULL;
|
|
},
|
|
/* Get Children */
|
|
function ( $k ) use ( $url, $menu, $appKey )
|
|
{
|
|
$rows = array();
|
|
$pos = 0;
|
|
foreach ( $menu[ $k ] as $id => $row )
|
|
{
|
|
$lang = "menu__{$appKey}_{$k}_{$id}";
|
|
$rows[ 's@' . $id ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, 's@' . $id, \IPS\Member::loggedIn()->language()->addToStack( $lang ), FALSE, array(
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=menuForm&module_key={$k}&id={$id}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('edit') ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=menuDelete&module_key={$k}&id={$id}" ),
|
|
'data' => array( 'delete' => '' )
|
|
),
|
|
), "app={$appKey}&module={$k}&controller={$row['controller']}" . ( $row['do'] ? "&do={$row['do']}" : '' ), NULL, ++$pos, FALSE, NULL, NULL, NULL, FALSE, FALSE, FALSE );
|
|
}
|
|
return $rows;
|
|
},
|
|
NULL,
|
|
FALSE,
|
|
FALSE,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Menu Form Item
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function menuForm()
|
|
{
|
|
/* Current Menu */
|
|
$menu = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json" );
|
|
|
|
/* Get module and controllers */
|
|
$module = $this->_loadModule();
|
|
|
|
/* And controllers */
|
|
$controllers = array();
|
|
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/modules/{$module->area}/{$module->key}" ) as $file )
|
|
{
|
|
if ( !$file->isDot() and mb_substr( $file, -4 ) === '.php' )
|
|
{
|
|
$controllers[ mb_substr( $file, 0, -4 ) ] = (string) $file;
|
|
}
|
|
}
|
|
|
|
/* And restrictions */
|
|
$restrictions = array_merge( array( '' => 'acpmenu_norestriction' ), $this->_getRestrictions( $module ) );
|
|
|
|
/* And tabs */
|
|
$tabs = $this->_getAcpMenuTabs();
|
|
|
|
/* Load existing */
|
|
$current = NULL;
|
|
if ( \IPS\Request::i()->id and isset( $menu[ $module->key ][ \IPS\Request::i()->id ] ) )
|
|
{
|
|
$current = $menu[ $module->key ][ \IPS\Request::i()->id ];
|
|
}
|
|
|
|
/* Show Form */
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Select( 'acpmenu_controller', ( $current ? $current['controller'] : NULL ), TRUE, array( 'options' => $controllers ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Text( 'acpmenu_tab', ( $current ? $current['tab'] : $module->application ), TRUE, array( 'autocomplete' => array(
|
|
'source' => $tabs,
|
|
'maxItems' => 1,
|
|
) ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Text( 'acpmenu_doaction', ( $current ? $current['do'] : NULL ), FALSE, array(), NULL, 'do=' ) );
|
|
$form->add( new \IPS\Helpers\Form\Select( 'acpmenu_restriction', ( $current ? $current['restriction'] : '' ), FALSE, array( 'options' => $restrictions ) ) );
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
$menu[ $module->key ][ $values['acpmenu_controller'] . ( $values['acpmenu_doaction'] ? "_{$values['acpmenu_doaction']}" : '' ) ] = array(
|
|
'tab' => $values['acpmenu_tab'],
|
|
'controller' => $values['acpmenu_controller'],
|
|
'do' => $values['acpmenu_doaction'],
|
|
'restriction' => $values['acpmenu_restriction']
|
|
);
|
|
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json", $menu );
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acpmenu&root={$module->key}" ) );
|
|
}
|
|
|
|
/* Display */
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'acpmenu_add', $form, FALSE );
|
|
}
|
|
|
|
/**
|
|
* Delete Menu Item
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function menuDelete()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
/* Get Menu */
|
|
$menu = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json" );
|
|
|
|
/* Get module and controllers */
|
|
$module = $this->_loadModule();
|
|
|
|
/* Delete It */
|
|
unset( $menu[ $module->key ][ \IPS\Request::i()->id ] );
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json", $menu );
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acpmenu&root={$module->key}" ) );
|
|
}
|
|
|
|
/**
|
|
* Manage ACP Restrictions
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _manageAcprestrictions()
|
|
{
|
|
$this->modules = $this->_getModules();
|
|
$this->url = \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acprestrictions" );
|
|
$this->restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
$appKey = $this->application->directory;
|
|
|
|
/* Handle reordering */
|
|
if ( \IPS\Request::i()->do === 'reorder' )
|
|
{
|
|
/* Init */
|
|
$id = explode( '-', mb_substr( \IPS\Request::i()->root, 1 ) );
|
|
if( isset( \IPS\Request::i()->ajax_order ) )
|
|
{
|
|
$order = array();
|
|
$position = 1;
|
|
foreach( \IPS\Request::i()->ajax_order as $i )
|
|
{
|
|
$order[ $i ] = $position++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$order = \IPS\Request::i()->order;
|
|
}
|
|
|
|
/* Sort */
|
|
uasort( $this->restrictions[ $id[0] ][ $id[1] ], function( $a, $b ) use ( $order ) {
|
|
return ( isset( $order[ str_replace( '_', '~', $a ) ] ) ? $order[ str_replace( '_', '~', $a ) ] : 0 ) - ( isset( $order[ str_replace( '_', '~', $b ) ] ) ? $order[ str_replace( '_', '~', $b ) ] : 0 );
|
|
});
|
|
|
|
/* Write */
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $this->restrictions );
|
|
|
|
if ( \IPS\Request::i()->isAjax() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Display the table */
|
|
return new \IPS\Helpers\Tree\Tree( $this->url, 'dev_acprestrictions', array( $this, 'restrictionsGetRoots' ), array( $this, 'restrictionsGetRow' ),
|
|
function ( $k )
|
|
{
|
|
if ( mb_substr( $k, 0, 1 ) === 'M' )
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return 'M' . mb_substr( $k, 1, mb_strpos( $k, '-' ) - 1 );
|
|
}
|
|
},
|
|
array( $this, 'restrictionsGetchildren' ),
|
|
NULL,
|
|
FALSE,
|
|
TRUE,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Restrictions: Get Root Rows
|
|
*
|
|
* @return array
|
|
*/
|
|
public function restrictionsGetRoots()
|
|
{
|
|
$rows = array();
|
|
|
|
if( !empty( $this->modules['admin'] ) )
|
|
{
|
|
foreach ( $this->modules['admin'] as $k => $module )
|
|
{
|
|
if ( empty( $module['protected'] ) )
|
|
{
|
|
$rows[] = $this->_restrictionsModuleRow( $this->url, $k, $this->restrictions, FALSE );
|
|
}
|
|
}
|
|
}
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Restrictions: Get Individual Row
|
|
*
|
|
* @param string $k ID
|
|
* @param bool $root Is root row?
|
|
* @return string
|
|
*/
|
|
public function restrictionsGetRow( $k, $root )
|
|
{
|
|
if ( mb_substr( $k, 0, 1 ) === 'M' )
|
|
{
|
|
return $this->_restrictionsModuleRow( $this->url, mb_substr( $k, 1 ), $this->restrictions, $root );
|
|
}
|
|
else
|
|
{
|
|
$moduleKey = mb_substr( $k, 1, mb_strpos( $k, '-' ) - 1 );
|
|
$groupKey = mb_substr( $k, mb_strpos( $k, '-' ) + 1 );
|
|
return $this->_restrictionsGroupRow( $this->url, $groupKey, $moduleKey, $this->restrictions[ $moduleKey ][ $groupKey ], $root );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restrictions: Get Child Rows
|
|
*
|
|
* @param string $k ID
|
|
* @return array
|
|
*/
|
|
public function restrictionsGetChildren( $k )
|
|
{
|
|
$rows = array();
|
|
|
|
if ( mb_substr( $k, 0, 1 ) === 'M' )
|
|
{
|
|
$k = mb_substr( $k, 1 );
|
|
foreach ( $this->restrictions[ $k ] as $groupKey => $r )
|
|
{
|
|
$rows[] = $this->_restrictionsGroupRow( $this->url, $groupKey, $k, $r, FALSE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$moduleKey = mb_substr( $k, 1, mb_strpos( $k, '-' ) - 1 );
|
|
$groupKey = mb_substr( $k, mb_strpos( $k, '-' ) + 1 );
|
|
|
|
$pos = 0;
|
|
foreach ( $this->restrictions[ $moduleKey ][ $groupKey ] as $rKey )
|
|
{
|
|
$lang = "r__{$rKey}";
|
|
$rows[ str_replace( '_', '~', $rKey ) ] = \IPS\Theme::i()->getTemplate( 'trees' )->row( $this->url, $rKey, \IPS\Member::loggedIn()->language()->addToStack( $lang ), FALSE, array(
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=restrictionRowDelete&module_key={$moduleKey}&group={$groupKey}&id={$rKey}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), $rKey, NULL, ++$pos );
|
|
}
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Get Module Row
|
|
*
|
|
* @param string $url URL
|
|
* @param string $k Key
|
|
* @param array $restrictions Restrictions JSON
|
|
* @param bool $root As root?
|
|
* @return string
|
|
*/
|
|
protected function _restrictionsModuleRow( $url, $k, $restrictions, $root )
|
|
{
|
|
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, 'M'.$k, $k, isset( $restrictions[ $k ] ), array(
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'acprestrictions_addgroup',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=restrictionGroupForm&module_key={$k}&id=0" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acprestrictions_addgroup') )
|
|
)
|
|
), '', NULL, NULL, $root );
|
|
}
|
|
|
|
/**
|
|
* Get Group Row
|
|
*
|
|
* @param string $url URL
|
|
* @param string $k Key
|
|
* @param string $moduleKey Module Key
|
|
* @param array $rows Rows in this group
|
|
* @param bool $root As root?
|
|
* @return string
|
|
*/
|
|
protected function _restrictionsGroupRow( $url, $groupKey, $moduleKey, $r, $root )
|
|
{
|
|
$lang = "r__{$groupKey}";
|
|
return \IPS\Theme::i()->getTemplate( 'trees' )->row( $url, "G{$moduleKey}-{$groupKey}", \IPS\Member::loggedIn()->language()->addToStack( $lang ), !empty( $r ), array(
|
|
'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'acprestrictions_addrow',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=restrictionRowForm&module_key={$moduleKey}&group={$groupKey}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acprestrictions_addrow') )
|
|
),
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=restrictionGroupForm&module_key={$moduleKey}&id={$groupKey}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('edit') ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete'=> array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=restrictionGroupDelete&module_key={$moduleKey}&id={$groupKey}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), '', NULL, NULL, $root );
|
|
}
|
|
|
|
/**
|
|
* Restriction Group Form
|
|
*
|
|
* @return void
|
|
*/
|
|
public function restrictionGroupForm()
|
|
{
|
|
/* Get restriction data */
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
|
|
/* Load module */
|
|
$module = $this->_loadModule();
|
|
|
|
/* Do we have a default value? */
|
|
$current = NULL;
|
|
if ( \IPS\Request::i()->id and isset( $restrictions[ $module->key ][ \IPS\Request::i()->id ] ) )
|
|
{
|
|
$current = \IPS\Request::i()->id;
|
|
}
|
|
|
|
/* Build Form */
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Text( 'acprestrictions_groupkey', $current ?: '', TRUE ) );
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
$rows = array();
|
|
if ( $current !== NULL )
|
|
{
|
|
$rows = $restrictions[ $module->key ][ $current ];
|
|
unset( $restrictions[ $module->key ][ $current ] );
|
|
}
|
|
|
|
$restrictions[ $module->key ][ $values['acprestrictions_groupkey'] ] = $rows;
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acprestrictions&root=M{$module->key}" ) );
|
|
}
|
|
|
|
/* Display */
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'acprestrictions_addgroup', $form, FALSE );
|
|
}
|
|
|
|
/**
|
|
* Delete Restriction Group
|
|
*
|
|
* @return void
|
|
*/
|
|
public function restrictionGroupDelete()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
$module = $this->_loadModule();
|
|
|
|
if( isset( $restrictions[ $module->key ][ \IPS\Request::i()->id ] ) )
|
|
{
|
|
unset( $restrictions[ $module->key ][ \IPS\Request::i()->id ] );
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acprestrictions&root=M{$module->key}" ) );
|
|
}
|
|
|
|
/**
|
|
* Restriction Row Form
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function restrictionRowForm()
|
|
{
|
|
$module = $this->_loadModule();
|
|
$group = \IPS\Request::i()->group;
|
|
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Text( 'acprestrictions_rowkey', NULL, TRUE ) );
|
|
if ( $values = $form->values() )
|
|
{
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
$restrictions[ $module->key ][ $group ][ $values['acprestrictions_rowkey'] ] = $values['acprestrictions_rowkey'];
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acprestrictions&root=G{$module->key}-{$group}" ) );
|
|
}
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'acprestrictions_addrow', $form, FALSE );
|
|
}
|
|
|
|
/**
|
|
* Delete Restriction Row
|
|
*
|
|
* @return void
|
|
*/
|
|
public function restrictionRowDelete()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
$module = $this->_loadModule();
|
|
$group = \IPS\Request::i()->group;
|
|
$id = \IPS\Request::i()->id;
|
|
|
|
if( isset( $restrictions[ $module->key ][ $group ][ $id ] ) )
|
|
{
|
|
unset( $restrictions[ $module->key ][ $group ][ $id ] );
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=acprestrictions&root=G{$module->key}-{$group}" ) );
|
|
}
|
|
|
|
/**
|
|
* Create Controller
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addController()
|
|
{
|
|
$module = $this->_loadModule();
|
|
$targetDir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/modules/" . \IPS\Request::i()->location . '/' . $module->key . '/';
|
|
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Text( 'filename', NULL, TRUE, array(), function( $val ) use ( $targetDir )
|
|
{
|
|
if ( file_exists( $targetDir . $val . '.php' ) )
|
|
{
|
|
throw new \DomainException( 'modules_controller_exists' );
|
|
}
|
|
}, NULL, '.php' ) );
|
|
|
|
if ( \IPS\Request::i()->location === 'admin' )
|
|
{
|
|
$form->add( new \IPS\Helpers\Form\Select( 'type', NULL, TRUE, array(
|
|
'options' => array(
|
|
'blank' => 'controllertype_blank',
|
|
'node' => 'controllertype_node',
|
|
'list' => 'controllertype_list',
|
|
),
|
|
'toggles' => array(
|
|
'node' => array( 'model_name' ),
|
|
'list' => array( 'database_table_name' ),
|
|
)
|
|
) ) );
|
|
|
|
$form->add( new \IPS\Helpers\Form\Text( 'model_name', NULL, FALSE, array(),NULL, NULL, NULL, 'model_name' ) );
|
|
|
|
$form->add( new \IPS\Helpers\Form\Text( 'database_table_name', NULL, FALSE, array(),NULL, NULL, NULL, 'database_table_name' ) );
|
|
|
|
$form->add( new \IPS\Helpers\Form\Text( 'acpmenu_tab', $this->application->directory, FALSE, array( 'autocomplete' => array(
|
|
'source' => $this->_getAcpMenuTabs(),
|
|
'maxItems' => 1
|
|
) ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Select( 'acpmenu_restriction', '__create', FALSE, array( 'options' => array_merge( array(
|
|
'' => 'acpmenu_norestriction',
|
|
'__create' => 'controller_rcreate',
|
|
), $this->_getRestrictions( $module ) ) ) ) );
|
|
}
|
|
|
|
if ( $values = $form->values() )
|
|
{
|
|
if( !isset($values['type']) )
|
|
{
|
|
$values['type'] = 'blank';
|
|
}
|
|
|
|
/* Create a restriction? */
|
|
$restriction = NULL;
|
|
if ( isset( $values['acpmenu_restriction'] ) and $values['acpmenu_restriction'] )
|
|
{
|
|
if ( $values['acpmenu_restriction'] === '__create' )
|
|
{
|
|
$restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
$restrictions[ $module->key ][ $values['filename'] ]["{$values['filename']}_manage"] = "{$values['filename']}_manage";
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json", $restrictions );
|
|
$restriction = "{$values['filename']}_manage";
|
|
}
|
|
else
|
|
{
|
|
$restriction = $values['acpmenu_restriction'];
|
|
}
|
|
}
|
|
|
|
/* Work out the contents */
|
|
$contents = str_replace(
|
|
array(
|
|
'{controller}',
|
|
"{subpackage}",
|
|
'{date}',
|
|
'{app}',
|
|
'{module}',
|
|
'{location}',
|
|
'{restriction}',
|
|
'{node_model}',
|
|
'{table_name}'
|
|
),
|
|
array(
|
|
$values['filename'],
|
|
( $this->application->directory != 'core' ) ? ( " * @subpackage\t" . \IPS\Member::loggedIn()->language()->get( "__app_{$this->application->directory}" ) ) : '',
|
|
date( 'd M Y' ),
|
|
$this->application->directory,
|
|
\IPS\Request::i()->module_key,
|
|
\IPS\Request::i()->location,
|
|
$restriction ? '\IPS\Dispatcher::i()->checkAcpPermission( \''.$restriction.'\' );' : '',
|
|
isset( $values['model_name'] ) ? $values['model_name'] : NULL,
|
|
isset( $values['database_table_name'] ) ? $values['database_table_name'] : NULL,
|
|
),
|
|
file_get_contents( \IPS\ROOT_PATH . "/applications/core/data/defaults/Controller" . mb_ucfirst( $values['type'] ) . '.txt' )
|
|
);
|
|
|
|
/* If this isn't an IPS app, strip out our header */
|
|
if ( !in_array( $this->application->directory, \IPS\Application::$ipsApps ) )
|
|
{
|
|
$contents = preg_replace( '/(<\?php\s)\/*.+?\*\//s', '$1', $contents );
|
|
}
|
|
|
|
/* Write */
|
|
if( @\file_put_contents( $targetDir . $values['filename'] . '.php', $contents ) === FALSE )
|
|
{
|
|
\IPS\Output::i()->error( 'dev_could_not_write_controller', '1C103/H', 403, '' );
|
|
}
|
|
|
|
@chmod( $targetDir . $values['filename'] . '.php', \IPS\FILE_PERMISSION_NO_WRITE );
|
|
|
|
/* Add to the menu? */
|
|
if ( isset( $values['acpmenu_tab'] ) and $values['acpmenu_tab'] )
|
|
{
|
|
$menu = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json" );
|
|
$menu[ $module->key ][ $values['filename'] ] = array(
|
|
'tab' => $values['acpmenu_tab'],
|
|
'controller' => $values['filename'],
|
|
'do' => '',
|
|
'restriction' => $restriction
|
|
);
|
|
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acpmenu.json", $menu );
|
|
}
|
|
|
|
/* Boink */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=modules-" . \IPS\Request::i()->location . "&root=" . \IPS\Request::i()->module_key ) );
|
|
}
|
|
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'modules_add_controller', $form, FALSE );
|
|
}
|
|
|
|
/**
|
|
* Database Schema: Show Tables
|
|
*
|
|
* @return string HTML to display
|
|
*/
|
|
protected function _manageSchema()
|
|
{
|
|
/* Create the file if it doesn't exist */
|
|
$json = $this->_getSchema();
|
|
|
|
/* Build list table */
|
|
$table = new \IPS\Helpers\Table\Custom( $json, \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=schema" ) );
|
|
$table->langPrefix = 'database_table_';
|
|
$table->mainColumn = 'name';
|
|
$table->limit = 150;
|
|
$table->include = array( 'name' );
|
|
|
|
/* Set default sort */
|
|
$table->sortBy = $table->sortBy ?: 'name';
|
|
$table->sortDirection = $table->sortDirection ?: 'asc';
|
|
|
|
/* Add the "add" button */
|
|
$table->rootButtons = array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'database_table_create',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addTable" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_table_create') )
|
|
)
|
|
);
|
|
|
|
/* Add the buttons for each row */
|
|
$appKey = $this->application->directory;
|
|
$table->rowButtons = function( $row ) use ( $appKey )
|
|
{
|
|
return array(
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'database_table_edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=editSchema&_name={$row['name']}" ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'database_table_delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=deleteTable&name={$row['name']}" ),
|
|
'data' => array( 'delete' => '', 'delete-warning' => \IPS\Member::loggedIn()->language()->addToStack( 'database_droptable_info' ) )
|
|
)
|
|
);
|
|
};
|
|
|
|
/* Return */
|
|
return (string) $table;
|
|
}
|
|
|
|
/**
|
|
* Database Schema: Add Table
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addTable()
|
|
{
|
|
/* Get our current working version queries */
|
|
$queriesJson = $this->_getQueries( 'working' );
|
|
|
|
/* Get form */
|
|
$message = NULL;
|
|
$activeTab = \IPS\Request::i()->tab ?: 'new';
|
|
$form = new \IPS\Helpers\Form( "database_table_{$activeTab}" );
|
|
switch ( $activeTab )
|
|
{
|
|
/* Create New */
|
|
case 'new':
|
|
$form->add( new \IPS\Helpers\Form\Text(
|
|
'database_table_name',
|
|
NULL,
|
|
TRUE,
|
|
array(
|
|
'maxLength' => ( 64 - \strlen( "{$this->application->directory}_" ) )
|
|
),
|
|
function( $value )
|
|
{
|
|
if( \IPS\Db::i()->checkForTable( \IPS\Request::i()->appKey . '_' . $value ) === TRUE )
|
|
{
|
|
throw new \DomainException( 'database_table_exists' );
|
|
}
|
|
},
|
|
"{$this->application->directory}_"
|
|
) );
|
|
$message = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( \IPS\Member::loggedIn()->language()->addToStack('database_newtable_info'), 'information' );
|
|
break;
|
|
|
|
/* Import */
|
|
case 'import':
|
|
|
|
/* Fetch tables */
|
|
$tables = array();
|
|
$stmt = \IPS\Db::i()->query( "SHOW TABLES;" );
|
|
while ( $row = $stmt->fetch_assoc() )
|
|
{
|
|
$tableName = array_pop( $row );
|
|
$tables[ $tableName ] = $tableName;
|
|
}
|
|
|
|
/* Add the form element */
|
|
$form->add( new \IPS\Helpers\Form\Select(
|
|
'database_table_import',
|
|
NULL,
|
|
TRUE,
|
|
array( 'options' => $tables, 'parse' => 'normal' )
|
|
) );
|
|
|
|
/* Warn the user we may rename the table */
|
|
$message = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( \IPS\Member::loggedIn()->language()->addToStack('database_renametable_info', FALSE, array( 'sprintf' => array( "{$this->application->directory}_" ) ) ), 'information' );
|
|
|
|
break;
|
|
|
|
/* Upload */
|
|
case 'upload':
|
|
$appKey = $this->application->directory;
|
|
$form->add( new \IPS\Helpers\Form\Upload(
|
|
'upload',
|
|
NULL,
|
|
TRUE,
|
|
array( 'allowedFileTypes' => array( 'sql' ), 'temporary' => TRUE ),
|
|
function( $value ) use ( $appKey )
|
|
{
|
|
/* Get contents and remove comments */
|
|
$contents = \IPS\Db::stripComments( file_get_contents( $value ) );
|
|
|
|
/* If there's more than one ; character - reject it */
|
|
if( mb_substr_count( $contents, ';' ) > 1 )
|
|
{
|
|
throw new \DomainException( 'database_upload_too_many_queries' );
|
|
}
|
|
|
|
/* Is it a CREATE TABLE statement */
|
|
preg_match( '/^CREATE (TEMPORARY )?TABLE (IF NOT EXISTS )?`?(.+?)`?\s+?\(/i', $contents, $matches );
|
|
if( empty( $matches ) or !$matches[3] )
|
|
{
|
|
throw new \DomainException( 'database_upload_no_create' );
|
|
}
|
|
|
|
/* Does the table already exist? */
|
|
if ( mb_substr( $matches[3], 0, mb_strlen( $appKey ) + 1 ) !== "{$appKey}_" )
|
|
{
|
|
$matches[3] = "{$appKey}_{$matches[3]}";
|
|
}
|
|
if( \IPS\Db::i()->checkForTable( $matches[3] ) )
|
|
{
|
|
throw new \DomainException( 'database_table_exists' );
|
|
}
|
|
}
|
|
) );
|
|
$message = \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( \IPS\Member::loggedIn()->language()->addToStack('database_newtable_info'), 'information' );
|
|
break;
|
|
}
|
|
|
|
/* Has the form been submitted? */
|
|
if( $values = $form->values() )
|
|
{
|
|
/* Work out defintion */
|
|
switch ( $activeTab )
|
|
{
|
|
/* New table */
|
|
case 'new':
|
|
/* Set definition */
|
|
$definition = array(
|
|
'name' => $this->application->directory . '_' . $values['database_table_name'],
|
|
'columns' => array(
|
|
'id' => array(
|
|
'name' => 'id',
|
|
'type' => 'BIGINT',
|
|
'length' => '20',
|
|
'unsigned' => TRUE,
|
|
'zerofill' => FALSE,
|
|
'binary' => FALSE,
|
|
'allow_null' => FALSE,
|
|
'default' => NULL,
|
|
'auto_increment' => TRUE,
|
|
'comment' => \IPS\Member::loggedIn()->language()->get('database_default_column_comment')
|
|
),
|
|
),
|
|
'indexes' => array(
|
|
'PRIMARY' => array(
|
|
'type' => 'primary',
|
|
'name' => 'PRIMARY',
|
|
'columns' => array( 'id' ),
|
|
'length' => array( NULL ),
|
|
),
|
|
),
|
|
);
|
|
|
|
/* Create table */
|
|
\IPS\Db::i()->createTable( $definition );
|
|
|
|
/* Add to the queries.json file */
|
|
$queriesJson = $this->_addQueryToJson( $queriesJson, array( 'method' => 'createTable', 'params' => array( $definition ) ) );
|
|
$this->_writeQueries( 'working', $queriesJson );
|
|
|
|
break;
|
|
|
|
/* Import existing table */
|
|
case 'import':
|
|
/* Get definition */
|
|
if ( \IPS\Db::i()->prefix AND mb_strpos( $values['database_table_import'], \IPS\Db::i()->prefix ) === 0 )
|
|
{
|
|
$values['database_table_import'] = mb_substr( $values['database_table_import'], mb_strlen( \IPS\Db::i()->prefix ) );
|
|
}
|
|
$definition = \IPS\Db::i()->getTableDefinition( $values['database_table_import'] );
|
|
|
|
/* Do we need to rename? */
|
|
if ( mb_substr( $definition['name'], 0, mb_strlen( $this->application->directory ) + 1 ) !== "{$this->application->directory}_" )
|
|
{
|
|
/* Do it */
|
|
\IPS\Db::i()->renameTable( $definition['name'], "{$this->application->directory}_{$definition['name']}" );
|
|
|
|
/* Add to the queries.json file */
|
|
$queriesJson = $this->_addQueryToJson( $queriesJson, array( 'method' => 'renameTable', 'params' => array( $definition['name'], "{$this->application->directory}_{$definition['name']}" ) ) );
|
|
$this->_writeQueries( 'working', $queriesJson );
|
|
|
|
/* Set the name for later */
|
|
$definition['name'] = "{$this->application->directory}_{$definition['name']}";
|
|
}
|
|
|
|
break;
|
|
|
|
/* Uploaded .sql file */
|
|
case 'upload':
|
|
/* Get contents */
|
|
$contents = \IPS\Db::stripComments( file_get_contents( $values['upload'] ) );
|
|
|
|
/* Put the app key in if it's not already */
|
|
$appKey = $this->application->directory;
|
|
$contents = preg_replace_callback( '/CREATE (TEMPORARY )?TABLE (IF NOT EXISTS )?`?(.+?)`?\s+?/i', function( $matches ) use ( $appKey )
|
|
{
|
|
$prefix = '';
|
|
if ( \IPS\Db::i()->prefix AND mb_substr( $matches[3], 0, mb_strlen( \IPS\Db::i()->prefix ) ) !== \IPS\Db::i()->prefix )
|
|
{
|
|
$prefix = \IPS\Db::i()->prefix;
|
|
}
|
|
|
|
if ( mb_substr( $matches[3], 0, mb_strlen( $appKey ) + 1 ) !== "{$appKey}_" )
|
|
{
|
|
return str_replace( $matches[3], "{$prefix}{$appKey}_{$matches[3]}", $matches[0] );
|
|
}
|
|
|
|
return $matches[0];
|
|
}, $contents );
|
|
|
|
/* Work out the name */
|
|
$prefix = \IPS\Db::i()->prefix;
|
|
preg_match( "/CREATE (TEMPORARY )?TABLE (IF NOT EXISTS )?`?{$prefix}(.+?)`?\s+?\(/i", $contents, $matches );
|
|
$name = $matches[3];
|
|
|
|
/* Run the query */
|
|
\IPS\Db::i()->query( $contents );
|
|
|
|
/* Now get the definition */
|
|
$definition = \IPS\Db::i()->getTableDefinition( $name );
|
|
|
|
/* Add to the queries.json file */
|
|
$queriesJson = $this->_addQueryToJson( $queriesJson, array( 'method' => 'createTable', 'params' => array( $definition ) ) );
|
|
$this->_writeQueries( 'working', $queriesJson );
|
|
|
|
/* Delete the file */
|
|
unlink( $values['upload'] );
|
|
|
|
break;
|
|
}
|
|
|
|
/* Add to schema.json */
|
|
$schema = $this->_getSchema();
|
|
$schema[ $definition['name'] ] = $definition;
|
|
$this->_writeSchema( $schema );
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}" ) );
|
|
}
|
|
|
|
/* If not, show it */
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs(
|
|
array(
|
|
'new' => 'database_table_new',
|
|
'import' => 'database_table_import',
|
|
'upload' => 'database_table_upload',
|
|
),
|
|
$activeTab,
|
|
$message . $form,
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addTable&existing=1" )
|
|
);
|
|
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
if( \IPS\Request::i()->existing )
|
|
{
|
|
\IPS\Output::i()->output = $message . $form;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Database Schema: View/Edit Table
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchema()
|
|
{
|
|
/* Get table definition */
|
|
$schema = $this->_getSchema();
|
|
if ( !isset( $schema[ \IPS\Request::i()->_name ] ) )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/A', 404, '' );
|
|
}
|
|
$definition = $schema[ \IPS\Request::i()->_name ];
|
|
|
|
/* Normalise it a little to stop unecessary conflict errors */
|
|
foreach ( $definition['columns'] as $k => $c )
|
|
{
|
|
foreach ( array( 'length', 'decimals' ) as $i )
|
|
{
|
|
if ( isset( $c[ $i ] ) )
|
|
{
|
|
$definition['columns'][ $k ][ $i ] = intval( $c[ $i ] );
|
|
}
|
|
else
|
|
{
|
|
$definition['columns'][ $k ][ $i ] = NULL;
|
|
}
|
|
}
|
|
|
|
if ( !isset( $c['values'] ) )
|
|
{
|
|
$definition['columns'][ $k ]['values'] = array();
|
|
}
|
|
|
|
if ( $c['type'] === 'BIT' )
|
|
{
|
|
$definition['columns'][ $k ]['default'] = ( is_null($c['default']) ) ? NULL : "b'{$c['default']}'";
|
|
}
|
|
|
|
ksort( $definition['columns'][ $k ] );
|
|
}
|
|
|
|
/* Init Output */
|
|
\IPS\Output::i()->title = $definition['name'];
|
|
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=schema" ), \IPS\Member::loggedIn()->language()->addToStack( 'database_tables' ) );
|
|
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global', 'core' )->message( \IPS\Member::loggedIn()->language()->addToStack('database_changes_info'), 'information' );
|
|
|
|
/* Does it match the database? */
|
|
$_definition = $definition;
|
|
unset( $_definition['inserts'] );
|
|
unset( $_definition['comment'] );
|
|
unset( $_definition['engine'] );
|
|
unset( $_definition['collation'] );
|
|
unset( $_definition['reporting'] );
|
|
try
|
|
{
|
|
$localDefinition = $this->_getTableDefinition( $definition['name'] );
|
|
}
|
|
catch ( \OutOfRangeException $e )
|
|
{
|
|
\IPS\Db::i()->createTable( $definition );
|
|
$localDefinition = $definition;
|
|
}
|
|
unset( $localDefinition['collation'] );
|
|
unset( $localDefinition['comment'] );
|
|
unset( $localDefinition['engine'] );
|
|
if ( $_definition != $localDefinition )
|
|
{
|
|
$string1 = str_replace( array( '<?php', '<br>', '<br />' ), "\n", highlight_string( "<?php\n" . var_export( $_definition, TRUE ), TRUE ) );
|
|
$string2 = str_replace( array( '<?php', '<br>', '<br />' ), "\n", highlight_string( "<?php\n" . var_export( $localDefinition, TRUE ), TRUE ) );
|
|
|
|
require_once \IPS\ROOT_PATH . "/system/3rd_party/Diff/class.Diff.php";
|
|
$diff = html_entity_decode( \Diff::toTable( \Diff::compare( $string1, $string2 ) ) );
|
|
|
|
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'system/diff.css', 'core', 'admin' ) );
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'applications', 'core' )->schemaConflict( $definition['name'], $diff );
|
|
return;
|
|
}
|
|
|
|
/* Get schema file */
|
|
$schemaJson = $this->_getSchema();
|
|
$_schemaJson = $schemaJson;
|
|
|
|
/* We'll probably also need the queries.json file */
|
|
$queriesJson = $this->_getQueries( 'working' );
|
|
$_queriesJson = $queriesJson;
|
|
$queries = array();
|
|
|
|
/* Display "Show Schema" button */
|
|
\IPS\Output::i()->sidebar['actions'] = array(
|
|
'settings' => array(
|
|
'title' => 'database_show_schema',
|
|
'icon' => 'code',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=showSchema&_name={$definition['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_show_schema') )
|
|
),
|
|
);
|
|
|
|
/* Work out tab */
|
|
$activeTab = \IPS\Request::i()->tab ?: 'info';
|
|
|
|
//-----------------------------------------
|
|
// Info
|
|
//-----------------------------------------
|
|
|
|
if ( $activeTab === 'info' )
|
|
{
|
|
/* Build Form */
|
|
$output = new \IPS\Helpers\Form();
|
|
|
|
$output->add( new \IPS\Helpers\Form\Text(
|
|
'database_table_name',
|
|
( mb_substr( $definition['name'], 0, mb_strlen( $this->application->directory ) ) === $this->application->directory ) ? mb_substr( $definition['name'], mb_strlen( $this->application->directory ) + 1 ) : $definition['name'],
|
|
TRUE,
|
|
array( 'maxLength' => ( 64 - mb_strlen( "{$this->application->directory}_" ) ) ),
|
|
NULL,
|
|
"{$this->application->directory}_"
|
|
) );
|
|
|
|
$output->add( new \IPS\Helpers\Form\Text(
|
|
'database_comment',
|
|
isset( $definition['comment'] ) ? $definition['comment'] : '',
|
|
FALSE,
|
|
array( 'maxLength' => 60, 'size' => 80 )
|
|
) );
|
|
$output->add( new \IPS\Helpers\Form\Select(
|
|
'database_table_engine',
|
|
isset( $definition['engine'] ) ? $definition['engine'] : '',
|
|
FALSE,
|
|
array(
|
|
'options' => array(
|
|
'' => 'database_table_engine_default',
|
|
'MyISAM' => 'MyISAM',
|
|
'InnoDB' => 'InnoDB',
|
|
'MEMORY' => 'MEMORY',
|
|
)
|
|
),
|
|
function( $v ) use ( $definition )
|
|
{
|
|
$fulltextSupported = FALSE;
|
|
|
|
if( $v AND $v === 'MyISAM' )
|
|
{
|
|
$fulltextSupported = TRUE;
|
|
}
|
|
else if( ( ( $v AND $v === 'InnoDB' ) OR !$v ) AND \IPS\Db::i()->server_version >= 50600 )
|
|
{
|
|
$fulltextSupported = TRUE;
|
|
}
|
|
|
|
if ( !$fulltextSupported )
|
|
{
|
|
foreach ( $definition['indexes'] as $index )
|
|
{
|
|
if ( $index['type'] === 'fulltext' )
|
|
{
|
|
throw new \DomainException( 'database_table_engine_fulltext' );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
) );
|
|
|
|
if ( in_array( $this->application->directory, \IPS\Application::$ipsApps ) )
|
|
{
|
|
$output->add( new \IPS\Helpers\Form\Select( 'database_table_reporting', isset( $definition['reporting'] ) ? $definition['reporting'] : 'none', FALSE, array( 'options' => array(
|
|
'none' => 'database_table_reporting_none',
|
|
'count' => 'database_table_reporting_count',
|
|
) ) ) );
|
|
}
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $output->values() )
|
|
{
|
|
/* Changed the comment? */
|
|
if ( !isset( $definition['comment'] ) or $values['database_comment'] !== $definition['comment'] )
|
|
{
|
|
$schemaJson[ $definition['name'] ]['comment'] = $values['database_comment'];
|
|
}
|
|
|
|
/* Changed the engine? */
|
|
if ( ( !isset( $definition['engine'] ) and $values['database_table_engine'] ) or ( isset( $definition['engine'] ) and $values['database_table_engine'] != $definition['engine'] ) )
|
|
{
|
|
if ( $values['database_table_engine'] )
|
|
{
|
|
$queries[] = array( 'method' => 'alterTable', 'params' => array( $definition['name'], NULL, $values['database_table_engine'] ) );
|
|
$schemaJson[ $definition['name'] ]['engine'] = $values['database_table_engine'];
|
|
}
|
|
else
|
|
{
|
|
unset( $schemaJson[ $definition['name'] ]['engine'] );
|
|
}
|
|
}
|
|
|
|
/* Changed reporting? */
|
|
if ( isset( $values['database_table_reporting'] ) AND ( !isset( $definition['reporting'] ) or $values['database_table_reporting'] !== $definition['reporting'] ) )
|
|
{
|
|
$schemaJson[ $definition['name'] ]['reporting'] = $values['database_table_reporting'];
|
|
}
|
|
|
|
/* Renamed table? */
|
|
$values['database_table_name'] = "{$this->application->directory}_{$values['database_table_name']}";
|
|
if ( $values['database_table_name'] !== $definition['name'] )
|
|
{
|
|
$queries[] = array( 'method' => 'renameTable', 'params' => array( $definition['name'], $values['database_table_name'] ) );
|
|
|
|
$schemaJson[ $values['database_table_name'] ] = $schemaJson[ $definition['name'] ];
|
|
$schemaJson[ $values['database_table_name'] ]['name'] = $values['database_table_name'];
|
|
unset( $schemaJson[ $definition['name'] ] );
|
|
$definition['name'] = $values['database_table_name'];
|
|
}
|
|
|
|
/* Run queries */
|
|
foreach ( $queries as $query )
|
|
{
|
|
/* Execute it */
|
|
try
|
|
{
|
|
call_user_func_array( array( \IPS\Db::i(), $query['method'] ), $query['params'] );
|
|
}
|
|
catch ( \IPS\Db\Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack('database_schema_error', FALSE, array( 'sprintf' => array( $e->query, $e->getCode(), $e->getMessage() ) ) ), '1C103/E', 403, '' );
|
|
}
|
|
|
|
/* Add it to the queries.json file */
|
|
$queriesJson = $this->_addQueryToJson( $queriesJson, $query );
|
|
}
|
|
|
|
/* Write the json files if we've changed it */
|
|
$changesMade = !empty( $queries );
|
|
if ( $_schemaJson !== $schemaJson )
|
|
{
|
|
$this->_writeSchema( $schemaJson );
|
|
}
|
|
if ( $_queriesJson !== $queriesJson )
|
|
{
|
|
$this->_writeQueries( 'working', $queriesJson );
|
|
}
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&tab={$activeTab}" ) );
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Columns
|
|
//-----------------------------------------
|
|
|
|
elseif ( $activeTab === 'columns' )
|
|
{
|
|
$output = new \IPS\Helpers\Table\Custom( $definition['columns'], \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&existing=1&tab=columns" ) );
|
|
$output->langPrefix = 'database_column_';
|
|
$output->include = array( 'name', 'type', 'length', 'unsigned', 'zerofill', 'binary', 'allow_null', 'default', 'auto_increment', 'comment' );
|
|
$output->limit = 150;
|
|
$output->rootButtons = array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'database_columns_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchemaColumn&_name={$definition['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_columns_add') )
|
|
),
|
|
);
|
|
|
|
$appKey = $this->application->directory;
|
|
$output->rowButtons = function( $row ) use ( $definition, $appKey )
|
|
{
|
|
return array(
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=editSchemaColumn&_name={$definition['name']}&column={$row['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => $row['name'] ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=editSchemaDeleteColumn&_name={$definition['name']}&column={$row['name']}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
);
|
|
};
|
|
|
|
$boolParser = function( $val )
|
|
{
|
|
return $val ? '✓' : '✗';
|
|
};
|
|
$output->parsers = array(
|
|
'length' => function( $val, $row )
|
|
{
|
|
if ( $row['decimals'] )
|
|
{
|
|
return "{$row['length']},{$row['decimals']}";
|
|
}
|
|
if ( $row['values'] )
|
|
{
|
|
return implode( '<br>', $row['values'] );
|
|
}
|
|
return $val;
|
|
},
|
|
'unsigned' => $boolParser,
|
|
'zerofill' => $boolParser,
|
|
'binary' => $boolParser,
|
|
'allow_null' => $boolParser,
|
|
'auto_increment'=> $boolParser,
|
|
);
|
|
|
|
$output = (string) $output . \IPS\Theme::i()->getTemplate('global')->message( 'database_schema_member_columns', 'warning', NULL, TRUE, TRUE );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Indexes
|
|
//-----------------------------------------
|
|
|
|
elseif ( $activeTab === 'indexes' )
|
|
{
|
|
$output = new \IPS\Helpers\Table\Custom( $definition['indexes'], \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&existing=1&tab=indexes" ) );
|
|
$output->langPrefix = 'database_index_';
|
|
$output->exclude = array( 'length' );
|
|
$output->limit = 150;
|
|
$output->rootButtons = array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'database_indexes_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchemaIndex&_name={$definition['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_indexes_add') )
|
|
),
|
|
);
|
|
|
|
$appKey = $this->application->directory;
|
|
$output->rowButtons = function( $row ) use ( $definition, $appKey )
|
|
{
|
|
return array(
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=editSchemaIndex&_name={$definition['name']}&index={$row['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => $row['name'] ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=editSchemaDeleteIndex&_name={$definition['name']}&index={$row['name']}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
);
|
|
};
|
|
|
|
$output->parsers = array(
|
|
'type' => function( $val )
|
|
{
|
|
return mb_strtoupper( $val );
|
|
},
|
|
'columns' => function( $val, $data )
|
|
{
|
|
$output = array();
|
|
|
|
foreach( $data['columns'] as $_idx => $value )
|
|
{
|
|
$output[] = $value . ' (' . (int) $data['length'][ $_idx ] . ')';
|
|
}
|
|
|
|
return implode( '<br>', $output );
|
|
}
|
|
);
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Default Inserts
|
|
//-----------------------------------------
|
|
|
|
elseif ( $activeTab === 'inserts' )
|
|
{
|
|
$output = new \IPS\Helpers\Table\Custom( isset( $definition['inserts'] ) ? $definition['inserts'] : array(), \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&existing=1&tab=inserts" ) );
|
|
$output->langPrefix = "‌";
|
|
$output->limit = 150;
|
|
|
|
$output->rootButtons = array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'database_inserts_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchemaInsert&_name={$definition['name']}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_inserts_add') )
|
|
),
|
|
);
|
|
$self = $this;
|
|
$output->rowButtons = function( $row, $k ) use ( $definition, $self )
|
|
{
|
|
return array(
|
|
'edit' => array(
|
|
'icon' => 'pencil',
|
|
'title' => 'edit',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$self->application->directory}&do=editSchemaInsert&_name={$definition['name']}&row={$k}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('database_inserts_edit') ),
|
|
'hotkey'=> 'e'
|
|
),
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$self->application->directory}&do=editSchemaDeleteInsert&_name={$definition['name']}&row={$k}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
);
|
|
};
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Display
|
|
//-----------------------------------------
|
|
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs(
|
|
array(
|
|
'info' => 'database_table_settings',
|
|
'columns' => 'database_columns',
|
|
'indexes' => 'database_indexes',
|
|
'inserts' => 'database_inserts'
|
|
),
|
|
$activeTab,
|
|
(string) $output,
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&existing=1" )
|
|
);
|
|
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
if( \IPS\Request::i()->existing )
|
|
{
|
|
\IPS\Output::i()->output = $output;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get definition from database, ignoring columns added by other apps
|
|
*
|
|
* @param string $name Table name
|
|
* @return array
|
|
*/
|
|
protected function _getTableDefinition( $name )
|
|
{
|
|
$definition = \IPS\Db::i()->getTableDefinition( $name );
|
|
foreach ( \IPS\Application::applications() as $app )
|
|
{
|
|
$file = \IPS\ROOT_PATH . "/applications/{$app->directory}/setup/install/queries.json";
|
|
if ( file_exists( $file ) )
|
|
{
|
|
foreach( json_decode( file_get_contents( $file ), TRUE ) as $query )
|
|
{
|
|
if ( $query['method'] === 'addColumn' and $query['params'][0] === $definition['name'] )
|
|
{
|
|
unset( $definition['columns'][ $query['params'][1]['name'] ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $definition;
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Add/Edit Column
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaColumn()
|
|
{
|
|
/* Get current column */
|
|
$column = NULL;
|
|
$schema = $this->_getSchema();
|
|
$definition = $schema[ \IPS\Request::i()->_name ];
|
|
if ( \IPS\Request::i()->column )
|
|
{
|
|
$column = $definition['columns'][ \IPS\Request::i()->column ];
|
|
}
|
|
|
|
/* Build form */
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Text( 'database_column_name', $column ? $column['name'] : '', TRUE, array( 'maxLength' => 64 ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Select( 'database_column_type', $column ? $column['type'] : 'VARCHAR', TRUE, array(
|
|
'options' => \IPS\Db::$dataTypes,
|
|
'toggles' => array(
|
|
'TINYINT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_auto_increment', 'database_column_default' ),
|
|
'SMALLINT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_auto_increment', 'database_column_default' ),
|
|
'MEDIUMINT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_auto_increment', 'database_column_default' ),
|
|
'INT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_auto_increment', 'database_column_default' ),
|
|
'BIGINT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_auto_increment', 'database_column_default' ),
|
|
'DECIMAL' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_decimals', 'database_column_default' ),
|
|
'FLOAT' => array( 'database_column_length', 'database_column_unsigned', 'database_column_zerofill', 'database_column_decimals', 'database_column_default' ),
|
|
'BIT' => array( 'database_column_length', 'database_column_default' ),
|
|
'DATE' => array( 'database_column_default' ),
|
|
'DATETIME' => array( 'database_column_default' ),
|
|
'TIMESTAMP' => array( 'database_column_default' ),
|
|
'TIME' => array( 'database_column_default' ),
|
|
'YEAR' => array( 'database_column_default' ),
|
|
'CHAR' => array( 'database_column_length', 'database_column_binary', 'database_column_default' ),
|
|
'VARCHAR' => array( 'database_column_length', 'database_column_binary', 'database_column_default' ),
|
|
'TINYTEXT' => array( 'database_column_binary' ),
|
|
'TEXT' => array( 'database_column_binary' ),
|
|
'MEDIUMTEXT'=> array( 'database_column_binary' ),
|
|
'LONGTEXT' => array( 'database_column_binary' ),
|
|
'BINARY' => array( 'database_column_length', 'database_column_default' ),
|
|
'VARBINARY' => array( 'database_column_length', 'database_column_default' ),
|
|
'TINYBLOB' => array( ),
|
|
'BLOB' => array( ),
|
|
'MEDIUMBLOB'=> array( ),
|
|
'BIGBLOB' => array( ),
|
|
'ENUM' => array( 'database_column_values', 'database_column_default' ),
|
|
'SET' => array( 'database_column_values', 'database_column_default' ),
|
|
),
|
|
) ) );
|
|
|
|
$form->add( new \IPS\Helpers\Form\Number( 'database_column_length', ( $column and $column['length'] !== NULL ) ? $column['length'] : -1, FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'no_value' ), NULL, NULL, NULL, 'database_column_length' ) );
|
|
$form->add( new \IPS\Helpers\Form\Number( 'database_column_decimals', ( $column and isset( $column['decimals'] ) and $column['decimals'] !== NULL ) ? $column['decimals'] : -1, FALSE, array( 'unlimited' => -1, 'unlimitedLang' => 'no_value' ), NULL, NULL, NULL, 'database_column_decimals' ) );
|
|
$form->add( new \IPS\Helpers\Form\Stack( 'database_column_values', ( $column and isset( $column['values'] ) ) ? $column['values'] : NULL, FALSE, array(), NULL, NULL, NULL, 'database_column_values' ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'database_column_allow_null', $column ? $column['allow_null'] : TRUE, TRUE ) );
|
|
$form->add( new \IPS\Helpers\Form\TextArea( 'database_column_default', $column ? $column['default'] : NULL, FALSE, array( 'nullLang' => 'NULL' ), NULL, NULL, NULL, 'database_column_default' ) );
|
|
$form->add( new \IPS\Helpers\Form\TextArea( 'database_column_comment', $column ? $column['comment'] : NULL, FALSE ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'database_column_unsigned', $column ? $column['unsigned'] : FALSE, FALSE, array(), NULL, NULL, NULL, 'database_column_unsigned' ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'database_column_zerofill', $column ? $column['zerofill'] : FALSE, FALSE, array(), NULL, NULL, NULL, 'database_column_zerofill' ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'database_column_auto_increment', $column ? $column['auto_increment'] : FALSE, FALSE, array(), NULL, NULL, NULL, 'database_column_auto_increment' ) );
|
|
$form->add( new \IPS\Helpers\Form\YesNo( 'database_column_binary', $column ? $column['binary'] : FALSE, FALSE, array(), NULL, NULL, NULL, 'database_column_binary' ) );
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
/* Change -1 to NULL where appropriate */
|
|
foreach ( array( 'database_column_length', 'database_column_decimals' ) as $k )
|
|
{
|
|
if ( $values[ $k ] === -1 )
|
|
{
|
|
$values[ $k ] = NULL;
|
|
}
|
|
}
|
|
|
|
/* Get a column definition */
|
|
$save = array();
|
|
foreach ( $values as $k => $v )
|
|
{
|
|
/* If this is a new column and we have set the auto_increment flag, or if this is an existing column and the auto_increment
|
|
flag was not previously set but we have toggled it on, then we need to add the primary key flag as well because MySQL
|
|
requires any auto_increment column to also be a primary key */
|
|
if( $k == 'database_column_auto_increment' AND $v AND ( !$column OR ( !$column['auto_increment'] ) ) )
|
|
{
|
|
$save['primary'] = true;
|
|
}
|
|
|
|
$save[ str_replace( 'database_column_', '', $k ) ] = $v;
|
|
}
|
|
|
|
/* Save */
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
$form->error = \IPS\Member::loggedIn()->language()->addToStack('dev_could_not_write_schema_data');
|
|
}
|
|
else
|
|
{
|
|
if ( !$column )
|
|
{
|
|
\IPS\Db::i()->addColumn( $definition['name'], $save );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'addColumn', 'params' => array( $definition['name'], $save ) ) ) );
|
|
}
|
|
else
|
|
{
|
|
\IPS\Db::i()->changeColumn( $definition['name'], $column['name'], $save );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'changeColumn', 'params' => array( $definition['name'], $column['name'], $save ) ) ) );
|
|
|
|
if ( $column['name'] != $save['name'] )
|
|
{
|
|
unset( $schema[ $definition['name'] ]['columns'][ $column['name'] ] );
|
|
}
|
|
}
|
|
|
|
/* If we added the 'primary' flag, remove it before saving schema.json because it should not be reflected there...BUT we
|
|
need to add primary key index definition to the schema.json instead in this case */
|
|
if( isset( $save['primary'] ) )
|
|
{
|
|
unset( $save['primary'] );
|
|
$schema[ $definition['name'] ]['columns'][ $save['name'] ] = $save;
|
|
|
|
$schema[ $definition['name'] ]['indexes']['PRIMARY'] = array(
|
|
'type' => 'primary',
|
|
'name' => 'PRIMARY',
|
|
'length' => array( 0 => NULL ),
|
|
'columns' => array( 0 => $save['name'] )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$schema[ $definition['name'] ]['columns'][ $save['name'] ] = $save;
|
|
}
|
|
|
|
/* Did we rename the column? */
|
|
if( $column AND $save['name'] !== $column['name'] )
|
|
{
|
|
/* Fix references to the column name in indexes */
|
|
if( isset( $schema[ $definition['name'] ]['indexes'] ) )
|
|
{
|
|
foreach( $schema[ $definition['name'] ]['indexes'] as $indexName => $indexDefinition )
|
|
{
|
|
foreach( $indexDefinition['columns'] as $_idx => $columnName )
|
|
{
|
|
if( $columnName == $column['name'] )
|
|
{
|
|
$schema[ $definition['name'] ]['indexes'][ $indexName ]['columns'][ $_idx ] = $save['name'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fix references to the column name in inserts */
|
|
if( isset( $schema[ $definition['name'] ]['inserts'] ) )
|
|
{
|
|
foreach( $schema[ $definition['name'] ]['inserts'] as $_idx => $insert )
|
|
{
|
|
$insert[ $save['name'] ] = $insert[ $column['name'] ];
|
|
unset( $insert[ $column['name'] ] );
|
|
|
|
$schema[ $definition['name'] ]['inserts'][ $_idx ] = $insert;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->_writeSchema( $schema );
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&tab=columns" ) );
|
|
}
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
$form->error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
/* Display */
|
|
\IPS\Output::i()->output = $form;
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Delete Column
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaDeleteColumn()
|
|
{
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
throw new \Exception('dev_could_not_write_schema_data');
|
|
}
|
|
|
|
\IPS\Db::i()->dropColumn( \IPS\Request::i()->_name, \IPS\Request::i()->column );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'dropColumn', 'params' => array( \IPS\Request::i()->_name, \IPS\Request::i()->column ) ) ) );
|
|
|
|
$schema = $this->_getSchema();
|
|
unset( $schema[ \IPS\Request::i()->_name ]['columns'][ \IPS\Request::i()->column ] );
|
|
|
|
/* Do any indexes use this column? */
|
|
if ( isset( $schema[ \IPS\Request::i()->_name ]['indexes'] ) and is_array( $schema[ \IPS\Request::i()->_name ]['indexes'] ) )
|
|
{
|
|
foreach( $schema[ \IPS\Request::i()->_name ]['indexes'] as $name => $definition )
|
|
{
|
|
$changed = false;
|
|
|
|
foreach( $definition['columns'] as $id => $colName )
|
|
{
|
|
if ( $colName === \IPS\Request::i()->column )
|
|
{
|
|
unset( $definition['columns'][ $id ] );
|
|
unset( $definition['length'][ $id ] );
|
|
|
|
$changed = true;
|
|
}
|
|
}
|
|
|
|
/* Still have columns? */
|
|
if ( ! count( $definition['columns'] ) )
|
|
{
|
|
/* Remove the index from schema.json, MySQL will do this automatically */
|
|
unset( $schema[ \IPS\Request::i()->_name ]['indexes'][ $name ] );
|
|
}
|
|
else if ( $changed )
|
|
{
|
|
/* Alter it */
|
|
\IPS\Db::i()->changeIndex( \IPS\Request::i()->_name, $name, $definition );
|
|
|
|
$schema[ \IPS\Request::i()->_name ]['indexes'][ $name ] = $definition;
|
|
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'changeIndex', 'params' => array( $name, $name, $definition ) ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->_writeSchema( $schema );
|
|
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
\IPS\Output::i()->output = 1;
|
|
return;
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name=" . \IPS\Request::i()->_name . "&tab=columns" ) );
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( $e->getMessage(), '1C103/N', 403, '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Add/Edit Index
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaIndex()
|
|
{
|
|
/* Get current index */
|
|
$maxIndexLength = 250;
|
|
$index = NULL;
|
|
$schema = $this->_getSchema();
|
|
$definition = $schema[ \IPS\Request::i()->_name ];
|
|
if ( \IPS\Request::i()->index )
|
|
{
|
|
$index = $definition['indexes'][ \IPS\Request::i()->index ];
|
|
}
|
|
|
|
/* Build form */
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->add( new \IPS\Helpers\Form\Select( 'database_index_type', $index ? $index['type'] : 'key', TRUE, array( 'options' => array(
|
|
'primary' => 'PRIMARY',
|
|
'unique' => 'UNIQUE',
|
|
'fulltext' => 'FULLTEXT',
|
|
'key' => 'KEY'
|
|
) ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Text( 'database_index_name', $index ? $index['name'] : NULL, TRUE, array( 'maxLength' => 64 ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Stack( 'database_index_columns', $index ? $index['columns'] : array(), TRUE, array(
|
|
'options' => array_combine( array_keys( $definition['columns'] ), array_keys( $definition['columns'] ) ),
|
|
'parse' => 'normal',
|
|
'stackFieldType' => 'Select',
|
|
) ) );
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
/* Get a definition */
|
|
$save = array();
|
|
foreach ( $values as $k => $v )
|
|
{
|
|
$save[ str_replace( 'database_index_', '', $k ) ] = $v;
|
|
}
|
|
|
|
foreach( $save['columns'] as $id => $field )
|
|
{
|
|
if ( isset( $definition['columns'][ $field ] ) )
|
|
{
|
|
if ( ( \mb_substr( \mb_strtolower( $definition['columns'][ $field ]['type'] ), -4 ) === 'text' ) OR ( ! empty( $definition['columns'][ $field ]['length'] ) AND is_integer( $definition['columns'][ $field ]['length']) AND $definition['columns'][ $field ]['length'] > $maxIndexLength ) )
|
|
{
|
|
$save['length'][ $id ] = $maxIndexLength;
|
|
}
|
|
}
|
|
|
|
if ( ! isset( $save['length'][ $id ] ) )
|
|
{
|
|
$save['length'][ $id ] = null;
|
|
}
|
|
}
|
|
|
|
/* Save */
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
$form->error = \IPS\Member::loggedIn()->language()->addToStack('dev_could_not_write_schema_data');
|
|
}
|
|
else
|
|
{
|
|
if ( !$index )
|
|
{
|
|
\IPS\Db::i()->addIndex( $definition['name'], $save );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'addIndex', 'params' => array( $definition['name'], $save ) ) ) );
|
|
|
|
if ( $index['name'] != $save['name'] )
|
|
{
|
|
unset( $schema[ $definition['name'] ]['indexes'][ $index['name'] ] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
\IPS\Db::i()->changeIndex( $definition['name'], $index['name'], $save );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'changeIndex', 'params' => array( $definition['name'], $index['name'], $save ) ) ) );
|
|
}
|
|
$schema[ $definition['name'] ]['indexes'][ $save['name'] ] = $save;
|
|
|
|
$this->_writeSchema( $schema );
|
|
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
\IPS\Output::i()->output = 1;
|
|
return;
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&tab=indexes" ) );
|
|
}
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
$form->error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
/* Display */
|
|
\IPS\Output::i()->output = $form;
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Delete Index
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaDeleteIndex()
|
|
{
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
throw new \Exception('dev_could_not_write_schema_data');
|
|
}
|
|
|
|
\IPS\Db::i()->dropIndex( \IPS\Request::i()->_name, \IPS\Request::i()->index );
|
|
$this->_writeQueries( 'working', $this->_addQueryToJson( $this->_getQueries( 'working' ), array( 'method' => 'dropIndex', 'params' => array( \IPS\Request::i()->_name, \IPS\Request::i()->index ) ) ) );
|
|
|
|
$schema = $this->_getSchema();
|
|
unset( $schema[ \IPS\Request::i()->_name ]['indexes'][ \IPS\Request::i()->index ] );
|
|
$this->_writeSchema( $schema );
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name=" . \IPS\Request::i()->_name . "&tab=indexes" ) );
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( $e->getMessage(), '1C103/O', 403, '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Add/Edit Insert Row
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaInsert()
|
|
{
|
|
/* Get current row */
|
|
$index = NULL;
|
|
$schema = $this->_getSchema();
|
|
$definition = $schema[ \IPS\Request::i()->_name ];
|
|
$data = array();
|
|
if ( isset( \IPS\Request::i()->row ) )
|
|
{
|
|
$index = \IPS\Request::i()->row;
|
|
$data = $definition['inserts'][ \IPS\Request::i()->row ];
|
|
}
|
|
|
|
/* Build form */
|
|
$form = new \IPS\Helpers\Form();
|
|
foreach ( $definition['columns'] as $column )
|
|
{
|
|
if ( array_key_exists( $column['type'], \IPS\Db::$dataTypes['database_column_type_numeric'] ) and $column['type'] !== 'BIT' )
|
|
{
|
|
$min = NULL;
|
|
$max = NULL;
|
|
|
|
switch ( $column['type'] )
|
|
{
|
|
case 'TINYINT':
|
|
$min = $column['unsigned'] ? 0 : -128;
|
|
$max = $column['unsigned'] ? 255 : 127;
|
|
break;
|
|
|
|
case 'SMALLINT':
|
|
$min = $column['unsigned'] ? 0 : -32768;
|
|
$max = $column['unsigned'] ? 65535 : 32767;
|
|
break;
|
|
|
|
case 'MEDIUMINT':
|
|
$min = $column['unsigned'] ? 0 : -8388608;
|
|
$max = $column['unsigned'] ? 16777215 : 8388607;
|
|
break;
|
|
|
|
case 'INT':
|
|
case 'INTEGER':
|
|
$min = $column['unsigned'] ? 0 : -2147483648;
|
|
$max = $column['unsigned'] ? 4294967295 : 2147483647;
|
|
break;
|
|
|
|
case 'BIGINT':
|
|
$min = $column['unsigned'] ? 0 : -9223372036854775808;
|
|
$max = $column['unsigned'] ? 18446744073709551615 : 9223372036854775807;
|
|
break;
|
|
}
|
|
|
|
$options = array();
|
|
if ( $column['allow_null'] or $column['auto_increment'] )
|
|
{
|
|
//$options = array( 'unlimited' => 'NULL', 'unlimitedLang' => 'NULL' );
|
|
$options = array( 'nullLang' => 'NULL' );
|
|
}
|
|
if ( isset( $column['decimals'] ) )
|
|
{
|
|
$options['decimals'] = $column['decimals'];
|
|
}
|
|
|
|
/*if ( isset( $data[ $column['name'] ] ) and $data[ $column['name'] ] === NULL )
|
|
{
|
|
$data[ $column['name'] ] = 'NULL';
|
|
}*/
|
|
|
|
$options['min'] = NULL;
|
|
|
|
$value = NULL;
|
|
if ( $data and isset( $data[ $column['name'] ] ) )
|
|
{
|
|
$value = $data[ $column['name'] ];
|
|
}
|
|
else
|
|
{
|
|
$value = ( $column['auto_increment'] or $column['default'] === NULL ) ? NULL : $column['default'];
|
|
}
|
|
|
|
$element = new \IPS\Helpers\Form\Text( $column['name'], $value, FALSE, $options );
|
|
}
|
|
elseif ( in_array( $column['type'], array( 'CHAR', 'VARCHAR', 'BINARY', 'VARBINARY' ) ) )
|
|
{
|
|
$element = new \IPS\Helpers\Form\Text( $column['name'], ( $data AND isset( $data[ $column['name'] ] ) ) ? $data[ $column['name'] ] : $column['default'], FALSE, $column['allow_null'] ? array( 'nullLang' => 'NULL' ) : array() );
|
|
}
|
|
elseif ( in_array( $column['type'], array( 'TEXT', 'MEDIUMTEXT', 'BIGTEXT', 'TINYBLOB', 'BLOB', 'MEDIUMBLOB', 'BIGBLOB' ) ) )
|
|
{
|
|
$element = new \IPS\Helpers\Form\TextArea( $column['name'], ( $data AND isset( $data[ $column['name'] ] ) ) ? $data[ $column['name'] ] : $column['default'], FALSE, $column['allow_null'] ? array( 'nullLang' => 'NULL' ) : array() );
|
|
}
|
|
elseif ( $column['type'] === 'ENUM' )
|
|
{
|
|
$element = new \IPS\Helpers\Form\Select( $column['name'], ( $data AND isset( $data[ $column['name'] ] ) ) ? $data[ $column['name'] ] : $column['default'], FALSE, array( 'options' => array_combine( $column['values'], $column['values'] ) ) );
|
|
}
|
|
elseif ( $column['type'] === 'SET' )
|
|
{
|
|
$element = new \IPS\Helpers\Form\Select( $column['name'], ( $data AND isset( $data[ $column['name'] ] ) ) ? explode( ',', $data[ $column['name'] ] ) : explode( ',', $column['default'] ), FALSE, array( 'options' => array_combine( $column['values'], $column['values'] ), 'multiple' => TRUE ) );
|
|
}
|
|
else
|
|
{
|
|
$element = new \IPS\Helpers\Form\Text( $column['name'], ( $data AND isset( $data[ $column['name'] ] ) ) ? $data[ $column['name'] ] : $column['default'], FALSE, $column['allow_null'] ? array( 'nullLang' => 'NULL' ) : array() );
|
|
}
|
|
|
|
$element->label = $column['name'];
|
|
$form->add( $element );
|
|
}
|
|
|
|
/* Handle submissions */
|
|
if ( $values = $form->values() )
|
|
{
|
|
foreach ( $definition['columns'] as $column )
|
|
{
|
|
if ( array_key_exists( $column['type'], \IPS\Db::$dataTypes['database_column_type_numeric'] ) and $column['type'] !== 'BIT' and $values[ $column['name'] ] === 'NULL' )
|
|
{
|
|
$values[ $column['name'] ] = NULL;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
$form->error = \IPS\Member::loggedIn()->language()->addToStack('dev_could_not_write_schema_data');
|
|
}
|
|
else
|
|
{
|
|
if ( $index !== NULL )
|
|
{
|
|
$schema[ $definition['name'] ]['inserts'][ $index ] = $values;
|
|
}
|
|
else
|
|
{
|
|
\IPS\Db::i()->insert( $definition['name'], $values );
|
|
$schema[ $definition['name'] ]['inserts'][] = $values;
|
|
}
|
|
|
|
$this->_writeSchema( $schema );
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$definition['name']}&tab=inserts" ) );
|
|
}
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
$form->error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
/* Display */
|
|
\IPS\Output::i()->output = $form;
|
|
}
|
|
|
|
/**
|
|
* Edit Schema: Delete Insert
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function editSchemaDeleteInsert()
|
|
{
|
|
try
|
|
{
|
|
if ( $this->_schemaJsonIsWritable() !== true )
|
|
{
|
|
throw new \Exception('dev_could_not_write_schema_data');
|
|
}
|
|
|
|
$schema = $this->_getSchema();
|
|
unset( $schema[ \IPS\Request::i()->_name ]['inserts'][ \IPS\Request::i()->row ] );
|
|
$this->_writeSchema( $schema );
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name=" . \IPS\Request::i()->_name . "&tab=inserts" ) );
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( $e->getMessage(), '1C103/P', 403, '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show Schema
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function showSchema()
|
|
{
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block(
|
|
\IPS\Request::i()->_name,
|
|
str_replace( '<?php', '', highlight_string( "<?php " . var_export(\IPS\Db::i()->getTableDefinition( \IPS\Request::i()->_name ), TRUE ), TRUE ) ),
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolve Schema Conflicts
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function resolveSchemaConflicts()
|
|
{
|
|
/* Get table definitions */
|
|
$schema = $this->_getSchema();
|
|
if ( !isset( $schema[ \IPS\Request::i()->_name ] ) )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/G', 404, '' );
|
|
}
|
|
$schemaDefinition = $schema[ \IPS\Request::i()->_name ];
|
|
$localDefinition = $this->_getTableDefinition( $schemaDefinition['name'] );
|
|
|
|
/* Use local database */
|
|
if ( \IPS\Request::i()->local )
|
|
{
|
|
foreach ( $localDefinition['columns'] as $i => $data )
|
|
{
|
|
if ( $data['type'] == 'BIT' )
|
|
{
|
|
$localDefinition['columns'][ $i ]['default'] = intval( preg_replace( "/^b'(\d+?)'\$/", '$1', $data['default'] ) );
|
|
}
|
|
}
|
|
$schema[ \IPS\Request::i()->_name ] = $localDefinition;
|
|
if ( isset( $schemaDefinition['inserts'] ) )
|
|
{
|
|
$schema[ \IPS\Request::i()->_name ]['inserts'] = $schemaDefinition['inserts'];
|
|
}
|
|
if ( isset( $schemaDefinition['engine'] ) )
|
|
{
|
|
$schema[ \IPS\Request::i()->_name ]['engine'] = $schemaDefinition['engine'];
|
|
}
|
|
else
|
|
{
|
|
unset( $schema[ \IPS\Request::i()->_name ]['engine'] );
|
|
}
|
|
|
|
if( isset( $schemaDefinition['reporting'] ) )
|
|
{
|
|
$schema[ \IPS\Request::i()->_name ]['reporting'] = $schemaDefinition['reporting'];
|
|
}
|
|
|
|
$this->_writeSchema( $schema );
|
|
}
|
|
/* Use schema file */
|
|
else
|
|
{
|
|
/* Create a new table */
|
|
$_newTable = $schemaDefinition;
|
|
$_newTable['name'] = $_newTable['name'] . '_temp';
|
|
\IPS\Db::i()->createTable( $_newTable );
|
|
|
|
/* Work out our columns */
|
|
$columns = array();
|
|
foreach ( array_keys( $schemaDefinition['columns'] ) as $column )
|
|
{
|
|
if ( isset( $localDefinition['columns'][ $column ] ) )
|
|
{
|
|
$columns[] = $column;
|
|
}
|
|
}
|
|
$columns = implode( ',', array_map( function( $v ){ return "`{$v}`"; }, $columns ) );
|
|
|
|
/* Insert the rows */
|
|
if ( !empty( $columns ) )
|
|
{
|
|
\IPS\Db::i()->query( 'INSERT IGNORE INTO `' . \IPS\Db::i()->prefix . $_newTable['name'] . "` ( {$columns} ) SELECT {$columns} FROM `" . \IPS\Db::i()->prefix . $schemaDefinition['name'] . '`' );
|
|
}
|
|
|
|
/* Drop the old table */
|
|
\IPS\Db::i()->dropTable( $schemaDefinition['name'] );
|
|
|
|
/* Rename the new table */
|
|
\IPS\Db::i()->renameTable( $_newTable['name'], $schemaDefinition['name'] );
|
|
}
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=editSchema&_name={$schemaDefinition['name']}" ) );
|
|
}
|
|
|
|
/**
|
|
* Delete Table
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function deleteTable()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
$table = \IPS\Request::i()->name;
|
|
|
|
/* Drop the table */
|
|
$query = array( 'method' => 'dropTable', 'params' => array( $table, TRUE ) );
|
|
call_user_func_array( array( \IPS\Db::i(), $query['method'] ), $query['params'] );
|
|
|
|
/* Add the drop to the queries.json file */
|
|
$queries = $this->_getQueries( 'working' );
|
|
$queries = $this->_addQueryToJson( $queries, $query );
|
|
$this->_writeQueries( 'working', $queries );
|
|
|
|
/* Remove from schema.json */
|
|
$schemaJson = $this->_getSchema();
|
|
unset( $schemaJson[ $table ] );
|
|
$this->_writeSchema( $schemaJson );
|
|
|
|
/* And redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=schema" ) );
|
|
}
|
|
|
|
/**
|
|
* Manage Extensions
|
|
*
|
|
* @return string HTML to display
|
|
*/
|
|
protected function _manageExtensions()
|
|
{
|
|
$this->url = \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=extensions" );
|
|
$table = new \IPS\Helpers\Tree\Tree( $this->url, 'dev_extensions', array( $this, 'extGetRoots' ), array( $this, 'extGetRow' ), array( $this, 'extGetRowParentId' ), array( $this, 'extGetChildren' ), NULL, FALSE, TRUE, TRUE );
|
|
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* Extensions: Get Root Rows
|
|
*
|
|
* @return array
|
|
*/
|
|
public function extGetRoots()
|
|
{
|
|
$rows = array();
|
|
|
|
foreach ( \IPS\Application::applications() as $app )
|
|
{
|
|
$haveExtensions = FALSE;
|
|
|
|
if( is_dir( \IPS\ROOT_PATH . "/applications/{$app->directory}/data/defaults/extensions" ) )
|
|
{
|
|
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$app->directory}/data/defaults/extensions" ) as $file )
|
|
{
|
|
if ( mb_substr( $file, 0, 1 ) !== '.' AND $file != 'index.html' )
|
|
{
|
|
$haveExtensions = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $haveExtensions === TRUE )
|
|
{
|
|
$rows[ $app->directory ] = \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, $app->directory, $app->directory, TRUE );
|
|
}
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Extensions: Get row
|
|
*
|
|
* @param string $row Row ID
|
|
* @param bool $root Is root?
|
|
* @return string
|
|
*/
|
|
public function extGetRow( $row, $root )
|
|
{
|
|
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, $row, $row, TRUE, array(), '', NULL, NULL, $root );
|
|
}
|
|
|
|
/**
|
|
* Extensions: Get Children
|
|
*
|
|
* @param string $folder Folder
|
|
* @return array
|
|
*/
|
|
public function extGetChildren( $folder )
|
|
{
|
|
$rows = array();
|
|
|
|
if ( is_dir( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/{$folder}" ) )
|
|
{
|
|
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/{$folder}" ) as $file )
|
|
{
|
|
if ( mb_substr( $file, 0, 1 ) !== '.' AND $file != 'index.html' )
|
|
{
|
|
$buttons = array();
|
|
|
|
if ( file_exists( \IPS\ROOT_PATH . "/applications/{$folder}/data/defaults/extensions/{$file}.txt" ) )
|
|
{
|
|
$buttons['add'] = array(
|
|
'icon' => 'plus-circle',
|
|
'title' => \IPS\Member::loggedIn()->language()->addToStack('dev_extensions_create', FALSE, array( 'sprintf' => array( $file ) ) ),
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addExtension&type={$file}&extapp={$folder}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('dev_extensions_create', FALSE, array( 'sprintf' => array( $file ) ) ) )
|
|
);
|
|
}
|
|
|
|
$name = str_replace( '\\', '/', mb_substr( $file->getPathName(), mb_strlen( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/" ) ) );
|
|
|
|
$rows[ $name ] = \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, $name, mb_substr( $name, mb_strlen( $folder ) + 1 ), $file->isDir() ? TRUE : FALSE, $buttons, $file->isDir() ? ( '<br>' . \IPS\Member::loggedIn()->language()->addToStack( 'ext__' . mb_substr( $name, mb_strlen( $folder ) + 1 ) ) ) : '', NULL, NULL, FALSE, NULL, NULL, NULL, FALSE, TRUE );
|
|
}
|
|
}
|
|
}
|
|
|
|
$extensionApp = explode( '/', $folder );
|
|
if( count( $extensionApp ) == 1 )
|
|
{
|
|
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/applications/{$extensionApp[0]}/data/defaults/extensions" ) as $file )
|
|
{
|
|
if ( mb_substr( $file, 0, 1 ) !== '.' AND $file != 'index.html' )
|
|
{
|
|
$name = $folder . '/' .mb_substr( $file->getPathName(), mb_strlen( \IPS\ROOT_PATH . "/applications/{$extensionApp[0]}/data/defaults/extensions/" ), -4 );
|
|
|
|
if ( !isset( $rows[ $name ] ) )
|
|
{
|
|
$rows[ $name ] = \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, $name, mb_substr( $name, mb_strlen( $folder ) + 1 ), $file->isDir() ? TRUE : FALSE, array( 'add' => array(
|
|
'icon' => 'plus-circle',
|
|
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'dev_extensions_create', FALSE, array( 'sprintf' => array( mb_substr( $file, 0, -4 ) ) ) ),
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addExtension&type=" . mb_substr( $file, 0, -4 ) . "&extapp={$folder}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('dev_extensions_create', FALSE, array( 'sprintf' => array( mb_substr( $file, 0, -4 ) ) ) ) )
|
|
) ), \IPS\Member::loggedIn()->language()->addToStack( 'ext__' . mb_substr( $name, mb_strlen( $folder ) + 1 ) ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ksort( $rows );
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Extensions: Get parent ID
|
|
*
|
|
* @return string
|
|
*/
|
|
public function extGetRowParentId( $folder )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Add Extension
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addExtension()
|
|
{
|
|
$form = new \IPS\Helpers\Form();
|
|
$form->hiddenFields['type'] = \IPS\Request::i()->type;
|
|
$form->add( new \IPS\Helpers\Form\Text( 'dev_extensions_classname', NULL, TRUE, array( 'regex' => '/^[A-Z0-9]+$/i' ) ) );
|
|
|
|
if ( $values = $form->values() )
|
|
{
|
|
$contents = str_replace(
|
|
array(
|
|
"{subpackage}",
|
|
'{date}',
|
|
'{app}',
|
|
'{class}',
|
|
),
|
|
array(
|
|
( $this->application->directory != 'core' ) ? ( " * @subpackage\t" . \IPS\Member::loggedIn()->language()->get( "__app_{$this->application->directory}" ) ) : '',
|
|
date( 'd M Y' ),
|
|
$this->application->directory,
|
|
$values['dev_extensions_classname']
|
|
|
|
),
|
|
file_get_contents( \IPS\ROOT_PATH . '/applications/' . \IPS\Request::i()->extapp . '/data/defaults/extensions/' . \IPS\Request::i()->type . '.txt' )
|
|
);
|
|
|
|
$dir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/" . \IPS\Request::i()->extapp . '/' . \IPS\Request::i()->type;
|
|
if ( !is_dir( $dir ) )
|
|
{
|
|
mkdir( $dir, \IPS\IPS_FOLDER_PERMISSION, TRUE );
|
|
}
|
|
|
|
\file_put_contents( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/extensions/" . \IPS\Request::i()->extapp . '/' . \IPS\Request::i()->type . "/{$values['dev_extensions_classname']}.php", $contents );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=extensions" ), 'file_created' );
|
|
}
|
|
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('global')->block( \IPS\Member::loggedIn()->language()->addToStack('dev_extensions_create', FALSE, array( 'sprintf' => array( \IPS\Request::i()->type ) ) ), $form, FALSE );
|
|
}
|
|
|
|
/**
|
|
* Manage Settings
|
|
*
|
|
* @return string HTML to display
|
|
*/
|
|
protected function _manageSettings()
|
|
{
|
|
$settings = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/settings.json" );
|
|
|
|
$form = new \IPS\Helpers\Form\Matrix();
|
|
$form->langPrefix = 'dev_settings_';
|
|
$form->columns = array(
|
|
'key' => array( 'Text', NULL, TRUE ),
|
|
'default' => array( 'Text' ),
|
|
);
|
|
if ( in_array( $this->application->directory, \IPS\Application::$ipsApps ) )
|
|
{
|
|
$form->columns['report'] = array( 'Select', 'none', FALSE, array( 'options' => array(
|
|
'none' => 'dev_settings_none',
|
|
'full' => 'dev_settings_full',
|
|
'bool' => 'dev_settings_bool',
|
|
) ) );
|
|
}
|
|
$form->rows = $settings;
|
|
|
|
if ( $form->values() !== FALSE )
|
|
{
|
|
$values = $form->values();
|
|
|
|
if ( !empty( $form->addedRows ) )
|
|
{
|
|
$insert = array();
|
|
foreach ( $form->addedRows as $key )
|
|
{
|
|
$insert[] = array( 'conf_key' => $values[ $key ]['key'], 'conf_value' => $values[ $key ]['default'], 'conf_default' => $values[ $key ]['default'], 'conf_app' => $this->application->directory );
|
|
}
|
|
|
|
\IPS\Db::i()->insert( 'core_sys_conf_settings', $insert );
|
|
}
|
|
if ( !empty( $form->changedRows ) )
|
|
{
|
|
foreach ( $form->changedRows as $key )
|
|
{
|
|
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_key' => $values[ $key ]['key'], 'conf_default' => $values[ $key ]['default'] ), array( 'conf_key=?', $form->rows[ $key ]['key'] ) );
|
|
}
|
|
}
|
|
if ( !empty( $form->removedRows ) )
|
|
{
|
|
$delete = array();
|
|
foreach ( $form->removedRows as $key )
|
|
{
|
|
$delete[] = $form->rows[ $key ]['key'];
|
|
}
|
|
|
|
\IPS\Db::i()->delete( 'core_sys_conf_settings', \IPS\Db::i()->in( 'conf_key', $delete ) );
|
|
}
|
|
|
|
$save = array();
|
|
foreach ( $values as $data )
|
|
{
|
|
if ( $data['key'] )
|
|
{
|
|
$save[] = $data;
|
|
}
|
|
}
|
|
|
|
usort( $save, function( $a, $b )
|
|
{
|
|
return strnatcmp( $a['key'], $b['key'] );
|
|
} );
|
|
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/settings.json", $save );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=settings" ) );
|
|
}
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Versions: Show Versions
|
|
*
|
|
* @return string HTML to display
|
|
*/
|
|
protected function _manageVersions()
|
|
{
|
|
/* Create the file if it doesn't exist */
|
|
$this->json = $this->_getVersions();
|
|
|
|
/* Reorder if necessary */
|
|
if ( \IPS\Request::i()->do === 'reorder' )
|
|
{
|
|
/* Normalise AJAX vs non-AJAX */
|
|
if( isset( \IPS\Request::i()->ajax_order ) )
|
|
{
|
|
$order = array();
|
|
$position = array();
|
|
foreach( \IPS\Request::i()->ajax_order as $id => $parent )
|
|
{
|
|
if ( !isset( $order[ $parent ] ) )
|
|
{
|
|
$order[ $parent ] = array();
|
|
$position[ $parent ] = 1;
|
|
}
|
|
$order[ $parent ][ $id ] = $position[ $parent ]++;
|
|
}
|
|
}
|
|
/* Non-AJAX way */
|
|
else
|
|
{
|
|
$order = array( \IPS\Request::i()->root ?: 'null' => \IPS\Request::i()->order );
|
|
}
|
|
|
|
/* Work out */
|
|
$queries = array();
|
|
$write = array();
|
|
foreach ( $order as $versionKey => $keys )
|
|
{
|
|
if ( $versionKey != 'null' )
|
|
{
|
|
asort( $keys );
|
|
foreach ( $keys as $key => $position )
|
|
{
|
|
$versionNumber = mb_substr( $key, 0, mb_strpos( $key, '.' ) );
|
|
if ( !isset( $queries[ $versionNumber ] ) )
|
|
{
|
|
$queries[ $versionNumber ] = $this->_getQueries( $versionNumber );
|
|
}
|
|
$write[ $versionNumber ][] = $queries[ $versionNumber ][ mb_substr( $key, mb_strpos( $key, '.' ) + 1 ) ];
|
|
}
|
|
}
|
|
}
|
|
foreach ( $write as $versionNumber => $queries )
|
|
{
|
|
$this->_writeQueries( $versionNumber, $queries );
|
|
}
|
|
|
|
if ( \IPS\Request::i()->isAjax() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Build node tree */
|
|
$appKey = $this->application->directory;
|
|
$this->url = \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions" );
|
|
$table = new \IPS\Helpers\Tree\Tree(
|
|
$this->url,
|
|
'dev_versions',
|
|
/* Get Roots */
|
|
array( $this, '_getVersionRows' ),
|
|
/* Get Row */
|
|
array( $this, '_getVersionRow' ),
|
|
/* Get Row's Parent ID */
|
|
function( $id )
|
|
{
|
|
return NULL;
|
|
},
|
|
/* Get Children */
|
|
array( $this, '_getQueriesRows' ),
|
|
/* Get Root Buttons */
|
|
function() use ( $appKey )
|
|
{
|
|
return array(
|
|
'add' => array(
|
|
'icon' => 'plus',
|
|
'title' => 'versions_add',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$appKey}&do=addVersion" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('versions_add') )
|
|
)
|
|
);
|
|
},
|
|
NULL,
|
|
FALSE,
|
|
TRUE
|
|
);
|
|
|
|
/* Return */
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* Get all version rows
|
|
*
|
|
* @return array
|
|
*/
|
|
public function _getVersionRows()
|
|
{
|
|
$rows = array( 'install' => $this->_getVersionRow( 'install' ) );
|
|
foreach ( $this->json as $long => $human )
|
|
{
|
|
array_unshift( $rows, $this->_getVersionRow( $long ) );
|
|
}
|
|
array_unshift( $rows, $this->_getVersionRow( 'working' ) );
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Get individual version row
|
|
*
|
|
* @param int $long Version ID
|
|
* @param bool $root Format this as the root node?
|
|
* @return string
|
|
*/
|
|
public function _getVersionRow( $long, $root=FALSE )
|
|
{
|
|
$dir = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/setup/" . ( $long === 'install' ? $long : "upg_{$long}" );
|
|
|
|
$hasChildren = FALSE;
|
|
if ( is_dir( $dir ) )
|
|
{
|
|
foreach ( new \DirectoryIterator( $dir ) as $file )
|
|
{
|
|
if ( !$file->isDot() and mb_substr( $file, 0, 1 ) !== '.' and $file != 'index.html' )
|
|
{
|
|
$hasChildren = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$buttons = array();
|
|
|
|
if( $long != 'install' )
|
|
{
|
|
$buttons['phpcode'] = array(
|
|
'icon' => 'code',
|
|
'title' => 'versions_code',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=versionCode&id={$long}" ),
|
|
);
|
|
}
|
|
|
|
$buttons['add'] = array(
|
|
'icon' => 'plus-circle',
|
|
'title' => 'versions_query',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addVersionQuery&id={$long}" ),
|
|
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('versions_query'), 'ipsDialog-remoteVerify' => "false" )
|
|
);
|
|
|
|
if( $long != 'install' and $long !== 'working' )
|
|
{
|
|
$buttons['delete'] = array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=deleteVersion&id={$long}" ),
|
|
'data' => array( 'delete' => '' )
|
|
);
|
|
}
|
|
|
|
if ( $long === 'install' )
|
|
{
|
|
$nameToDisplay = \IPS\Member::loggedIn()->language()->addToStack('versions_install');
|
|
}
|
|
elseif ( $long === 'working' )
|
|
{
|
|
$nameToDisplay = \IPS\Member::loggedIn()->language()->addToStack('versions_working');
|
|
}
|
|
else
|
|
{
|
|
$nameToDisplay = isset( $this->json[ $long ] ) ? $this->json[ $long ] : $long;
|
|
}
|
|
|
|
return \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, $long, $nameToDisplay, $hasChildren, $buttons, $long, NULL, NULL, $root );
|
|
}
|
|
|
|
/**
|
|
* Get the queries rows for a version
|
|
*
|
|
* @param int $long Version ID
|
|
* @return array
|
|
*/
|
|
public function _getQueriesRows( $long )
|
|
{
|
|
$queries = $this->_getQueries( $long );
|
|
$order = 1;
|
|
$rows = array();
|
|
foreach ( $queries as $qid => $data )
|
|
{
|
|
$params = array();
|
|
if ( isset( $data['params'] ) and is_array( $data['params'] ) )
|
|
{
|
|
foreach ( $data['params'] as $v )
|
|
{
|
|
$params[] = var_export( $v, TRUE );
|
|
}
|
|
}
|
|
|
|
$rows["{$long}.{$qid}"] = \IPS\Theme::i()->getTemplate( 'trees', 'core' )->row( $this->url, "{$long}.{$qid}", str_replace( '<?php', '', highlight_string( "<?php \\IPS\\Db::i()->{$data['method']}( ". implode( ', ', $params ) ." )", TRUE ) ), FALSE, array(
|
|
'delete' => array(
|
|
'icon' => 'times-circle',
|
|
'title' => 'delete',
|
|
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=deleteVersionQuery&version={$long}&query={$qid}" ),
|
|
'data' => array( 'delete' => '' )
|
|
)
|
|
), NULL, NULL, $order, FALSE, NULL, NULL, NULL, TRUE );
|
|
|
|
$order++;
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Versions: Add Version
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addVersion()
|
|
{
|
|
/* Load existing versions.json file */
|
|
$json = $this->_getVersions();
|
|
|
|
/* Get form */
|
|
$activeTab = \IPS\Request::i()->tab ?: 'new';
|
|
$form = new \IPS\Helpers\Form( 'versions_add' );
|
|
switch ( $activeTab )
|
|
{
|
|
/* Create New */
|
|
case 'new':
|
|
$form->addMessage( 'versions_add_information' );
|
|
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', NULL, TRUE, array( 'placeholder' => '1.0.0' ) ) );
|
|
$form->add( new \IPS\Helpers\Form\Text( 'versions_long', NULL, TRUE, array( 'placeholder' => '10000' ), function( $val ) use ( $json )
|
|
{
|
|
if ( !preg_match( '/^\d*$/', $val ) )
|
|
{
|
|
throw new \DomainException( 'form_number_bad' );
|
|
}
|
|
if( isset( $json[ $val ] ) )
|
|
{
|
|
throw new \DomainException( 'versions_long_exists' );
|
|
}
|
|
} ) );
|
|
break;
|
|
|
|
/* Upload */
|
|
case 'upload':
|
|
$appKey = $this->application->directory;
|
|
$form->add( new \IPS\Helpers\Form\Upload(
|
|
'upload',
|
|
NULL,
|
|
TRUE,
|
|
array( 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE )
|
|
) );
|
|
|
|
break;
|
|
}
|
|
|
|
/* Has the form been submitted? */
|
|
if( $values = $form->values() )
|
|
{
|
|
/* Add values */
|
|
$toAdd = array();
|
|
switch ( $activeTab )
|
|
{
|
|
/* New Version */
|
|
case 'new':
|
|
$json[ $values['versions_long'] ] = $values['versions_human'];
|
|
|
|
/* If this was for the core, add it to all IPS apps */
|
|
if ( $this->application->directory === 'core' )
|
|
{
|
|
foreach ( \IPS\Application::$ipsApps as $_appKey )
|
|
{
|
|
$appJson = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$_appKey}/data/versions.json" );
|
|
$appJson[ $values['versions_long'] ] = $values['versions_human'];
|
|
$this->_writeJson( \IPS\ROOT_PATH . "/applications/{$_appKey}/data/versions.json", $appJson );
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
/* Uploaded versions.xml file */
|
|
case 'upload':
|
|
$xml = NULL;
|
|
try
|
|
{
|
|
$xml = \IPS\Xml\SimpleXML::loadFile( $values['upload'] );
|
|
}
|
|
catch ( \InvalidArgumentException $e ) {}
|
|
|
|
if ( !$xml or $xml->getName() !== 'versions' )
|
|
{
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions" ), 'versions_upload_badxml' );
|
|
}
|
|
|
|
foreach ( $xml as $version )
|
|
{
|
|
$json[ (int) $version->long ] = (string) $version->human;
|
|
}
|
|
unlink( $values['upload'] );
|
|
break;
|
|
}
|
|
|
|
/* Save a snapshot of the default theme for diffs */
|
|
if ( $this->application->directory === 'core' )
|
|
{
|
|
\IPS\Theme::master()->saveHistorySnapshot();
|
|
}
|
|
|
|
/* Save it */
|
|
$this->_writeVersions( $json );
|
|
|
|
/* Redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions" ) );
|
|
}
|
|
|
|
if( \IPS\Request::i()->isAjax() and $activeTab == 'upload' )
|
|
{
|
|
\IPS\Output::i()->output = $form;
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* If not, show it */
|
|
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs(
|
|
array(
|
|
'new' => 'versions_add_new',
|
|
'upload' => 'versions_add_upload',
|
|
),
|
|
$activeTab,
|
|
$form,
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&do=addVersion" )
|
|
);
|
|
|
|
if( \IPS\Request::i()->isAjax() )
|
|
{
|
|
if( \IPS\Request::i()->existing )
|
|
{
|
|
\IPS\Output::i()->output = $form;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete Version
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function deleteVersion()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
$versionsFile = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/versions.json";
|
|
$json = $this->_getVersions();
|
|
if ( isset( $json[ intval( \IPS\Request::i()->id ) ] ) )
|
|
{
|
|
unset( $json[ intval( \IPS\Request::i()->id ) ] );
|
|
}
|
|
$this->_writeVersions( $json );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions" ) );
|
|
}
|
|
|
|
/**
|
|
* Add version query
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addVersionQuery()
|
|
{
|
|
/* Build Form */
|
|
$form = new \IPS\Helpers\Form( 'add_version_query' );
|
|
$form->add( new \IPS\Helpers\Form\TextArea( 'versions_query_code', '\IPS\Db::i()->', TRUE, array( 'size' => 45 ), function( $val )
|
|
{
|
|
/* Check it starts with \IPS\Db::i()-> */
|
|
$val = trim( $val );
|
|
if ( mb_substr( $val, 0, 14 ) !== '\IPS\Db::i()->' )
|
|
{
|
|
throw new \DomainException( 'versions_query_start' );
|
|
}
|
|
|
|
/* Check there's only one query */
|
|
if ( mb_substr( $val, -1 ) !== ';' )
|
|
{
|
|
$val .= ';';
|
|
}
|
|
if ( mb_substr_count( $val, ';' ) > 1 )
|
|
{
|
|
throw new \DomainException( 'versions_query_one' );
|
|
}
|
|
|
|
/* Check our Regex will be okay with it */
|
|
preg_match( '/^\\\IPS\\\Db::i\(\)->(.+?)\(\s*[\'"](.+?)[\'"]\s*(,\s*(.+?))?\)\s*;$/', $val, $matches );
|
|
if ( empty( $matches ) )
|
|
{
|
|
throw new \DomainException( 'versions_query_format' );
|
|
}
|
|
|
|
/* Run it if we're adding it to the current working version */
|
|
if( \IPS\Request::i()->id == 'working' )
|
|
{
|
|
try
|
|
{
|
|
try
|
|
{
|
|
if ( @eval( $val ) === FALSE )
|
|
{
|
|
throw new \DomainException( 'versions_query_phperror' );
|
|
}
|
|
}
|
|
catch ( \ParseError $e )
|
|
{
|
|
throw new \DomainException( 'versions_query_phperror' );
|
|
}
|
|
}
|
|
catch ( \IPS\Db\Exception $e )
|
|
{
|
|
throw new \DomainException( $e->getMessage() );
|
|
}
|
|
}
|
|
} ) );
|
|
|
|
/* If submitted, add to json file */
|
|
if ( $values = $form->values() )
|
|
{
|
|
/* Get our file */
|
|
$version = \IPS\Request::i()->id;
|
|
$json = $this->_getQueries( $version );
|
|
|
|
/* Work out the different parts of the query */
|
|
$val = trim( $values['versions_query_code'] );
|
|
if ( mb_substr( $val, -1 ) !== ';' )
|
|
{
|
|
$val .= ';';
|
|
}
|
|
preg_match( '/^\\\IPS\\\Db::i\(\)->(.+?)\(\s*(.+?)\s*\)\s*;$/', $val, $matches );
|
|
|
|
/* Add it on */
|
|
$json[] = array( 'method' => $matches[1], 'params' => eval( 'return array( ' . $matches[2] . ' );' ) );
|
|
|
|
/* Write it */
|
|
$this->_writeQueries( $version, $json );
|
|
|
|
/* Redirect us */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions&root={$version}" ) );
|
|
}
|
|
|
|
/* Or display it */
|
|
else
|
|
{
|
|
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global' )->block( 'versions_query', $form, FALSE );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete Version Query
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function deleteVersionQuery()
|
|
{
|
|
/* Make sure the user confirmed the deletion */
|
|
\IPS\Request::i()->confirmedDelete();
|
|
|
|
$version = ( \IPS\Request::i()->version == 'install' ? 'install' : \IPS\Request::i()->version );
|
|
|
|
$json = $this->_getQueries( $version );
|
|
unset( $json[ intval( \IPS\Request::i()->query ) ] );
|
|
$this->_writeQueries( $version, $json );
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions&root={$version}" ) );
|
|
}
|
|
|
|
/**
|
|
* Create a PHP class for a version
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function versionCode()
|
|
{
|
|
/* Get version */
|
|
if ( \IPS\Request::i()->id !== 'working' )
|
|
{
|
|
$long = intval( \IPS\Request::i()->id );
|
|
$json = $this->_getVersions();
|
|
if ( !isset( $json[ $long ] ) )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/8', 404, '' );
|
|
}
|
|
$human = $json[ $long ];
|
|
}
|
|
else
|
|
{
|
|
$long = 'working';
|
|
$human = '{version_human}';
|
|
}
|
|
|
|
/* Write the file if we don't already have one */
|
|
$phpFilePath = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/setup/upg_{$long}";
|
|
$phpFile = $phpFilePath . '/upgrade.php';
|
|
if ( !file_exists( $phpFile ) )
|
|
{
|
|
/* Work out the contents */
|
|
$contents = str_replace(
|
|
array(
|
|
'{version_human}',
|
|
"{subpackage}",
|
|
'{date}',
|
|
'{app}',
|
|
'{version_long}',
|
|
),
|
|
array(
|
|
$human,
|
|
( $this->application->directory != 'core' ) ? ( " * @subpackage\t" . \IPS\Member::loggedIn()->language()->get( "__app_{$this->application->directory}" ) ) : '',
|
|
date( 'd M Y' ),
|
|
$this->application->directory,
|
|
$long,
|
|
),
|
|
file_get_contents( \IPS\ROOT_PATH . "/applications/core/data/defaults/Upgrade.txt" )
|
|
);
|
|
|
|
/* If this isn't an IPS app, strip out our header */
|
|
if ( !in_array( $this->application->directory, \IPS\Application::$ipsApps ) )
|
|
{
|
|
$contents = preg_replace( '/(<\?php\s)\/*.+?\*\//s', '$1', $contents );
|
|
}
|
|
|
|
/* Write */
|
|
if ( !is_dir( $phpFilePath ) )
|
|
{
|
|
mkdir( $phpFilePath );
|
|
chmod( $phpFilePath, \IPS\IPS_FOLDER_PERMISSION );
|
|
}
|
|
if( @\file_put_contents( $phpFile, $contents ) === FALSE )
|
|
{
|
|
\IPS\Output::i()->error( 'dev_could_not_write_setup', '1C103/9', 403, '' );
|
|
}
|
|
}
|
|
|
|
/* And redirect */
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=versions" ), 'file_created' );
|
|
}
|
|
|
|
/**
|
|
* Manage Tasks
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _manageTasks()
|
|
{
|
|
return \IPS\Task::devTable(
|
|
\IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/tasks.json",
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=tasks" ),
|
|
\IPS\ROOT_PATH . "/applications/{$this->application->directory}/tasks",
|
|
$this->application->directory,
|
|
$this->application->directory . '\tasks',
|
|
$this->application->directory
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Manage Widgets
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _manageWidgets()
|
|
{
|
|
return \IPS\Widget::devTable(
|
|
\IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/widgets.json",
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=widgets" ),
|
|
\IPS\ROOT_PATH . "/applications/{$this->application->directory}/widgets",
|
|
$this->application->directory,
|
|
$this->application->directory,
|
|
$this->application->directory
|
|
);
|
|
}
|
|
/**
|
|
* Manage Hooks
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _manageHooks()
|
|
{
|
|
return \IPS\Plugin\Hook::devTable(
|
|
\IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=hooks" ),
|
|
$this->application->directory,
|
|
\IPS\ROOT_PATH . "/applications/{$this->application->directory}/hooks"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Edit Hook
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function editHook()
|
|
{
|
|
try
|
|
{
|
|
if ( \IPS\Request::i()->hookApp and \IPS\Request::i()->hookFilename )
|
|
{
|
|
$hook = \IPS\Plugin\Hook::constructFromData( \IPS\Db::i()->select( '*', 'core_hooks', array( 'app=? AND filename=?', \IPS\Request::i()->hookApp, \IPS\Request::i()->hookFilename ) )->first() );
|
|
}
|
|
else
|
|
{
|
|
$hook = \IPS\Plugin\Hook::load( \IPS\Request::i()->hook );
|
|
}
|
|
|
|
$hook->editForm( \IPS\Http\Url::internal( "app=core&module=applications&controller=developer&appKey={$this->application->directory}&tab=hooks" ) );
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/Q', 404, '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get ACP Menu Tabs
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function _getAcpMenuTabs()
|
|
{
|
|
return array_map( function( $val )
|
|
{
|
|
return mb_substr( $val, 9 );
|
|
}, array_filter( array_keys( \IPS\Member::loggedIn()->language()->words ), function( $val )
|
|
{
|
|
return preg_match( '/^menutab__[a-z]*$/i', $val );
|
|
} ) );
|
|
}
|
|
|
|
/**
|
|
* Get available ACP restrictions
|
|
*
|
|
* @param \IPS\Application\Module $module The module to get restrictions for
|
|
* @return array
|
|
*/
|
|
protected function _getRestrictions( $module )
|
|
{
|
|
$restrictions = array();
|
|
$_restrictions = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/acprestrictions.json" );
|
|
if ( isset( $_restrictions[ $module->key ] ) )
|
|
{
|
|
foreach ( $_restrictions[ $module->key ] as $groupKey => $rows )
|
|
{
|
|
foreach ( $rows as $key )
|
|
{
|
|
$restrictions[ 'r__'.$groupKey ][ $key ] = 'r__'.$key;
|
|
}
|
|
}
|
|
}
|
|
return $restrictions;
|
|
}
|
|
|
|
/**
|
|
* Load Module
|
|
*
|
|
* @return \IPS\Application\Module
|
|
*/
|
|
protected function _loadModule()
|
|
{
|
|
try
|
|
{
|
|
$module = \IPS\Application\Module::get( $this->application->directory, \IPS\Request::i()->module_key, \IPS\Request::i()->location );
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( 'node_error', '2C103/L', 404, '' );
|
|
}
|
|
|
|
return $module;
|
|
}
|
|
|
|
/**
|
|
* Get modules.json
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function _getModules()
|
|
{
|
|
$file = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/modules.json";
|
|
$json = $this->_getJson( $file );
|
|
$modules = array();
|
|
$extra = array();
|
|
$db = array();
|
|
|
|
foreach ( \IPS\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 ( is_array( $json ) AND count( $json ) )
|
|
{
|
|
$modules = $json;
|
|
}
|
|
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']
|
|
);
|
|
}
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
/**
|
|
* Write modules.json file
|
|
*
|
|
* @param array $json Data
|
|
* @return void
|
|
*/
|
|
protected function _writeModules( $json )
|
|
{
|
|
foreach( $json as $location => $module )
|
|
{
|
|
foreach( $module as $name => $data )
|
|
{
|
|
foreach( $data as $k => $v )
|
|
{
|
|
if ( ! in_array( $k, array( 'protected', 'default_controller' ) ) )
|
|
{
|
|
unset( $json[ $location ][ $name ][ $k ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/modules.json", $json );
|
|
}
|
|
|
|
/**
|
|
* Get schema.json
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function _getSchema()
|
|
{
|
|
return $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/schema.json" );
|
|
}
|
|
|
|
/**
|
|
* Write schema.json file
|
|
*
|
|
* @param array $json Data
|
|
* @return void
|
|
*/
|
|
protected function _writeSchema( $json )
|
|
{
|
|
return $this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/schema.json", $json );
|
|
}
|
|
|
|
/**
|
|
* Checks to see if schema JSON is writeable
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function _schemaJsonIsWritable()
|
|
{
|
|
if ( !is_writable( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/schema.json" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$file = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/setup/upg_working/queries.json";
|
|
if ( file_exists( $file ) and !is_writable( $file ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get versions
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function _getVersions()
|
|
{
|
|
$result = $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/versions.json" );
|
|
ksort( $result );
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Write versions.json file
|
|
*
|
|
* @param array $json Data
|
|
* @return void
|
|
*/
|
|
protected function _writeVersions( $json )
|
|
{
|
|
return $this->_writeJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/data/versions.json", $json );
|
|
}
|
|
|
|
/**
|
|
* Get queries for a version
|
|
*
|
|
* @param int $long Version ID
|
|
*/
|
|
protected function _getQueries( $long )
|
|
{
|
|
return $this->_getJson( \IPS\ROOT_PATH . "/applications/{$this->application->directory}/setup/" . ( $long === 'install' ? $long : "upg_{$long}" ) . '/queries.json' );
|
|
}
|
|
|
|
/**
|
|
* Add a query to a queries.json file, looking for CREATE TABLE statements and
|
|
* adjusting those instead if necessary
|
|
*
|
|
* @param array $queriesJson Decoded queries.json file
|
|
* @param array $query The query to add
|
|
* @return array Decoded queries.json file, modified as necessary
|
|
*/
|
|
protected function _addQueryToJson( $queriesJson, $query )
|
|
{
|
|
$added = FALSE;
|
|
|
|
$tableName = NULL;
|
|
switch ( $query['method'] )
|
|
{
|
|
case 'renameTable':
|
|
case 'dropTable':
|
|
case 'addColumn':
|
|
case 'changeColumn':
|
|
case 'dropColumn':
|
|
case 'addIndex':
|
|
case 'changeIndex':
|
|
case 'dropIndex':
|
|
$tableName = $query['params'][0];
|
|
break;
|
|
}
|
|
|
|
if ( $tableName !== NULL )
|
|
{
|
|
foreach ( $queriesJson as $i => $q )
|
|
{
|
|
if ( $q['method'] === 'createTable' and $q['params'][0]['name'] === $tableName )
|
|
{
|
|
switch ( $query['method'] )
|
|
{
|
|
case 'renameTable':
|
|
$queriesJson[ $i ]['params'][0]['name'] = $query['params'][1];
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'dropTable':
|
|
unset( $queriesJson[ $i ] );
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'addColumn':
|
|
$queriesJson[ $i ]['params'][0]['columns'][ $query['params'][1]['name'] ] = $query['params'][1];
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'changeColumn':
|
|
unset( $queriesJson[ $i ]['params'][0]['columns'][ $query['params'][1] ] );
|
|
$queriesJson[ $i ]['params'][0]['columns'][ $query['params'][2]['name'] ] = $query['params'][2];
|
|
|
|
/* Fix references to the column name in indexes */
|
|
if ( isset( $queriesJson[ $i ]['params'][0]['indexes'] ) )
|
|
{
|
|
foreach( $queriesJson[ $i ]['params'][0]['indexes'] as $indexName => $indexDefinition )
|
|
{
|
|
foreach( $indexDefinition['columns'] as $_idx => $columnName )
|
|
{
|
|
if( $columnName == $query['params'][1] )
|
|
{
|
|
$queriesJson[ $i ]['params'][0]['indexes'][ $indexName ]['columns'][ $_idx ] = $query['params'][2]['name'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'dropColumn':
|
|
unset( $queriesJson[ $i ]['params'][0]['columns'][ $query['params'][1] ] );
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'addIndex':
|
|
$queriesJson[ $i ]['params'][0]['indexes'][ $query['params'][1]['name'] ] = $query['params'][1];
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'changeIndex':
|
|
unset( $queriesJson[ $i ]['params'][0]['indexes'][ $query['params'][1] ] );
|
|
$queriesJson[ $i ]['params'][0]['indexes'][ $query['params'][2]['name'] ] = $query['params'][2];
|
|
$added = TRUE;
|
|
break;
|
|
|
|
case 'dropIndex':
|
|
unset( $queriesJson[ $i ]['params'][0]['indexes'][ $query['params'][1] ] );
|
|
$added = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $added === FALSE )
|
|
{
|
|
$queriesJson[] = $query;
|
|
}
|
|
|
|
return $queriesJson;
|
|
}
|
|
|
|
/**
|
|
* Write queries.json file
|
|
*
|
|
* @param int $long Version ID
|
|
* @param array $json Data
|
|
* @return void
|
|
*/
|
|
protected function _writeQueries( $long, $json )
|
|
{
|
|
/* Create a directory if we don't already have one */
|
|
$path = \IPS\ROOT_PATH . "/applications/{$this->application->directory}/setup/" . ( $long === 'install' ? $long : "upg_{$long}" );
|
|
if ( ! is_dir( $path ) )
|
|
{
|
|
mkdir( $path );
|
|
chmod( $path, \IPS\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 */
|
|
\IPS\Db::i()->update( 'core_dev', array(
|
|
'last_sync' => time(),
|
|
'ran' => json_encode( $write ),
|
|
), array( 'app_key=? AND working_version=?', $this->application->directory, $long ) );
|
|
}
|
|
|
|
/**
|
|
* Get JSON file
|
|
*
|
|
* @param string $file Filepath
|
|
* @return array Decoded JSON data
|
|
*/
|
|
protected function _getJson( $file )
|
|
{
|
|
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( $file, $data )
|
|
{
|
|
try
|
|
{
|
|
\IPS\Application::writeJson( $file, $data );
|
|
}
|
|
catch ( \RuntimeException $e )
|
|
{
|
|
\IPS\Output::i()->error( 'dev_could_not_write_data', '1C103/4', 403, '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build for release
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function build()
|
|
{
|
|
try
|
|
{
|
|
$this->application->build();
|
|
}
|
|
catch ( \Exception $e )
|
|
{
|
|
\IPS\Output::i()->error( $e->getMessage(), '' );
|
|
}
|
|
|
|
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=applications" ), 'dev_built' );
|
|
}
|
|
} |