Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
@@ -531,14 +531,18 @@ EOF;
|
||||
$block_editor = ( isset( Request::i()->block_editor ) ) ? Request::i()->block_editor : ( $this->id ? $this->getConfig('editor') : null );
|
||||
$block_plugin = ( isset( Request::i()->block_plugin ) ) ? Request::i()->block_plugin : ( $this->id ? $this->plugin : null );
|
||||
|
||||
/* Build form */
|
||||
$form->addTab( 'content_block_form_tab__details' );
|
||||
|
||||
/* Double check that we didn't force the PHP blocks */
|
||||
if( $block_editor == 'php' and !Bridge::i()->featureIsEnabled( 'phpblocks' ) )
|
||||
{
|
||||
/* If PHP blocks are disabled, stop here */
|
||||
$form->addMessage( 'content_block_php_disabled', 'ipsMessage ipsMessage--warning' );
|
||||
|
||||
$block_editor = 'html';
|
||||
}
|
||||
|
||||
/* Build form */
|
||||
$form->addTab( 'content_block_form_tab__details' );
|
||||
|
||||
$form->add( new Translatable( 'block_name', NULL, TRUE, array(
|
||||
'app' => 'cms',
|
||||
'key' => ( $this->id ? "content_block_name_" . $this->id : NULL )
|
||||
@@ -1111,7 +1115,7 @@ EOF;
|
||||
}
|
||||
elseif( $widget['key'] == 'Codemirror' )
|
||||
{
|
||||
if( isset( $widget['configuration'] ) and $widget['configuration']['content'] )
|
||||
if( isset( $widget['configuration']['content'] ) )
|
||||
{
|
||||
preg_match_all( '/\{block="(.+?)"}/is', $widget['configuration']['content'], $matches );
|
||||
foreach( $matches[1] as $blockKey )
|
||||
|
||||
@@ -423,7 +423,18 @@ class Categories extends Model implements Permissions
|
||||
if( array_key_exists( 'page_can_view', $values ) )
|
||||
{
|
||||
$this->club_view_permission= implode( ",", $values['page_can_view'] );
|
||||
$values['club_node_public'] = intval( in_array( 'nonmember', $values['page_can_view'] ) or mb_strpos( '*', $this->club_view_permission ) !== false );
|
||||
if( !in_array( 'nonmember', $values['page_can_view'] ) and !in_array( 'member', $values['page_can_view'] ) )
|
||||
{
|
||||
$values['club_node_public'] = Club::NODE_MODERATORS;
|
||||
}
|
||||
elseif( in_array( 'nonmember', $values['page_can_view'] ) or mb_strpos( '*', $this->club_view_permission ) !== false )
|
||||
{
|
||||
$values['club_node_public'] = Club::NODE_PUBLIC;
|
||||
}
|
||||
else
|
||||
{
|
||||
$values['club_node_public'] = Club::NODE_PRIVATE;
|
||||
}
|
||||
unset( $values['page_can_view'] );
|
||||
}
|
||||
|
||||
@@ -2243,7 +2254,7 @@ class Categories extends Model implements Permissions
|
||||
*/
|
||||
public function metaDescription(): ?string
|
||||
{
|
||||
return $this->meta_description ?: static::database()->metaDescription();
|
||||
return $this->meta_description ?: parent::metaDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2253,7 +2264,7 @@ class Categories extends Model implements Permissions
|
||||
*/
|
||||
public function metaTitle(): string
|
||||
{
|
||||
return $this->page_title ?: static::database()->metaTitle();
|
||||
return $this->page_title ?: parent::metaTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1023,7 +1023,7 @@ class Databases extends Model implements Permissions
|
||||
/* Editor Areas */
|
||||
Member::loggedIn()->language()->words['editor__cms_Records' . $this->id ] = $this->_title;
|
||||
|
||||
foreach( array( 'pin', 'unpin', 'feature', 'unfeature', 'edit', 'hide', 'unhide', 'view_hidden', 'future_publish', 'view_future', 'move', 'lock', 'unlock', 'reply_to_locked', 'delete', 'feature_comments', 'unfeature_comments', 'add_item_message', 'edit_item_message', 'delete_item_message', 'view_reports' ) as $lang )
|
||||
foreach( array( 'pin', 'unpin', 'feature', 'unfeature', 'edit', 'hide', 'unhide', 'view_hidden', 'future_publish', 'view_future', 'move', 'lock', 'unlock', 'reply_to_locked', 'delete', 'feature_comments', 'unfeature_comments', 'add_item_message', 'edit_item_message', 'delete_item_message', 'view_reports', 'assign' ) as $lang )
|
||||
{
|
||||
Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_sl_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord(1) ) ) );
|
||||
Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_su_' . $this->id ] = Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord(1, TRUE) ) ) );
|
||||
|
||||
@@ -21,8 +21,10 @@ use IPS\cms\Records;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\Dispatcher as SystemDispatcher;
|
||||
use IPS\Dispatcher\Controller;
|
||||
use IPS\Dispatcher\Front;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Output;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use OutOfRangeException;
|
||||
@@ -52,7 +54,12 @@ class Dispatcher extends SystemDispatcher
|
||||
* @brief Controller location
|
||||
*/
|
||||
public string $controllerLocation = 'front';
|
||||
|
||||
|
||||
/**
|
||||
* Controller
|
||||
*/
|
||||
public ?string $controller = NULL;
|
||||
|
||||
/**
|
||||
* @brief Database Id
|
||||
*/
|
||||
@@ -198,7 +205,7 @@ class Dispatcher extends SystemDispatcher
|
||||
}
|
||||
}
|
||||
|
||||
list( $path, $pageNumber ) = Page::getStrippedPagePath( $path );
|
||||
[ $path, $pageNumber ] = Page::getStrippedPagePath( $path );
|
||||
|
||||
$this->databaseId = $database->id;
|
||||
|
||||
@@ -210,16 +217,16 @@ class Dispatcher extends SystemDispatcher
|
||||
/* Got a specific category ID? */
|
||||
if ( $this->categoryId !== NULL and ! $path and ( ( $database->use_categories and $database->cat_index_type !== 1 ) OR ( ! $database->use_categories and isset( Request::i()->do ) ) ) )
|
||||
{
|
||||
$this->module = 'category';
|
||||
$this->controller = 'category';
|
||||
}
|
||||
else if ( isset( Request::i()->c ) AND is_numeric( Request::i()->c ) )
|
||||
{
|
||||
$this->categoryId = Request::i()->c;
|
||||
$this->module = 'category';
|
||||
$this->controller = 'category';
|
||||
}
|
||||
else if ( empty( $path ) )
|
||||
{
|
||||
$this->module = 'index';
|
||||
$this->controller = 'index';
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -322,18 +329,18 @@ class Dispatcher extends SystemDispatcher
|
||||
$this->_redirectToCorrectUrl( $record->url() );
|
||||
}
|
||||
|
||||
$this->module = 'record';
|
||||
$this->controller = 'record';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's a category listing */
|
||||
$this->module = 'category';
|
||||
$this->controller = 'category';
|
||||
}
|
||||
}
|
||||
|
||||
$this->url = Url::internal( $url, 'front', 'content_page_path' );
|
||||
$className = '\\IPS\\cms\\modules\\front\\database\\' . $this->module;
|
||||
|
||||
$className = '\\IPS\\cms\\modules\\front\\database\\' . $this->controller;
|
||||
|
||||
/* Init class */
|
||||
if( !class_exists( $className ) )
|
||||
{
|
||||
@@ -372,7 +379,7 @@ class Dispatcher extends SystemDispatcher
|
||||
$controller->execute();
|
||||
|
||||
/* Data Layer Context */
|
||||
if ( $this->databaseId AND Settings::i()->core_datalayer_enabled )
|
||||
if ( $this->databaseId AND DataLayer::enabled() )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -445,6 +452,25 @@ class Dispatcher extends SystemDispatcher
|
||||
*/
|
||||
public function finish() : void
|
||||
{
|
||||
Bridge::i()->frontDispatcherFinish();
|
||||
|
||||
if( $this->recordId )
|
||||
{
|
||||
Output::i()->bodyAttributes['contentClass'] = 'IPS\cms\Records' . $this->databaseId;
|
||||
}
|
||||
elseif( $this->categoryId )
|
||||
{
|
||||
Output::i()->bodyAttributes['contentClass'] = 'IPS\cms\Categories' . $this->databaseId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Output::i()->bodyAttributes['contentClass'] = 'IPS\cms\Databases';
|
||||
}
|
||||
|
||||
/* Data Attributes */
|
||||
Output::i()->setBodyAttributes();
|
||||
Front::checkAlerts( $this );
|
||||
|
||||
Output::i()->output = $this->output ?: Output::i()->output;
|
||||
|
||||
/* Loader Extension */
|
||||
|
||||
@@ -1290,9 +1290,9 @@ class Fields extends CustomField implements Permissions
|
||||
/**
|
||||
* Get the "extra" field
|
||||
*
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
public function get_extra(): mixed
|
||||
public function get_extra(): ?array
|
||||
{
|
||||
return isset( $this->_data['extra'] ) ? json_decode( $this->_data['extra'], TRUE ) : [];
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class Soundcloud extends Text
|
||||
* @param string|null $suffix HTML to show after input field
|
||||
* @param string|null $id The ID to add to the row
|
||||
*/
|
||||
public function __construct( string $name, mixed $defaultValue=NULL, bool $required=FALSE, array $options=array(), callable $customValidationCode=NULL, string $prefix=NULL, string $suffix=NULL, string $id=NULL )
|
||||
public function __construct( string $name, mixed $defaultValue=NULL, ?bool $required=FALSE, array $options=array(), callable $customValidationCode=NULL, string $prefix=NULL, string $suffix=NULL, string $id=NULL )
|
||||
{
|
||||
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_soundcloud');
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class Spotify extends Text
|
||||
* @param string|null $suffix HTML to show after input field
|
||||
* @param string|null $id The ID to add to the row
|
||||
*/
|
||||
public function __construct( string $name, mixed $defaultValue=null, bool $required=FALSE, array $options=array(), callable $customValidationCode=null, string $prefix=null, string $suffix=null, string $id=null )
|
||||
public function __construct( string $name, mixed $defaultValue=null, ?bool $required=FALSE, array $options=array(), callable $customValidationCode=null, string $prefix=null, string $suffix=null, string $id=null )
|
||||
{
|
||||
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_spotify');
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class Youtube extends Text
|
||||
* @param string|null $suffix HTML to show after input field
|
||||
* @param string|null $id The ID to add to the row
|
||||
*/
|
||||
public function __construct( string $name, mixed $defaultValue=NULL, bool $required=FALSE, array $options=array(), callable $customValidationCode=NULL, string $prefix=NULL, string $suffix=NULL, string $id=NULL )
|
||||
public function __construct( string $name, mixed $defaultValue=NULL, ?bool $required=FALSE, array $options=array(), callable $customValidationCode=NULL, string $prefix=NULL, string $suffix=NULL, string $id=NULL )
|
||||
{
|
||||
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_youtube');
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class Page extends Model implements Permissions
|
||||
* @brief [ActiveRecord] Caches
|
||||
* @note Defined cache keys will be cleared automatically as needed
|
||||
*/
|
||||
protected array $caches = array( 'frontNavigation' );
|
||||
protected array $caches = array( 'frontNavigation', 'pageDefaults' );
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
@@ -348,11 +348,11 @@ class Page extends Model implements Permissions
|
||||
/* @var $class Model */
|
||||
$folder = $class::load( $path, 'folder_path' );
|
||||
|
||||
static::$loadedPagesFromPath[ $path ] = static::getDefaultPage( $folder->id );
|
||||
static::$loadedPagesFromPath[ $path ] = static::getDefaultForMember( $folder->id );
|
||||
}
|
||||
else
|
||||
{
|
||||
static::$loadedPagesFromPath[ $path ] = static::getDefaultPage();
|
||||
static::$loadedPagesFromPath[ $path ] = static::getDefaultForMember();
|
||||
}
|
||||
}
|
||||
catch ( OutOfRangeException $e )
|
||||
@@ -474,6 +474,13 @@ class Page extends Model implements Permissions
|
||||
/* Still here? Consistent with AR pattern */
|
||||
throw new OutOfRangeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constants for default page settings
|
||||
*/
|
||||
const PAGE_NODEFAULT = 0;
|
||||
const PAGE_DEFAULT = 1;
|
||||
const PAGE_DEFAULT_OVERRIDE = 2;
|
||||
|
||||
/**
|
||||
* Return the default page for this folder
|
||||
@@ -488,7 +495,7 @@ class Page extends Model implements Permissions
|
||||
/* Try the easiest method first */
|
||||
try
|
||||
{
|
||||
static::$defaultPage[ $folderId ] = Page::load( Db::i()->select( 'page_id', static::$databaseTable, array( 'page_default=? AND page_folder_id=?', 1, $folderId ) )->first() );
|
||||
static::$defaultPage[ $folderId ] = Page::load( Db::i()->select( 'page_id', static::$databaseTable, array( 'page_default=? AND page_folder_id=?', static::PAGE_DEFAULT, $folderId ) )->first() );
|
||||
}
|
||||
catch( Exception $ex )
|
||||
{
|
||||
@@ -510,6 +517,159 @@ class Page extends Model implements Permissions
|
||||
|
||||
return ( isset( static::$defaultPage[ $folderId ] ) ) ? static::$defaultPage[ $folderId ] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of default pages for each group
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function loadDefaultsPerGroup() : array
|
||||
{
|
||||
try
|
||||
{
|
||||
return Store::i()->pageDefaults;
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
|
||||
$folderRoots = iterator_to_array(
|
||||
Db::i()->select( 'folder_id', 'cms_folders' )
|
||||
);
|
||||
array_unshift( $folderRoots, 0 );
|
||||
|
||||
$defaults = [];
|
||||
|
||||
foreach( $folderRoots as $folderId )
|
||||
{
|
||||
$defaults[ $folderId ] = [];
|
||||
foreach( Db::i()->select( 'page_id,page_group_defaults', static::$databaseTable, [ 'page_default=? and page_folder_id=?', static::PAGE_DEFAULT_OVERRIDE, $folderId ] ) as $row )
|
||||
{
|
||||
foreach( explode( ",", $row['page_group_defaults'] ) as $groupId )
|
||||
{
|
||||
$defaults[ $folderId ][ $groupId ] = $row['page_id'];
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$primaryDefault = static::getDefaultPage( $folderId );
|
||||
foreach( Group::groups() as $group )
|
||||
{
|
||||
if( !isset( $defaults[ $folderId ][ $group->g_id ] ) )
|
||||
{
|
||||
$defaults[ $folderId ][ $group->g_id ] = $primaryDefault->_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
|
||||
Store::i()->pageDefaults = $defaults;
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the default application for this member
|
||||
*
|
||||
* @param int $folderId
|
||||
* @param Member|null $member
|
||||
* @return static
|
||||
*/
|
||||
public static function getDefaultForMember( int $folderId=0, ?Member $member=null ) : static
|
||||
{
|
||||
/* We're only going to use the primary group here */
|
||||
$member = $member ?? Member::loggedIn();
|
||||
|
||||
$pageId = static::loadDefaultsPerGroup()[ $folderId ][ $member->member_group_id ] ?? null;
|
||||
if( $pageId )
|
||||
{
|
||||
try
|
||||
{
|
||||
return static::load( $pageId );
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
|
||||
return static::getDefaultPage( $folderId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the page as a default for specific groups
|
||||
*
|
||||
* @param array $groups
|
||||
* @return void
|
||||
*/
|
||||
public function setAsDefaultForGroups( array $groups ) : void
|
||||
{
|
||||
$currentDefaults = static::loadDefaultsPerGroup();
|
||||
$newGroups = [];
|
||||
$pagesToUpdate = [];
|
||||
foreach( $groups as $group )
|
||||
{
|
||||
$groupId = ( $group instanceof Group ) ? $group->g_id : $group;
|
||||
$currentPage = $currentDefaults[ $this->folder_id ][ $groupId ] ?? null;
|
||||
if( $currentPage !== null and $currentPage != $this->id )
|
||||
{
|
||||
$pagesToUpdate[ $currentPage ] = [];
|
||||
foreach( $currentDefaults[ $this->folder_id ] as $k => $v )
|
||||
{
|
||||
if( $v == $currentPage and $k != $groupId )
|
||||
{
|
||||
$pagesToUpdate[ $currentPage ][] = $k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$newGroups[] = $groupId;
|
||||
}
|
||||
|
||||
/* Did we make this the default for all the groups? */
|
||||
if( count( $newGroups ) == count( Group::groups() ) )
|
||||
{
|
||||
$this->setAsDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
if( empty( $newGroups ) )
|
||||
{
|
||||
$this->default = static::PAGE_NODEFAULT;
|
||||
$this->group_defaults = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->default = static::PAGE_DEFAULT_OVERRIDE;
|
||||
$this->group_defaults = implode( ",", $newGroups );
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/* Now update any pages that need to be changed */
|
||||
foreach( $pagesToUpdate as $pageId => $_values )
|
||||
{
|
||||
try
|
||||
{
|
||||
$page = static::load( $pageId );
|
||||
|
||||
/* If it's set as default 1, we can leave that alone */
|
||||
if( $page->default == static::PAGE_DEFAULT_OVERRIDE )
|
||||
{
|
||||
$groupsToUse = array_diff( $_values, $newGroups );
|
||||
if( empty( $groupsToUse ) )
|
||||
{
|
||||
$page->default = static::PAGE_NODEFAULT;
|
||||
$page->group_defaults = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$page->group_defaults = implode( ",", $groupsToUse );
|
||||
}
|
||||
|
||||
$page->save();
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete compiled versions
|
||||
@@ -756,7 +916,7 @@ class Page extends Model implements Permissions
|
||||
|
||||
if ( $pageType == 'html' )
|
||||
{
|
||||
$return['tab_content'] = array( 'content_page_form_tab__content', NULL, NULL, 'ipsForm_vertical' );
|
||||
$return['tab_content'] = array( 'content_page_form_tab__content', NULL, NULL, 'ipsForm--vertical' );
|
||||
|
||||
$tagSource = Url::internal( "app=cms&module=pages&controller=ajax&do=loadTags" );
|
||||
if ( $item )
|
||||
@@ -809,7 +969,6 @@ class Page extends Model implements Permissions
|
||||
{
|
||||
$page = new self;
|
||||
$page->type = $pageType;
|
||||
$page->save();
|
||||
|
||||
$page->saveForm( $page->formatFormValues( $values ) );
|
||||
|
||||
@@ -1241,6 +1400,17 @@ class Page extends Model implements Permissions
|
||||
public function getAreasFromDatabase( ?string $area=null ) : array
|
||||
{
|
||||
$areas = array();
|
||||
|
||||
if( $this->_version !== null )
|
||||
{
|
||||
foreach( $this->_version->data['areas'] as $id => $tree )
|
||||
{
|
||||
$areas[ $id ] = new Area( $tree, $id );
|
||||
}
|
||||
|
||||
return $areas;
|
||||
}
|
||||
|
||||
$where = [ array( 'area_page_id=?', $this->id ) ];
|
||||
|
||||
if ( $area !== null )
|
||||
@@ -1267,10 +1437,23 @@ class Page extends Model implements Permissions
|
||||
* Save an area to the database and link it to the page
|
||||
*
|
||||
* @param Area $area
|
||||
* @param bool $backup Should we store a copy?
|
||||
* @return void
|
||||
*/
|
||||
public function saveArea( Area $area ) : void
|
||||
public function saveArea( Area $area, bool $backup=true ) : void
|
||||
{
|
||||
/* If we are viewing an old version, do nothing! These should be read-only */
|
||||
if( $this->_version !== null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a backup before we do anything here */
|
||||
if( $backup )
|
||||
{
|
||||
Revision::store( $this );
|
||||
}
|
||||
|
||||
/* If we have no content, clear this out entirely */
|
||||
if( !$area->hasWidgets() )
|
||||
{
|
||||
@@ -1429,22 +1612,33 @@ class Page extends Model implements Permissions
|
||||
$return['edit'] = $buttons['edit'];
|
||||
}
|
||||
|
||||
if ( Member::loggedIn()->hasAcpRestriction( 'cms', 'pages', 'page_edit' ) and !$this->default )
|
||||
if ( Member::loggedIn()->hasAcpRestriction( 'cms', 'pages', 'page_edit' ) )
|
||||
{
|
||||
$return['default'] = array(
|
||||
'icon' => $this->default ? 'star' : 'regular fa-star',
|
||||
'title' => 'content_default_page',
|
||||
'link' => $url->setQueryString( array( 'id' => $this->id, 'subnode' => 1, 'do' => 'setAsDefault' ) )->csrf()
|
||||
);
|
||||
}
|
||||
$return['default'] = array(
|
||||
'icon' => ( $this->default == static::PAGE_DEFAULT ) ? 'star' : 'regular fa-star',
|
||||
'title' => 'content_default_page',
|
||||
'link' => $url->setQueryString( array( 'id' => $this->id, 'subnode' => 1, 'do' => 'setAsDefault' ) ),
|
||||
'data' => [ 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack( 'content_default_page' ) ]
|
||||
);
|
||||
|
||||
if ( Member::loggedIn()->hasAcpRestriction( 'cms', 'pages', 'page_edit' ) and $this->type !== 'builder' )
|
||||
{
|
||||
$return['default_error'] = array(
|
||||
'icon' => Settings::i()->cms_error_page == $this->id ? 'exclamation-circle' : 'exclamation',
|
||||
'title' => Settings::i()->cms_error_page == $this->id ? 'content_remove_error_page' : 'content_default_error_page',
|
||||
'link' => $url->setQueryString( array( 'id' => Settings::i()->cms_error_page ? 0 : $this->id, 'subnode' => 1, 'do' => 'toggleDefaultError' ) )->csrf()
|
||||
);
|
||||
if( $this->type !== 'builder' )
|
||||
{
|
||||
$return['default_error'] = array(
|
||||
'icon' => Settings::i()->cms_error_page == $this->id ? 'exclamation-circle' : 'exclamation',
|
||||
'title' => Settings::i()->cms_error_page == $this->id ? 'content_remove_error_page' : 'content_default_error_page',
|
||||
'link' => $url->setQueryString( array( 'id' => Settings::i()->cms_error_page ? 0 : $this->id, 'subnode' => 1, 'do' => 'toggleDefaultError' ) )->csrf()
|
||||
);
|
||||
}
|
||||
|
||||
/* Do we have at least one stored version? */
|
||||
if( $previous = Revision::previousVersion( $this ) )
|
||||
{
|
||||
$return['history'] = array(
|
||||
'icon' => 'history',
|
||||
'title' => 'content_page_history',
|
||||
'link' => $url->setQueryString( array( 'id' => $this->id, 'subnode' => 1, 'do' => 'history' ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$return['view'] = array(
|
||||
@@ -1587,6 +1781,12 @@ class Page extends Model implements Permissions
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/* If this is not a new page, store the current page in the history before we make any changes */
|
||||
if( !$isNew )
|
||||
{
|
||||
Revision::store( $this );
|
||||
}
|
||||
|
||||
if( isset( $values['page_name'] ) )
|
||||
{
|
||||
$_copied = $values['page_name'];
|
||||
@@ -1810,7 +2010,7 @@ class Page extends Model implements Permissions
|
||||
{
|
||||
if( $this->_url === NULL )
|
||||
{
|
||||
if ( Application::load('cms')->default AND $this->default AND ! $this->folder_id )
|
||||
if ( Application::load('cms')->default AND $this->default == static::PAGE_DEFAULT AND ! $this->folder_id )
|
||||
{
|
||||
/* No - that's easy */
|
||||
$this->_url = Url::internal( '', 'front' );
|
||||
@@ -1841,6 +2041,33 @@ class Page extends Model implements Permissions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Revision|null
|
||||
*/
|
||||
protected ?Revision $_version = null;
|
||||
|
||||
/**
|
||||
* View as a specific version
|
||||
*
|
||||
* @param int|null $version
|
||||
* @return void
|
||||
*/
|
||||
public function setVersion( ?int $version=null ) : void
|
||||
{
|
||||
/* Clear it first */
|
||||
$this->_version = null;
|
||||
|
||||
try
|
||||
{
|
||||
$this->_version = Revision::load( $version );
|
||||
foreach( $this->_version->data['settings'] as $k => $v )
|
||||
{
|
||||
$this->_data[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map this database to a specific page
|
||||
*
|
||||
@@ -1951,6 +2178,9 @@ class Page extends Model implements Permissions
|
||||
|
||||
/* Remove any widgets for this page */
|
||||
Db::i()->delete( 'cms_page_widget_areas', array( 'area_page_id=?', $this->id ) );
|
||||
|
||||
/* Clear history */
|
||||
Db::i()->delete( 'cms_page_revisions', array( 'revision_page_id=?', $this->id ) );
|
||||
|
||||
parent::delete();
|
||||
|
||||
@@ -2020,10 +2250,22 @@ class Page extends Model implements Permissions
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
/* If we are viewing an older version, don't allow any changes */
|
||||
if( $this->_version !== null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->id )
|
||||
{
|
||||
static::deleteCompiled( $this->id );
|
||||
}
|
||||
|
||||
/* If this is not new and the folder changed, check defaults */
|
||||
if( !$this->_new and isset( $this->changed['folder_id'] ) )
|
||||
{
|
||||
$this->checkDefaultPage();
|
||||
}
|
||||
|
||||
parent::save();
|
||||
|
||||
@@ -2047,10 +2289,39 @@ class Page extends Model implements Permissions
|
||||
*/
|
||||
public function setAsDefault() : void
|
||||
{
|
||||
Db::i()->update( 'cms_pages', array( 'page_default' => 0 ), array( 'page_folder_id=?', $this->folder_id ) );
|
||||
Db::i()->update( 'cms_pages', array( 'page_default' => 1 ), array( 'page_id=?', $this->id ) );
|
||||
Db::i()->update( 'cms_pages', array( 'page_default' => static::PAGE_NODEFAULT, 'page_group_defaults' => null ), array( 'page_folder_id=? and page_default=?', $this->folder_id, static::PAGE_DEFAULT ) );
|
||||
Db::i()->update( 'cms_pages', array( 'page_default' => static::PAGE_DEFAULT, 'page_group_defaults' => null ), array( 'page_id=?', $this->id ) );
|
||||
|
||||
static::buildPageUrlStore();
|
||||
|
||||
unset( Store::i()->pageDefaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that we only have one default page per folder.
|
||||
* This is called only when the folder is changed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkDefaultPage() : void
|
||||
{
|
||||
if( $this->default == static::PAGE_DEFAULT )
|
||||
{
|
||||
try
|
||||
{
|
||||
$currentDefault = Db::i()->select( 'page_id', 'cms_pages', [ 'page_default=? and page_folder_id=? and page_id <> ?', static::PAGE_DEFAULT, $this->folder_id, (int) $this->_id ] )->first();
|
||||
|
||||
/* If we found a different default page, remove the default from this one */
|
||||
$this->default = static::PAGE_NODEFAULT;
|
||||
}
|
||||
catch( UnderflowException ){}
|
||||
}
|
||||
elseif( $this->default == static::PAGE_DEFAULT_OVERRIDE )
|
||||
{
|
||||
/* Group overrides are always removed when we move a page to another folder */
|
||||
$this->default = static::PAGE_NODEFAULT;
|
||||
$this->group_defaults = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2121,7 +2392,7 @@ class Page extends Model implements Permissions
|
||||
/* Make sure we initialize the main area */
|
||||
Output::i()->sidebar['widgets']['col1'] = [];
|
||||
|
||||
Output::i()->output = Theme::i()->getTemplate( 'pages' )->globalWrap( $nav, $this->getPageContent(), $this );
|
||||
Output::i()->output = Theme::i()->getTemplate( 'pages', 'cms' )->globalWrap( $nav, $this->getPageContent(), $this );
|
||||
|
||||
if ( isset( Settings::i()->cms_error_page ) and Settings::i()->cms_error_page and Settings::i()->cms_error_page == $this->id )
|
||||
{
|
||||
@@ -2138,7 +2409,7 @@ class Page extends Model implements Permissions
|
||||
}
|
||||
catch ( OutOfRangeException | OutOfBoundsException )
|
||||
{
|
||||
if ( !( Application::load( 'cms' )->default and !$this->folder_id and $this->default ) )
|
||||
if ( !( Application::load( 'cms' )->default and !$this->folder_id and $this->default == static::PAGE_DEFAULT ) )
|
||||
{
|
||||
Output::i()->breadcrumb['module'] = array( $this->url(), $this->_title );
|
||||
}
|
||||
@@ -2156,10 +2427,6 @@ class Page extends Model implements Permissions
|
||||
/* Send straight to the output engine */
|
||||
Output::i()->sendOutput( Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( Output::i()->title, Output::i()->output, array( 'app' => Dispatcher::i()->application->directory, 'module' => Dispatcher::i()->module->key, 'controller' => Dispatcher::i()->controller ) ), ( $httpStatusCode ?: 200 ), 'text/html', ( $httpHeaders ?: array() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Output::i()->allowDefaultWidgets = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2200,9 +2467,15 @@ class Page extends Model implements Permissions
|
||||
*/
|
||||
protected function getPageContent() : string
|
||||
{
|
||||
$content = '';
|
||||
if( $this->_version !== null )
|
||||
{
|
||||
Output::i()->customHeader .= Theme::i()->getTemplate( 'pages', 'cms' )->revision( $this->_version );
|
||||
}
|
||||
|
||||
if( $this->type == 'html' )
|
||||
{
|
||||
return $this->getHtmlContent();
|
||||
return $content . $this->getHtmlContent();
|
||||
}
|
||||
|
||||
$widgets = [];
|
||||
@@ -2216,6 +2489,15 @@ class Page extends Model implements Permissions
|
||||
}
|
||||
|
||||
$tree = $item['area_tree'] ? json_decode( $item['area_tree'], true ) : [];
|
||||
if( $this->_version !== null )
|
||||
{
|
||||
if ( isset( $this->_version->data['areas'][ $item['area_area'] ] ) )
|
||||
{
|
||||
/* We have a previous version we want to show */
|
||||
$tree = $this->_version->data['areas'][ $item['area_area'] ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $tree ) )
|
||||
{
|
||||
/* If the tree is empty, generate one from the database rows */
|
||||
@@ -2242,7 +2524,7 @@ class Page extends Model implements Permissions
|
||||
$widgets['col1'] = new Area( [], 'col1' );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'pages', 'cms' )->mainArea( $this, $widgets );
|
||||
return $content . Theme::i()->getTemplate( 'pages', 'cms' )->mainArea( $this, $widgets );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @brief Revision
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage
|
||||
* @since 6/25/2025
|
||||
*/
|
||||
|
||||
namespace IPS\cms\Pages;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Widget\Area;
|
||||
use UnderflowException;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
class Revision extends ActiveRecord
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
public static ?string $databaseTable = 'cms_page_revisions';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static string $databasePrefix = 'revision_';
|
||||
|
||||
/**
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static array $multitons;
|
||||
|
||||
/**
|
||||
* @return Page
|
||||
*/
|
||||
public function get_page() : Page
|
||||
{
|
||||
return Page::load( $this->page_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_data() : array
|
||||
{
|
||||
return isset( $this->_data['data'] ) ? json_decode( $this->_data['data'], true ) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $val
|
||||
* @return void
|
||||
*/
|
||||
public function set_data( mixed $val ) : void
|
||||
{
|
||||
$this->_data['data'] = ( is_array( $val ) and count( $val ) ) ? json_encode( $val ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Member
|
||||
*/
|
||||
public function author() : Member
|
||||
{
|
||||
return Member::load( $this->member_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a version of the page history
|
||||
*
|
||||
* @param Page $page
|
||||
* @param bool $manualSave Whether this is being called manually (e.g. from finish editing)
|
||||
* @return void
|
||||
*/
|
||||
public static function store( Page $page, bool $manualSave=false ) : void
|
||||
{
|
||||
$data = [
|
||||
'settings' => [
|
||||
'name' => Member::loggedIn()->language()->get( Page::$titleLangPrefix . $page->id ),
|
||||
'js_css_ids' => $page->js_css_ids,
|
||||
'wrapper_template' => $page->wrapper_template,
|
||||
'theme' => $page->theme,
|
||||
'template' => $page->template,
|
||||
'ipb_wrapper' => $page->ipb_wrapper,
|
||||
'default' => $page->default
|
||||
],
|
||||
'areas' => []
|
||||
];
|
||||
|
||||
if( $page->type == 'html' )
|
||||
{
|
||||
$data['settings']['content'] = $page->content;
|
||||
}
|
||||
|
||||
/* Now handle all the areas */
|
||||
foreach( $page->getAreasFromDatabase() as $area )
|
||||
{
|
||||
$data['areas'][ $area->id ] = $area->toArray();
|
||||
}
|
||||
|
||||
/* Get the latest revision; if nothing has changed, don't store it */
|
||||
if( $previous = static::previousVersion( $page ) )
|
||||
{
|
||||
if( ! $manualSave and ( $previous->data === $data ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$obj = new static;
|
||||
$obj->page_id = $page->id;
|
||||
$obj->data = $data;
|
||||
$obj->manual_save = (int) $manualSave;
|
||||
$obj->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this the latest version.
|
||||
* If no version is specified, just use the latest
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function revert() : void
|
||||
{
|
||||
/* @var Page $page */
|
||||
$page = $this->page;
|
||||
|
||||
/* First, store the current page as is */
|
||||
static::store( $page );
|
||||
|
||||
/* Now loop through the page configuration and set all properties */
|
||||
foreach( $this->data['settings'] as $k => $v )
|
||||
{
|
||||
$page->$k = $v;
|
||||
if( $k == 'name' )
|
||||
{
|
||||
Lang::saveCustom( 'cms', Page::$titleLangPrefix . $page->id, $v );
|
||||
}
|
||||
}
|
||||
$page->save();
|
||||
|
||||
/* Load the current areas, in case we need to remove one */
|
||||
$currentAreas = $page->getAreasFromDatabase();
|
||||
|
||||
/* Now the areas */
|
||||
$newAreas = [];
|
||||
foreach( $this->data['areas'] as $id => $tree )
|
||||
{
|
||||
$newAreas[] = $id;
|
||||
$area = new Area( $tree, $id );
|
||||
$page->saveArea( $area, false );
|
||||
}
|
||||
|
||||
/* Remove any areas that we no longer need */
|
||||
foreach( $currentAreas as $area )
|
||||
{
|
||||
if( !in_array( $area->id, $newAreas ) )
|
||||
{
|
||||
Db::i()->delete( 'cms_page_widget_areas', [ 'area_area=? and area_page_id=?', $area->id, $page->id ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previously saved version
|
||||
*
|
||||
* @param Page $page
|
||||
* @return static|null
|
||||
*/
|
||||
public static function previousVersion( Page $page ) : static|null
|
||||
{
|
||||
try
|
||||
{
|
||||
$row = Db::i()->select( '*', static::$databaseTable, [ 'revision_page_id=?', $page->id ], 'revision_date desc', [ 0, 1 ] )->first();
|
||||
return static::constructFromData( $row );
|
||||
}
|
||||
catch( UnderflowException ){}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save() : void
|
||||
{
|
||||
if( $this->_new )
|
||||
{
|
||||
$this->member_id = Member::loggedIn()->member_id;
|
||||
$this->date = time();
|
||||
}
|
||||
|
||||
parent::save();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ use IPS\Content\Filter;
|
||||
use IPS\Content\Hideable;
|
||||
use IPS\Content\Featurable;
|
||||
use IPS\Content\Reactable;
|
||||
use IPS\Content\Recognizable;
|
||||
use IPS\Content\Reportable;
|
||||
use IPS\Content\Shareable;
|
||||
use IPS\Db;
|
||||
@@ -51,7 +52,8 @@ class Comment extends ContentComment implements Embeddable,
|
||||
Shareable,
|
||||
EditHistory,
|
||||
Hideable,
|
||||
Featurable;
|
||||
Featurable,
|
||||
Recognizable;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
|
||||
@@ -46,6 +46,7 @@ use IPS\Content\Shareable;
|
||||
use IPS\Content\Taggable;
|
||||
use IPS\Content\Statistics;
|
||||
use IPS\Content\ViewUpdates;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Events\Event;
|
||||
@@ -287,20 +288,22 @@ class Records extends Item implements
|
||||
* Most, if not all of these are the same for different events, so we can just have one method
|
||||
*
|
||||
* @param Comment|null $comment A comment item, leave null for these keys to be omitted
|
||||
* @param array $createOrEditValues =[] Values from the create or edit form, if applicable.
|
||||
* @param bool $clearCache=false
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDataLayerProperties( ?Comment $comment = null ): array
|
||||
public function getDataLayerProperties( ?Comment $comment = null, array $createOrEditValues = [], bool $clearCache = false ): array
|
||||
{
|
||||
$commentIdColumn = $comment ? $comment::$databaseColumnId : null;
|
||||
$index = $commentIdColumn ? ( $comment->$commentIdColumn ?: 0 ) : 0;
|
||||
if ( isset( $this->_dataLayerProperties[$index] ) )
|
||||
$index = "idx_" . ( $commentIdColumn ? ( $comment->$commentIdColumn ?: 0 ) : -1 );
|
||||
if ( !$clearCache and isset( $this->_dataLayerProperties[$index] ) )
|
||||
{
|
||||
return $this->_dataLayerProperties[$index];
|
||||
}
|
||||
|
||||
/* Set the content_type and comment_type to lower case for consistency */
|
||||
$properties = parent::getDataLayerProperties( $comment );
|
||||
$properties = parent::getDataLayerProperties( $comment, $createOrEditValues, $clearCache );
|
||||
if ( isset( $properties['content_type'] ) )
|
||||
{
|
||||
$properties['content_type'] = strtolower( Lang::load( Lang::defaultLanguage() )->addToStack( static::$title ) );
|
||||
@@ -1520,9 +1523,18 @@ class Records extends Item implements
|
||||
{
|
||||
if ( ! isset( $this->customDisplayFields['all'][ $type ] ) )
|
||||
{
|
||||
$this->customDisplayFields['all'][ $type ] = [];
|
||||
$fieldsClass = '\IPS\cms\Fields' . static::$customDatabaseId;
|
||||
/* @var $fieldsClass Fields */
|
||||
$this->customDisplayFields['all'][ $type ] = $fieldsClass::display( $this->fieldValues(), $type, $this->container(), 'key', $this );
|
||||
|
||||
/* @var $fieldsClass Fields */
|
||||
foreach( $fieldsClass::display( $this->fieldValues(), $type, $this->container(), 'key', $this ) as $k => $fieldValue )
|
||||
{
|
||||
/* We only show these if there is a value anyway */
|
||||
if( $fieldValue )
|
||||
{
|
||||
$this->customDisplayFields['all'][ $type ][ $k ] = $fieldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->customDisplayFields['all'][ $type ];
|
||||
@@ -1924,7 +1936,7 @@ class Records extends Item implements
|
||||
*/
|
||||
public function get__record_image_thumb() : string
|
||||
{
|
||||
return $this->record_image_thumb ?: $this->record_image;
|
||||
return $this->record_image_thumb ?: ( $this->record_image ?? '' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3027,19 +3039,7 @@ class Records extends Item implements
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Am I a Moderator with edit permissions ?*/
|
||||
if ( static::modPermission( 'edit', $member, $this->containerWrapper() ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ( parent::canEdit( $member ) )
|
||||
{
|
||||
/* Test against specific perms for this category */
|
||||
return $this->container()->can( 'edit', $member );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
return parent::canEdit( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3814,7 +3814,7 @@ class Records extends Item implements
|
||||
*/
|
||||
public function isTopicSyncEnabled() : bool
|
||||
{
|
||||
return $this->_forum_record and $this->_forum_comments and Application::appIsEnabled('forums');
|
||||
return $this->_forum_record and Application::appIsEnabled('forums');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,15 +70,15 @@ class Widget extends SystemWidget
|
||||
$uniqueIds = array();
|
||||
foreach( Db::i()->select( '*', 'cms_page_widget_areas' ) as $item )
|
||||
{
|
||||
if( $item['area_widgets'] )
|
||||
if( !empty( $item['area_tree'] ) )
|
||||
{
|
||||
$area = new Area( json_decode( $item['area_tree'], true ), $item['area_area'] );
|
||||
$widgets = $area->getAllWidgets();
|
||||
}
|
||||
elseif( $item['area_widgets'] )
|
||||
{
|
||||
$widgets = json_decode( $item['area_widgets'], TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
$area = new Area( json_decode( $item['area_tree'], true ), $item['area_area'] );
|
||||
$widgets = $area->getAllWidgets();
|
||||
}
|
||||
|
||||
if( !is_array( $widgets ) or !count( $widgets ) )
|
||||
{
|
||||
@@ -99,15 +99,15 @@ class Widget extends SystemWidget
|
||||
|
||||
foreach( Db::i()->select( '*', 'core_widget_areas' ) as $item )
|
||||
{
|
||||
if( $item['widgets'] )
|
||||
if( !empty( $item['tree'] ) )
|
||||
{
|
||||
$area = new Area( json_decode( $item['tree'], true ), $item['area'] );
|
||||
$widgets = $area->getAllWidgets();
|
||||
}
|
||||
elseif( $item['widgets'] )
|
||||
{
|
||||
$widgets = json_decode( $item['widgets'], TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
$area = new Area( $item['area'], json_decode( $item['tree'], true ) );
|
||||
$widgets = $area->getAllWidgets();
|
||||
}
|
||||
|
||||
if( !is_array( $widgets ) or !count( $widgets ) )
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user