<?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( $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' );
}
}