Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

@@ -11,7 +11,7 @@
namespace IPS\core\modules\admin\customization;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -22,6 +22,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _ajax extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Return a CSS or HTML menu
*
@@ -189,7 +194,7 @@ class _ajax extends \IPS\Dispatcher\Controller
}
$where[] = array( \IPS\Db::i()->in( "MD5( CONCAT_WS( ',', template_app, template_location, template_group, template_name ) )", $uniqueIds ) );
}
if ( in_array( 'inherited', explode( ',', \IPS\Request::i()->filters ) ) )
if ( \in_array( 'inherited', explode( ',', \IPS\Request::i()->filters ) ) )
{
try
{
@@ -201,15 +206,15 @@ class _ajax extends \IPS\Dispatcher\Controller
catch( \OutOfRangeException $e ) { }
}
array_push( $parents, 0 );
if ( !in_array( 'unique', explode( ',', \IPS\Request::i()->filters ) ) )
if ( !\in_array( 'unique', explode( ',', \IPS\Request::i()->filters ) ) )
{
$where[] = array( 'template_user_added=0' );
}
if ( !in_array( 'unmodified', explode( ',', \IPS\Request::i()->filters ) ) )
if ( !\in_array( 'unmodified', explode( ',', \IPS\Request::i()->filters ) ) )
{
$where[] = array( 'template_user_edited>0 or template_added_to > 0' );
}
if ( !in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) )
if ( !\in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) )
{
$where[] = array( 'template_added_to=0' );
}
@@ -229,7 +234,7 @@ class _ajax extends \IPS\Dispatcher\Controller
{
$outOfDate = $result['template_user_edited'] < \IPS\Application::load( $result['template_app'] )->long_version;
if ( ( $outOfDate and !in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) )
if ( ( $outOfDate and !\in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !\in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) )
{
unset( $return[ $result['template_app'] ][ $result['template_location'] ][ $result['template_group'] ][ $result['template_name'] ] );
continue;
@@ -276,7 +281,7 @@ class _ajax extends \IPS\Dispatcher\Controller
}
$where[] = array( \IPS\Db::i()->in( "MD5( CONCAT_WS( ',', css_app, css_location, css_path, css_name ) )", $uniqueIds ) );
}
if ( in_array( 'inherited', explode( ',', \IPS\Request::i()->filters ) ) )
if ( \in_array( 'inherited', explode( ',', \IPS\Request::i()->filters ) ) )
{
try
{
@@ -288,11 +293,11 @@ class _ajax extends \IPS\Dispatcher\Controller
catch( \OutOfRangeException $e ) { }
}
array_push( $parents, 0 );
if ( !in_array( 'unique', explode( ',', \IPS\Request::i()->filters ) ) )
if ( !\in_array( 'unique', explode( ',', \IPS\Request::i()->filters ) ) )
{
$where[] = array( 'css_added_to=?', \IPS\Request::i()->id );
}
if ( !in_array( 'unmodified', explode( ',', \IPS\Request::i()->filters ) ) )
if ( !\in_array( 'unmodified', explode( ',', \IPS\Request::i()->filters ) ) )
{
$where[] = array( 'css_user_edited>0' );
}
@@ -310,7 +315,7 @@ class _ajax extends \IPS\Dispatcher\Controller
{
$outOfDate = $result['css_user_edited'] < \IPS\Application::load( $result['css_app'] )->long_version;
if ( !$result['css_added_to'] and ( ( $outOfDate and !in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) ) )
if ( !$result['css_added_to'] and ( ( $outOfDate and !\in_array( 'outofdate', explode( ',', \IPS\Request::i()->filters ) ) ) or ( !$outOfDate and !\in_array( 'modified', explode( ',', \IPS\Request::i()->filters ) ) ) ) )
{
unset( $return[ $result['css_app'] ][ $result['css_location'] ][ $result['css_path'] ][ $result['css_name'] ] );
continue;
@@ -11,7 +11,7 @@
namespace IPS\core\modules\admin\customization;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -22,6 +22,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _emails extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
@@ -60,7 +65,7 @@ class _emails extends \IPS\Dispatcher\Controller
$table->quickSearch = function( $val )
{
$matches = \IPS\Member::loggedIn()->language()->searchCustom( 'emailtpl_', $val, TRUE );
if ( count( $matches ) )
if ( \count( $matches ) )
{
return '(' . \IPS\Db::i()->in( '`template_name`', array_keys( $matches ) ) . " OR `template_name` LIKE '%{$val}%')";
}
@@ -98,8 +103,8 @@ class _emails extends \IPS\Dispatcher\Controller
$return['revert'] = array(
'icon' => 'undo',
'title' => 'revert',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=emails&do=revert&key=' ) . $row['template_key'],
'data' => array( 'delete' => '', 'delete-warning' => \IPS\Member::loggedIn()->language()->addToStack('email_revert_confirm') )
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=emails&do=revert&key=' . $row['template_key'] )->csrf(),
'data' => array( 'confirm' => '', 'confirmMessage' => \IPS\Member::loggedIn()->language()->addToStack('email_revert_confirm') )
);
$return['export'] = array(
@@ -184,7 +189,7 @@ class _emails extends \IPS\Dispatcher\Controller
{
$xml = \IPS\Xml\SimpleXML::loadFile( $values['email_template_file'] );
if( !count($xml->template) )
if( !\count($xml->template) )
{
\IPS\Output::i()->error( 'email_template_badform', '1S128/4', 403, '' );
}
@@ -248,11 +253,11 @@ class _emails extends \IPS\Dispatcher\Controller
\IPS\Db::i()->update( 'core_email_templates', array( 'template_edited' => 0 ), array( 'template_id=?', $template['template_id'] ) );
/* Rebuild template */
$this->buildTemplate( $template );
$this->_buildTemplate( $template );
/* Log and redirect */
\IPS\Session::i()->log( 'acplog__emailtpl_reverted', array( $template['template_name'] => FALSE ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=emails' ), 'reverted' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=emails' ), 'reverted_emailtpl' );
}
/**
@@ -317,7 +322,7 @@ class _emails extends \IPS\Dispatcher\Controller
\IPS\Db::i()->update( 'core_email_templates', array( 'template_edited' => 1 ), array( 'template_id=?', $template['template_id'] ) );
}
$this->buildTemplate( $save );
$this->_buildTemplate( $save );
\IPS\Session::i()->log( 'acplogs__emailtpl_edited', array( $template['template_name'] => FALSE ) );
@@ -326,7 +331,7 @@ class _emails extends \IPS\Dispatcher\Controller
/* Output */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('emailtpl_edit');
\IPS\Output::i()->output = $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'customization', 'core', 'admin' ) ), 'email' ) );;
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'customization', 'core', 'admin' ), 'email' ) );;
}
/**
@@ -335,7 +340,7 @@ class _emails extends \IPS\Dispatcher\Controller
* @param array $template Template data from core_email_templates
* @return void
*/
public function buildTemplate( $template )
protected function _buildTemplate( $template )
{
$htmlFunction = 'namespace IPS\Theme;' . "\n" . \IPS\Theme::compileTemplate( $template['template_content_html'], "email_html_{$template['template_app']}_{$template['template_name']}", $template['template_data'] );
$ptFunction = 'namespace IPS\Theme;' . "\n" . \IPS\Theme::compileTemplate( $template['template_content_plaintext'], "email_plaintext_{$template['template_app']}_{$template['template_name']}", $template['template_data'] );
@@ -0,0 +1,256 @@
<?php
/**
* @brief icons
* @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 2018
*/
namespace IPS\core\modules\admin\customization;
/* 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;
}
/**
* icons
*/
class _icons extends \IPS\Dispatcher\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_sets_manage' );
parent::execute();
}
/**
* Show the form
*
* @return void
*/
protected function manage()
{
$form = new \IPS\Helpers\Form;
/* Generic favicon - easy enough */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_favicon', \IPS\Settings::i()->icons_favicon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_favicon ) : NULL, FALSE, array( 'obscure' => false, 'allowedFileTypes' => array( 'ico', 'png', 'gif', 'jpeg', 'jpg', 'jpe' ), 'storageExtension' => 'core_Icons' ) ) );
/* Sharer logos, allows multiple images to be uploaded */
$shareLogos = \IPS\Settings::i()->icons_sharer_logo ? json_decode( \IPS\Settings::i()->icons_sharer_logo, true ) : array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_sharer_logo', \count( $shareLogos ) ? array_map( function( $val ) { return \IPS\File::get( 'core_Icons', $val ); }, $shareLogos ) : array(), FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'multiple' => true ) ) );
/* Homescreen icons - we accept one upload and create the images we need */
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen', ( isset( $homeScreen['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreen['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
/* Safari pinned tabs icon and highlight color */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_mask_icon', \IPS\Settings::i()->icons_mask_icon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'icons_mask_color', \IPS\Settings::i()->icons_mask_color, FALSE ) );
/* And finally, additional manifest and livetile details */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$form->add( new \IPS\Helpers\Form\YesNo( 'configure_manifest', \count( $manifestDetails ) > 0, FALSE, array(
'togglesOn' => array( 'manifest_shortname', 'manifest_fullname', 'manifest_description', 'manifest_defaultapp', 'manifest_themecolor', 'manifest_bgcolor', 'manifest_display', 'manifest_custom_url_toggle' ),
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_shortname', ( isset( $manifestDetails['short_name'] ) ) ? $manifestDetails['short_name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_shortname' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_fullname', ( isset( $manifestDetails['name'] ) ) ? $manifestDetails['name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_fullname' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'manifest_description', ( isset( $manifestDetails['description'] ) ) ? $manifestDetails['description'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_description' ) );
$formStartUrl = '';
if( isset( $manifestDetails['start_url'] ) )
{
$formStartUrl = str_replace( 'index.php?/', '', \IPS\Http\Url\Friendly::fixComponentPath( $manifestDetails['start_url'] ) );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'manifest_custom_url_toggle', ( isset( $manifestDetails['start_url'] ) and empty( $formStartUrl ) ? FALSE : TRUE ), FALSE, array(
'togglesOn' => array( 'manifest_short_url' ),
), NULL, NULL, NULL, 'manifest_custom_url_toggle' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_short_url', $formStartUrl, FALSE, array(), function( $val )
{
if ( $val and \IPS\Request::i()->manifest_custom_url_toggle_checkbox )
{
if ( mb_substr( $val, -1 ) !== '/' )
{
$val .= '/';
}
$response = \IPS\Http\Url::external( \IPS\Http\Url::baseUrl() . ( \IPS\Settings::i()->htaccess_mod_rewrite ? $val : 'index.php?/' . $val ) )->request( NULL, NULL, FALSE )->get();
if ( $response->httpResponseCode != 200 and $response->httpResponseCode != 303 and ( \IPS\Settings::i()->site_online OR $response->httpResponseCode != 503 ) )
{
throw new \LogicException( 'pwa_start_url_incorrect' );
}
}
}, \IPS\Http\Url::baseUrl() . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ), NULL, 'manifest_short_url' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_themecolor', ( isset( $manifestDetails['theme_color'] ) ) ? $manifestDetails['theme_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_themecolor' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_bgcolor', ( isset( $manifestDetails['background_color'] ) ) ? $manifestDetails['background_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_bgcolor' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'manifest_display', ( isset( $manifestDetails['display'] ) ) ? $manifestDetails['display'] : 'standalone', FALSE, array( 'options' => array( 'fullscreen' => 'manifest_fullscreen', 'standalone' => 'manifest_standalone', 'minimal-ui' => 'manifest_minimalui', 'browser' => 'manifest_browser' ) ), NULL, NULL, NULL, 'manifest_display' ) );
/* We've submitted, check our values! */
if ( $values = $form->values() )
{
/* Favicon is easy, we just store the string value of the file object */
$values['icons_favicon'] = (string) $values['icons_favicon'];
/* Sharer logos are easy too, except it's an array of images instead of a single image */
if( \count( $values['icons_sharer_logo'] ) )
{
$values['icons_sharer_logo'] = json_encode( array_map( function( $val ){ return (string) $val; }, $values['icons_sharer_logo'] ) );
}
$path = \IPS\Http\Url::createFromString( \IPS\Http\Url::baseUrl() )->data[ \IPS\Http\Url::COMPONENT_PATH ];
$startUrl = $path ?? '';
if ( $values['manifest_custom_url_toggle'] !== FALSE and ! empty( $values['manifest_short_url'] ) )
{
$startUrl = '/' . trim( $values['manifest_short_url'], '/' ) . '/';
if( !empty( $path ) )
{
$startUrl = '/' . trim( $path . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ) . ltrim( $values['manifest_short_url'], '/' ), '/' ) . '/';
}
}
/* Homescreen icons are the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen );
/* We need the string value of this uploaded file as well */
$values['icons_mask_icon'] = (string) $values['icons_mask_icon'];
/* Finally, handle the manifest details */
$values['manifest_details'] = array();
if( $values['configure_manifest'] )
{
$values['manifest_details']['short_name'] = $values['manifest_shortname'];
$values['manifest_details']['start_url'] = $startUrl;
$values['manifest_details']['name'] = $values['manifest_fullname'];
$values['manifest_details']['description'] = $values['manifest_description'];
$values['manifest_details']['theme_color'] = $values['manifest_themecolor'];
$values['manifest_details']['background_color'] = $values['manifest_bgcolor'];
$values['manifest_details']['display'] = $values['manifest_display'];
}
unset( $values['configure_manifest'], $values['manifest_shortname'], $values['manifest_fullname'], $values['manifest_description'], $values['manifest_display'], $values['manifest_bgcolor'], $values['manifest_themecolor'], $values['manifest_custom_url_toggle'], $values['manifest_short_url'] );
$values['manifest_details'] = json_encode( $values['manifest_details'] );
/* Save the settings */
$form->saveAsSettings( $values );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Clear manifest and ie browser data stores */
unset( \IPS\Data\Store::i()->manifest, \IPS\Data\Store::i()->iebrowserconfig );
/* And log */
\IPS\Session::i()->log( 'acplogs__icons_and_logos' );
/* And Redirect */
\IPS\Output::i()->redirect( $this->url , 'saved' );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_customization_icons');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global' )->block( 'menu__core_customization_icons', $form );
}
/**
* Process application icon
*
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @return array
*/
public static function processApplicationIcon( $values, $homeScreen = array() )
{
if( $values['icons_homescreen'] )
{
$sizes = array(
'android-chrome-36x36' => array( 36, 36 ),
'android-chrome-48x48' => array( 48, 48 ),
'android-chrome-72x72' => array( 72, 72 ),
'android-chrome-96x96' => array( 96, 96 ),
'android-chrome-144x144' => array( 144, 144 ),
'android-chrome-192x192' => array( 192, 192 ),
'android-chrome-256x256' => array( 256, 256 ),
'android-chrome-384x384' => array( 384, 384 ),
'android-chrome-512x512' => array( 512, 512 ),
'msapplication-square70x70logo' => array( 128, 128 ),
'msapplication-TileImage' => array( 144, 144 ),
'msapplication-square150x150logo' => array( 270, 270 ),
'msapplication-wide310x150logo' => array( 558, 558 ),
'msapplication-square310x310logo' => array( 558, 270 ),
'apple-touch-icon-57x57' => array( 57, 57 ),
'apple-touch-icon-60x60' => array( 60, 60 ),
'apple-touch-icon-72x72' => array( 72, 72 ),
'apple-touch-icon-76x76' => array( 76, 76 ),
'apple-touch-icon-114x114' => array( 114, 114 ),
'apple-touch-icon-120x120' => array( 120, 120 ),
'apple-touch-icon-144x144' => array( 144, 144 ),
'apple-touch-icon-152x152' => array( 152, 152 ),
'apple-touch-icon-180x180' => array( 180, 180 ),
);
$setting = array( 'original' => (string) $values['icons_homescreen'] );
foreach( $sizes as $filename => $_sizes )
{
try
{
$image = \IPS\Image::create( $values['icons_homescreen']->contents() );
if( $image::exifSupported() )
{
$image->setExifData( $values['icons_homescreen']->contents() );
}
$image->crop( $_sizes[0], $_sizes[1] );
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
);
}
catch ( \Exception $e ) { }
}
$values['icons_homescreen'] = json_encode( $setting );
}
else
{
/* Delete any images that may already exist */
foreach( $homeScreen as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_homescreen'] = '';
}
return $values;
}
}
@@ -11,7 +11,7 @@
namespace IPS\core\modules\admin\customization;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -22,6 +22,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _themes extends \IPS\Node\Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Node Class
*/
@@ -31,6 +36,11 @@ class _themes extends \IPS\Node\Controller
* @brief If true, will prevent any item from being moved out of its current parent, only allowing them to be reordered within their current parent
*/
protected $lockParents = TRUE;
/**
* Title can contain HTML?
*/
public $_titleHtml = TRUE;
/**
* Description can contain HTML?
@@ -72,18 +82,30 @@ class _themes extends \IPS\Node\Controller
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('theme_designers_mode_title') )
);
}
\IPS\Output::i()->sidebar['actions']['add'] = array(
'primary' => true,
'icon' => 'plus',
'title' => 'theme_set_add_button',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=add' ),
'title' => 'add',
'link' => \IPS\Http\Url::internal( ( \IPS\DEMO_MODE ) ? 'app=core&module=customization&controller=themes&do=form&type=vse' : 'app=core&module=customization&controller=themes&do=add' ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('theme_set_add_button') )
);
parent::manage();
}
/**
* Allow overloading to change how the title is displayed in the tree
*
* @param $node \IPS\Node Node
* @return string
*/
protected static function nodeTitle( $node )
{
return \IPS\Theme::i()->getTemplate('customization')->themeRowTitle( $node );
}
/**
* Get Root Buttons
*
@@ -101,6 +123,8 @@ class _themes extends \IPS\Node\Controller
*/
public function revertThemeSetting()
{
\IPS\Session::i()->csrfCheck();
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
$setting = NULL;
$value = NULL;
@@ -191,7 +215,7 @@ class _themes extends \IPS\Node\Controller
/* Switching on */
if ( isset( $values['theme_designers_mode'] ) and $values['theme_designers_mode'] )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeon' ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeon' )->csrf() );
}
else
{
@@ -200,7 +224,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Data\Store::i()->designers_mode_theme_sync = ( $values['theme_designers_themes'] == 0 ) ? NULL : array_keys( $values['theme_designers_themes'] );
\IPS\Data\Store::i()->designers_mode_theme_wipe = $values['theme_designers_mode_wipe'];
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeoff' ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeoff' )->csrf() );
}
else
{
@@ -223,25 +247,32 @@ class _themes extends \IPS\Node\Controller
*/
public function designersmodeoff()
{
\IPS\Session::i()->csrfCheck();
if ( \IPS\NO_WRITES or \IPS\CIC )
{
\IPS\Output::i()->error( 'no_writes', '1C163/B', 403, '' );
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeoff' ),
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeoff' )->csrf(),
function( $data )
{
/* Is this the first cycle? */
if ( ! is_array( $data ) )
if ( ! \is_array( $data ) )
{
$toImport = array();
foreach( \IPS\Theme::themes() as $id => $theme )
{
if( !$theme->id )
{
continue;
}
if ( isset( \IPS\Data\Store::i()->designers_mode_theme_sync ) )
{
if ( \IPS\Data\Store::i()->designers_mode_theme_sync !== NULL and ! in_array( $id, \IPS\Data\Store::i()->designers_mode_theme_sync ) )
if ( \IPS\Data\Store::i()->designers_mode_theme_sync !== NULL and ! \in_array( $id, \IPS\Data\Store::i()->designers_mode_theme_sync ) )
{
continue;
}
@@ -257,11 +288,11 @@ class _themes extends \IPS\Node\Controller
$extensions = array();
foreach ( \IPS\Application::allExtensions( 'core', 'DesignersMode', NULL, NULL, NULL, TRUE ) as $class )
{
$extensions[] = get_class( $class );
$extensions[] = \get_class( $class );
}
/* Start importing */
$data = array( 'toImport' => count( $toImport ) ? true : false, 'extensions' => $extensions, 'extra' => array() );
$data = array( 'toImport' => \count( $toImport ) ? true : false, 'extensions' => $extensions, 'extra' => array() );
/* Could be too large for a simple URL */
$_SESSION['theme_dev_import'] = $toImport;
@@ -269,7 +300,7 @@ class _themes extends \IPS\Node\Controller
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('processing') );
}
if ( count( $data['extensions'] ) )
if ( \count( $data['extensions'] ) )
{
/* Do this first */
$array = $data['extensions'];
@@ -298,11 +329,11 @@ class _themes extends \IPS\Node\Controller
reset( $toImport );
$themeId = key( $toImport );
$theme = \IPS\Theme::load( $themeId );
$theme = \IPS\Theme\Advanced\Theme::load( $themeId );
$app = key( $toImport[ $themeId ] );
$type = array_shift( $toImport[ $themeId ][ $app ] );
if( !count( $toImport[ $themeId ][ $app ] ) )
if( !\count( $toImport[ $themeId ][ $app ] ) )
{
unset( $toImport[ $themeId ][ $app ] );
}
@@ -353,22 +384,22 @@ class _themes extends \IPS\Node\Controller
}
break;
case 'html':
\IPS\Theme::i()->importDevHtml( $app, $themeId );
$theme->importDevHtml( $app, $themeId );
break;
case 'css':
\IPS\Theme::i()->importDevCss( $app, $themeId );
$theme->importDevCss( $app, $themeId );
break;
case 'resources':
\IPS\Theme::i()->importDevResources( $app, $themeId );
$theme->importDevResources( $app, $themeId );
break;
}
if ( ! count( $toImport[ $themeId ] ) )
if ( ! \count( $toImport[ $themeId ] ) )
{
unset( $toImport[ $themeId ] );
}
if ( ! count( $toImport ) )
if ( ! \count( $toImport ) )
{
$data['toImport'] = false;
}
@@ -394,7 +425,7 @@ class _themes extends \IPS\Node\Controller
{
\IPS\Theme\Advanced\Theme::removeThemeDirectory( \IPS\ROOT_PATH . "/themes" );
}
else if ( is_array( \IPS\Data\Store::i()->designers_mode_theme_sync ) )
else if ( \is_array( \IPS\Data\Store::i()->designers_mode_theme_sync ) )
{
foreach( \IPS\Data\Store::i()->designers_mode_theme_sync as $id )
{
@@ -426,6 +457,8 @@ class _themes extends \IPS\Node\Controller
*/
public function designersmodeon()
{
\IPS\Session::i()->csrfCheck();
if ( \IPS\NO_WRITES or \IPS\CIC )
{
\IPS\Output::i()->error( 'no_writes', '1C163/A', 403, '' );
@@ -463,11 +496,11 @@ class _themes extends \IPS\Node\Controller
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeon' ),
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=designersmodeon' )->csrf(),
function( $data )
{
/* Is this the first cycle? */
if ( !is_array( $data ) )
if ( !\is_array( $data ) )
{
if ( ! is_dir( \IPS\ROOT_PATH . "/themes" ) )
{
@@ -493,6 +526,11 @@ class _themes extends \IPS\Node\Controller
{
foreach ( \IPS\Theme\Advanced\Theme::getToBuild() as $id )
{
if( !$id )
{
continue;
}
$toBuild[$id] = array( 'html' => 'html', 'css' => 'css', 'resources' => 'resources' );
}
}
@@ -503,21 +541,21 @@ class _themes extends \IPS\Node\Controller
{
if ( $class->toBuild() === TRUE )
{
$extensions[] = get_class( $class );
$extensions[] = \get_class( $class );
}
}
/* Start importing */
$data = array( 'buildingDesignersFiles' => true, 'toBuild' => $toBuild, 'extensions' => $extensions, 'totalItems' => count( array_keys( $toBuild ) ) + count( array_keys( $extensions ) ), 'totalDone' => 0, 'extra' => array() );
$data = array( 'buildingDesignersFiles' => true, 'toBuild' => $toBuild, 'extensions' => $extensions, 'totalItems' => \count( array_keys( $toBuild ) ) + \count( array_keys( $extensions ) ), 'totalDone' => 0, 'extra' => array() );
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('theme_dm_building') );
}
/* Grab something to build */
$msg = NULL;
if ( count( $data['extensions'] ) or count( $data['toBuild'] ) )
if ( \count( $data['extensions'] ) or \count( $data['toBuild'] ) )
{
if ( count( $data['extensions'] ) )
if ( \count( $data['extensions'] ) )
{
/* Do this first */
$array = $data['extensions'];
@@ -541,14 +579,14 @@ class _themes extends \IPS\Node\Controller
$msg = \IPS\Member::loggedIn()->language()->addToStack( 'theme_dm_building_extensions' );
}
else if ( count( $data['toBuild'] ) )
else if ( \count( $data['toBuild'] ) )
{
reset( $data['toBuild'] );
$themeId = key( $data['toBuild'] );
$type = array_shift( $data['toBuild'][$themeId] );
if( !count( $data['toBuild'][ $themeId ] ) )
if( !\count( $data['toBuild'][ $themeId ] ) )
{
unset( $data['toBuild'][ $themeId ] );
}
@@ -577,7 +615,8 @@ class _themes extends \IPS\Node\Controller
$data['totalDone']++;
}
return array( $data, $msg, ( $data['totalDone'] / $data['totalItems'] * 100 ) );
$percent = ( $data['totalDone'] / $data['totalItems'] * 100 );
return array( $data, $msg, ( $percent <= 100 ? $percent : 100 ) );
}
else
{
@@ -603,7 +642,7 @@ class _themes extends \IPS\Node\Controller
*/
public function resources()
{
$setId = intval( \IPS\Request::i()->set_id );
$setId = \intval( \IPS\Request::i()->set_id );
try
{
@@ -625,7 +664,7 @@ class _themes extends \IPS\Node\Controller
$table->parsers = array(
'resource_data' => function( $val, $row ) use ($set)
{
if ( in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), \IPS\Image::$imageExtensions ) )
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::$imageExtensions, array( 'svg' ) ) ) )
{
$row['resource_filename'] = $set->resource( ltrim( $row['resource_path'], '/' ) . $row['resource_name'], $row['resource_app'], $row['resource_location'] );
}
@@ -634,7 +673,8 @@ class _themes extends \IPS\Node\Controller
},
'resource_name' => function( $val, $row ) use ($set)
{
if ( in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), \IPS\Image::$imageExtensions ) )
if ( \in_array( mb_substr( $row['resource_name'], mb_strrpos( $row['resource_name'], '.' ) + 1 ), array_merge( \IPS\Image::$imageExtensions, array( 'svg' ) ) ) )
{
$row['resource_filename'] = $set->resource( ltrim( $row['resource_path'], '/' ) . $row['resource_name'], $row['resource_app'], $row['resource_location'] );
}
@@ -701,7 +741,7 @@ class _themes extends \IPS\Node\Controller
/* Make sure the user confirmed the deletion */
\IPS\Request::i()->confirmedDelete();
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
try
{
@@ -751,7 +791,7 @@ class _themes extends \IPS\Node\Controller
*/
public function resourceForm()
{
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
try
{
@@ -906,6 +946,8 @@ class _themes extends \IPS\Node\Controller
*/
public function launchvse()
{
\IPS\Session::i()->csrfCheck();
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
if ( $theme->by_skin_gen AND \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'customization', 'theme_easy_editor' ) )
@@ -929,6 +971,13 @@ class _themes extends \IPS\Node\Controller
*/
public function convertToCustom()
{
if ( \IPS\DEMO_MODE )
{
\IPS\Output::i()->error( 'demo_mode_function_blocked', '1C163/J', 403, '' );
}
\IPS\Session::i()->csrfCheck();
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
$theme->by_skin_gen = 0;
$theme->save();
@@ -943,6 +992,53 @@ class _themes extends \IPS\Node\Controller
}
}
/**
* Manual Theme Upload
*
* @return void
*/
public function upload()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_download_upload' );
$form = new \IPS\Helpers\Form( 'form', 'next' );
$form->addMessage('themes_manual_install_warning');
$form->add(
new \IPS\Helpers\Form\Upload(
'core_theme_set_new_import', NULL, FALSE, array(
'allowedFileTypes' => array( 'xml' ),
'temporary' => TRUE
), NULL, NULL, NULL, 'core_theme_set_new_import'
)
);
if ( $values = $form->values() )
{
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['core_theme_set_new_import'], $tempFile );
$max = \IPS\Db::i()->select( 'MAX(set_order)', 'core_themes' )->first();
/* Create a default theme */
$theme = new \IPS\Theme;
$theme->parent_array = '[]';
$theme->child_array = '[]';
$theme->parent_id = 0;
$theme->by_skin_gen = 0;
$theme->editor_skin = 'ips';
$theme->order = $max + 1;
$theme->save();
$theme->copyResourcesFromSet();
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => $theme->id ) )->csrf() );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'theme_set_add_button', $form, FALSE );
}
/**
* Add a theme dialog
*
@@ -950,63 +1046,30 @@ class _themes extends \IPS\Node\Controller
*/
public function add()
{
$form = new \IPS\Helpers\Form( 'form', 'next' );
$form->addTab( 'core_theme_add_new' );
$form->add( new \IPS\Helpers\Form\Radio(
'core_theme_add_theme_type',
NULL,
FALSE,
array( 'options'=> array( 'vse' => 'core_theme_add_theme_vse', 'custom' => 'core_theme_add_theme_custom' ),
'descriptions' => array( 'vse' => 'core_theme_add_theme_vse_desc', 'custom' => 'core_theme_add_theme_custom_desc' ) ),
NULL,
NULL,
NULL,
'core_theme_add_theme_type'
) );
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'customization', 'theme_download_upload' ) )
if ( \IPS\DEMO_MODE )
{
$form->addTab( 'core_theme_add_upload' );
$form->add(
new \IPS\Helpers\Form\Upload(
'core_theme_set_new_import', NULL, FALSE, array(
'allowedFileTypes' => array( 'xml' ),
'temporary' => TRUE
), NULL, NULL, NULL, 'core_theme_set_new_import'
)
);
/* We only allow easy mode themes */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=form&type=vse' ) );
}
$form = new \IPS\Helpers\Form( 'form', 'next' );
$form->addMessage('themes_create_blurb');
$form->add( new \IPS\Helpers\Form\Radio(
'core_theme_add_theme_type',
NULL,
FALSE,
array( 'options'=> array( 'vse' => 'core_theme_add_theme_vse', 'custom' => 'core_theme_add_theme_custom' ),
'descriptions' => array( 'vse' => 'core_theme_add_theme_vse_desc', 'custom' => 'core_theme_add_theme_custom_desc' ) ),
NULL,
NULL,
NULL,
'core_theme_add_theme_type'
) );
if ( $values = $form->values() )
{
if ( isset( $values['core_theme_set_new_import'] ) and $values['core_theme_set_new_import'] )
{
/* Move it to a temporary location */
$tempFile = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' );
move_uploaded_file( $values['core_theme_set_new_import'], $tempFile );
$max = \IPS\Db::i()->select( 'MAX(set_order)', 'core_themes' )->first();
/* Create a default theme */
$theme = new \IPS\Theme;
$theme->parent_array = '[]';
$theme->child_array = '[]';
$theme->parent_id = 0;
$theme->by_skin_gen = 0;
$theme->editor_skin = 'ips';
$theme->order = $max + 1;
$theme->save();
$theme->copyResourcesFromSet();
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => $theme->id ) ) );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=form&type=' . $values['core_theme_add_theme_type'] ) );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=form&type=' . $values['core_theme_add_theme_type'] ) );
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'theme_set_add_button', $form, FALSE );
@@ -1019,13 +1082,15 @@ class _themes extends \IPS\Node\Controller
*/
public function import()
{
\IPS\Session::i()->csrfCheck();
if ( !file_exists( \IPS\Request::i()->file ) or md5_file( \IPS\Request::i()->file ) !== \IPS\Request::i()->key )
{
\IPS\Output::i()->error( 'generic_error', '3C130/1', 500, '' );
}
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => \IPS\Request::i()->file, 'key' => \IPS\Request::i()->key, 'id' => \IPS\Request::i()->id ) ),
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => \IPS\Request::i()->file, 'key' => \IPS\Request::i()->key, 'id' => \IPS\Request::i()->id ) )->csrf(),
function( $data )
{
$set = \IPS\Theme::load( \IPS\Request::i()->id );
@@ -1047,7 +1112,7 @@ class _themes extends \IPS\Node\Controller
}
/* Is this the first batch? */
if ( !is_array( $data ) )
if ( !\is_array( $data ) )
{
$_SESSION['theme_import'] = array( 'css' => array(), 'isNewSet' => false );
@@ -1206,7 +1271,7 @@ class _themes extends \IPS\Node\Controller
}
}
if ( count( $files ) )
if ( \count( $files ) )
{
$value = implode( ',', $files );
}
@@ -1312,7 +1377,7 @@ class _themes extends \IPS\Node\Controller
/* Theme resources should be raw binary data everywhere (filesystem and DB) except in the theme XML download where they are base64 encoded. */
$content = base64_decode( $xml->readString() );
$name = \IPS\Theme::makeBuiltTemplateLookupHash( $appKey, $xml->getAttribute('location'), $xml->getAttribute('path') ) . '_' . $xml->getAttribute('name');
$fileName = (string) \IPS\File::create( 'core_Theme', $name, $content, 'set_resources_' . \IPS\Request::i()->id, FALSE, NULL, FALSE );
$fileName = (string) \IPS\File::create( 'core_Theme', $name, $content, 'set_resources_' . \IPS\Request::i()->id, TRUE, NULL, FALSE );
try
{
@@ -1343,7 +1408,7 @@ class _themes extends \IPS\Node\Controller
'resource_data' => $content,
'resource_added' => time(),
'resource_filename' => $fileName,
'resource_user_edited' => intval( $xml->getAttribute('user_edited') )
'resource_user_edited' => \intval( $xml->getAttribute('user_edited') )
) );
break;
}
@@ -1414,26 +1479,22 @@ class _themes extends \IPS\Node\Controller
$type = $xml->getAttribute('type');
$name = $xml->getAttribute('name');
if( $set->logo[ $type ]['url'] !== null )
/* Sharer logo and favicons may be present from themes generated in 4.3 and earlier, but are no longer used */
if( $type != 'sharer' AND $type != 'favicon' )
{
\IPS\File::get( 'core_Theme', $set->logo[ $type ]['url'] )->delete();
}
if( $set->logo[ $type ]['url'] !== null )
{
\IPS\File::get( 'core_Theme', $set->logo[ $type ]['url'] )->delete();
}
$url = (string) \IPS\File::create( 'core_Theme', $name, base64_decode( $xml->readString() ) );
$url = (string) \IPS\File::create( 'core_Theme', $name, base64_decode( $xml->readString() ) );
if( $type != 'favicon' )
{
$image = \IPS\Image::create( base64_decode( $xml->readString() ) );
$width = $image->width;
$height = $image->height;
}
else
{
$width = 0;
$height = 0;
}
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'width' => $width, 'height' => $height ) ) ) );
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'width' => $width, 'height' => $height ) ) ) );
}
$xml->next();
}
@@ -1465,12 +1526,18 @@ class _themes extends \IPS\Node\Controller
}
\IPS\Theme::setThemeCustomized( $set->id );
$set->resetCacheKey();
/* Conflicts to fix? */
if ( \IPS\Db::i()->select( 'count(*)', 'core_theme_conflict', array( 'conflict_set_id=?', \IPS\Request::i()->id ) )->first() )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=conflicts&id=' . \IPS\Request::i()->id ) );
}
elseif( $set->marketplace_id )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=marketplace&controller=marketplace&do=viewFile&id=' . $set->marketplace_id ), 'theme_now_installed' );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes' ) );
@@ -1489,7 +1556,7 @@ class _themes extends \IPS\Node\Controller
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
$form = new \IPS\Helpers\Form( 'form', 'theme_conflict_save' );
$themeSet = \IPS\Theme::load( $id );
$templates = array();
@@ -1503,7 +1570,7 @@ class _themes extends \IPS\Node\Controller
$conflicts[ $data['conflict_type'] ][ $key ] = $data;
}
if ( isset( $conflicts['template'] ) AND count( $conflicts['template'] ) )
if ( isset( $conflicts['template'] ) AND \count( $conflicts['template'] ) )
{
$templates = iterator_to_array( \IPS\Db::i()->select(
'*, MD5( CONCAT( template_app, \'.\', template_location, \'.\', template_group, \'.\', template_name ) ) as bit_key',
@@ -1516,7 +1583,7 @@ class _themes extends \IPS\Node\Controller
$conflicts['template'] = array();
}
if ( isset( $conflicts['css'] ) AND count( $conflicts['css'] ) )
if ( isset( $conflicts['css'] ) AND \count( $conflicts['css'] ) )
{
$css = iterator_to_array( \IPS\Db::i()->select(
'*, MD5( CONCAT( css_app, \'.\', css_location, \'.\', css_path, \'.\', css_name ) ) as bit_key',
@@ -1593,7 +1660,7 @@ class _themes extends \IPS\Node\Controller
}
}
if ( count( $conflictIds ) )
if ( \count( $conflictIds ) )
{
/* Get conflict data */
foreach( \IPS\Db::i()->select( '*', 'core_theme_conflict', \IPS\Db::i()->in( 'conflict_id', array_keys( $conflictIds ) ) )->setKeyField( 'conflict_id' ) as $cid => $data )
@@ -1604,7 +1671,7 @@ class _themes extends \IPS\Node\Controller
}
}
if ( isset( $conflicts['template'] ) AND count( $conflicts['template'] ) )
if ( isset( $conflicts['template'] ) AND \count( $conflicts['template'] ) )
{
$templates = iterator_to_array( \IPS\Db::i()->select(
'*, MD5( CONCAT( template_app, \'.\', template_location, \'.\', template_group, \'.\', template_name ) ) as bit_key',
@@ -1613,7 +1680,7 @@ class _themes extends \IPS\Node\Controller
)->setKeyField( 'bit_key' ) );
}
if ( isset( $conflicts['css'] ) AND count( $conflicts['css'] ) )
if ( isset( $conflicts['css'] ) AND \count( $conflicts['css'] ) )
{
$css = iterator_to_array( \IPS\Db::i()->select(
'*, MD5( CONCAT( css_app, \'.\', css_location, \'.\', css_path, \'.\', css_name ) ) as bit_key',
@@ -1660,7 +1727,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes' ), 'completed' );
}
if ( count( $conflicts['css'] ) OR count( $conflicts['template'] ) )
if ( \count( $conflicts['css'] ) OR \count( $conflicts['template'] ) )
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'system/diff.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
@@ -1687,7 +1754,7 @@ class _themes extends \IPS\Node\Controller
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
/* Build form */
$form = new \IPS\Helpers\Form();
@@ -1758,7 +1825,7 @@ class _themes extends \IPS\Node\Controller
if ( $values = $form->values() )
{
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
$save = array( 'app' => $values['theme_css_app'],
'location' => $values['theme_css_location'],
'path' => ( $values['theme_css_group_type'] == 'existing' ) ? $values['theme_css_group_existing'] : $values['theme_css_group_new'],
@@ -1798,13 +1865,18 @@ class _themes extends \IPS\Node\Controller
*/
public function importForm()
{
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_download_upload' );
$themeSet = \IPS\Theme::load( $id );
$apps = array();
if( $themeSet->marketplace_id )
{
\IPS\Output::i()->error( 'app_upload_marketplace_only', '2C163/K', 403, '' );
}
foreach( \IPS\Application::applications() as $key => $data )
{
@@ -1823,7 +1895,7 @@ class _themes extends \IPS\Node\Controller
move_uploaded_file( $values['core_theme_set_new_import'], $tempFile );
/* Initate a redirector */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => \IPS\Request::i()->id) ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=import' )->setQueryString( array( 'file' => $tempFile, 'key' => md5_file( $tempFile ), 'id' => \IPS\Request::i()->id) )->csrf() );
}
/* Display */
@@ -1837,7 +1909,7 @@ class _themes extends \IPS\Node\Controller
*/
public function exportForm()
{
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_download_upload' );
@@ -1853,7 +1925,7 @@ class _themes extends \IPS\Node\Controller
$form = new \IPS\Helpers\Form( 'form', 'theme_set_export_button' );
$storedAuthor = ( isset( \IPS\Data\Store::i()->theme_stored_author ) AND is_array( \IPS\Data\Store::i()->theme_stored_author ) ) ? \IPS\Data\Store::i()->theme_stored_author : null;
$storedAuthor = ( isset( \IPS\Data\Store::i()->theme_stored_author ) AND \is_array( \IPS\Data\Store::i()->theme_stored_author ) ) ? \IPS\Data\Store::i()->theme_stored_author : null;
$form->addHeader( \IPS\Member::loggedIn()->language()->addToStack('theme_set_export_title', FALSE, array( 'sprintf' => array( $themeSet->_title ) ) ) );
@@ -1861,7 +1933,7 @@ class _themes extends \IPS\Node\Controller
{
if ( ! isset( \IPS\Request::i()->rebuildDone ) )
{
$url = \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportBeforeExport&id=' . $id );
$url = \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportBeforeExport&id=' . $id )->csrf();
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack('theme_export_designer_mode_build_message', NULL, array( 'sprintf' => array( $url ) ) ), 'ipsMessage ipsMessage_info', FALSE );
}
@@ -1878,7 +1950,7 @@ class _themes extends \IPS\Node\Controller
$authorName = $values['theme_template_export_author_name'] ? $values['theme_template_export_author_name'] : $themeSet->author_name;
$authorUrl = $values['theme_template_export_author_url'] ? $values['theme_template_export_author_url'] : $themeSet->author_url;
$version = $values['theme_template_export_version'] ? $values['theme_template_export_version'] : $themeSet->version;
$longVersion = is_int( $values['theme_template_export_long_version'] ) ? $values['theme_template_export_long_version'] : $themeSet->long_version;
$longVersion = \is_int( $values['theme_template_export_long_version'] ) ? $values['theme_template_export_long_version'] : $themeSet->long_version;
$updateCheck = $values['theme_update_check'] ? $values['theme_update_check'] : $themeSet->update_check;
\IPS\Data\Store::i()->theme_stored_author = array(
@@ -1929,7 +2001,7 @@ class _themes extends \IPS\Node\Controller
{
$logos = json_decode( $themeSet->logo_data, TRUE );
if( is_array( $logos ) )
if( \is_array( $logos ) )
{
foreach ( $logos as $file => $data )
{
@@ -1981,7 +2053,7 @@ class _themes extends \IPS\Node\Controller
/* Templates */
$templates = $themeSet->getRawTemplates( $appDir, '', '', \IPS\Theme::RETURN_ALL );
if ( isset( $templates[ $appDir->directory ] ) AND count( $templates[ $appDir->directory ] ) )
if ( isset( $templates[ $appDir->directory ] ) AND \count( $templates[ $appDir->directory ] ) )
{
foreach( $templates[ $appDir->directory ] as $loc => $lv )
{
@@ -2006,7 +2078,7 @@ class _themes extends \IPS\Node\Controller
foreach( $templates[ $appDir->directory ][ $loc ][ $group ][ $name ] as $k => $v )
{
if ( in_array( \substr( $k, 9 ), array( 'location', 'group', 'name', 'data' ) ) )
if ( \in_array( \substr( $k, 9 ), array( 'location', 'group', 'name', 'data' ) ) )
{
$xml->startAttribute($k);
$xml->text( $v );
@@ -2050,7 +2122,7 @@ class _themes extends \IPS\Node\Controller
foreach( $css[ $appDir->directory ][ $loc ][ $path ][ $name ] as $k => $v )
{
if ( in_array( \substr( $k, 4 ), array( 'location', 'path', 'name', 'attributes' ) ) )
if ( \in_array( \substr( $k, 4 ), array( 'location', 'path', 'name', 'attributes' ) ) )
{
$xml->startAttribute($k);
$xml->text( $v );
@@ -2130,7 +2202,7 @@ class _themes extends \IPS\Node\Controller
/* Custom fields */
$settings = \IPS\Db::i()->select( '*', 'core_theme_settings_fields', array( 'sc_set_id=? AND sc_app=?', $themeSet->id, $appDir->directory ) )->join( 'core_theme_settings_values', 'sc_id=sv_id' );
if ( count( $settings ) )
if ( \count( $settings ) )
{
foreach ( $settings as $row )
{
@@ -2141,7 +2213,7 @@ class _themes extends \IPS\Node\Controller
foreach( $row as $k => $v )
{
if ( !in_array( $k, array( 'sc_updated', 'sv_value', 'sc_app', 'sv_id' ) ) )
if ( !\in_array( $k, array( 'sc_updated', 'sv_value', 'sc_app', 'sv_id' ) ) )
{
$xml->startAttribute( $k );
$xml->text( $v );
@@ -2269,10 +2341,12 @@ class _themes extends \IPS\Node\Controller
}
catch ( \OutOfRangeException $e ) {}
$parentResult = parent::delete();
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
return parent::delete();
return $parentResult;
}
/**
@@ -2286,7 +2360,7 @@ class _themes extends \IPS\Node\Controller
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
$locations = array();
$groups = array();
@@ -2361,7 +2435,7 @@ class _themes extends \IPS\Node\Controller
if ( $values = $form->values() )
{
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
$save = array( 'set_id' => $id,
'app' => $values['theme_template_app'],
'location' => $values['theme_template_location'],
@@ -2410,6 +2484,8 @@ class _themes extends \IPS\Node\Controller
*/
public function build()
{
\IPS\Session::i()->csrfCheck();
$set = \IPS\Theme::load( \IPS\Request::i()->id );
/* Resources has to come before CSS otherwise CSS url()s are out of date as resource build changes resource URL after CSS has been built */
@@ -2435,8 +2511,8 @@ class _themes extends \IPS\Node\Controller
\IPS\Request::i()->confirmedDelete();
$type = \IPS\Request::i()->t_type;
$id = intval( \IPS\Request::i()->id );
$item_id = intval( \IPS\Request::i()->t_item_id );
$id = \intval( \IPS\Request::i()->id );
$item_id = \intval( \IPS\Request::i()->t_item_id );
try
{
@@ -2503,6 +2579,8 @@ class _themes extends \IPS\Node\Controller
*/
public function saveTemplate()
{
\IPS\Session::i()->csrfCheck();
$type = \IPS\Request::i()->t_type;
$editorName = 'editor_' . \IPS\Request::i()->t_key;
$url = array();
@@ -2512,8 +2590,8 @@ class _themes extends \IPS\Node\Controller
'location' => \IPS\Request::i()->t_location,
'group' => \IPS\Request::i()->t_group,
'name' => \IPS\Request::i()->t_name,
'set_id' => intval( \IPS\Request::i()->id ),
'item_id' => intval( \IPS\Request::i()->t_item_id ),
'set_id' => \intval( \IPS\Request::i()->id ),
'item_id' => \intval( \IPS\Request::i()->t_item_id ),
'key' => \IPS\Request::i()->t_key,
'content' => \IPS\Request::i()->$editorName );
try
@@ -2561,7 +2639,7 @@ class _themes extends \IPS\Node\Controller
foreach( $save as $k => $v )
{
if ( in_array( $k, array( 'app', 'location', 'group', 'name' ) ) )
if ( \in_array( $k, array( 'app', 'location', 'group', 'name' ) ) )
{
$url[] = 't_' . $k . '=' . $v;
$log[ $k . ': ' . $v ] = FALSE;
@@ -2611,12 +2689,14 @@ class _themes extends \IPS\Node\Controller
*/
public function devImportMultipleRedirect( $finalUrl=NULL )
{
\IPS\Session::i()->csrfCheck();
\IPS\Output::i()->output = new \IPS\Helpers\MultipleRedirect(
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportMultipleRedirect&id=' . \IPS\Request::i()->id . '&finalUrl=' . base64_encode( $finalUrl ) ),
\IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportMultipleRedirect&id=' . \IPS\Request::i()->id . '&finalUrl=' . base64_encode( $finalUrl ) )->csrf(),
function( $data )
{
/* Is this the first cycle? */
if ( ! is_array( $data ) )
if ( ! \is_array( $data ) )
{
$toImport = array();
@@ -2632,7 +2712,7 @@ class _themes extends \IPS\Node\Controller
}
/* Grab something to build */
if ( count( $data['toImport'] ) )
if ( \count( $data['toImport'] ) )
{
reset( $data['toImport'] );
$app = key( $data['toImport'] );
@@ -2643,18 +2723,43 @@ class _themes extends \IPS\Node\Controller
switch( $type )
{
case 'html':
\IPS\Theme::i()->importDevHtml( $app, ( ( \IPS\Theme::designersModeEnabled() !== true ) ? 0 : \IPS\Request::i()->id ) );
\IPS\Theme::load( ( ( \IPS\IN_DEV AND ! \IPS\Request::i()->id ) ? 1 : \IPS\Request::i()->id ) )->compileTemplates( $app );
if( \IPS\Theme::designersModeEnabled() !== true )
{
\IPS\Theme::i()->importDevHtml( $app, 0 );
\IPS\Theme::load( 1 )->compileTemplates( $app );
}
else
{
\IPS\Theme\Advanced\Theme::load( \IPS\Request::i()->id )->importDevHtml( $app, \IPS\Request::i()->id );
\IPS\Theme::load( \IPS\Request::i()->id )->compileTemplates( $app );
}
unset( $data['toImport'][ $app ][0] );
break;
case 'css':
\IPS\Theme::i()->importDevCss( $app, ( ( \IPS\Theme::designersModeEnabled() !== true ) ? 0 : \IPS\Request::i()->id ) );
\IPS\Theme::load( ( ( \IPS\IN_DEV AND ! \IPS\Request::i()->id ) ? 1 : \IPS\Request::i()->id ) )->compileCss( $app );
if( \IPS\Theme::designersModeEnabled() !== true )
{
\IPS\Theme::i()->importDevCss( $app, 0 );
\IPS\Theme::load( 1 )->compileCss( $app );
}
else
{
\IPS\Theme\Advanced\Theme::load( \IPS\Request::i()->id )->importDevCss( $app, \IPS\Request::i()->id );
\IPS\Theme::load( \IPS\Request::i()->id )->compileCss( $app );
}
unset( $data['toImport'][ $app ][1] );
break;
case 'resources':
\IPS\Theme::i()->importDevResources( $app, ( ( \IPS\Theme::designersModeEnabled() !== true ) ? 1 : \IPS\Request::i()->id ) );
\IPS\Theme::load( ( ( \IPS\IN_DEV AND ! \IPS\Request::i()->id ) ? 1 : \IPS\Request::i()->id ) )->buildResourceMap( $app );
if( \IPS\Theme::designersModeEnabled() !== true )
{
\IPS\Theme::i()->importDevResources( $app, 0 );
\IPS\Theme::load( 1 )->buildResourceMap( $app );
}
else
{
\IPS\Theme\Advanced\Theme::load( \IPS\Request::i()->id )->importDevResources( $app, \IPS\Request::i()->id );
\IPS\Theme::load( \IPS\Request::i()->id )->buildResourceMap( $app );
}
/* All done */
unset( $data['toImport'][ $app ] );
break;
@@ -2688,7 +2793,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
/* Grab groups */
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
if ( $id === 1 AND \IPS\IN_DEV )
{
@@ -2735,7 +2840,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
/* Grab groups */
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
if ( $id === 1 AND \IPS\IN_DEV )
{
@@ -2789,7 +2894,7 @@ class _themes extends \IPS\Node\Controller
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
/* Grab groups */
$id = intval( \IPS\Request::i()->id );
$id = \intval( \IPS\Request::i()->id );
$t_type = \IPS\Request::i()->t_type ?: 'templates';
$t_app = \IPS\Request::i()->t_app ?: 'core';
$t_location = \IPS\Request::i()->t_location ?: ( ( \IPS\Request::i()->t_app ) ? false : 'front' );
@@ -11,7 +11,7 @@
namespace IPS\core\modules\admin\customization;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -21,7 +21,12 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
* themesettings
*/
class _themesettings extends \IPS\Dispatcher\Controller
{
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
/**
* Execute
*
@@ -41,7 +46,7 @@ class _themesettings extends \IPS\Dispatcher\Controller
),
array(
NULL,
\IPS\Member::loggedIn()->language()->addToStack( 'core_theme_set_title_' . intval( \IPS\Request::i()->set_id ) )
\IPS\Member::loggedIn()->language()->addToStack( 'core_theme_set_title_' . \intval( \IPS\Request::i()->set_id ) )
)
);
}
@@ -97,14 +102,14 @@ class _themesettings extends \IPS\Dispatcher\Controller
$return = array();
foreach( \IPS\Db::i()->select( '*', 'core_theme_settings_fields', array( 'sc_set_id=? AND sc_tab_key=?', $theme->id, $activeTab ), 'sc_order' ) as $row )
{
$return[ $row['sc_id'] ] = call_user_func( array( $self, '_settingRow' ), $row );
$return[ $row['sc_id'] ] = $self->_settingRow( $row );
}
return $return;
},
// getRow
function( $id ) use ( $self )
{
return call_user_func( array( $self, '_settingRow' ), \IPS\Db::i()->select( '*', 'core_theme_settings_fields', array( 'sc_id=?', $id ) )->first() );
return $self->_settingRow( \IPS\Db::i()->select( '*', 'core_theme_settings_fields', array( 'sc_id=?', $id ) )->first() );
},
// getRowParentId
function()
@@ -202,6 +207,8 @@ class _themesettings extends \IPS\Dispatcher\Controller
*/
public function reorder()
{
\IPS\Session::i()->csrfCheck();
/* Normalise AJAX vs non-AJAX */
if( isset( \IPS\Request::i()->ajax_order ) )
{
@@ -250,8 +257,8 @@ class _themesettings extends \IPS\Dispatcher\Controller
public function form()
{
$current = NULL;
$setId = intval( \IPS\Request::i()->set_id );
$id = intval( \IPS\Request::i()->id );
$setId = \intval( \IPS\Request::i()->set_id );
$id = \intval( \IPS\Request::i()->id );
$set = \IPS\Theme::load( $setId );
if ( $id )
@@ -288,6 +295,12 @@ class _themesettings extends \IPS\Dispatcher\Controller
),
function( $val )
{
/* Check key format */
if( !preg_match( '/^[a-zA-Z0-9_]+$/', $val ) )
{
throw new \InvalidArgumentException('core_theme_settings_error_key_disallowed_characters');
}
/* Make sure key is unique */
try
{
@@ -313,7 +326,7 @@ class _themesettings extends \IPS\Dispatcher\Controller
return true;
} ) );
if ( count( $tabs ) )
if ( \count( $tabs ) )
{
$form->add( new \IPS\Helpers\Form\Radio( 'theme_custom_setting_tab_key_type', 'existing', FALSE, array(
'options' => array( 'existing' => 'theme_custom_setting_tab_key_o_existing',
@@ -504,7 +517,7 @@ class _themesettings extends \IPS\Dispatcher\Controller
*/
public function delete()
{
$setId = intval( \IPS\Request::i()->set_id );
$setId = \intval( \IPS\Request::i()->set_id );
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_settings_remove' );