<?php
/**
* @brief Plugins
* @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 25 Jul 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;
}
/**
* Plugins
*/
class _plugins extends \IPS\Node\Controller
{
/**
* Node Class
*/
protected $nodeClass = 'IPS\Plugin';
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'plugins_view' );
parent::execute();
}
/**
* Manage
*
* @return void
*/
protected function manage()
{
if ( \IPS\Settings::i()->disable_all_plugins )
{
\IPS\Output::i()->sidebar['actions'][] = array(
'icon' => 'undo',
'link' => \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins&do=reenableAll' ),
'title' => 'plugins_reenable_all',
);
}
else
{
\IPS\Output::i()->sidebar['actions'][] = array(
'icon' => 'times',
'link' => \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins&do=disableAll' ),
'title' => 'plugins_disable_all',
);
}
parent::manage();
}
/**
* Disable All
*
* @return void
*/
protected function disableAll()
{
$disabledPlugins = array();
foreach ( \IPS\Plugin::plugins() as $plugin )
{
if ( $plugin->enabled )
{
$plugin->enabled = FALSE;
$plugin->save();
$disabledPlugins[] = $plugin->id;
}
}
\IPS\Settings::i()->changeValues( array( 'disable_all_plugins' => implode( ',', $disabledPlugins ) ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins' ) );
}
/**
* Re-enable All
*
* @return void
*/
protected function reenableAll()
{
foreach ( explode( ',', \IPS\Settings::i()->disable_all_plugins ) as $plugin )
{
try
{
$plugin = \IPS\Plugin::load( $plugin );
$plugin->enabled = TRUE;
$plugin->save();
}
catch ( \Exception $e ) {}
}
\IPS\Settings::i()->changeValues( array( 'disable_all_plugins' => '' ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins' ) );
}
/**
* Toggle Enabled/Disable
*
* @return void
*/
protected function enableToggle()
{
if ( \IPS\NO_WRITES )
{
\IPS\Output::i()->error( 'no_writes', '1C145/G', 403, '' );
}
/* Remove plugin.js so it can be rebuilt with only active plugins */
\IPS\Output\Javascript::deleteCompiled( 'core', 'plugins', 'plugins.js' );
return parent::enableToggle();
}
/**
* Get Root Buttons
*
* @return array
*/
public function _getRootButtons()
{
$buttons = parent::_getRootButtons();
if( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'plugins_install' ) and !\IPS\IN_DEV )
{
$buttons['install'] = array(
'icon' => 'upload',
'title' => 'install_new_plugin',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=install" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('install') )
);
}
return $buttons;
}
/**
* Install Form
*
* @return void
*/
public function install()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'plugins_install' );
if ( \IPS\NO_WRITES )
{
\IPS\Output::i()->error( 'no_writes', '1C145/1', 403, '' );
}
if( !is_writable( \IPS\ROOT_PATH . "/plugins/" ) )
{
\IPS\Output::i()->error( 'plugin_dir_not_write', '4C145/2', 403, '' );
}
/* Build form */
$form = new \IPS\Helpers\Form( NULL, 'install' );
if ( isset( \IPS\Request::i()->id ) )
{
$form->hiddenValues['id'] = \IPS\Request::i()->id;
}
$form->add( new \IPS\Helpers\Form\Upload( 'plugin_upload', NULL, TRUE, array( 'allowedFileTypes' => array( 'xml' ), 'temporary' => TRUE ) ) );
$activeTabContents = $form;
/* Handle submissions */
if ( $values = $form->values() )
{
/* Already installed? */
$xml = new \IPS\Xml\XMLReader;
$xml->open( $values['plugin_upload'] );
if ( !@$xml->read() )
{
\IPS\Output::i()->error( 'xml_upload_invalid', '2C145/D', 403, '' );
}
if ( !isset( \IPS\Request::i()->id ) )
{
try
{
$id = \IPS\Db::i()->select( 'plugin_id', 'core_plugins', array( 'plugin_name=? AND plugin_author=?', $xml->getAttribute('name'), $xml->getAttribute('author') ) )->first();
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'plugin_already_installed', FALSE, array( 'sprintf' => array( (string) \IPS\Http\Url::internal("app=core&module=applications&controller=plugins&do=install&id={$id}") ) ) ), '1C145/F', 403, '' );
}
catch ( \UnderflowException $e ) { }
}
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['plugin_upload'], $tempFile );
/* Initate a redirector */
$url = \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins&do=doInstall' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ) ) );
if ( isset( \IPS\Request::i()->id ) )
{
$url = $url->setQueryString( 'id', \IPS\Request::i()->id );
}
\IPS\Output::i()->redirect( $url );
}
/* Display */
\IPS\Output::i()->output = $form;
}
/**
* Install
*
* @return void
*/
public function doInstall()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'plugins_install' );
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C145/3', 500, '' );
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins&do=doInstall' )->setQueryString( array( 'file' => \IPS\Request::i()->file, 'key' => \IPS\Request::i()->key, 'id' => \IPS\Request::i()->id ) ),
function( $data )
{
/* Open XML file */
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\Request::i()->file );
$new = FALSE;
$xml->read();
$version = $xml->getAttribute('version');
/* Initial insert */
if ( !is_array( $data ) )
{
if( !$xml->getAttribute('name') )
{
@unlink( \IPS\Request::i()->file );
\IPS\Output::i()->error( 'xml_upload_invalid', '2C145/E', 403, '' );
}
if ( isset( \IPS\Request::i()->id ) )
{
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
/* Disable the plugin to prevent errors if core classes are extended */
$plugin->enabled = FALSE;
$plugin->save();
/* If we're upgrading, remove current HTML, CSS, etc. We'll insert again in a moment */
\IPS\Theme::removeTemplates( 'core', 'global', 'plugins', $plugin->id );
\IPS\Theme::removeCss( 'core', 'front', 'custom', $plugin->id );
\IPS\Theme::removeResources( 'core', 'global', 'plugins', $plugin->id );
}
catch ( \OutOfRangeException $e )
{
$plugin = new \IPS\Plugin;
$new = TRUE;
}
}
else
{
$plugin = new \IPS\Plugin;
$new = TRUE;
}
$currentVersionId = $plugin->version_long;
$plugin->name = $xml->getAttribute('name');
$plugin->update_check = $xml->getAttribute('update_check');
$plugin->author = $xml->getAttribute('author');
$plugin->website = $xml->getAttribute('website');
if ( !$plugin->location )
{
$directory = \mb_strtolower( preg_replace( '#[^a-zA-Z0-9_]#', '', $plugin->name ) );
$plugin->location = file_exists( \IPS\ROOT_PATH . "/plugins/" . $directory ) ? 'p' . mb_substr( md5( mt_rand() ), 0, 10 ) : $directory;
}
$plugin->version_long = $xml->getAttribute('version_long');
$plugin->version_human = $xml->getAttribute('version_human');
$plugin->save();
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}" ) )
{
\mkdir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}" );
chmod( \IPS\ROOT_PATH . "/plugins/{$plugin->location}", \IPS\IPS_FOLDER_PERMISSION );
}
/* Check to make sure that worked */
if( !\is_dir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}" ) )
{
\IPS\Output::i()->error( 'plugin_mkdir_perm', '4C145/H', 403, '' );
}
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/index.html", '' );
/* Check to make sure that worked */
if( !\file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/index.html" ) )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'plugin_fpc_perm', FALSE, array( 'sprintf' => array( $plugin->location ) ) ), '4C145/J', 403, '' );
}
return array( array(
'id' => $plugin->id,
'currentVersionId' => $currentVersionId,
'storeKeys' => array(),
'setUpClasses' => array(),
'step' => 0,
'upgradeData' => array(),
'done' => array(),
'isNew' => $new
), \IPS\Member::loggedIn()->language()->addToStack('processing') );
}
/* Load plugin */
$plugin = \IPS\Plugin::load( $data['id'] );
/* Skip to whatever we're doing */
$xml->read();
while ( TRUE )
{
if ( !in_array( $xml->name, $data['done'] ) )
{
/* What are we doing? */
$step = $xml->name;
switch ( $step )
{
case 'plugin':
break 2;
/* Hooks */
case 'hooks':
/* Make the directory, or if we're upgrading, empty it */
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks" ) )
{
\mkdir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks" );
chmod( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks", \IPS\IPS_FOLDER_PERMISSION );
}
else
{
foreach ( \IPS\Db::i()->select( 'class', 'core_hooks', array( 'plugin=? AND type=?', $plugin->id, 'S' ) ) as $class )
{
$data['recompileTemplates'][ $class ] = $class;
}
\IPS\Db::i()->delete( 'core_hooks', array( 'plugin=?', $plugin->id ) );
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks" ) as $file )
{
if ( !$file->isDot() )
{
unlink( $file->getPathname() );
}
}
\IPS\Plugin\Hook::writeDataFile();
}
/* Loop hooks */
while ( $xml->read() and $xml->name == 'hook' )
{
/* Make up a filename */
$filename = $xml->getAttribute( 'filename' ) ?: md5( mt_rand() );
/* Insert into DB */
$insertId = \IPS\Db::i()->insert( 'core_hooks', array( 'plugin' => $plugin->id, 'type' => $xml->getAttribute('type'), 'class' => $xml->getAttribute('class'), 'filename' => $filename ) );
/* Write contents */
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks/{$filename}.php", preg_replace( '/class hook(\d+?) extends _HOOK_CLASS_/', "class hook{$insertId} extends _HOOK_CLASS_", $xml->readString() ) );
/* Clear zend opcache if enabled */
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks/{$filename}.php" );
}
/* If that was a skin hook, trash our compiled version of that template */
if ( $xml->getAttribute('type') == 'S' )
{
$class = $xml->getAttribute('class');
$data['recompileTemplates'][ $class ] = $class;
}
/* Move onto next */
$xml->read();
$xml->next();
}
break;
/* Settings */
case 'settings':
$inserts = array();
while ( $xml->read() and $xml->name == 'setting' )
{
$xml->read();
$key = $xml->readString();
$xml->next();
$value = $xml->readString();
if( isset( \IPS\Settings::i()->$key ) )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array(
'conf_default' => $value,
'conf_plugin' => $plugin->id
), array( 'conf_key=?', $key ) );
}
else
{
$inserts[] = array(
'conf_key' => $key,
'conf_value' => $value,
'conf_default' => $value,
'conf_plugin' => $plugin->id
);
}
$xml->next();
}
if( count( $inserts ) )
{
\IPS\Db::i()->insert( 'core_sys_conf_settings', $inserts , TRUE );
}
\IPS\Settings::i()->clearCache();
break;
/* Settings code */
case 'settingsCode':
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/settings.php", $xml->readString() );
/* Clear zend opcache if enabled */
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/settings.php" );
}
break;
/* Tasks */
case 'tasks':
/* Make the directory */
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks" ) )
{
\mkdir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks" );
chmod( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks", \IPS\IPS_FOLDER_PERMISSION );
}
/* Loop tasks */
while ( $xml->read() and $xml->name == 'task' )
{
$key = $xml->getAttribute('key');
/* Insert into DB */
try
{
$task = \IPS\Task::load( $key, 'key', array( 'plugin=?', $plugin->id ) );
}
catch ( \OutOfRangeException $e )
{
$task = new \IPS\Task;
}
$task->plugin = $plugin->id;
$task->key = $key;
$task->frequency = $xml->getAttribute('frequency');
$task->save();
/* Write contents */
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks/{$key}.php", $xml->readString() );
/* Clear zend opcache if enabled */
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks/{$key}.php" );
}
/* Move onto next */
$xml->read();
$xml->next();
}
break;
/* Files */
case 'htmlFiles':
case 'cssFiles':
case 'jsFiles':
case 'resourcesFiles':
$class = ( \IPS\Theme::designersModeEnabled() ) ? '\IPS\Theme\Advanced\Theme' : '\IPS\Theme';
while ( $xml->read() and in_array( $xml->name, array( 'html', 'css', 'js', 'resources' ) ) )
{
switch ( $xml->name )
{
case 'html':
$name = $xml->getAttribute('filename');
$content = base64_decode( $xml->readString() );
preg_match('/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', $content, $matches );
$output = preg_replace( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', '', $content );
$class::addTemplate( array(
'app' => 'core',
'location' => 'global',
'group' => 'plugins',
'name' => mb_substr( $name, 0, -6 ),
'variables' => $matches[1],
'content' => $output,
'plugin' => $plugin->id,
'_default_template' => TRUE
), TRUE );
break;
case 'css':
$class::addCss( array(
'app' => 'core',
'location' => 'front',
'path' => 'custom',
'name' => $xml->getAttribute('filename'),
'content' => base64_decode( $xml->readString() ),
'plugin' => $plugin->id
), TRUE );
break;
case 'js':
$name = $xml->getAttribute('filename');
try
{
$js = \IPS\Output\Javascript::find( 'core', 'plugins', '/', $name );
$js->delete();
}
catch ( \OutOfRangeException $e ) {}
$js = new \IPS\Output\Javascript;
$js->plugin = $plugin->id;
$js->name = $name;
$js->content = base64_decode( $xml->readString() );
$js->version = $plugin->version_long;
$js->save();
break;
case 'resources':
$class::addResource( array(
'app' => 'core',
'location' => 'global',
'path' => '/plugins/',
'name' => $xml->getAttribute('filename'),
'content' => base64_decode( $xml->readString() ),
'plugin' => $plugin->id
) );
break;
}
$xml->read();
$xml->next();
}
break;
/* Lang */
case 'lang':
/* Fetch existing language keys */
$existingLanguageKeys = iterator_to_array( \IPS\Db::i()->select( 'word_key', 'core_sys_lang_words', array( 'word_plugin=? and lang_id=?', $plugin->id, \IPS\Lang::defaultLanguage() ) ) );
$keysToDelete = $existingLanguageKeys;
$inserts = array();
$batchSize = 25;
$i = 0;
while ( $xml->read() and $xml->name == 'word' )
{
$key = $xml->getAttribute('key');
$js = $xml->getAttribute('js');
$value = $xml->readString();
$i++;
foreach ( \IPS\Lang::languages() as $lang )
{
if ( count( $existingLanguageKeys ) and in_array( $key, $existingLanguageKeys ) )
{
/* Exists so do not delete */
$keysToDelete = array_diff( $keysToDelete, array( $key ) );
\IPS\Db::i()->update( 'core_sys_lang_words', array(
'word_default' => $value,
'word_default_version' => $plugin->version_long,
'word_js' => $js
),
array( 'lang_id=? and word_plugin=? and word_key=?', $lang->id, $plugin->id, $key )
);
}
else
{
$inserts[] = array(
'lang_id' => $lang->id,
'word_app' => NULL,
'word_plugin' => $plugin->id,
'word_key' => $key,
'word_default' => $value,
'word_custom' => NULL,
'word_default_version' => $plugin->version_long,
'word_custom_version' => NULL,
'word_js' => $js,
'word_export' => 1,
);
}
}
if ( $i % $batchSize === 0 )
{
if ( count( $inserts ) )
{
\IPS\Db::i()->replace( 'core_sys_lang_words', $inserts );
$inserts = array();
}
}
if ( !$xml->isEmptyElement )
{
$xml->read();
$xml->next();
}
}
/* Anything left that doesn't quite match up with a batch size? */
if( count( $inserts ) )
{
\IPS\Db::i()->replace( 'core_sys_lang_words', $inserts );
}
if ( count( $keysToDelete ) )
{
\IPS\Db::i()->delete( 'core_sys_lang_words', array( 'word_plugin=? AND ' . \IPS\Db::i()->in( 'word_key', $keysToDelete ), $plugin->id ) );
}
break;
/* Versions */
case 'versions':
while ( $xml->read() and $xml->name == 'version' )
{
$class = $xml->readString();
if ( $class AND $xml->getAttribute('long') )
{
if ( $xml->getAttribute('long') == 10000 AND $data['isNew'] )
{
/* Installing, so use install file which is bundled with <version long="10000"> */
$key = 'plugin_' . $plugin->id . '_setup_install_class';
\IPS\Data\Store::i()->$key = $class;
$data['storeKeys'][] = $key;
$data['setUpClasses']['install'] = 'install';
}
else if ( $data['currentVersionId'] < $xml->getAttribute('long') )
{
$key = 'plugin_' . $plugin->id . '_setup_' . $xml->getAttribute('long') . '_class';
\IPS\Data\Store::i()->$key = $class;
$data['storeKeys'][] = $key;
$data['setUpClasses'][ $xml->getAttribute('long') ] = $xml->getAttribute('long');
}
}
$xml->read();
$xml->next();
}
break;
/* Uninstall Code */
case 'uninstall':
/* Write contents */
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/uninstall.php", $xml->readString() );
/* Clear zend opcache if enabled */
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/uninstall.php" );
}
break;
/* Sidebar Widgets */
case 'widgets':
/* Make the directory */
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets" ) )
{
\mkdir( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets" );
chmod( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets", \IPS\IPS_FOLDER_PERMISSION );
}
/* Loop widgets */
while ( $xml->read() and $xml->name == 'widget' )
{
/** delete the widgets cache */
$data['storeKeys'][] = 'widgets';
$key = $xml->getAttribute('key');
/* Insert into DB */
try
{
$widget = \IPS\Db::i()->select( '*', 'core_widgets', array( '`key`=? and plugin=?', $key, $plugin->id ) )->first();
\IPS\Db::i()->update( 'core_widgets', array(
'plugin' => $plugin->id,
'key' => $key,
'class' => $xml->getAttribute('class'),
'restrict' => json_encode( explode( ",", $xml->getAttribute('restrict') ) ),
'default_area' => $xml->getAttribute('default_area'),
'allow_reuse' => intval( $xml->getAttribute('allow_reuse') ),
'menu_style' => $xml->getAttribute('menu_style'),
'embeddable' => intval( $xml->getAttribute('embeddable') )
), array( '`id`=?', $widget['id'] ) );
}
catch ( \UnderflowException $e )
{
$inserts[] = array(
'plugin' => $plugin->id,
'key' => $key,
'class' => $xml->getAttribute('class'),
'restrict' => json_encode( explode( ",", $xml->getAttribute('restrict') ) ),
'default_area' => $xml->getAttribute('default_area'),
'allow_reuse' => intval( $xml->getAttribute('allow_reuse') ),
'menu_style' => $xml->getAttribute('menu_style'),
'embeddable' => intval( $xml->getAttribute('embeddable') )
);
\IPS\Db::i()->insert( 'core_widgets', $inserts, TRUE );
}
/* Write contents */
$contents = $xml->readString();
$contents = str_replace( '<{ID}>', $plugin->id, $contents );
$contents = str_replace( '<{LOCATION}>', $plugin->location, $contents );
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets/{$key}.php", $contents );
/* Clear zend opcache if enabled */
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets/{$key}.php" );
}
/* Move onto next */
$xml->read();
$xml->next();
}
break;
}
/* Move on */
$data['done'][] = $step;
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('plugins_install_setup_done_step', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'plugin_step_' . $step ) ) ) ) );
}
else
{
$xml->next();
}
}
/* Do upgrade classes */
if ( count( $data['setUpClasses'] ) )
{
\IPS\Log::debug( "Plugin setup: found " . count( $data['setUpClasses'] ). " set up classes to run ", 'plugin_setup' );
/* Grab class and run it, step by step */
$versionToRun = current( $data['setUpClasses'] );
$key = 'plugin_' . $plugin->id . '_setup_' . $versionToRun . '_class';
$class = ( $versionToRun === 'install' ) ? 'ips_plugins_setup_install' : 'ips_plugins_setup_upg_' . $versionToRun;
\IPS\Log::debug( "Plugin setup: looking for class key " . $key, 'plugin_setup' );
if ( isset( \IPS\Data\Store::i()->$key ) )
{
\IPS\Log::debug( "Plugin setup: found class key " . $key . " class " . $class, 'plugin_setup' );
\IPS\Log::debug( \IPS\Data\Store::i()->$key, 'plugin_setup' );
/* As this is to be evaled, make sure PHP tags aren't there */
\IPS\Data\Store::i()->$key = preg_replace( '/^<' . '?php(\n)/', '', \IPS\Data\Store::i()->$key );
eval( \IPS\Data\Store::i()->$key );
\IPS\Log::debug( "Plugin setup: looking for class " . $class, 'plugin_setup' );
if ( class_exists( $class ) )
{
$upgrader = new $class();
$stepToRun = $data['step'] + 1;
$method = 'step' . $stepToRun;
$more = FALSE;
\IPS\Log::debug( "Plugin setup: looking for class key " . $key . ", " . $method, 'plugin_setup' );
if ( method_exists( $upgrader, $method ) )
{
\IPS\Log::debug( "Plugin setup: Running " . $key . ", " . $method, 'plugin_setup' );
$result = $upgrader->$method();
if ( $result === TRUE )
{
$method = 'step' . ( $stepToRun + 1 );
if ( method_exists( $upgrader, $method ) )
{
\IPS\Log::debug( "Plugin setup: Running " . $key . ", next method " . $method . " found", 'plugin_setup' );
/* Hit it on the next redirect */
$data['step']++;
$more = TRUE;
}
}
/* If the result is an array with 'html' key, we show that */
else if( is_array( $result ) AND isset( $result['html'] ) )
{
return $result['html'];
}
else if ( ! empty( $result ) )
{
$data['upgradeData'] = $result;
$more = TRUE;
}
}
/* Go for another hit on multiredirector */
if ( $more )
{
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('plugins_install_setup_method', FALSE, array( 'sprintf' => array( $versionToRun ) ) ) );
}
}
}
\IPS\Log::debug( "Plugin setup: Class " . $versionToRun . " completed", 'plugin_setup' );
/* Done this class completely */
$data['step'] = 0;
unset( $data['setUpClasses'][ $versionToRun ] );
\IPS\Log::debug( json_encode( $data ), 'plugin_setup' );
/* Go for another hit on multiredirector */
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('plugins_install_setup_method', FALSE, array( 'sprintf' => array( $versionToRun ) ) ) );
}
\IPS\Log::debug( "Plugin setup: All set up classes run", 'plugin_setup' );
/* All set up classes are done, so delete stored data so far */
if ( count( $data['storeKeys'] ) )
{
foreach( $data['storeKeys'] as $sk )
{
if ( isset( \IPS\Data\Store::i()->$sk ) )
{
unset( \IPS\Data\Store::i()->$sk );
}
}
$data['storeKeys'] = array();
}
/* Update data file */
\IPS\Plugin\Hook::writeDataFile();
/* Recompile CSS */
\IPS\Theme::deleteCompiledCss( 'core', 'front', 'custom' );
/* Recompile templates */
\IPS\Theme::deleteCompiledTemplate( 'core', 'global', 'plugins' );
if ( isset( $data['recompileTemplates'] ) )
{
foreach ( $data['recompileTemplates'] as $k )
{
$exploded = explode( '_', $k );
\IPS\Theme::deleteCompiledTemplate( $exploded[1], $exploded[2], $exploded[3] );
}
}
/* Clear javascript map to rebuild automatically */
unset( \IPS\Data\Store::i()->javascript_file_map, \IPS\Data\Store::i()->javascript_map );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Log */
\IPS\Session::i()->log( 'acplog__plugin_installed', array( $plugin->name => FALSE ) );
/* Re-enable the plugin */
$plugin->enabled = TRUE;
$plugin->save();
/* IPS Cloud Sync */
\IPS\IPS::resyncIPSCloud('Installed plugin');
/* Invalidate disk templates */
\IPS\Theme::resetAllCacheKeys();
/* All done */
return NULL;
},
function()
{
@unlink( \IPS\Request::i()->file );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=applications&controller=plugins' ) );
}
);
}
/**
* Edit Settings
*
* @return void
*/
protected function settings()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'plugins_edit' );
/* Load */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/4', 404, '' );
}
/* Build form */
$form = new \IPS\Helpers\Form;
try
{
eval( file_get_contents( \IPS\ROOT_PATH . '/plugins/' . $plugin->location . '/settings.php' ) );
}
catch ( \ParseError $e )
{
\IPS\Output::i()->error( 'plugin_parse_error', '3C145/L', 500, '', array(), $e->getMessage() );
}
/* Display */
if ( $form->values() )
{
\IPS\Session::i()->log( 'acplog__plugin_settings', array( $plugin->name => FALSE ) );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins" ), 'saved' );
}
\IPS\Output::i()->output = $form;
}
/**
* Developer Mode
*
* @return void
*/
protected function developer()
{
if( !\IPS\IN_DEV )
{
\IPS\Output::i()->error( 'not_in_dev', '2C145/C', 403, '' );
}
/* Load */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/4', 404, '' );
}
/* Get tab contents */
$activeTab = \IPS\Request::i()->tab ?: 'hooks';
$activeTabContents = call_user_func( array( $this, '_manage' . mb_ucfirst( $activeTab ) ), $plugin );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = $activeTabContents;
return;
}
/* Work out tabs */
$tabs = array();
$tabs['info'] = 'plugin_information';
$tabs['hooks'] = 'plugin_hooks';
$tabs['settings'] = 'dev_settings';
$tabs['tasks'] = 'dev_tasks';
$tabs['versions'] = 'dev_versions';
$tabs['widgets'] = 'dev_widgets';
/* Display */
if ( $activeTabContents )
{
/* Add Download Button */
\IPS\Output::i()->sidebar['actions'] = array(
'download' => array(
'icon' => 'download',
'title' => 'download',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=download&id={$plugin->id}" ),
)
);
\IPS\Output::i()->title = $plugin->name;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}" ) );
}
}
/**
* Developer Mode: Plugin Information
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageInfo( $plugin )
{
$form = new \IPS\Helpers\Form;
$plugin->form( $form );
if ( $values = $form->values() )
{
$plugin->saveForm( $plugin->formatFormValues( $values ) );
$plugin->save();
}
return $form;
}
/**
* Developer Mode: Hooks
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageHooks( $plugin )
{
return \IPS\Plugin\Hook::devTable(
\IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=hooks" ),
$plugin->id,
\IPS\ROOT_PATH . "/plugins/{$plugin->location}/hooks"
);
}
/**
* Edit Hook
*
* @return string
*/
protected function editHook()
{
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=hooks" ), $plugin->_title );
\IPS\Plugin\Hook::load( \IPS\Request::i()->hook )->editForm( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id=" . $plugin->id . "&tab=hooks" ) );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/5', 404, '' );
}
}
/**
* Show Template Tree
*
* @return void
*/
protected function templateTree()
{
$exploded = explode( '_', \IPS\Request::i()->class );
$bits = \IPS\Theme::load( \IPS\Theme::defaultTheme() )->getRawTemplates( $exploded[1], $exploded[2], $exploded[3], \IPS\Theme::RETURN_ALL );
$document = new \IPS\Xml\DOMDocument();
$document->strictErrorChecking = FALSE;
/* Get the template content */
$code = $bits[ $exploded[1] ][ $exploded[2] ][ $exploded[3] ][ \IPS\Request::i()->template ]['template_content'];
/* We need to fix special wrapping html tags or dom document will mess with them */
$code = preg_replace( '/<(\/?)(html|head|body)(>|\s)/', '<$1x_$2_x$3', $code );
/* Fix else statement - basic replacement */
$code = str_replace( "{{else}}", "<else>", $code );
/* Fix if/foreach/for tags...htmlspecialchars the content in case there is a -> which will break things */
$code = preg_replace_callback( '/\{\{(if|foreach|for)\s+?(.+?)\}\}/i', function( $matches )
{
return '<' . $matches[1] . ' code="' . htmlspecialchars( $matches[2], ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) . '">';
}, $code );
/* Fix ending if/foreach/for tags */
$code = preg_replace( '/\{\{end(if|foreach|for)\}\}/i', '</$1>', $code );
/* Fix regular template tags such as url to htmlspecialchars the content */
$code = preg_replace_callback( '/\{([a-z]+?=([\'"]).+?\\2 ?+)}/', function( $matches )
{
return htmlspecialchars( $matches[0], ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE );
}, $code );
/* Strip any raw replacement tags remaining */
$code = preg_replace( '/\{{(.+?)}}/', '', $code );
/* Ensure attributes with variable data are encoded before the cheeky DOM parser gets to it */
$code = preg_replace_callback( '/=([\'"])(\{.+?\})([\'"])/', function( $matches )
{
return "=" . $matches[1] . htmlspecialchars( $matches[2], ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) . $matches[3];
}, $code );
/* Now fix <if/foreach/for tags that are embedded into attributes */
while( preg_match( '/\<([^>]+?)(\<(\/*?)(if|foreach|for|else).*?\>([^>]*?)(\<\/\4\>))/ms', $code ) )
{
$code = preg_replace_callback( '/\<([^>]+?)(\<(\/*?)(if|foreach|for|else).*?\>([^>]*?)(\<\/\4\>))/ms', function( $matches )
{
return str_replace( $matches[2], '', $matches[0] );
}, $code );
}
/* Fix embedded single quotes */
$code = preg_replace_callback( '/=\'([^\']*?){.+?}([^\']*?)\'/', function( $matches )
{
return "=''";
}, $code );
/* Now load the HTML - doctype necessary sometimes */
$document->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( '<ipscontent id="ipscontent">' . $code . '</ipscontent>' ) );
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'applications' )->themeHookEditorTreeRoot( $document->getElementById('ipscontent') );
}
/**
* Get the CSS selector for a node
*
* @param \DOMNode $node The node
* @return string
*/
public static function getSelector( \DOMNode $node )
{
$bits = array();
while ( TRUE )
{
if ( $node->tagName == 'ipscontent' )
{
break;
}
elseif ( in_array( $node->tagName, array( 'if', 'foreach', 'for', 'else' ) ) )
{
$node = $node->parentNode;
}
else
{
if ( $node->hasAttributes() )
{
if ( $node->attributes->getNamedItem('id') AND $node->attributes->getNamedItem('id')->nodeValue AND mb_strpos( $node->attributes->getNamedItem('id')->nodeValue, '$' ) === FALSE )
{
$bits[] = '#' . $node->attributes->getNamedItem('id')->nodeValue;
break;
}
else
{
$bit = preg_replace( '/^(x_)?([a-z]+)(_x)?$/i', '$2', $node->nodeName );
for ( $i = 0; $i < $node->attributes->length; ++$i )
{
if ( $node->attributes->item( $i )->nodeName === 'class' AND $node->attributes->item( $i )->nodeValue AND mb_strpos( $node->attributes->item( $i )->nodeValue, '$' ) === FALSE AND mb_strpos( $node->attributes->item( $i )->nodeValue, '{' ) === FALSE )
{
foreach ( array_filter( explode( ' ', $node->attributes->item( $i )->nodeValue ) ) as $class )
{
$bit .= '.' . $class;
}
}
elseif ( !in_array( $node->attributes->item( $i )->nodeName, array( 'href', 'src', 'value', 'class', 'id' ) ) AND $node->attributes->item( $i )->nodeValue AND mb_strpos( $node->attributes->item( $i )->nodeValue, '$' ) === FALSE AND mb_strpos( $node->attributes->item( $i )->nodeValue, '{' ) === FALSE )
{
if ( $node->attributes->item( $i )->nodeValue )
{
$bit .= "[{$node->attributes->item( $i )->nodeName}='{$node->attributes->item( $i )->nodeValue}']";
}
else
{
$bit .= "[{$node->attributes->item( $i )->nodeName}]";
}
}
}
$bits[] = $bit;
}
}
else
{
$bits[] = preg_replace( '/^(x_)?([a-z]+)(_x)?$/i', '$2', $node->tagName );
}
$node = $node->parentNode;
}
}
return implode( ' > ', array_reverse( $bits ) );
}
/**
* Manage Settings
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageSettings( $plugin )
{
$file = \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/settings.json";
$matrix = new \IPS\Helpers\Form\Matrix;
$matrix->langPrefix = 'dev_settings_';
$matrix->columns = array(
'key' => array( 'Text', NULL, TRUE ),
'default' => array( 'Text' )
);
$matrix->rows = file_exists( $file ) ? json_decode( file_get_contents( $file ), TRUE ) : array();
if ( $matrix->values() !== FALSE )
{
$values = $matrix->values();
if ( !empty( $matrix->addedRows ) )
{
$insert = array();
foreach ( $matrix->addedRows as $key )
{
$insert[] = array( 'conf_key' => $values[ $key ]['key'], 'conf_value' => $values[ $key ]['default'], 'conf_default' => $values[ $key ]['default'], 'conf_plugin' => $plugin->id );
}
\IPS\Db::i()->insert( 'core_sys_conf_settings', $insert );
}
if ( !empty( $matrix->changedRows ) )
{
foreach ( $matrix->changedRows as $key )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_default' => $values[ $key ]['default'] ), array( 'conf_key=?', $values[ $key ]['key'] ) );
}
}
if ( !empty( $matrix->removedRows ) )
{
$delete = array();
foreach ( $matrix->removedRows as $key )
{
$delete[] = $matrix->rows[ $key ]['key'];
}
\IPS\Db::i()->delete( 'core_sys_conf_settings', \IPS\Db::i()->in( 'conf_key', $delete ) );
}
\IPS\Settings::i()->clearCache();
\file_put_contents( $file, json_encode( array_filter( array_values( $values ), function ( $v )
{
return (bool) $v['key'];
} ) ) );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=settings" ) );
}
return $matrix;
}
/**
* Manage Tasks
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageTasks( $plugin )
{
return \IPS\Task::devTable(
\IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/tasks.json",
\IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=tasks" ),
\IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks",
$plugin->location,
'pluginTasks',
$plugin->id
);
}
/**
* Manage Widgets
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageWidgets( $plugin )
{
return \IPS\Widget::devTable(
\IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/widgets.json",
\IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=widgets" ),
\IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets",
$plugin->location,
"plugins\\" . $plugin->location,
$plugin->id
);
}
/**
* Manage Versions
*
* @param \IPS\Plugin $plugin The plugin
* @return string
*/
protected function _manageVersions( $plugin )
{
if ( !file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json" ) )
{
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json", json_encode( array() ) );
}
$versions = array();
foreach ( json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json" ) ) as $long => $human )
{
$versions[] = array(
'versions_long' => $long,
'versions_human' => $human
);
}
$table = new \IPS\Helpers\Table\Custom( $versions, \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=versions" ) );
$table->rootButtons = array(
'add' => array(
'title' => 'versions_add',
'icon' => 'plus',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=addVersion&plugin={$plugin->id}" ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('versions_add') )
)
);
$table->sortBy = $table->sortBy ?: 'versions_long';
$table->sortDirection = $table->sortDirection ?: 'desc';
$table->rowButtons = function( $row ) use ( $plugin )
{
return array(
'delete' => array(
'title' => 'delete',
'icon' => 'times-circle',
'link' => \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=deleteVersion&plugin={$plugin->id}&id={$row['versions_long']}" ),
'data' => array( 'delete' => '' )
)
);
};
return (string) $table;
}
/**
* Versions: Add Version
*
* @return void
*/
protected function addVersion()
{
/* Load Plugin */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->plugin );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/8', 404, '' );
}
/* Load existing versions.json file */
$json = json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json" ), TRUE );
/* Build form */
$form = new \IPS\Helpers\Form( 'versions_add' );
$defaults = array( 'human' => '1.0.0', 'long' => '10000' );
foreach ( array_reverse( $json, TRUE ) as $long => $human )
{
$exploded = explode( '.', $human );
$defaults['human'] = "{$exploded[0]}.{$exploded[1]}." . ( intval( $exploded[2] ) + 1 );
$defaults['long'] = $long + 1;
break;
}
$form->add( new \IPS\Helpers\Form\Text( 'versions_human', $defaults['human'], TRUE ) );
$form->add( new \IPS\Helpers\Form\Text( 'versions_long', $defaults['long'], TRUE, array(), function( $val ) use ( $json )
{
if ( !preg_match( '/^\d*$/', $val ) )
{
throw new \DomainException( 'form_number_bad' );
}
if( $val < 10000 )
{
throw new \DomainException( 'versions_long_too_low' );
}
if( isset( $json[ $val ] ) )
{
throw new \DomainException( 'versions_long_exists' );
}
} ) );
/* Has the form been submitted? */
if( $values = $form->values() )
{
$json[ $values['versions_long'] ] = $values['versions_human'];
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/setup/{$values['versions_long']}.php", preg_replace( '/(<\?php\s)\/*.+?\*\//s', '$1', str_replace(
array(
'{version_human}',
'{app}',
'{version_long}',
),
array(
$values['versions_human'],
'plugins',
$values['versions_long'],
),
file_get_contents( \IPS\ROOT_PATH . "/applications/core/data/defaults/UpgradePlugin.txt" )
) ) );
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json", json_encode( $json ) );
krsort( $json );
foreach ( $json as $long => $human )
{
$plugin->version_long = $long;
$plugin->version_human = $human;
$plugin->save();
break;
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=versions" ) );
}
/* If not, show it */
\IPS\Output::i()->output = $form;
}
/**
* Delete Version
*
* @return void
*/
protected function deleteVersion()
{
/* Make sure the user confirmed the deletion */
\IPS\Request::i()->confirmedDelete();
/* Load Plugin */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->plugin );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/A', 404, '' );
}
/* Load existing versions.json file */
$json = json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json" ), TRUE );
/* Unset */
if ( isset( $json[ intval( \IPS\Request::i()->id ) ] ) )
{
unset( $json[ intval( \IPS\Request::i()->id ) ] );
}
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/setup/" . intval( \IPS\Request::i()->id ) . ".php" ) )
{
unlink( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/setup/" . intval( \IPS\Request::i()->id ) . ".php" );
}
/* Write */
\file_put_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json", json_encode( $json ) );
/* Redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=developer&id={$plugin->id}&tab=versions" ) );
}
/**
* Developer Mode: Download
*
* @return void
*/
public function download()
{
/* Load */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/B', 404, '' );
}
/* Init */
$xml = \IPS\Xml\SimpleXML::create('plugin');
$xml->addAttribute( 'name', $plugin->name );
$xml->addAttribute( 'version_long', $plugin->version_long );
$xml->addAttribute( 'version_human', $plugin->version_human );
$xml->addAttribute( 'author', $plugin->author );
$xml->addAttribute( 'website', $plugin->website );
$xml->addAttribute( 'update_check', $plugin->update_check );
/* It's intentional that $plugin->location is skipped for the XML file. The location is being built automatically while the installation to avoid conflicts when another plugin uses the same location */
/* Get Hooks */
$hooks = $xml->addChild( 'hooks' );
foreach ( \IPS\Db::i()->select( '*', 'core_hooks', array( 'plugin=?', $plugin->id ) ) as $hook )
{
$hookNode = $hooks->addChild( 'hook', \IPS\Plugin::addExceptionHandlingToHookFile( \IPS\ROOT_PATH . '/plugins/' . $plugin->location . '/hooks/' . $hook['filename'] . '.php' ) );
$hookNode->addAttribute( 'type', $hook['type'] );
$hookNode->addAttribute( 'class', $hook['class'] );
$hookNode->addAttribute( 'filename', $hook['filename'] );
}
/* Get Settings */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/settings.json" ) )
{
$settings = json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/settings.json" ), TRUE );
if ( !empty( $settings ) )
{
$inserts = array();
$xml->addChild( 'settings', $settings );
foreach( $settings as $setting )
{
$key = $setting['key'];
if( isset( \IPS\Settings::i()->$key ) )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array(
'conf_default' => $setting['default'],
'conf_plugin' => $plugin->id
), array( 'conf_key=?', $key ) );
}
else
{
$inserts[] = array(
'conf_key' => $key,
'conf_value' => $setting['default'],
'conf_default' => $setting['default'],
'conf_plugin' => $plugin->id
);
}
}
if( count( $inserts ) )
{
\IPS\Db::i()->insert( 'core_sys_conf_settings', $inserts , TRUE );
}
\IPS\Settings::i()->clearCache();
}
}
/* Uninstall Code */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/uninstall.php" ) )
{
$xml->addChild( 'uninstall', file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/uninstall.php" ) );
}
/* Add the settings code */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/settings.php" ) )
{
$xml->addChild( 'settingsCode', file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/settings.php" ) );
}
/* Get tasks */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/tasks.json" ) )
{
$tasksNode = $xml->addChild( 'tasks' );
foreach ( json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/tasks.json" ), TRUE ) as $key => $frequency )
{
$taskNode = $tasksNode->addChild( 'task', file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/tasks/{$key}.php" ) );
$taskNode->addAttribute( 'key', $key );
$taskNode->addAttribute( 'frequency', $frequency );
/* Insert into DB */
try
{
$task = \IPS\Task::load( $key, 'key', array( 'plugin=?', $plugin->id ) );
}
catch ( \OutOfRangeException $e )
{
$task = new \IPS\Task;
}
$task->plugin = $plugin->id;
$task->key = $key;
$task->frequency = $frequency;
$task->save();
}
}
/* Get sidebar widgets */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/widgets.json" ) )
{
$widgetsNode = $xml->addChild( 'widgets' );
foreach ( json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/widgets.json" ), TRUE ) as $key => $json )
{
$content = file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/widgets/{$key}.php" );
$content = str_replace( "namespace IPS\\plugins\\{$plugin->location}\\widgets", "namespace IPS\\plugins\\<{LOCATION}>\\widgets", $content );
$content = str_replace( "public \$plugin = '{$plugin->id}';", "public \$plugin = '<{ID}>';", $content );
$content = str_replace( "public \$app = '';", "", $content );
$widgetNode = $widgetsNode->addChild( 'widget', $content );
$widgetNode->addAttribute('key', $key );
foreach ($json as $dataKey => $value)
{
if( is_array( $value ) )
{
$value = implode( ",", $value );
}
$widgetNode->addAttribute( $dataKey, $value );
}
/* Automatically import everything into the local database to avoid having to toggle IN_DEV to import data first */
try
{
$widget = \IPS\Db::i()->select( '*', 'core_widgets', array( '`key`=? and plugin=?', $key, $plugin->id ) )->first();
\IPS\Db::i()->update( 'core_widgets', array(
'plugin' => $plugin->id,
'key' => $key,
'class' => $json['class'],
'restrict' => json_encode( $json['restrict'] ),
'default_area' => $json['default_area'],
'allow_reuse' => intval( $json['allow_reuse'] ),
'menu_style' => $json['menu_style'],
'embeddable' => intval( $json['embeddable'] )
), array( '`id`=?', $widget['id'] ) );
}
catch ( \UnderflowException $e )
{
$inserts[] = array(
'plugin' => $plugin->id,
'key' => $key,
'class' => $json['class'],
'restrict' => json_encode( $json['restrict'] ),
'default_area' => $json['default_area'],
'allow_reuse' => intval( $json['allow_reuse'] ),
'menu_style' => $json['menu_style'],
'embeddable' => intval( $json['embeddable'] )
);
\IPS\Db::i()->insert( 'core_widgets', $inserts, TRUE );
}
}
}
/* If we're upgrading, remove current HTML, CSS, etc. We'll insert again in a moment */
\IPS\Theme::removeTemplates( 'core', 'global', 'plugins', $plugin->id );
\IPS\Theme::removeCss( 'core', 'front', 'custom', $plugin->id );
\IPS\Theme::removeResources( 'core', 'global', 'plugins', $plugin->id );
\IPS\Db::i()->delete( 'core_javascript', array( 'javascript_plugin=?', $plugin->id ) );
/* Get HTML, CSS, JS, Resources */
foreach ( array( 'html' => 'phtml', 'css' => 'css', 'js' => 'js', 'resources' => '*' ) as $k => $ext )
{
$resourcesNode = $xml->addChild( "{$k}Files" );
foreach ( new \DirectoryIterator( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/{$k}" ) as $file )
{
if ( !$file->isDot() and mb_substr( $file, 0, 1 ) != '.' and ( $ext === '*' or mb_substr( $file, - ( mb_strlen( $ext ) + 1 ) ) === ".{$ext}" ) )
{
$content = file_get_contents( $file->getPathname() );
$resourcesNode->addChild( $k, base64_encode( $content ) )->addAttribute( 'filename', $file );
/* Automatically import everything into the local database to avoid having to toggle IN_DEV to import data first */
switch( $k )
{
case 'html':
preg_match('/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', $content, $matches );
$output = preg_replace( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', '', $content );
\IPS\Theme::addTemplate( array(
'app' => 'core',
'location' => 'global',
'group' => 'plugins',
'name' => mb_substr( $file, 0, -6 ),
'variables' => $matches[1],
'content' => $output,
'plugin' => $plugin->id,
'_default_template' => TRUE
), TRUE );
break;
case 'css':
\IPS\Theme::addCss( array(
'app' => 'core',
'location' => 'front',
'path' => 'custom',
'name' => $file,
'content' => $content,
'plugin' => $plugin->id
), TRUE );
break;
case 'js':
try
{
$js = \IPS\Output\Javascript::find( 'core', 'plugins', '/', (string) $file );
$js->delete();
}
catch ( \OutOfRangeException $e ) {}
$js = new \IPS\Output\Javascript;
$js->plugin = $plugin->id;
$js->name = (string) $file;
$js->content = $content;
$js->version = $plugin->version_long;
$js->save();
break;
case 'resources':
\IPS\Theme::addResource( array(
'app' => 'core',
'location' => 'global',
'path' => '/plugins/',
'name' => $file,
'content' => $content,
'plugin' => $plugin->id
) );
break;
}
}
}
}
/* Get language strings */
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/lang.php" ) or file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/jslang.php" ) )
{
$existingLanguageKeys = iterator_to_array( \IPS\Db::i()->select( 'word_key', 'core_sys_lang_words', array( 'word_plugin=? and lang_id=?', $plugin->id, \IPS\Lang::defaultLanguage() ) ) );
$keysToDelete = $existingLanguageKeys;
$inserts = array();
$langNode = $xml->addChild( 'lang' );
foreach ( array( 'lang' => 0, 'jslang' => 1 ) as $file => $js )
{
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/{$file}.php" ) )
{
require \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/{$file}.php";
foreach ( $lang as $k => $v )
{
$word = $langNode->addChild( 'word', $v );
$word->addAttribute( 'key', $k );
$word->addAttribute( 'js', $js );
/* Automatically import everything into the local database to avoid having to toggle IN_DEV to import data first */
foreach ( \IPS\Lang::languages() as $lang )
{
if ( count( $existingLanguageKeys ) and in_array( $k, $existingLanguageKeys ) )
{
/* Exists so do not delete */
$keysToDelete = array_diff( $keysToDelete, array( $k ) );
\IPS\Db::i()->update( 'core_sys_lang_words', array(
'word_default' => $v,
'word_default_version' => $plugin->version_long,
'word_js' => $js
),
array( 'lang_id=? and word_plugin=? and word_key=?', $lang->id, $plugin->id, $k )
);
}
else
{
$inserts[] = array(
'lang_id' => $lang->id,
'word_app' => NULL,
'word_plugin' => $plugin->id,
'word_key' => $k,
'word_default' => $v,
'word_custom' => NULL,
'word_default_version' => $plugin->version_long,
'word_custom_version' => NULL,
'word_js' => $js,
'word_export' => 1,
);
}
}
}
}
}
if( count( $inserts ) )
{
\IPS\Db::i()->replace( 'core_sys_lang_words', $inserts );
}
if ( count( $keysToDelete ) )
{
\IPS\Db::i()->delete( 'core_sys_lang_words', array( 'word_plugin=? AND ' . \IPS\Db::i()->in( 'word_key', $keysToDelete ), $plugin->id ) );
}
}
/* Get versions */
$versionsNode = $xml->addChild( 'versions' );
$versions = json_decode( file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/versions.json" ), TRUE );
ksort( $versions );
foreach ( $versions as $k => $v )
{
$setupFile = ( $k == 10000 ) ? 'install.php' : $k . '.php';
$node = $versionsNode->addChild( 'version', file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/setup/" . $setupFile ) ? file_get_contents( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/setup/" . $setupFile ) : '' );
$node->addAttribute( 'long', $k );
$node->addAttribute( 'human', $v );
}
/* Build */
\IPS\Output::i()->sendOutput( $xml->asXML(), 200, 'application/xml', array( "Content-Disposition" => \IPS\Output::getContentDisposition( 'attachment', $plugin->name . '.xml' ) ), FALSE, FALSE, FALSE );
}
/**
* View plugin details
*
* @return void
*/
public function details()
{
/* Load */
try
{
$plugin = \IPS\Plugin::load( \IPS\Request::i()->id );
}
catch ( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C145/M', 404, '' );
}
/* Work out tab */
$tabs = array( 'details' => 'plugin_details', 'hooks' => 'plugin_hooks' );
$activeTab = ( \IPS\Request::i()->tab and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'details';
$activeTabContents = '';
/* Tab contents */
if ( $activeTab === 'details' )
{
$activeTabContents = \IPS\Theme::i()->getTemplate( 'plugins' )->details( $plugin );
}
elseif ( $activeTab === 'hooks' )
{
$table = new \IPS\Helpers\Table\Db( 'core_hooks', \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=details&id={$plugin->id}&tab=hooks" ), array( 'plugin=?', $plugin->id ) );
$table->include = array( 'filename', 'class' );
$table->langPrefix = 'plugin_hook_';
if ( !$table->sortBy )
{
$table->sortBy = 'class';
$table->sortDirection = 'asc';
}
$table->parsers = array(
'filename' => function( $val, $row )
{
return $val . '.php';
}
);
$activeTabContents = (string) $table;
}
/* Output */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'plugin_details' );
if ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->tab ) )
{
\IPS\Output::i()->output = $activeTabContents;
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=core&module=applications&controller=plugins&do=details&id={$plugin->id}" ) );
}
}
}