Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

File diff suppressed because it is too large. Load diff
+46 -35
View File
@@ -12,90 +12,101 @@
namespace IPS\cms\Blocks;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Helpers\Form;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Model;
use IPS\Request;
use function defined;
use function get_called_class;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Block Container Model
*/
class _Container extends \IPS\Node\Model
class Container extends Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_containers';
public static ?string $databaseTable = 'cms_containers';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'container_';
public static string $databasePrefix = 'container_';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('container_key');
protected static array $databaseIdFields = array('container_key');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnParent = 'parent_id';
public static ?string $databaseColumnParent = 'parent_id';
/**
* @brief [Node] Parent ID Root Value
* @note This normally doesn't need changing though some legacy areas use -1 to indicate a root node
*/
public static $databaseColumnParentRootValue = 0;
public static int $databaseColumnParentRootValue = 0;
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'order';
public static ?string $databaseColumnOrder = 'order';
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = '';
public static string $nodeTitle = '';
/**
* @brief [Node] Subnode class
*/
public static $subnodeClass = 'IPS\cms\Blocks\Block';
public static ?string $subnodeClass = 'IPS\cms\Blocks\Block';
/**
* @brief [Node] Show forms modally?
*/
public static $modalForms = TRUE;
public static bool $modalForms = TRUE;
/**
* @brief [Node] Sortable?
*/
public static $nodeSortable = TRUE;
public static bool $nodeSortable = TRUE;
/**
* [Node] Get Title
*
* @return string|null
* @return string
*/
protected function get__title()
protected function get__title(): string
{
return $this->name;
}
@@ -104,12 +115,12 @@ class _Container extends \IPS\Node\Model
* Fetch All Root Nodes
*
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
* @param Member|NULL $member The member to check permissions for or NULL for the currently logged in member
* @param mixed $where Additional WHERE clause
* @param array|NULL $limit Limit/offset to use, or NULL for no limit (default)
* @return array
*/
public static function roots( $permissionCheck='view', $member=NULL, $where=array(), $limit=NULL )
public static function roots( ?string $permissionCheck='view', Member $member=NULL, mixed $where=array(), array $limit=NULL ): array
{
return parent::roots( $permissionCheck, $member, array( array( 'container_type=?', 'block' ) ), $limit );
}
@@ -117,11 +128,11 @@ class _Container extends \IPS\Node\Model
/**
* [Node] Get buttons to display in tree
*
* @param string $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
* @param Url $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
public function getButtons( Url $url, bool $subnode=FALSE ): array
{
$buttons = parent::getButtons( $url, $subnode );
$return = array();
@@ -135,7 +146,7 @@ class _Container extends \IPS\Node\Model
{
$buttons['add']['icon'] = 'folder-open';
$buttons['add']['title'] = 'content_block_cat_add';
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('content_block_cat_add') );
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('content_block_cat_add') );
$buttons['add']['link'] = $url->setQueryString( array( 'subnode' => 0, 'do' => 'form', 'parent' => $this->_id ) );
$buttons['add_block'] = array(
@@ -172,9 +183,9 @@ class _Container extends \IPS\Node\Model
/**
* [Node] Does the currently logged in user have permission to delete this node?
*
* @return bool
* @return bool
*/
public function canDelete()
public function canDelete(): bool
{
if ( $this->key == 'block_custom' OR $this->key == 'block_plugins' )
{
@@ -187,17 +198,17 @@ class _Container extends \IPS\Node\Model
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
/* Build form */
$form->add( new \IPS\Helpers\Form\Text( 'container_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ) ) );
$form->add( new Text( 'container_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ) ) );
$class = \get_called_class();
$class = get_called_class();
$form->add( new \IPS\Helpers\Form\Node( 'container_parent_id', $this->parent_id ? $this->parent_id : 0, FALSE, array(
$form->add( new Node( 'container_parent_id', $this->parent_id ? $this->parent_id : 0, FALSE, array(
'class' => '\IPS\cms\Blocks\Container',
'zeroVal' => 'node_no_parent',
'permissionCheck' => function( $node ) use ( $class )
@@ -207,7 +218,7 @@ class _Container extends \IPS\Node\Model
return FALSE;
}
return !isset( \IPS\Request::i()->id ) or ( $node->id != \IPS\Request::i()->id and !$node->isChildOf( $node::load( \IPS\Request::i()->id ) ) );
return !isset( Request::i()->id ) or ( $node->id != Request::i()->id and !$node->isChildOf( $node::load( Request::i()->id ) ) );
}
) ) );
}
@@ -218,7 +229,7 @@ class _Container extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if ( isset( $values['container_parent_id'] ) AND ( ! empty( $values['container_parent_id'] ) OR $values['container_parent_id'] === 0 ) )
{
@@ -239,7 +250,7 @@ class _Container extends \IPS\Node\Model
* @param mixed $where Where clause
* @return array
*/
public static function search( $column, $query, $order, $where=array() )
public static function search( string $column, string $query, ?string $order=NULL, mixed $where=array() ): array
{
if ( $column === '_title' )
{
File diff suppressed because it is too large. Load diff
@@ -12,35 +12,41 @@
namespace IPS\cms\Databases;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Dispatcher\Controller as DispatcherController;
use IPS\Http\Url;
use function defined;
use function get_called_class;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Abstract class that Controllers should extend
*/
abstract class _Controller extends \IPS\Dispatcher\Controller
abstract class Controller extends DispatcherController
{
/**
* @brief Base URL
*/
public $url;
public mixed $url;
/**
* Constructor
*
* @param \IPS\Http\Url|NULL $url The base URL for this controller or NULL to calculate automatically
* @param mixed|null $url The base URL for this controller or NULL to calculate automatically
* @return void
*/
public function __construct( $url=NULL )
public function __construct( mixed $url=NULL )
{
if ( $url === NULL )
{
$class = \get_called_class();
$class = get_called_class();
$exploded = explode( '\\', $class );
$this->url = \IPS\Http\Url::internal( "app=cms&module=database", 'front' ); /* @todo fix URL */
$this->url = Url::internal( "app=cms&module=database", 'front' ); /* @todo fix URL */
}
else
{
File diff suppressed because it is too large. Load diff
+104 -76
View File
@@ -12,75 +12,95 @@
namespace IPS\cms\Databases;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application;
use IPS\cms\Categories;
use IPS\cms\Databases;
use IPS\cms\Pages\Page;
use IPS\cms\Records;
use IPS\core\DataLayer;
use IPS\Dispatcher as SystemDispatcher;
use IPS\Dispatcher\Controller;
use IPS\Http\Url;
use IPS\Output;
use IPS\Request;
use IPS\Settings;
use OutOfRangeException;
use UnderflowException;
use function count;
use function defined;
use function intval;
use function is_numeric;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Database Dispatcher
*/
class _Dispatcher extends \IPS\Dispatcher
class Dispatcher extends SystemDispatcher
{
/**
* @brief Singleton Instance (So we don't re-use the regular dispatcher)
*/
protected static $instance = NULL;
protected static mixed $instance = NULL;
/**
* @brief Controller location
*/
public $controllerLocation = 'front';
public string $controllerLocation = 'front';
/**
* @brief Database Id
*/
public $databaseId = NULL;
public ?int $databaseId = NULL;
/**
* @brief Category Id
*/
public $categoryId = NULL;
public ?int $categoryId = NULL;
/**
* @brief Record Id
*/
public $recordId = NULL;
public ?int $recordId = NULL;
/**
* @brief Url
*/
public $url = NULL;
public mixed $url = NULL;
/**
* @brief Module
*/
public $module = NULL;
public ?string $module = NULL;
/**
* @brief Output to return
*/
public $output = "";
public string $output = "";
/**
* Set Database ID
*
* @param mixed $databaseId Database key or ID
* @return \IPS\Dispatcher
* @return Dispatcher
*/
public function setDatabase( $databaseId )
public function setDatabase( mixed $databaseId ): Dispatcher
{
/* Other areas rely on $this->databaseId being numeric */
if ( !\is_numeric( $databaseId ) )
if ( !is_numeric( $databaseId ) )
{
$database = \IPS\cms\Databases::load( $databaseId, 'database_key' );
$database = Databases::load( $databaseId, 'database_key' );
$databaseId = $database->id;
}
$this->databaseId = $databaseId;
$database = \IPS\cms\Databases::load( $databaseId );
$database = Databases::load( $databaseId );
if ( ! $database->use_categories )
{
$this->categoryId = $database->_default_category;
@@ -93,9 +113,9 @@ class _Dispatcher extends \IPS\Dispatcher
* Set Category ID
*
* @param mixed $categoryId Category ID
* @return \IPS\Dispatcher
* @return Dispatcher
*/
public function setCategory( $categoryId )
public function setCategory( mixed $categoryId ): Dispatcher
{
$this->categoryId = $categoryId;
return $this;
@@ -106,11 +126,11 @@ class _Dispatcher extends \IPS\Dispatcher
*
* @return void
*/
public function init()
public function init() : void
{
if ( ( \IPS\cms\Pages\Page::$currentPage AND ! ( \IPS\Application::load('cms')->default AND ! \IPS\cms\Pages\Page::$currentPage->folder_id AND \IPS\cms\Pages\Page::$currentPage->default ) ) )
if ( ( Page::$currentPage AND ! ( Application::load('cms')->default AND ! Page::$currentPage->folder_id AND Page::$currentPage->default ) ) )
{
\IPS\Output::i()->breadcrumb['module'] = array( \IPS\cms\Pages\Page::$currentPage->url(), \IPS\cms\Pages\Page::$currentPage->_title );
Output::i()->breadcrumb['module'] = array( Page::$currentPage->url(), Page::$currentPage->_title );
}
}
@@ -119,60 +139,62 @@ class _Dispatcher extends \IPS\Dispatcher
*
* @return void
*/
public function run()
public function run() : void
{
/* Coming from a widget? */
if ( isset( \IPS\Request::i()->pageID ) and isset( \IPS\Request::i()->blockID ) )
if ( isset( Request::i()->pageID ) and isset( Request::i()->blockID ) )
{
if ( \IPS\cms\Pages\Page::$currentPage === NULL )
if ( Page::$currentPage === NULL )
{
/* make sure this is a valid widgetized page to stop tampering */
try
{
foreach ( \IPS\Db::i()->select( '*', 'cms_page_widget_areas', array( 'area_page_id=?', \IPS\Request::i()->pageID ) ) as $item )
$page = Page::load( Request::i()->pageID );
foreach( $page->getAreasFromDatabase() as $area )
{
foreach( json_decode( $item['area_widgets'], TRUE ) as $block )
foreach( $area->getAllWidgets() as $block )
{
if ( $block['key'] === 'Database' and isset( $block['configuration']['database'] ) and \intval( $block['configuration']['database'] ) === $this->databaseId )
if ( $block['key'] === 'Database' and isset( $block['configuration']['database'] ) and intval( $block['configuration']['database'] ) === $this->databaseId )
{
\IPS\cms\Pages\Page::$currentPage = \IPS\cms\Pages\Page::load( \IPS\Request::i()->pageID );
Page::$currentPage = $page;
break;
}
}
}
}
catch( \UnderflowException $e ) { }
catch( UnderflowException $e ) { }
}
/* Try again */
if ( \IPS\cms\Pages\Page::$currentPage === NULL )
if ( Page::$currentPage === NULL )
{
\IPS\Output::i()->error( 'page_doesnt_exist', '2T251/1', 404 );
Output::i()->error( 'page_doesnt_exist', '2T251/1', 404 );
}
/* Unset do query param otherwise it confuses the controller->execute(); */
\IPS\Request::i()->do = NULL;
Request::i()->do = NULL;
}
$url = 'app=cms&module=pages&controller=page&path=' . \IPS\cms\Pages\Page::$currentPage->full_path;
$url = 'app=cms&module=pages&controller=page&path=' . Page::$currentPage->full_path;
try
{
$database = \IPS\cms\Databases::load( $this->databaseId );
$database = Databases::load( $this->databaseId );
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
\IPS\Output::i()->error( 'page_doesnt_exist', '2T251/2', 404 );
Output::i()->error( 'page_doesnt_exist', '2T251/2', 404 );
}
$path = trim( preg_replace( '#' . \IPS\cms\Pages\Page::$currentPage->full_path . '#', '', \IPS\Request::i()->path, 1 ), '/' );
$path = trim( preg_replace( '#' . Page::$currentPage->full_path . '#', '', Request::i()->path, 1 ), '/' );
/* If we visited the default page in a folder, the full_path will be like folder/page but the request path will just be folder */
if( \IPS\Request::i()->path . '/' . \IPS\cms\Pages\Page::$currentPage->seo_name == \IPS\cms\Pages\Page::$currentPage->full_path )
if( Request::i()->path . '/' . Page::$currentPage->seo_name == Page::$currentPage->full_path )
{
$path = '';
}
list( $path, $pageNumber ) = \IPS\cms\Pages\Page::getStrippedPagePath( $path );
list( $path, $pageNumber ) = Page::getStrippedPagePath( $path );
$this->databaseId = $database->id;
@@ -182,13 +204,13 @@ class _Dispatcher extends \IPS\Dispatcher
}
/* 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( \IPS\Request::i()->do ) ) ) )
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';
}
else if ( isset( \IPS\Request::i()->c ) AND \is_numeric( \IPS\Request::i()->c ) )
else if ( isset( Request::i()->c ) AND is_numeric( Request::i()->c ) )
{
$this->categoryId = \IPS\Request::i()->c;
$this->categoryId = Request::i()->c;
$this->module = 'category';
}
else if ( empty( $path ) )
@@ -198,11 +220,14 @@ class _Dispatcher extends \IPS\Dispatcher
else
{
$url .= '/' . $path;
/* @var Records $recordClass */
$recordClass = '\IPS\cms\Records' . $database->id;
$isLegacyCategoryUrl = FALSE;
if ( $database->use_categories )
{
/* @var Categories $catClass */
$catClass = '\IPS\cms\Categories' . $database->id;
$category = $catClass::loadFromPath( $path, $database->id );
@@ -231,7 +256,7 @@ class _Dispatcher extends \IPS\Dispatcher
$this->_redirectToCorrectUrl( $record->url() );
}
catch ( \OutOfRangeException $ex )
catch ( OutOfRangeException $ex )
{
/* Check slug history */
try
@@ -240,9 +265,9 @@ class _Dispatcher extends \IPS\Dispatcher
$this->_redirectToCorrectUrl( $record->url() );
}
catch ( \OutOfRangeException $ex )
catch ( OutOfRangeException $ex )
{
\IPS\Output::i()->error( 'page_doesnt_exist', '2T251/4', 404 );
Output::i()->error( 'page_doesnt_exist', '2T251/4', 404 );
}
}
}
@@ -264,9 +289,9 @@ class _Dispatcher extends \IPS\Dispatcher
$record = $recordClass::loadFromSlug( $whatsLeft, TRUE, $this->categoryId );
/* Make the Content controller all kinds of happy */
\IPS\Request::i()->id = $this->recordId = $record->primary_id_field;
Request::i()->id = $this->recordId = $record->primary_id_field;
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
/* Check slug history */
try
@@ -275,15 +300,15 @@ class _Dispatcher extends \IPS\Dispatcher
$this->_redirectToCorrectUrl( $record->url() );
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
/* We are absolutely certain this is not a record, but we have found a legacy category - redirect */
if ( $isLegacyCategoryUrl === TRUE )
{
\IPS\Output::i()->redirect( $category->url() );
Output::i()->redirect( $category->url() );
}
\IPS\Output::i()->error( 'page_doesnt_exist', '2T251/5', 404 );
Output::i()->error( 'page_doesnt_exist', '2T251/5', 404 );
}
}
@@ -302,56 +327,57 @@ class _Dispatcher extends \IPS\Dispatcher
}
}
$this->url = \IPS\Http\Url::internal( $url, 'front', 'content_page_path' );
$this->url = Url::internal( $url, 'front', 'content_page_path' );
$className = '\\IPS\\cms\\modules\\front\\database\\' . $this->module;
/* Init class */
if( !class_exists( $className ) )
{
\IPS\Output::i()->error( 'page_doesnt_exist', '2T251/6', 404 );
Output::i()->error( 'page_doesnt_exist', '2T251/6', 404 );
}
$controller = new $className;
if( !( $controller instanceof \IPS\Dispatcher\Controller ) )
if( !( $controller instanceof Controller ) )
{
\IPS\Output::i()->error( 'page_not_found', '3T251/7', 500, '' );
Output::i()->error( 'page_not_found', '3T251/7', 500, '' );
}
\IPS\Dispatcher::i()->dispatcherController = $controller;
Dispatcher::i()->dispatcherController = $controller;
/* Is this the default application? Default app should search all */
if ( ! \IPS\Application::load('cms')->default )
if ( ! Application::load('cms')->default )
{
/* If database doesn't allow search, default to all */
if ( ! $database->search )
{
\IPS\Output::i()->defaultSearchOption = array( 'all', 'search_everything' );
Output::i()->defaultSearchOption = array( 'all', 'search_everything' );
}
else
{
\IPS\Output::i()->defaultSearchOption = array( "cms_records{$this->databaseId}", "cms_records{$this->databaseId}_pl" );
Output::i()->defaultSearchOption = array( "cms_records{$this->databaseId}", "cms_records{$this->databaseId}_pl" );
}
}
else
{
\IPS\Output::i()->defaultSearchOption = array( 'all', 'search_everything' );
Output::i()->defaultSearchOption = array( 'all', 'search_everything' );
}
/* Add database key to body classes for easier database specific themeing */
\IPS\Output::i()->bodyClasses[] = 'cCmsDatabase_' . $database->key;
Output::i()->bodyClasses[] = 'cCmsDatabase_' . $database->key;
/* Execute */
$controller->execute();
/* Data Layer Context */
if ( $this->databaseId AND \IPS\Settings::i()->core_datalayer_enabled )
if ( $this->databaseId AND Settings::i()->core_datalayer_enabled )
{
try
{
$databaseOrCategory = \IPS\cms\Databases::load( $this->databaseId );
$databaseOrCategory = Databases::load( $this->databaseId );
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return $this->finish();
$this->finish();
return;
}
/* Use the category instead of the database if there is one */
@@ -359,24 +385,25 @@ class _Dispatcher extends \IPS\Dispatcher
{
try
{
/* @var Categories $catClass */
$catClass = '\IPS\cms\Categories' . $this->databaseId;
$databaseOrCategory = $catClass::load( $this->categoryId );
}
catch ( \UnderflowException $e ) {}
catch ( UnderflowException $e ) {}
}
/* Add the database's/category's data layer properties to the page context */
foreach ( $databaseOrCategory->getDataLayerProperties() as $key => $property )
{
\IPS\core\DataLayer::i()->addContextProperty( $key, $property, true );
DataLayer::i()->addContextProperty( $key, $property );
if ( $key === 'content_area' )
{
\IPS\core\DataLayer::i()->addContextProperty( 'community_area', $property, true );
DataLayer::i()->addContextProperty( 'community_area', $property );
}
}
}
return $this->finish();
$this->finish();
}
/**
@@ -386,25 +413,25 @@ class _Dispatcher extends \IPS\Dispatcher
* If the record is moved so the URL is cat-2, the embed needs to redirect while retaining
* the /?do=embed
*
* @param \IPS\Http\Url $correctUrl The URL for the record
* @param Url $correctUrl The URL for the record
* @return void
*/
protected function _redirectToCorrectUrl( $correctUrl )
protected function _redirectToCorrectUrl( Url $correctUrl ) : void
{
$paramsToSet = array();
foreach ( \IPS\Request::i()->url()->queryString as $k => $v )
foreach ( Request::i()->url()->queryString as $k => $v )
{
if ( !array_key_exists( $k, $correctUrl->queryString ) and !array_key_exists( $k, $correctUrl->hiddenQueryString ) )
{
$paramsToSet[ $k ] = $v;
}
}
if ( \count( $paramsToSet ) )
if ( count( $paramsToSet ) )
{
$correctUrl = $correctUrl->setQueryString( $paramsToSet );
}
\IPS\Output::i()->redirect( $correctUrl, NULL, 301 );
Output::i()->redirect( $correctUrl, NULL );
}
/**
@@ -412,8 +439,9 @@ class _Dispatcher extends \IPS\Dispatcher
*
* @return void
*/
public function finish()
public function finish() : void
{
return (string) $this->output ?: \IPS\Output::i()->output;
Output::i()->output = $this->output ?: Output::i()->output;
parent::finish();
}
}
File diff suppressed because it is too large. Load diff
+50 -39
View File
@@ -12,55 +12,64 @@
namespace IPS\cms\Fields;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use InvalidArgumentException;
use IPS\cms\Fields;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Member;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Soundcloud input class for Form Builder
*/
class _Soundcloud extends \IPS\Helpers\Form\Text
class Soundcloud extends Text
{
/**
* @brief Default Options
*/
public $childDefaultOptions = array(
public array $childDefaultOptions = array(
'parameters' => array()
);
/**
* Constructor
*
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|NULL $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callback $customValidationCode Custom validation code
* @param string $prefix HTML to show before input field
* @param string $suffix HTML to show after input field
* @param string $id The ID to add to the row
* @return void
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|null $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callable|null $customValidationCode Custom validation code
* @param string|null $prefix HTML to show before input field
* @param string|null $suffix HTML to show after input field
* @param string|null $id The ID to add to the row
*/
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $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'] = \IPS\Member::loggedIn()->language()->addToStack('field_placeholder_soundcloud');
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_soundcloud');
/* Call parent constructor */
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
$this->formType = 'text';
}
/**
* Get the display value
*
* @param mixed $value Stored value from form
* @param \IPS\cms\Field $customField Custom Field Object
* @return string
*
* @param mixed $value Stored value from form
* @param Fields $customField Custom Field Object
* @return string|null
*/
public static function displayValue( $value, $customField )
public static function displayValue( mixed $value, Fields $customField ): ?string
{
if( !$value )
{
@@ -79,34 +88,36 @@ class _Soundcloud extends \IPS\Helpers\Form\Text
$params['height'] = 166;
}
return \IPS\Theme::i()->getTemplate( 'records', 'cms', 'global' )->soundcloud( urlencode( $value ), $params );
return Theme::i()->getTemplate( 'records', 'cms', 'global' )->soundcloud( urlencode( $value ), $params );
}
/**
* Validate
*
* @throws \InvalidArgumentException
* @throws \DomainException
* @return TRUE
*/
public function validate()
* @return bool
* @throws DomainException
* @throws InvalidArgumentException
*/
public function validate(): bool
{
parent::validate();
if ( $this->value )
{
/* Check the URL is valid */
if ( !( $this->value instanceof \IPS\Http\Url ) )
if ( !( $this->value instanceof Url ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
/* Check its a valid Soundcloud URL */
/* Check it's a valid Soundcloud URL */
if ( ! mb_stristr( $this->value->data['host'], 'soundcloud.com' ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
}
return true;
}
/**
@@ -114,12 +125,12 @@ class _Soundcloud extends \IPS\Helpers\Form\Text
*
* @return string
*/
public function getValue()
public function getValue(): mixed
{
$val = parent::getValue();
if ( $val and !mb_strpos( $val, '://' ) )
{
$val = "http://{$val}";
$val = "https://{$val}";
}
return $val;
@@ -128,17 +139,17 @@ class _Soundcloud extends \IPS\Helpers\Form\Text
/**
* Format Value
*
* @return \IPS\Http\Url|string
* @return Url|string
*/
public function formatValue()
public function formatValue(): mixed
{
if ( $this->value and !( $this->value instanceof \IPS\Http\Url ) )
if ( $this->value and !( $this->value instanceof Url ) )
{
try
{
return new \IPS\Http\Url( $this->value );
return new Url( $this->value );
}
catch ( \InvalidArgumentException $e )
catch ( InvalidArgumentException $e )
{
return $this->value;
}
+45 -34
View File
@@ -12,40 +12,49 @@
namespace IPS\cms\Fields;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use InvalidArgumentException;
use IPS\cms\Fields;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Member;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Spotify input class for Form Builder
*/
class _Spotify extends \IPS\Helpers\Form\Text
class Spotify extends Text
{
/**
* @brief Default Options
*/
public $childDefaultOptions = array(
public array $childDefaultOptions = array(
'parameters' => array()
);
/**
* Constructor
*
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|NULL $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callback $customValidationCode Custom validation code
* @param string $prefix HTML to show before input field
* @param string $suffix HTML to show after input field
* @param string $id The ID to add to the row
* @return void
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|NULL $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callable|null $customValidationCode Custom validation code
* @param string|null $prefix HTML to show before input field
* @param string|null $suffix HTML to show after input field
* @param string|null $id The ID to add to the row
*/
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $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'] = \IPS\Member::loggedIn()->language()->addToStack('field_placeholder_spotify');
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_spotify');
/* Call parent constructor */
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
@@ -57,10 +66,10 @@ class _Spotify extends \IPS\Helpers\Form\Text
* Get the display value
*
* @param mixed $value Stored value from form
* @param \IPS\cms\Field $customField Custom Field Object
* @return string
* @param Fields $customField Custom Field Object
* @return string|null
*/
public static function displayValue( $value, $customField )
public static function displayValue( mixed $value, Fields $customField ): ?string
{
if( !$value )
{
@@ -79,34 +88,36 @@ class _Spotify extends \IPS\Helpers\Form\Text
$params['height'] = 80;
}
return \IPS\Theme::i()->getTemplate( 'records', 'cms', 'global' )->spotify( $value, $params );
return Theme::i()->getTemplate( 'records', 'cms', 'global' )->spotify( $value, $params );
}
/**
* Validate
*
* @throws \InvalidArgumentException
* @throws \DomainException
* @return TRUE
* @throws InvalidArgumentException
* @throws DomainException
* @return bool
*/
public function validate()
public function validate(): bool
{
parent::validate();
if ( $this->value )
{
/* Check the URL is valid */
if ( !( $this->value instanceof \IPS\Http\Url ) )
if ( !( $this->value instanceof Url ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
/* Check its a valid Spotify URL */
if ( ! mb_stristr( $this->value->data['host'], 'spotify.com' ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
}
return true;
}
/**
@@ -114,12 +125,12 @@ class _Spotify extends \IPS\Helpers\Form\Text
*
* @return string
*/
public function getValue()
public function getValue(): mixed
{
$val = parent::getValue();
if ( $val and !mb_strpos( $val, '://' ) )
{
$val = "http://{$val}";
$val = "https://{$val}";
}
return $val;
@@ -128,17 +139,17 @@ class _Spotify extends \IPS\Helpers\Form\Text
/**
* Format Value
*
* @return \IPS\Http\Url|string
* @return Url|string
*/
public function formatValue()
public function formatValue(): mixed
{
if ( $this->value and !( $this->value instanceof \IPS\Http\Url ) )
if ( $this->value and !( $this->value instanceof Url ) )
{
try
{
return new \IPS\Http\Url( $this->value );
return new Url( $this->value );
}
catch ( \InvalidArgumentException $e )
catch ( InvalidArgumentException $e )
{
return $this->value;
}
+48 -37
View File
@@ -12,40 +12,49 @@
namespace IPS\cms\Fields;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use InvalidArgumentException;
use IPS\cms\Fields;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Member;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Youtube input class for Form Builder
* YouTube input class for Form Builder
*/
class _Youtube extends \IPS\Helpers\Form\Text
class Youtube extends Text
{
/**
* @brief Default Options
*/
public $childDefaultOptions = array(
public array $childDefaultOptions = array(
'parameters' => array()
);
/**
* Constructor
*
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|NULL $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callback $customValidationCode Custom validation code
* @param string $prefix HTML to show before input field
* @param string $suffix HTML to show after input field
* @param string $id The ID to add to the row
* @return void
* @param string $name Name
* @param mixed $defaultValue Default value
* @param bool|null $required Required? (NULL for not required, but appears to be so)
* @param array $options Type-specific options
* @param callable|null $customValidationCode Custom validation code
* @param string|null $prefix HTML to show before input field
* @param string|null $suffix HTML to show after input field
* @param string|null $id The ID to add to the row
*/
public function __construct( $name, $defaultValue=NULL, $required=FALSE, $options=array(), $customValidationCode=NULL, $prefix=NULL, $suffix=NULL, $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'] = \IPS\Member::loggedIn()->language()->addToStack('field_placeholder_youtube');
$this->childDefaultOptions['placeholder'] = Member::loggedIn()->language()->addToStack('field_placeholder_youtube');
/* Call parent constructor */
parent::__construct( $name, $defaultValue, $required, $options, $customValidationCode, $prefix, $suffix, $id );
@@ -57,17 +66,17 @@ class _Youtube extends \IPS\Helpers\Form\Text
* Get the display value
*
* @param mixed $value Stored value from form
* @param \IPS\cms\Field $customField Custom Field Object
* @return string
* @param Fields $customField Custom Field Object
* @return string|null
*/
public static function displayValue( $value, $customField )
public static function displayValue( mixed $value, Fields $customField ): ?string
{
if( !$value )
{
return '';
}
$url = new \IPS\Http\Url( $value );
$url = new Url( $value );
if ( isset( $url->queryString['v'] ) )
{
@@ -95,36 +104,38 @@ class _Youtube extends \IPS\Helpers\Form\Text
$params['height'] = ( $params['width'] * ( 9 / 16 ) ) + 30;
}
$url = \IPS\Http\Url::external( $url )->setQueryString( $params );
$url = Url::external( $url )->setQueryString( $params );
return \IPS\Theme::i()->getTemplate( 'records', 'cms', 'global' )->youtube( $url, array( 'width' => $params['width'], 'height' => $params['height'] ) );
return Theme::i()->getTemplate( 'records', 'cms', 'global' )->youtube( $url, array( 'width' => $params['width'], 'height' => $params['height'] ) );
}
/**
* Validate
*
* @throws \InvalidArgumentException
* @throws \DomainException
* @return TRUE
* @throws InvalidArgumentException
* @throws DomainException
* @return bool
*/
public function validate()
public function validate(): bool
{
parent::validate();
if ( $this->value )
{
/* Check the URL is valid */
if ( !( $this->value instanceof \IPS\Http\Url ) )
if ( !( $this->value instanceof Url ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
/* Check its a valid Youtube URL */
if ( ! mb_stristr( $this->value->data['host'], 'youtube.' ) and ! mb_stristr( $this->value->data['host'], 'youtu.be' ) )
{
throw new \InvalidArgumentException('form_url_bad');
throw new InvalidArgumentException('form_url_bad');
}
}
return true;
}
/**
@@ -132,12 +143,12 @@ class _Youtube extends \IPS\Helpers\Form\Text
*
* @return string
*/
public function getValue()
public function getValue(): mixed
{
$val = parent::getValue();
if ( $val and !mb_strpos( $val, '://' ) )
{
$val = "http://{$val}";
$val = "https://{$val}";
}
return $val;
@@ -146,17 +157,17 @@ class _Youtube extends \IPS\Helpers\Form\Text
/**
* Format Value
*
* @return \IPS\Http\Url|string
* @return Url|string
*/
public function formatValue()
public function formatValue(): mixed
{
if ( $this->value and !( $this->value instanceof \IPS\Http\Url ) )
if ( $this->value and !( $this->value instanceof Url ) )
{
try
{
return new \IPS\Http\Url( $this->value );
return new Url( $this->value );
}
catch ( \InvalidArgumentException $e )
catch ( InvalidArgumentException $e )
{
return $this->value;
}
+68 -50
View File
@@ -12,26 +12,42 @@
namespace IPS\cms\Media;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use InvalidArgumentException;
use IPS\cms\Media;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Member;
use IPS\Node\Model;
use IPS\Request;
use OutOfRangeException;
use function count;
use function defined;
use function func_get_args;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Folder Model
*/
class _Folder extends \IPS\Node\Model
class Folder extends Model
{
/**
* Munge different record types
*
* @return array
*/
public static function munge()
public static function munge() : array
{
$rows = array();
$args = \func_get_args();
$args = func_get_args();
foreach( $args as $arg )
{
@@ -49,73 +65,73 @@ class _Folder extends \IPS\Node\Model
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_media_folders';
public static ?string $databaseTable = 'cms_media_folders';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'media_folder_';
public static string $databasePrefix = 'media_folder_';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('media_folder_path');
protected static array $databaseIdFields = array('media_folder_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnParent = 'parent';
public static ?string $databaseColumnParent = 'parent';
/**
* @brief [Node] Parent ID Root Value
* @note This normally doesn't need changing though some legacy areas use -1 to indicate a root node
*/
public static $databaseColumnParentRootValue = 0;
public static int $databaseColumnParentRootValue = 0;
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'path';
public static ?string $databaseColumnOrder = 'path';
/**
* @brief [Node] Automatically set position for new nodes
*/
public static $automaticPositionDetermination = FALSE;
public static bool $automaticPositionDetermination = FALSE;
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'folder';
public static string $nodeTitle = 'folder';
/**
* @brief [Node] Subnode class
*/
public static $subnodeClass = 'IPS\cms\Media';
public static ?string $subnodeClass = 'IPS\cms\Media';
/**
* @brief [Node] Show forms modally?
*/
public static $modalForms = TRUE;
public static bool $modalForms = TRUE;
/**
* @brief [Node] Restrictions
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'cms',
'module' => 'pages',
'all' => 'media_manage',
@@ -125,9 +141,9 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Get Title
*
* @return string|null
* @return string
*/
protected function get__title()
protected function get__title(): string
{
return $this->name;
}
@@ -137,7 +153,7 @@ class _Folder extends \IPS\Node\Model
*
* @return string
*/
public function getSortableName()
public function getSortableName() : string
{
return $this->name;
}
@@ -145,11 +161,11 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Get buttons to display in tree
*
* @param string $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
* @param Url $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
public function getButtons( Url $url, bool $subnode=FALSE ): array
{
$buttons = parent::getButtons( $url, $subnode );
$return = array();
@@ -163,14 +179,14 @@ class _Folder extends \IPS\Node\Model
{
$buttons['add']['icon'] = 'folder-open';
$buttons['add']['title'] = 'cms_add_media_folder';
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('cms_add_media_folder') );
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('cms_add_media_folder') );
$buttons['add']['link'] = $url->setQueryString( array( 'subnode' => 0, 'do' => 'form', 'parent' => $this->_id ) );
$buttons['add_page'] = array(
'icon' => 'plus-circle',
'title' => 'cms_add_media',
'link' => $url->setQueryString( array( 'subnode' => 1, 'do' => 'add', 'parent' => $this->_id ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('cms_add_media') )
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('cms_add_media') )
);
}
@@ -201,42 +217,44 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
/* Build form */
$form->add( new \IPS\Helpers\Form\Text( 'media_folder_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ), function( $val )
$form->add( new Text( 'media_folder_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ), function( $val )
{
try
{
$test = \IPS\cms\Media\Folder::load( \IPS\Http\Url\Friendly::seoTitle( $val ), 'media_folder_name' );
$test = Folder::load( Friendly::seoTitle( $val ), 'media_folder_name' );
if ( ! empty( \IPS\Request::i()->id ) and $test->id != \IPS\Request::i()->id )
if ( ! empty( Request::i()->id ) and $test->id != Request::i()->id )
{
throw new \InvalidArgumentException('content_folder_name_in_use');
throw new InvalidArgumentException('content_folder_name_in_use');
}
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
}
} ) );
$form->add( new \IPS\Helpers\Form\Node( 'media_folder_parent', $this->parent ? $this->parent : 0, FALSE, array(
$form->add( new Node( 'media_folder_parent', $this->parent ? $this->parent : 0, FALSE, array(
'class' => '\IPS\cms\Media\Folder',
'zeroVal' => 'node_no_parent',
'permissionCheck' => function( $node )
{
if ( ! isset( \IPS\Request::i()->id ) )
if ( ! isset( Request::i()->id ) )
{
return true;
}
if ( ! isset( \IPS\Request::i()->parent ) )
if ( ! isset( Request::i()->parent ) )
{
return $node->id != \IPS\Request::i()->id;
return $node->id != Request::i()->id;
}
return true;
}
) ) );
}
@@ -244,12 +262,12 @@ class _Folder extends \IPS\Node\Model
/**
* @brief Original parent ID
*/
protected $origParentId;
protected int $origParentId;
/**
* @brief Original parent Name
*/
protected $origName;
protected string $origName;
/**
* [Node] Format form values from add/edit form for save
@@ -257,15 +275,15 @@ class _Folder extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if ( ! $this->id )
{
$this->save();
}
$this->origParentId = $this->parent;
$this->origName = $this->name;
$this->origParentId = (int) $this->parent;
$this->origName = (string) $this->name;
if ( isset( $values['media_folder_parent'] ) AND ( ! empty( $values['media_folder_parent'] ) OR $values['media_folder_parent'] === 0 ) )
{
@@ -275,7 +293,7 @@ class _Folder extends \IPS\Node\Model
if( isset( $values['media_folder_name'] ) )
{
$values['name'] = \IPS\Http\Url\Friendly::seoTitle( $values['media_folder_name'] );
$values['name'] = Friendly::seoTitle( $values['media_folder_name'] );
unset( $values['media_folder_name'] );
}
@@ -288,7 +306,7 @@ class _Folder extends \IPS\Node\Model
* @param array $values Values from the form
* @return void
*/
public function postSaveForm( $values )
public function postSaveForm( array $values ) : void
{
if ( $this->origParentId !== $values['parent'] OR $this->origName !== $values['name'] )
{
@@ -302,7 +320,7 @@ class _Folder extends \IPS\Node\Model
* @param boolean $recursivelyCheck Recursively reset up and down the tree
* @return void
*/
public function resetPath( $recursivelyCheck=true )
public function resetPath( bool $recursivelyCheck=true ) : void
{
$path = array();
@@ -311,13 +329,13 @@ class _Folder extends \IPS\Node\Model
$path[] = $obj->name;
}
$this->path = ( \count( $path ) ) ? implode( '/', $path ) . '/' . $this->name : $this->name;
$this->path = ( count( $path ) ) ? implode( '/', $path ) . '/' . $this->name : $this->name;
/* Save path update */
parent::save();
/* Update media */
\IPS\cms\Media::resetPath( $this->id );
Media::resetPath( $this->id );
if ( $recursivelyCheck === true )
{
@@ -341,7 +359,7 @@ class _Folder extends \IPS\Node\Model
*
* @return void
*/
protected function _recursivelyResetChildPaths()
protected function _recursivelyResetChildPaths() : void
{
foreach( $this->children( NULL, NULL, FALSE ) as $child )
{
+100 -173
View File
@@ -12,76 +12,94 @@
namespace IPS\cms;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\cms\Media\Folder;
use IPS\Db;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Upload;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Model;
use OutOfRangeException;
use RuntimeException;
use function count;
use function defined;
use function in_array;
use function intval;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Media Model
*/
class _Media extends \IPS\Node\Model
class Media extends Model
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_media';
public static ?string $databaseTable = 'cms_media';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'media_';
public static string $databasePrefix = 'media_';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('media_full_path');
protected static array $databaseIdFields = array('media_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent Node ID Database Column
*/
public static $parentNodeColumnId = 'parent';
public static string $parentNodeColumnId = 'parent';
/**
* @brief [Node] Parent Node Class
*/
public static $parentNodeClass = 'IPS\cms\Media\Folder';
public static string $parentNodeClass = 'IPS\cms\Media\Folder';
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnOrder = 'filename';
public static ?string $databaseColumnOrder = 'filename';
/**
* @brief [Node] Automatically set position for new nodes
*/
public static $automaticPositionDetermination = FALSE;
public static bool $automaticPositionDetermination = FALSE;
/**
* @brief [Node] Show forms modally?
*/
public static $modalForms = TRUE;
public static bool $modalForms = TRUE;
/**
* @brief [Node] Title
*/
public static $nodeTitle = 'page';
public static string $nodeTitle = 'page';
/**
* @brief [Node] ACP Restrictions
@@ -99,7 +117,7 @@ class _Media extends \IPS\Node\Model
'prefix' => 'foo_', // [Optional] Rather than specifying each key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
* @endcode
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'cms',
'module' => 'pages',
'prefix' => 'media_'
@@ -110,11 +128,20 @@ class _Media extends \IPS\Node\Model
*
* @return void
*/
public function setDefaultValues()
public function setDefaultValues() : void
{
$this->parent = 0;
$this->full_path = '';
}
/**
* @return string
*/
public function get_file_icon() : string
{
$extension = strtolower( substr( $this->file_object, strrpos( $this->file_object, '.' ) + 1 ) );
return File::$fileIconMap[ $extension ] ?? '';
}
/**
* Resets a media path
@@ -122,15 +149,15 @@ class _Media extends \IPS\Node\Model
* @param int $folderId Folder ID to reset
* @return void
*/
public static function resetPath( $folderId )
public static function resetPath( int $folderId ) : void
{
try
{
$path = \IPS\cms\Media\Folder::load( $folderId )->path;
$path = Folder::load( $folderId )->path;
}
catch ( \OutOfRangeException $ex )
catch ( OutOfRangeException $ex )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
$children = static::getChildren( $folderId );
@@ -147,10 +174,10 @@ class _Media extends \IPS\Node\Model
* @param INT $folderId Folder ID to fetch children from
* @return array
*/
public static function getChildren( $folderId=0 )
public static function getChildren( int $folderId=0 ) : array
{
$children = array();
foreach( \IPS\Db::i()->select( '*', static::$databaseTable, array( 'media_parent=?', \intval( $folderId ) ), 'media_filename ASC' ) as $child )
foreach( Db::i()->select( '*', static::$databaseTable, array( 'media_parent=?', intval( $folderId ) ), 'media_filename ASC' ) as $child )
{
$children[ $child[ static::$databasePrefix . static::$databaseColumnId ] ] = static::load( $child[ static::$databasePrefix . static::$databaseColumnId ] );
}
@@ -164,7 +191,7 @@ class _Media extends \IPS\Node\Model
* @param array $ids Array of IDs to remove
* @return void
*/
public static function deleteByFileIds( $ids=array() )
public static function deleteByFileIds( array $ids=array() ) : void
{
foreach( $ids as $id )
{
@@ -172,42 +199,35 @@ class _Media extends \IPS\Node\Model
{
static::load( $id )->delete();
}
catch( \Exception $ex ) { }
catch( Exception $ex ) { }
}
}
/**
* Get URL
*
* @return \IPS\Http\Url object
* @return Url|string|null object
*/
public function url()
function url(): Url|string|null
{
if ( \IPS\Theme::designersModeEnabled() )
{
return \IPS\Settings::i()->base_url . 'themes/cms/media/' . $this->full_path;
}
else
{
return (string)\IPS\File::get( 'cms_Media', $this->file_object )->url;
}
return (string)File::get( 'cms_Media', $this->file_object )->url;
}
/**
* [ActiveRecord] Delete Record
*
* @return void
* @return void
*/
public function delete()
public function delete(): void
{
try
{
if ( $this->file_object )
{
\IPS\File::get( 'cms_Media', $this->file_object )->delete();
File::get( 'cms_Media', $this->file_object )->delete();
}
}
catch( \Exception $ex ) { }
catch( Exception $ex ) { }
parent::delete();
}
@@ -215,11 +235,11 @@ class _Media extends \IPS\Node\Model
/**
* [Node] Get buttons to display in tree
*
* @param string $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
* @param Url $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
public function getButtons( Url $url, bool $subnode=FALSE ): array
{
$buttons = parent::getButtons( $url, $subnode );
$delete = NULL;
@@ -241,10 +261,10 @@ class _Media extends \IPS\Node\Model
}
$buttons['key'] = array(
'icon' => 'file-code-o',
'icon' => 'file-code',
'title' => 'cms_media_key',
'link' => \IPS\Http\Url::internal( 'app=cms&module=pages&controller=media&do=key&id=' . $this->id ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('cms_media_key') )
'link' => Url::internal( 'app=cms&module=pages&controller=media&do=key&id=' . $this->id ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('cms_media_key') )
);
if ( $this->is_image )
@@ -252,8 +272,8 @@ class _Media extends \IPS\Node\Model
$buttons['preview'] = array(
'icon' => 'search',
'title' => 'cms_media_preview',
'link' => \IPS\Http\Url::internal( 'app=cms&module=pages&controller=media&do=preview&id=' . $this->id ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('cms_media_preview') )
'link' => Url::internal( 'app=cms&module=pages&controller=media&do=preview&id=' . $this->id ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('cms_media_preview') )
);
}
@@ -269,15 +289,15 @@ class _Media extends \IPS\Node\Model
* [Node] Add/Edit Form
*
* @note This is not used currently. See \IPS\cms\modules\admin\media.php upload()
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
/* Build form */
$form->add( new \IPS\Helpers\Form\Upload( 'media_filename', ( ( $this->filename ) ? \IPS\File::get( 'cms_Media', $this->file_object ) : NULL ), FALSE, array( 'obscure' => FALSE, 'maxFileSize' => 5, 'storageExtension' => 'cms_Media', 'storageContainer' => 'pages_media' ), NULL, NULL, NULL, 'media_filename' ) );
$form->add( new Upload( 'media_filename', ( ( $this->filename ) ? File::get( 'cms_Media', $this->file_object ) : NULL ), FALSE, array( 'obscure' => FALSE, 'maxFileSize' => 5, 'storageExtension' => 'cms_Media', 'storageContainer' => 'pages_media' ), NULL, NULL, NULL, 'media_filename' ) );
$form->add( new \IPS\Helpers\Form\Node( 'media_parent', $this->parent ? $this->parent : 0, FALSE, array(
$form->add( new Node( 'media_parent', $this->parent ?: 0, FALSE, array(
'class' => '\IPS\cms\Media\Folder',
'zeroVal' => 'node_no_parent'
) ) );
@@ -290,7 +310,7 @@ class _Media extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
if ( isset( $values['media_parent'] ) AND ( ! empty( $values['media_parent'] ) OR $values['media_parent'] === 0 ) )
{
@@ -315,8 +335,7 @@ class _Media extends \IPS\Node\Model
$values['filename'] = $filename;
$values['filename_stored'] = $values['parent'] . '_' . $values['filename'];
$values['is_image'] = $values['media_filename']->isImage();
$values['file_object'] = (string) $values['media_filename'];
$values['file_object'] = (string) $values['media_filename'];
unset( $values['media_filename'] );
}
@@ -336,9 +355,9 @@ class _Media extends \IPS\Node\Model
* @param array $values Values from the form
* @return void
*/
public function postSaveForm( $values )
public function postSaveForm( array $values ) : void
{
$this->setFullPath( ( $this->parent ? \IPS\cms\Media\Folder::load( $this->parent )->path : '' ) );
$this->setFullPath( ( $this->parent ? Folder::load( $this->parent )->path : '' ) );
$this->save();
}
@@ -347,7 +366,7 @@ class _Media extends \IPS\Node\Model
*
* @return string
*/
public function getSortableName()
public function getSortableName() : string
{
return $this->full_path;
}
@@ -358,63 +377,21 @@ class _Media extends \IPS\Node\Model
* @param string $path Path to reset
* @return void
*/
public function setFullPath( $path )
public function setFullPath( string $path ) : void
{
$this->full_path = trim( $path . '/' . $this->filename, '/' );
$this->save();
}
/**
* Write media to disk for designer's mode
*
* @return void
*/
public static function exportDesignersModeMedia()
{
/* Make sure our media folder exists */
if ( !is_dir( \IPS\ROOT_PATH . '/themes/cms/media' ) )
{
mkdir( \IPS\ROOT_PATH . '/themes/cms/media', \IPS\IPS_FOLDER_PERMISSION );
chmod( \IPS\ROOT_PATH . '/themes/cms/media', \IPS\IPS_FOLDER_PERMISSION );
}
foreach( \IPS\Db::i()->select( '*', 'cms_media' ) as $media )
{
/* We could use recursive mode but it wouldn't correctly chmod the intermediate dirs */
$bits = explode( '/', "/themes/cms/media/" . $media['media_full_path'] );
$dir = '';
$filename = array_pop( $bits );
foreach( $bits as $part )
{
$dir .= $part . '/';
if ( ! is_dir( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ) ) )
{
mkdir( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ), \IPS\IPS_FOLDER_PERMISSION );
chmod( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ), \IPS\IPS_FOLDER_PERMISSION );
}
}
try
{
\file_put_contents( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ) . '/' . $filename, \IPS\File::get( 'cms_Media', $media['media_file_object'] )->contents() );
@chmod( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ) . '/' . $filename, \IPS\IPS_FILE_PERMISSION );
}
catch( \RuntimeException $e ) { }
}
}
/**
* Removes folders that are empty
*
* @return void
*/
public static function removeEmptyFolders()
public static function removeEmptyFolders() : void
{
$folders = iterator_to_array( \IPS\Db::i()->select( 'DISTINCT(media_parent)', 'cms_media', array( 'media_parent > 0' ) ) );
$allFolders = iterator_to_array( \IPS\Db::i()->select( '*', 'cms_media_folders' )->setKeyField( 'media_folder_id' ) );
$folders = iterator_to_array( Db::i()->select( 'DISTINCT(media_parent)', 'cms_media', array( 'media_parent > 0' ) ) );
$allFolders = iterator_to_array( Db::i()->select( '*', 'cms_media_folders' )->setKeyField( 'media_folder_id' ) );
foreach( $folders as $id )
{
@@ -430,7 +407,7 @@ class _Media extends \IPS\Node\Model
break;
}
if ( ! \in_array( $currentParent, $folders ) )
if ( ! in_array( $currentParent, $folders ) )
{
$folders[] = $currentParent;
}
@@ -440,59 +417,7 @@ class _Media extends \IPS\Node\Model
}
}
\IPS\Db::i()->delete( 'cms_media_folders', array( \IPS\Db::i()->in( 'media_folder_id', array_values( $folders ), TRUE ) ) );
}
/**
* Import media from disk for designer's mode
*
* @return void
*/
public static function importDesignersModeMedia()
{
$path = \IPS\ROOT_PATH . '/themes/cms/media';
$seen = array();
if ( is_dir( $path ) )
{
static::importDesignersModeMediaRecurse( $seen, $path );
}
\IPS\Db::i()->delete( 'cms_media', array( \IPS\Db::i()->in( 'media_id', $seen, TRUE ) ) );
static::removeEmptyFolders();
}
/**
* Import media from disk for designer's mode recursive method
*
* @param array $seen Files we've seen already
* @param string $path Path to look in
* @return void
*/
public static function importDesignersModeMediaRecurse( &$seen, $path )
{
if ( is_dir( $path ) )
{
foreach ( new \DirectoryIterator( $path ) as $dir )
{
if ( $dir->isDot() || mb_substr( $dir->getFilename(), 0, 1 ) === '.' )
{
continue;
}
if ( $dir->isDir() )
{
static::importDesignersModeMediaRecurse( $seen, $path . '/' . $dir->getFilename() );
}
else
{
$contents = \file_get_contents( $dir->getRealPath() );
/* Create */
$seen[] = static::createMedia( trim( str_replace( str_replace( '\\', '/', \IPS\ROOT_PATH ) . '/themes/cms/media', '', str_replace( '\\', '/', $dir->getRealPath() ) ), '/' ), $contents );
}
}
}
Db::i()->delete( 'cms_media_folders', array( Db::i()->in( 'media_folder_id', array_values( $folders ), TRUE ) ) );
}
/**
@@ -500,49 +425,49 @@ class _Media extends \IPS\Node\Model
*
* @param string $path File path (/folder/file.txt)
* @param string $contents File contents
* @return int ID of existing media or of new media
* @return int|null ID of existing media or of new media
*/
public static function createMedia( $path, $contents )
public static function createMedia( string $path, string $contents ): ?int
{
try
{
$test = static::load( $path, 'media_full_path' );
$test->file_object = \IPS\File::create( 'cms_Media', $test->filename_stored, $contents, 'pages_media', TRUE, NULL, FALSE );
$test->file_object = File::create( 'cms_Media', $test->filename_stored, $contents, 'pages_media', TRUE, NULL, FALSE );
$test->save();
return $test->id;
}
catch( \RuntimeException $ex )
catch( RuntimeException $ex )
{
try
{
\IPS\File::get( 'cms_Media', $path );
File::get( 'cms_Media', $path );
}
catch( \Exception $x )
catch( Exception $x )
{
/* File doesn't exist already */
throw $ex;
}
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
/* It doesn't exist */
$exploded = explode( '/', $path );
$filename = array_pop( $exploded );
$folderId = 0;
if ( \count( $exploded ) )
if ( count( $exploded ) )
{
$testDir = trim( implode( '/', $exploded ), '/' );
try
{
$test = \IPS\cms\Media\Folder::load( $testDir, 'media_folder_path' );
$test = Folder::load( $testDir, 'media_folder_path' );
/* Yep */
$folderId = $test->id;
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
$testDir = '';
foreach( $exploded as $dir )
@@ -551,12 +476,12 @@ class _Media extends \IPS\Node\Model
try
{
$test = \IPS\cms\Media\Folder::load( $testDir, 'media_folder_path' );
$test = Folder::load( $testDir, 'media_folder_path' );
$folderId = $test->id;
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
$folder = new \IPS\cms\Media\Folder;
$folder = new Folder;
$folder->parent = $folderId;
$folder->name = $dir;
$folder->path = $testDir;
@@ -567,17 +492,19 @@ class _Media extends \IPS\Node\Model
}
}
$media = new \IPS\cms\Media;
$media = new Media;
$media->parent = $folderId;
$media->filename = $filename;
$media->added = time();
$media->full_path = $path;
$media->filename_stored = $folderId . '_' . $filename;
$media->file_object = \IPS\File::create( 'cms_Media', $media->filename_stored, $contents, 'pages_media', TRUE, NULL, FALSE );
$media->file_object = File::create( 'cms_Media', $media->filename_stored, $contents, 'pages_media', TRUE, NULL, FALSE );
$media->is_image = $media->file_object->isImage();
$media->save();
return $media->id;
}
return null;
}
}
+95 -75
View File
@@ -12,26 +12,47 @@
namespace IPS\cms\Pages;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use InvalidArgumentException;
use IPS\Data\Store;
use IPS\Db;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Text;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Member;
use IPS\Node\Model;
use IPS\Request;
use IPS\Session;
use OutOfRangeException;
use function count;
use function defined;
use function func_get_args;
use function get_called_class;
use function get_class;
use function intval;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Folder Model
*/
class _Folder extends \IPS\Node\Model
class Folder extends Model
{
/**
* Munge different record types
*
* @return array
*/
public static function munge()
public static function munge() : array
{
$rows = array();
$args = \func_get_args();
$args = func_get_args();
foreach( $args as $arg )
{
@@ -49,73 +70,73 @@ class _Folder extends \IPS\Node\Model
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_folders';
public static ?string $databaseTable = 'cms_folders';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'folder_';
public static string $databasePrefix = 'folder_';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('folder_name', 'folder_path');
protected static array $databaseIdFields = array('folder_name', 'folder_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnParent = 'parent_id';
public static ?string $databaseColumnParent = 'parent_id';
/**
* @brief [Node] Parent ID Root Value
* @note This normally doesn't need changing though some legacy areas use -1 to indicate a root node
*/
public static $databaseColumnParentRootValue = 0;
public static int $databaseColumnParentRootValue = 0;
/**
* @brief [Node] Order Database Column
*/
public static $databaseColumnOrder = 'path';
public static ?string $databaseColumnOrder = 'path';
/**
* @brief [Node] Automatically set position for new nodes
*/
public static $automaticPositionDetermination = FALSE;
public static bool $automaticPositionDetermination = FALSE;
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'folder';
public static string $nodeTitle = 'folder';
/**
* @brief [Node] Subnode class
*/
public static $subnodeClass = 'IPS\cms\Pages\Page';
public static ?string $subnodeClass = 'IPS\cms\Pages\Page';
/**
* @brief [Node] Show forms modally?
*/
public static $modalForms = TRUE;
public static bool $modalForms = TRUE;
/**
* @brief [Node] Restrictions
*/
protected static $restrictions = array(
protected static ?array $restrictions = array(
'app' => 'cms',
'module' => 'pages',
'all' => 'page_manage',
@@ -125,9 +146,9 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Get Title
*
* @return string|null
* @return string
*/
protected function get__title()
protected function get__title(): string
{
return $this->name;
}
@@ -137,7 +158,7 @@ class _Folder extends \IPS\Node\Model
*
* @return string
*/
public function getSortableName()
public function getSortableName() : string
{
return $this->name;
}
@@ -145,11 +166,11 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Get buttons to display in tree
*
* @param string $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
* @param Url $url Base URL
* @param bool $subnode Is this a subnode?
* @return array
*/
public function getButtons( $url, $subnode=FALSE )
public function getButtons( Url $url, bool $subnode=FALSE ): array
{
$buttons = parent::getButtons( $url, $subnode );
$return = array();
@@ -163,14 +184,13 @@ class _Folder extends \IPS\Node\Model
{
$buttons['add']['icon'] = 'folder-open';
$buttons['add']['title'] = 'content_add_folder';
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('content_add_folder') );
$buttons['add']['data'] = array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('content_add_folder') );
$buttons['add']['link'] = $url->setQueryString( array( 'subnode' => 0, 'do' => 'form', 'parent' => $this->_id ) );
$buttons['add_page'] = array(
'icon' => 'plus-circle',
'title' => 'content_add_page',
'link' => $url->setQueryString( array( 'subnode' => 1, 'do' => 'add', 'parent' => $this->_id ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('content_add_page') )
'link' => $url->setQueryString( array( 'subnode' => 1, 'do' => 'form', 'parent' => $this->_id ) )
);
}
@@ -195,10 +215,10 @@ class _Folder extends \IPS\Node\Model
if ( $this->getItemCount() )
{
$return['delete'] = array(
'icon' => 'trash-o',
'icon' => 'trash',
'title' => 'empty',
'link' => $url->setQueryString( array( 'do' => 'delete', 'id' => $this->_id ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('empty') ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => Member::loggedIn()->language()->addToStack('empty') ),
'hotkey'=> 'd'
);
}
@@ -214,52 +234,52 @@ class _Folder extends \IPS\Node\Model
/**
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param Form $form The form
* @return void
*/
public function form( &$form )
public function form( Form &$form ) : void
{
/* Build form */
$form->add( new \IPS\Helpers\Form\Text( 'folder_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ), function( $val )
$form->add( new Text( 'folder_name', $this->id ? $this->name : '', TRUE, array( 'maxLength' => 64 ), function( $val )
{
try
{
$testPage = \IPS\cms\Pages\Page::load( $val, 'page_seo_name' );
$testPage = Page::load( $val, 'page_seo_name' );
/* Ok, we have a folder, but is it on the same tree as us ?*/
if ( \intval( \IPS\Request::i()->folder_parent_id ) == $testPage->folder_id )
if ( intval( Request::i()->folder_parent_id ) == $testPage->folder_id )
{
/* Yep, this will break designers' mode and may confuse the FURL engine so we cannot allow this */
throw new \InvalidArgumentException('content_folder_name_furl_collision_folder');
throw new InvalidArgumentException('content_folder_name_furl_collision_folder');
}
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
/* Nothing with the same name, so that's proper good that is */
}
try
{
$test = \IPS\cms\Pages\Folder::load( ( $val === '.well-known' ) ? $val : \IPS\Http\Url\Friendly::seoTitle( $val ), 'folder_name' );
$test = Folder::load( ( $val === '.well-known' ) ? $val : Friendly::seoTitle( $val ), 'folder_name' );
if ( empty( \IPS\Request::i()->id ) or $test->id != \IPS\Request::i()->id )
if ( empty( Request::i()->id ) or $test->id != Request::i()->id )
{
throw new \InvalidArgumentException('content_folder_name_in_use');
throw new InvalidArgumentException('content_folder_name_in_use');
}
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
/* If we hit here, we don't have an existing folder by that name so check for a collision */
if ( \IPS\Request::i()->folder_parent_id == 0 AND \IPS\cms\Pages\Page::isFurlCollision( ( $val === '.well-known' ) ? $val : \IPS\Http\Url\Friendly::seoTitle( $val ) ) )
if ( Request::i()->folder_parent_id == 0 AND Page::isFurlCollision( ( $val === '.well-known' ) ? $val : Friendly::seoTitle( $val ) ) )
{
throw new \InvalidArgumentException('content_folder_name_furl_collision');
throw new InvalidArgumentException('content_folder_name_furl_collision');
}
}
} ) );
$class = \get_called_class();
$class = get_called_class();
$form->add( new \IPS\Helpers\Form\Node( 'folder_parent_id', $this->parent_id ? $this->parent_id : 0, FALSE, array(
$form->add( new Node( 'folder_parent_id', $this->parent_id ? $this->parent_id : 0, FALSE, array(
'class' => '\IPS\cms\Pages\Folder',
'zeroVal' => 'node_no_parent',
'permissionCheck' => function( $node ) use ( $class )
@@ -269,7 +289,7 @@ class _Folder extends \IPS\Node\Model
return FALSE;
}
return !isset( \IPS\Request::i()->id ) or ( $node->id != \IPS\Request::i()->id and !$node->isChildOf( $node::load( \IPS\Request::i()->id ) ) );
return !isset( Request::i()->id ) or ( $node->id != Request::i()->id and !$node->isChildOf( $node::load( Request::i()->id ) ) );
}
) ) );
}
@@ -280,7 +300,7 @@ class _Folder extends \IPS\Node\Model
* @param array $values Values from the form
* @return array
*/
public function formatFormValues( $values )
public function formatFormValues( array $values ): array
{
$isNew = $this->_new;
@@ -307,7 +327,7 @@ class _Folder extends \IPS\Node\Model
}
else
{
$values['name'] = \IPS\Http\Url\Friendly::seoTitle( $values['folder_name'] );
$values['name'] = Friendly::seoTitle( $values['folder_name'] );
}
unset( $values['folder_name'] );
}
@@ -323,12 +343,12 @@ class _Folder extends \IPS\Node\Model
/**
* @brief Original parent ID
*/
protected $origParentId;
protected ?int $origParentId = null;
/**
* @brief Original Name
*/
protected $origName;
protected ?string $origName = null;
/**
* [Node] Perform actions after saving the form
@@ -336,7 +356,7 @@ class _Folder extends \IPS\Node\Model
* @param array $values Values from the form
* @return void
*/
public function postSaveForm( $values )
public function postSaveForm( array $values ) : void
{
if ( $this->origParentId !== $values['parent_id'] OR $this->origName !== $values['name'] )
{
@@ -349,9 +369,9 @@ class _Folder extends \IPS\Node\Model
*
* @return void
*/
public function storeUrl()
public function storeUrl() : void
{
\IPS\Db::i()->insert( 'cms_url_store', array(
Db::i()->insert( 'cms_url_store', array(
'store_path' => $this->path,
'store_current_id' => $this->_id,
'store_type' => 'folder'
@@ -363,7 +383,7 @@ class _Folder extends \IPS\Node\Model
*
* @return void
*/
public function save()
public function save(): void
{
$this->last_modified = time();
@@ -373,12 +393,12 @@ class _Folder extends \IPS\Node\Model
/**
* Retrieve item \count(if applicable) for a node.
*
* @return int|bool
* @return int
*/
public function getItemCount()
public function getItemCount() : int
{
$pages = (int) \IPS\Db::i()->select( 'COUNT(*)', 'cms_pages', array( 'page_folder_id=?', $this->id ) )->first();
$subFolders = (int) \IPS\Db::i()->select( 'COUNT(*)', 'cms_folders', array( 'folder_parent_id=?', $this->id ) )->first();
$pages = (int) Db::i()->select( 'COUNT(*)', 'cms_pages', array( 'page_folder_id=?', $this->id ) )->first();
$subFolders = (int) Db::i()->select( 'COUNT(*)', 'cms_folders', array( 'folder_parent_id=?', $this->id ) )->first();
return $pages + $subFolders;
}
@@ -387,19 +407,19 @@ class _Folder extends \IPS\Node\Model
* Form to delete or move content
*
* @param bool $showMoveToChildren If TRUE, will show "move to children" even if there are no children
* @return \IPS\Helpers\Form
* @return Form
*/
public function deleteOrMoveForm( $showMoveToChildren=FALSE )
public function deleteOrMoveForm( bool $showMoveToChildren=FALSE ): Form
{
$hasContent = $this->getItemCount();
$form = new \IPS\Helpers\Form( 'form', 'delete' );
$form = new Form( 'form', 'delete' );
if ( $hasContent )
{
$form->add( new \IPS\Helpers\Form\Node( 'cms_move_pages', 0, TRUE, array( 'class' => \get_class( $this ), 'disabled' => array( $this->_id ), 'disabledLang' => 'node_move_delete', 'zeroVal' => 'cms_move_to_root', 'subnodes' => FALSE, 'permissionCheck' => function( $node )
$form->add( new Node( 'cms_move_pages', 0, TRUE, array( 'class' => get_class( $this ), 'disabled' => array( $this->_id ), 'disabledLang' => 'node_move_delete', 'zeroVal' => 'cms_move_to_root', 'subnodes' => FALSE, 'permissionCheck' => function( $node )
{
return \IPS\Request::i()->id != $node->id;
return Request::i()->id != $node->id;
} ) ) );
}
@@ -412,7 +432,7 @@ class _Folder extends \IPS\Node\Model
* @param array $values Values from form
* @return void
*/
public function deleteOrMoveFormSubmit( $values )
public function deleteOrMoveFormSubmit( array $values ) : void
{
if ( $values['cms_move_pages'] )
{
@@ -423,17 +443,17 @@ class _Folder extends \IPS\Node\Model
$folderId = 0;
}
\IPS\Db::i()->update( 'cms_pages', array( 'page_folder_id' => $folderId ), array( 'page_folder_id=?', $this->_id ) );
\IPS\Db::i()->update( 'cms_folders', array( 'folder_parent_id' => $folderId ), array( 'folder_id=?', $this->_id ) );
\IPS\Db::i()->update( 'cms_folders', array( 'folder_parent_id' => $folderId ), array( 'folder_parent_id=?', $this->_id ) );
Db::i()->update( 'cms_pages', array( 'page_folder_id' => $folderId ), array( 'page_folder_id=?', $this->_id ) );
Db::i()->update( 'cms_folders', array( 'folder_parent_id' => $folderId ), array( 'folder_id=?', $this->_id ) );
Db::i()->update( 'cms_folders', array( 'folder_parent_id' => $folderId ), array( 'folder_parent_id=?', $this->_id ) );
unset( \IPS\Data\Store::i()->pages_page_urls );
unset( Store::i()->pages_page_urls );
/* Update pages */
\IPS\cms\Pages\Page::resetPath( $folderId );
Page::resetPath( $folderId );
/* Delete it */
\IPS\Session::i()->log( 'acplog__node_deleted', array( $this->title => TRUE, $this->titleForLog() => FALSE ) );
Session::i()->log( 'acplog__node_deleted', array( $this->title => TRUE, $this->titleForLog() => FALSE ) );
$this->delete();
}
@@ -443,7 +463,7 @@ class _Folder extends \IPS\Node\Model
* @param boolean $recursivelyCheck Recursively reset up and down the tree
* @return void
*/
public function resetPath( $recursivelyCheck=true )
public function resetPath( bool $recursivelyCheck=true ) : void
{
$path = array();
@@ -452,13 +472,13 @@ class _Folder extends \IPS\Node\Model
$path[] = $obj->name;
}
$this->path = ( \count( $path ) ) ? implode( '/', $path ) . '/' . $this->name : $this->name;
$this->path = ( count( $path ) ) ? implode( '/', $path ) . '/' . $this->name : $this->name;
/* Save path update */
parent::save();
/* Update pages */
\IPS\cms\Pages\Page::resetPath( $this->id );
Page::resetPath( $this->id );
if ( $recursivelyCheck === true )
{
@@ -482,7 +502,7 @@ class _Folder extends \IPS\Node\Model
*
* @return void
*/
protected function _recursivelyResetChildPaths()
protected function _recursivelyResetChildPaths() : void
{
foreach( $this->children( NULL, NULL, FALSE ) as $child )
{
File diff suppressed because it is too large. Load diff
+32 -197
View File
@@ -12,272 +12,107 @@
namespace IPS\cms\Pages;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Content\Item;
use IPS\Content\ViewUpdates;
use IPS\Http\Url;
use IPS\Node\Model;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Package Item Model
*/
class _PageItem extends \IPS\Content\Item implements \IPS\Content\Searchable
class PageItem extends Item
{
use \IPS\Content\ViewUpdates;
use ViewUpdates;
/**
* @brief Application
*/
public static $application = 'cms';
public static string $application = 'cms';
/**
* @brief Module
*/
public static $module = 'pages';
public static string $module = 'pages';
/**
* @brief Database Table
*/
public static $databaseTable = 'cms_pages';
public static ?string $databaseTable = 'cms_pages';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'page_';
public static string $databasePrefix = 'page_';
/**
* @brief Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'views' => 'views'
);
/**
* @brief Title
*/
public static $title = 'cms_page';
public static string $title = 'cms_page';
/**
* @brief Icon
*/
public static $icon = 'files-o';
public static string $icon = 'file';
/**
* @brief Include In Sitemap
*/
public static $includeInSitemap = FALSE;
public static bool $includeInSitemap = FALSE;
/**
* @brief Can this content be moderated normally from the front-end (will be FALSE for things like Pages and Commerce Products)
*/
public static $canBeModeratedFromFrontend = FALSE;
public static bool $canBeModeratedFromFrontend = FALSE;
/**
* Columns needed to query for search result / stream view
*
* @return array
*/
public static function basicDataColumns()
public static function basicDataColumns(): array
{
return array( 'page_id', 'page_folder_id', 'page_full_path', 'page_default' );
}
/**
* Get URL from index data
*
* @param array $indexData Data from the search index
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param string|NULL $action Action
* @return \IPS\Http\Url
*/
public static function urlFromIndexData( $indexData, $itemData, $action = NULL )
{
if ( \IPS\Application::load('cms')->default AND $itemData['page_default'] AND !$itemData['page_folder_id'] )
{
/* No - that's easy */
return \IPS\Http\Url::internal( '', 'front' );
}
else
{
return \IPS\Http\Url::internal( 'app=cms&module=pages&controller=page&path=' . $itemData['page_full_path'], 'front', 'content_page_path', array( $itemData['page_full_path'] ) );
}
}
/**
* Get HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array $template Optional custom template
* @param array $reactions Reaction Data
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
{
$indexData['index_title'] = \IPS\Member::loggedIn()->language()->addToStack( 'cms_page_' . $indexData['index_item_id'] );
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template, $reactions );
}
/**
* Title for search index
*
* @return string
*/
public function searchIndexTitle()
{
$titles = array();
foreach ( \IPS\Lang::languages() as $lang )
{
$titles[] = $lang->get("cms_page_{$this->id}");
}
return implode( ' ', $titles );
}
/**
* Content for search index
*
* @return string
*/
public function searchIndexContent()
{
if ( $this->type == 'builder' )
{
$content = array();
foreach( \IPS\Db::i()->select( '*', 'cms_page_widget_areas', array( 'area_page_id=?', $this->id ) ) as $widgetArea )
{
foreach ( json_decode( $widgetArea['area_widgets'], TRUE ) as $widget )
{
if ( $widget['app'] == 'cms' and $widget['key'] == 'Wysiwyg' )
{
$content[] = trim( $widget['configuration']['content'] );
}
}
}
return implode( ' ', $content );
}
else
{
/* Remove {tags="foo"} */
$this->content = preg_replace( '#\{([a-z]+?=([\'"]).+?\\2 ?+)}#', '', $this->content );
/* Convert {{}} logic into html tags */
$this->content = preg_replace( '#{{(if|foreach)([^{]+?)}}#', '<ips$1 data="$2">', $this->content );
$this->content = preg_replace( '#{{end(if|foreach)}}#', '</ips$1>', $this->content );
/* Remove custom PHP {{$foo = $this->test();}} */
$this->content = preg_replace( '#{{(.+?)}}#', '', $this->content );
$source = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
$source->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( $this->content ) );
/* And then remove the HTML logic */
$domElemsToRemove = array();
foreach ( $source->getElementsByTagname('ipsif') as $domElement )
{
$domElemsToRemove[] = $domElement;
}
foreach ( $source->getElementsByTagname('ipsforeach') as $domElement )
{
$domElemsToRemove[] = $domElement;
}
foreach( $domElemsToRemove as $domElement )
{
$domElement->parentNode->removeChild($domElement);
}
$content = \IPS\Text\DOMParser::getDocumentBodyContents( $source );
return $content;
}
}
/**
* Search Index Permissions
*
* @return string Comma-delimited values or '*'
* @li Number indicates a group
* @li Number prepended by "m" indicates a member
* @li Number prepended by "s" indicates a social group
*/
public function searchIndexPermissions()
{
try
{
return \IPS\Db::i()->select( 'perm_view', 'core_permission_index', array( "app='cms' AND perm_type='pages' AND perm_type_id=?", $this->id ) )->first();
}
catch ( \UnderflowException $e )
{
return '';
}
}
/**
* Should posting this increment the poster's post count?
*
* @param \IPS\Node\Model|NULL $container Container
* @return void
* @param Model|NULL $container Container
* @return bool
*/
public static function incrementPostCount( \IPS\Node\Model $container = NULL )
public static function incrementPostCount( Model $container = NULL ): bool
{
return FALSE;
}
/**
* Title for Analytics
* Get URL
*
* @return string
* @param string|null $action Action
* @return Url
*/
public function titleForAnalytics(): string
public function url( ?string $action=NULL ): Url
{
try
{
return \IPS\Lang::load( \IPS\Lang::defaultLanguage() )->get( \IPS\cms\Pages\Page::$titleLangPrefix . $this->id );
}
catch( \OutOfRangeException | \UnderflowException )
{
return \IPS\cms\Pages\Page::$titleLangPrefix . $this->id;
}
}
/**
* Get node
*
* @return \IPS\cms\Pages\Page
*/
public function node()
{
$data = array();
foreach ( $this->_data as $k => $v )
{
$data[ 'page_' . $k ] = $v;
}
return \IPS\cms\Pages\Page::constructFromData( $data );
}
/**
* URL
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
*/
public function url( $action = null )
{
return $this->node()->url();
return Page::constructFromData( $this->_data )->url();
}
}
+28 -17
View File
@@ -12,37 +12,48 @@
namespace IPS\cms\Pages;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use InvalidArgumentException;
use IPS\cms\Categories;
use IPS\cms\Records;
use IPS\Db;
use IPS\Http\Url;
use IPS\Patterns\ActiveRecord;
use OutOfRangeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Page Model
*/
class _Router extends \IPS\Patterns\ActiveRecord
class Router extends ActiveRecord
{
/**
* Load Pages Thing based on a URL.
* The URL is sometimes complex to figure out, so this will help
*
* @param \IPS\Http\Url $url URL to load from
* @return \IPS\cms\Pages\Page
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
* @param Url $url URL to load from
* @return mixed
* @throws InvalidArgumentException
* @throws OutOfRangeException
*/
public static function loadFromUrl( \IPS\Http\Url $url )
public static function loadFromUrl( Url $url ): mixed
{
if ( ! isset( $url->queryString['path'] ) )
{
throw new \OutOfRangeException();
throw new OutOfRangeException();
}
$path = $url->queryString['path'];
/* First, we need a page */
$page = \IPS\cms\Pages\Page::loadFromPath( $path );
$page = Page::loadFromPath( $path );
/* What do we have left? */
$whatsLeft = trim( preg_replace( '#' . $page->full_path . '#', '', $path, 1 ), '/' );
@@ -50,23 +61,23 @@ class _Router extends \IPS\Patterns\ActiveRecord
if ( $whatsLeft )
{
/* Check databases */
$databases = iterator_to_array( \IPS\Db::i()->select( '*', 'cms_databases', array( 'database_page_id > 0' ) ) );
$databases = iterator_to_array( Db::i()->select( '*', 'cms_databases', array( 'database_page_id > 0' ) ) );
foreach( $databases as $db )
{
$classToTry = 'IPS\cms\Records' . $db['database_id'];
/* @var $classToTry Records */
try
{
$record = $classToTry::loadFromSlug( $whatsLeft, FALSE, FALSE );
return $record;
return $classToTry::loadFromSlug( $whatsLeft, FALSE, FALSE );
}
catch( \Exception $ex ) { }
catch( Exception $ex ) { }
}
/* Check categories */
foreach( $databases as $db )
{
$classToTry = 'IPS\cms\Categories' . $db['database_id'];
/* @var $classToTry Categories */
try
{
$category = $classToTry::loadFromPath( $whatsLeft );
@@ -76,7 +87,7 @@ class _Router extends \IPS\Patterns\ActiveRecord
return $category;
}
}
catch( \Exception $ex ) { }
catch( Exception $ex ) { }
}
}
else
@@ -86,6 +97,6 @@ class _Router extends \IPS\Patterns\ActiveRecord
}
/* No idea, sorry */
throw new \InvalidArgumentException;
throw new InvalidArgumentException;
}
}
+87 -110
View File
@@ -12,58 +12,86 @@
namespace IPS\cms\Records;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\cms\Databases;
use IPS\cms\Records;
use IPS\Content\Anonymous;
use IPS\Content\Comment as ContentComment;
use IPS\Content\EditHistory;
use IPS\Content\Embeddable;
use IPS\Content\Filter;
use IPS\Content\Hideable;
use IPS\Content\Featurable;
use IPS\Content\Reactable;
use IPS\Content\Reportable;
use IPS\Content\Shareable;
use IPS\Db;
use IPS\Http\Url;
use IPS\Member;
use IPS\Output;
use IPS\Patterns\ActiveRecord;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Post Model
*/
class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \IPS\Content\Hideable, \IPS\Content\Shareable, \IPS\Content\Searchable, \IPS\Content\Embeddable, \IPS\Content\Anonymous
class Comment extends ContentComment implements Embeddable,
Filter
{
use \IPS\Content\Reactable, \IPS\Content\Reportable;
use Reactable,
Reportable,
Anonymous,
Shareable,
EditHistory,
Hideable,
Featurable;
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [Content\Comment] Item Class
*/
public static $itemClass = NULL;
public static ?string $itemClass = NULL;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_database_comments';
public static ?string $databaseTable = 'cms_database_comments';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'comment_';
public static string $databasePrefix = 'comment_';
/**
* @brief Application
*/
public static $application = 'cms';
public static string $application = 'cms';
/**
* @brief Title
*/
public static $title = 'content_comment';
public static string $title = 'content_comment';
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'item' => 'record_id',
'author' => 'user',
'author_name' => 'author',
@@ -83,35 +111,28 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
/**
* @brief Icon
*/
public static $icon = 'comment';
public static string $icon = 'comments';
/**
* @brief [Content\Comment] Comment Template
*/
public static $commentTemplate = array( array( 'display', 'cms', 'database' ), 'commentContainer' );
public static array $commentTemplate = array( array( 'display', 'cms', 'database' ), 'commentContainer' );
/**
* @brief Database ID
*/
public static $customDatabaseId = NULL;
/**
* @brief Store generated search index titles for efficiency
*/
protected static $searchIndexTitles = array();
public static ?int $customDatabaseId = NULL;
/**
* Load Record
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
* @param int|string|null $id ID
* @param string|null $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
* @return ActiveRecord|Comment
* @see Db::build
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
public static function load( int|string|null $id, string $idField=NULL, mixed $extraWhereClause=NULL ): ActiveRecord|static
{
if ( static::commentWhere() !== NULL )
{
@@ -131,10 +152,12 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
/**
* Post count for member
*
* @param \IPS\Member $member The memner
* @return int
* @param Member $member The memner
* @param bool $includeNonPostCountIncreasing
* @param bool $includeHiddenAndPendingApproval
* @return int
*/
public static function memberPostCount( \IPS\Member $member, bool $includeNonPostCountIncreasing = FALSE, bool $includeHiddenAndPendingApproval = TRUE )
public static function memberPostCount( Member $member, bool $includeNonPostCountIncreasing = FALSE, bool $includeHiddenAndPendingApproval = TRUE ): int
{
$where = [];
$where[] = [ 'comment_database_id=?', static::$customDatabaseId ];
@@ -145,7 +168,7 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
$where[] = [ 'comment_approved=1' ];
}
return \IPS\Db::i()->select( 'COUNT(*)', static::$databaseTable, $where )->first();
return Db::i()->select( 'COUNT(*)', static::$databaseTable, $where )->first();
}
/**
@@ -154,7 +177,7 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
* @param int $id Content item to delete from
* @return array
*/
public static function deleteWhereSql( $id )
public static function deleteWhereSql( int $id ) : array
{
return array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=?', $id ), array( static::$databasePrefix . 'database_id=?', static::$customDatabaseId ) );
}
@@ -164,73 +187,36 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
*
* @return string
*/
public static function findByIPWhere()
public static function findByIPWhere() : string
{
return " AND comment_database_id=" . static::$customDatabaseId;
}
/**
* Get items with permisison check
*
* @param array $where Where clause
* @param string $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array $limit Limit clause
* @param string $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index
* @param mixed $includeHiddenItems Include hidden comments? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @return array|NULL|\IPS\Content\Comment If $limit is 1, will return \IPS\Content\Comment or NULL for no results. For any other number, will return an array.
* @param array $where Where clause
* @param string|null $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array|null $limit Limit clause
* @param string|null $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index
* @param mixed $includeHiddenComments
* @param int $queryFlags Select bitwise flags
* @param Member|null $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @return array|NULL|Comment If $limit is 1, will return \IPS\Content\Comment or NULL for no results. For any other number, will return an array.
*/
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=\IPS\Content\Hideable::FILTER_AUTOMATIC, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
public static function getItemsWithPermission( array $where=array(), string $order= null, int|array|null $limit=10, string|null $permissionKey='read', mixed $includeHiddenComments= Filter::FILTER_AUTOMATIC, int $queryFlags=0, Member|null $member= null, bool $joinContainer=FALSE, bool $joinComments=FALSE, bool $joinReviews=FALSE, bool $countOnly=FALSE, array|null $joins= null ): mixed
{
$class = '\IPS\cms\Records' . static::$customDatabaseId;
/* @var $class Records */
$where = $class::getItemsWithPermissionWhere( $where, $permissionKey, $member, $joinContainer );
return parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins );
}
/**
* Get HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array $template Optional custom template
* @param array $reactions Reaction Data
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
{
/* Ensure that the comment title is formatted correctly */
try
{
$databases = \IPS\cms\Databases::databases();
if ( ! isset( static::$searchIndexTitles[ $itemData['primary_id_field'] ] ) )
{
$fields = '\IPS\cms\Fields' . static::$customDatabaseId;
$titleField = $databases[ static::$customDatabaseId ]->field_title;
static::$searchIndexTitles[ $itemData['primary_id_field'] ] = $fields::load( $databases[ static::$customDatabaseId ]->field_title )->displayValue( $itemData['field_' . $databases[ static::$customDatabaseId ]->field_title ] );
}
$itemData['field_' . $databases[ static::$customDatabaseId ]->field_title ] = static::$searchIndexTitles[ $itemData['primary_id_field'] ];
}
catch ( \Exception $ex ) { }
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template, $reactions );
return parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenComments, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins );
}
/**
@@ -238,13 +224,14 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
*
* @return void
*/
public function postCreate()
public function postCreate(): void
{
$this->database_id = static::$customDatabaseId;
$this->save();
$item = $this->item();
if ( $item->hidden() OR ( \IPS\cms\Databases::load( static::$customDatabaseId )->_comment_bump & \IPS\cms\Databases::BUMP_ON_COMMENT ) )
/* @var $item Records */
if ( $item->hidden() OR ( Databases::load( static::$customDatabaseId )->_comment_bump & Databases::BUMP_ON_COMMENT ) )
{
parent::postCreate();
}
@@ -270,7 +257,7 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
*
* @return string
*/
public function html()
public function html(): string
{
$template = static::$commentTemplate[1];
static::$commentTemplate[0][0] = $this->item()->database()->template_display;
@@ -282,9 +269,9 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
* Get URL for doing stuff
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
* @return Url
*/
public function url( $action='find' )
public function url( ?string $action='find' ): Url
{
$url = parent::url( $action );
@@ -303,7 +290,7 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
*
* @return array
*/
public function attachmentIds()
public function attachmentIds(): array
{
$item = $this->item();
$idColumn = $item::$databaseColumnId;
@@ -316,27 +303,17 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
*
* @return array|NULL
*/
public static function commentWhere()
public static function commentWhere(): ?array
{
return array( 'comment_database_id=?', static::$customDatabaseId );
}
/**
* WHERE clause for getting items for ACP overview statistics
*
* @return array
*/
public static function overviewStatisticsWhere()
{
return [ static::commentWhere() ];
}
/**
* Reaction Type
*
* @return string
*/
public static function reactionType()
public static function reactionType(): string
{
$databaseId = static::$customDatabaseId;
return "comment_id_{$databaseId}";
@@ -348,9 +325,9 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\EditHistory,
* @param array $params Additional parameters to add to URL
* @return string
*/
public function embedContent( $params )
public function embedContent( array $params ): string
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'embed.css', 'cms', 'front' ) );
return \IPS\Theme::i()->getTemplate( 'global', 'cms' )->embedRecordComment( $this, $this->item(), $this->url()->setQueryString( $params ) );
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'embed.css', 'cms', 'front' ) );
return Theme::i()->getTemplate( 'global', 'cms' )->embedRecordComment( $this, $this->item(), $this->url()->setQueryString( $params ) );
}
}
@@ -12,41 +12,53 @@
namespace IPS\cms\Records;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\cms\Records;
use IPS\cms\Theme;
use IPS\Content\Item;
use IPS\core\Reports\Report;
use IPS\forums\Topic\Post;
use IPS\Member;
use IPS\Request;
use OutOfRangeException;
use UnexpectedValueException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Records Model
*/
class _CommentTopicSync extends \IPS\cms\Records\Comment
class CommentTopicSync extends Comment
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'pid';
public static string $databaseColumnId = 'pid';
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'forums_posts';
public static ?string $databaseTable = 'forums_posts';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = '';
public static string $databasePrefix = '';
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'item' => 'topic_id',
'author' => 'author_id',
'author_name' => 'author_name',
@@ -64,27 +76,27 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
/**
* @brief Application
*/
public static $application = 'forums';
public static string $application = 'forums';
/**
* @brief Icon
*/
public static $icon = 'comment';
public static string $icon = 'comment';
/**
* @brief [Content] Key for hide reasons
*/
public static $hideLogKey = 'post';
public static ?string $hideLogKey = 'post';
/**
* @brief [Content\Comment] Comment Template
*/
public static $commentTemplate = array( array( 'display', 'cms', 'database' ), 'commentContainer' );
public static array $commentTemplate = array( array( 'display', 'cms', 'database' ), 'commentContainer' );
/**
* @brief Bitwise values for post_bwoptions field
*/
public static $bitOptions = array(
public static array $bitOptions = array(
'post_bwoptions' => array(
'post_bwoptions' => array(
'best_answer' => 1
@@ -95,11 +107,12 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
/**
* Get containing item
*
* @return \IPS\cms\Record
* @return Item
*/
public function item()
public function item(): Item
{
$itemClass = static::$itemClass;
/* @var $itemClass Records */
$lookFor = 'IPS\cms\Records\RecordsTopicSync';
if ( mb_substr( $itemClass, 0, mb_strlen( $lookFor ) ) === $lookFor )
@@ -108,7 +121,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
}
else
{
$id = \IPS\Request::i()->id;
$id = Request::i()->id;
}
return $itemClass::load( $id );
@@ -117,24 +130,24 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
/**
* Delete Comment
*
* @return void
* @return void
*/
public function delete()
public function delete(): void
{
/* We need the classname to be \IPS\forums\Topic\Post so topic/forum sync occur and to ensure post is removed from search index */
$comment = \IPS\forums\Topic\Post::load( $this->pid );
$comment = Post::load( $this->pid );
$comment->delete();
}
/**
* Edit Comment Contents - Note: does not add edit log
*
* @param string $newContent New content
* @return string|NULL
* @param string $newContent New content
* @return void
*/
public function editContents( $newContent )
public function editContents( string $newContent ): void
{
$comment = \IPS\forums\Topic\Post::load( $this->pid );
$comment = Post::load( $this->pid );
$comment->editContents( $newContent );
}
@@ -143,19 +156,19 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
*
* @return string
*/
public function html()
public function html(): string
{
if ( ! empty( $this->pid ) )
{
try
{
$comment = \IPS\forums\Topic\Post::load( $this->pid );
$comment = Post::load( $this->pid );
$template = static::$commentTemplate[1];
static::$commentTemplate[0][0] = $this->item()->database()->template_display;
return \IPS\cms\Theme::i()->getTemplate( static::$commentTemplate[0][0], static::$commentTemplate[0][1], static::$commentTemplate[0][2] )->$template( $comment->item(), $comment );
return Theme::i()->getTemplate( static::$commentTemplate[0][0], static::$commentTemplate[0][1], static::$commentTemplate[0][2] )->$template( $comment->item(), $comment );
}
catch( \OutOfRangeException $e )
catch( OutOfRangeException $e )
{
return parent::html();
}
@@ -165,20 +178,19 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
return parent::html();
}
}
/**
* Report
*
* @param string $reportContent Report content message from member
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report)
* @param \IPS\Member|NULL $member Member making the report (or NULL for loggedIn())
* @param array $guestDetails Details for a guest report
* @return \IPS\core\Reports\Report
* @throws \UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
* @param string $reportContent Report content message from member
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report
* @param Member|null $member Member making the report (or NULL for loggedIn())
* @return Report
* @throws UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
*/
public function report( $reportContent, $reportType=1, $member=NULL, $guestDetails=array() )
public function report( string $reportContent, int $reportType=1, Member|null $member=null ): Report
{
$comment = \IPS\forums\Topic\Post::load( $this->pid );
$comment = Post::load( $this->pid );
return $comment->report( $reportContent, $reportType, $member );
}
@@ -187,7 +199,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
*
* @return array|NULL
*/
public static function commentWhere()
public static function commentWhere(): ?array
{
return NULL;
}
@@ -198,7 +210,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
* @param int $id Content item to delete from
* @return array
*/
public static function deleteWhereSql( $id )
public static function deleteWhereSql( int $id ): array
{
return array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=?', $id ) );
}
@@ -206,12 +218,12 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
/**
* Post count for member
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @param bool $includeNonPostCountIncreasing If FALSE, will skip any posts which would not cause the user's post count to increase
* @param bool $includeHiddenAndPendingApproval If FALSE, will skip any hidden posts, or posts pending approval
* @return int
*/
public static function memberPostCount( \IPS\Member $member, bool $includeNonPostCountIncreasing = FALSE, bool $includeHiddenAndPendingApproval = TRUE )
public static function memberPostCount( Member $member, bool $includeNonPostCountIncreasing = FALSE, bool $includeHiddenAndPendingApproval = TRUE ): int
{
return 0; // We explicitely return 0 because any posts will be counted via the forums application inherently already
}
@@ -221,7 +233,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
*
* @return string
*/
public static function reactionType()
public static function reactionType(): string
{
return 'pid';
}
@@ -231,7 +243,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
*
* @return string
*/
public static function reactionClass()
public static function reactionClass() : string
{
return 'IPS\forums\Topic\Post';
}
File diff suppressed because it is too large. Load diff
+69 -60
View File
@@ -12,58 +12,79 @@
namespace IPS\cms\Records;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use BadMethodCallException;
use IPS\Content\EditHistory;
use IPS\Content\Embeddable;
use IPS\Content\Filter;
use IPS\Content\Hideable;
use IPS\Content\Item;
use IPS\Content\Reactable;
use IPS\Content\Reportable;
use IPS\Content\Review as ContentReview;
use IPS\Content\Shareable;
use IPS\DateTime;
use IPS\Http\Url;
use IPS\Http\Url\Exception;
use IPS\Member;
use IPS\Output;
use IPS\Patterns\ActiveRecord;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Post Model
*/
class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \IPS\Content\Hideable, \IPS\Content\Shareable, \IPS\Content\Searchable, \IPS\Content\Embeddable
class Review extends ContentReview implements Embeddable,
Filter
{
use \IPS\Content\Reactable, \IPS\Content\Reportable;
use Reactable, Reportable, Shareable, EditHistory, Hideable;
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [Content\Comment] Item Class
*/
public static $itemClass = NULL;
public static ?string $itemClass = NULL;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_database_reviews';
public static ?string $databaseTable = 'cms_database_reviews';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'review_';
public static string $databasePrefix = 'review_';
/**
* @brief Application
*/
public static $application = 'cms';
public static string $application = 'cms';
/**
* @brief Title
*/
public static $title = 'content_review';
public static string $title = 'content_review';
/**
* @brief Database Column Map
*/
public static $databaseColumnMap = array(
public static array $databaseColumnMap = array(
'item' => 'item',
'author' => 'author',
'author_name' => 'author_name',
@@ -84,30 +105,28 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
/**
* @brief Icon
*/
public static $icon = 'comment';
public static string $icon = 'star';
/**
* @brief [Content\Comment] Comment Template
*/
public static $commentTemplate = array( array( 'display', 'cms', 'database' ), 'reviewContainer' );
public static array $commentTemplate = array( array( 'display', 'cms', 'database' ), 'reviewContainer' );
/**
* @brief Database ID
*/
public static $customDatabaseId = NULL;
public static ?int $customDatabaseId = NULL;
/**
* Load Record
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
* @param int|string|null $id ID
* @param string|null $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details) - if used will cause multiton store to be skipped and a query always ran
* @return ActiveRecord|Review
* @see \IPS\Db::build
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
public static function load( int|string|null $id, string $idField=NULL, mixed $extraWhereClause=NULL ): ActiveRecord|static
{
if( $extraWhereClause === NULL )
{
@@ -124,52 +143,42 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
/**
* Create first comment (created with content item)
*
* @param \IPS\Content\Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param int $rating The rating (1-5)
* @param string $guestName If author is a guest, the name to use
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @param int|NULL $anonymous NULL for no value, 0 or 1 for a value (0=no, 1=yes)
* @return static
* @throws \InvalidArgumentException
* @param Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param string|null $guestName If author is a guest, the name to use
* @param bool|null $incrementPostCount
* @param Member|null $member The author of this comment. If NULL, uses currently logged in member.
* @param DateTime|null $time The time
* @param string|null $ipAddress The IP address or NULL to detect automatically
* @param int|null $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @param int|null $anonymous NULL for no value, 0 or 1 for a value (0=no, 1=yes)
* @return Review|null
*/
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL, $anonymous=NULL )
public static function create( Item $item, string $comment, bool $first=false, string|null $guestName=null, bool|null $incrementPostCount= null, Member|null $member= null, DateTime|null $time= null, string|null $ipAddress= null, int|null $hiddenStatus= null, int|null $anonymous= null ): static|null
{
$review = parent::create( $item, $comment, $first, $rating, $guestName, $member, $time, $ipAddress, $hiddenStatus, $anonymous );
$review = parent::create( $item, $comment, $first, $guestName, $member, $time, $ipAddress, $hiddenStatus, $anonymous );
$review->database_id = static::$customDatabaseId;
$review->save();
/* Have to do these AFTER database id is set */
/* @var array $databaseColumnMap */
$ratingField = $item::$databaseColumnMap['rating'];
$review->item()->$ratingField = $review->item()->averageReviewRating() ?: 0;
$review->item()->$ratingField = (int) $review->item()->averageReviewRating() ?: 0;
$review->item()->save();
return $review;
}
/**
* WHERE clause for getting items for ACP overview statistics
*
* @return array
*/
public static function overviewStatisticsWhere()
{
return [ static::commentWhere() ];
}
/**
* Return custom where for SQL delete
*
* @param int $id Content item to delete from
* @return array
*/
public static function deleteWhereSql( $id )
public static function deleteWhereSql( int $id ) : array
{
return array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=?', $id ), array( static::$databasePrefix . 'database_id=?', static::$customDatabaseId ) );
}
@@ -179,7 +188,7 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
*
* @return string
*/
public function html()
public function html(): string
{
$template = static::$commentTemplate[1];
static::$commentTemplate[0][0] = $this->item()->database()->template_display;
@@ -191,11 +200,11 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
* Get URL for doing stuff
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
* @throws \BadMethodCallException
* @throws \IPS\Http\Url\Exception
* @return Url
* @throws BadMethodCallException
* @throws Exception
*/
public function url( $action='find' )
public function url( ?string $action='find' ): Url
{
$url = parent::url( $action );
@@ -212,7 +221,7 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
*
* @return array
*/
public function attachmentIds()
public function attachmentIds(): array
{
$item = $this->item();
$idColumn = $item::$databaseColumnId;
@@ -225,7 +234,7 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
*
* @return array|NULL
*/
public static function commentWhere()
public static function commentWhere(): ?array
{
return array( 'review_database_id=?', static::$customDatabaseId );
}
@@ -236,7 +245,7 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
* @return string
* @todo This was implemented improperly before - look into upgrade routine to fix
*/
public static function reactionType()
public static function reactionType(): string
{
$databaseId = static::$customDatabaseId;
return "review_id_{$databaseId}";
@@ -248,9 +257,9 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\EditHistory, \
* @param array $params Additional parameters to add to URL
* @return string
*/
public function embedContent( $params )
public function embedContent( array $params ): string
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'embed.css', 'cms', 'front' ) );
return \IPS\Theme::i()->getTemplate( 'global', 'cms' )->embedRecordReview( $this, $this->item(), $this->url()->setQueryString( $params ) );
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'embed.css', 'cms', 'front' ) );
return Theme::i()->getTemplate( 'global', 'cms' )->embedRecordReview( $this, $this->item(), $this->url()->setQueryString( $params ) );
}
}
+28 -18
View File
@@ -12,41 +12,50 @@
namespace IPS\cms\Records;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\cms\Fields;
use IPS\cms\Records;
use IPS\Login;
use IPS\Member;
use IPS\Patterns\ActiveRecord;
use function defined;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Records Model
*/
class _Revisions extends \IPS\Patterns\ActiveRecord
class Revisions extends ActiveRecord
{
/**
* @brief Multiton Store
*/
protected static $multitons = array();
protected static array $multitons = array();
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_database_revisions';
public static ?string $databaseTable = 'cms_database_revisions';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief Database Prefix
*/
public static $databasePrefix = 'revision_';
public static string $databasePrefix = 'revision_';
/**
* @brief Unpacked data
*/
protected $_dataJson = NULL;
protected ?array $_dataJson = NULL;
/**
* Constructor - Create a blank object with default values
@@ -59,7 +68,7 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
if ( $this->_new )
{
$this->member_id = \IPS\Member::loggedIn()->member_id;
$this->member_id = Member::loggedIn()->member_id;
$this->date = time();
}
}
@@ -70,7 +79,7 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
* @param string $key Key of value to return
* @return mixed
*/
public function get( $key )
public function get( string $key ) : mixed
{
if ( $this->_dataJson === NULL )
{
@@ -88,14 +97,15 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
/**
* Compute differences
*
* @param int $databaseId Database ID
* @param \IPS\cms\Records $record Record
* @param boolean $justChanged Get changed only
* @param int $databaseId Database ID
* @param Records $record Record
* @param boolean $justChanged Get changed only
* @return array
*/
public function getDiffHtmlTables( $databaseId, $record, $justChanged=FALSE )
public function getDiffHtmlTables( int $databaseId, Records $record, bool $justChanged=FALSE ): array
{
$fieldsClass = 'IPS\cms\Fields' . $databaseId;
/* @var $fieldsClass Fields */
$customFields = $fieldsClass::data( 'view' );
$conflicts = array();
@@ -104,7 +114,7 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
{
$key = 'field_' . $field->id;
if( $justChanged === FALSE OR !\IPS\Login::compareHashes( md5( $record->$key ), md5( $this->get( $key ) ) ) )
if( $justChanged === FALSE OR !Login::compareHashes( md5( $record->$key ), md5( $this->get( $key ) ) ) )
{
$conflicts[] = array( 'original' => $this->get( $key ), 'current' => $record->$key, 'field' => $field );
}
@@ -119,9 +129,9 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
* @param string|array $value
* @return void
*/
public function set_data( $value )
public function set_data( string|array $value ) : void
{
$this->_data['data'] = ( \is_array( $value ) ? json_encode( $value ) : $value );
$this->_data['data'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
@@ -129,7 +139,7 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
*
* @return array
*/
public function get_data()
public function get_data() : array
{
return json_decode( $this->_data['data'], TRUE );
}
@@ -12,116 +12,125 @@
namespace IPS\cms\Selector;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\cms\Databases;
use IPS\cms\Pages\Page;
use IPS\Member;
use IPS\Node\Model;
use IPS\Node\Permissions;
use OutOfRangeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Categories Model
*/
class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
class Categories extends Model implements Permissions
{
/**
* @brief Multiton Store
*/
protected static $multitons = array();
protected static array $multitons = array();
/**
* @brief [Records] Custom Database Id
*/
public static $customDatabaseId = NULL;
public static ?int $customDatabaseId = NULL;
/**
* @brief [Records] Content item class
*/
public static $contentItemClass = NULL;
public static ?string $contentItemClass = NULL;
/**
* @brief [ActiveRecord] Database Table
*/
public static $databaseTable = 'cms_database_categories';
public static ?string $databaseTable = 'cms_database_categories';
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'category_';
public static string $databasePrefix = 'category_';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('category_furl_name', 'category_full_path');
protected static array $databaseIdFields = array('category_furl_name', 'category_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnParent = 'parent_id';
public static ?string $databaseColumnParent = 'parent_id';
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnOrder = 'position';
public static ?string $databaseColumnOrder = 'position';
/**
* @brief [Node] Parent Node ID Database Column
*/
public static $parentNodeColumnId = 'database_id';
public static string $parentNodeColumnId = 'database_id';
/**
* @brief [Node] Parent Node Class
*/
public static $parentNodeClass = 'IPS\cms\Selector\Databases';
public static string $parentNodeClass = 'IPS\cms\Selector\Databases';
/**
* @brief [Node] Show forms modally?
*/
public static $modalForms = FALSE;
public static bool $modalForms = FALSE;
/**
* @brief [Node] Sortable?
*/
public static $nodeSortable = TRUE;
public static bool $nodeSortable = TRUE;
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = 'r__categories';
public static string $nodeTitle = 'r__categories';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
*/
public static $titleLangPrefix = 'content_cat_name_';
public static ?string $titleLangPrefix = 'content_cat_name_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
public static ?string $descriptionLangSuffix = '_desc';
/**
* @brief [Node] App for permission index
*/
public static $permApp = 'cms';
public static ?string $permApp = 'cms';
/**
* @brief [Node] Type for permission index
*/
public static $permType = 'categories';
public static ?string $permType = 'categories';
/**
* @brief The map of permission columns
*/
public static $permissionMap = array(
public static array $permissionMap = array(
'view' => 'view',
'read' => 2,
'add' => 3,
@@ -134,31 +143,31 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* @brief [Node] Moderator Permission
*/
public static $modPerm = 'cms';
public static string $modPerm = 'cms';
/**
* @brief [Node] Prefix string that is automatically prepended to permission matrix language strings
*/
public static $permissionLangPrefix = 'perm_content_';
public static string $permissionLangPrefix = 'perm_content_';
/**
* Get title of category
*
* @return string
*/
protected function get__title()
protected function get__title(): string
{
/* If the DB is in a page, and we're not using categories, then return the page title, not the category title for continuity */
if ( ! \IPS\cms\Databases::load( $this->database_id )->use_categories )
if ( ! Databases::load( $this->database_id )->use_categories )
{
if ( ! $this->_catTitle )
{
try
{
$page = \IPS\cms\Pages\Page::loadByDatabaseId( $this->database_id );
$page = Page::loadByDatabaseId( $this->database_id );
$this->_catTitle = $page->_title;
}
catch( \OutOfRangeException $e )
catch( OutOfRangeException $e )
{
$this->_catTitle = parent::get__title();
}
@@ -172,19 +181,39 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
}
}
/**
* @brief [Records] Database objects
*/
protected static array $database = array();
/**
* Returns the database parent
*
* @return Databases
*/
public static function database(): Databases
{
if ( !isset( static::$database[ static::$customDatabaseId ] ) )
{
static::$database[ static::$customDatabaseId ] = Databases::load( static::$customDatabaseId );
}
return static::$database[ static::$customDatabaseId ];
}
/**
* [Node] Get Description
*
* @return string|null
*/
protected function get__description()
protected function get__description(): ?string
{
if ( ! static::database()->use_categories )
{
return static::database()->_description;
}
return ( \IPS\Member::loggedIn()->language()->addToStack('content_cat_name_' . $this->id . '_desc') === 'content_cat_name_' . $this->id . '_desc' ) ? $this->description : \IPS\Member::loggedIn()->language()->addToStack('content_cat_name_' . $this->id . '_desc');
return ( Member::loggedIn()->language()->addToStack('content_cat_name_' . $this->id . '_desc') === 'content_cat_name_' . $this->id . '_desc' ) ? $this->description : Member::loggedIn()->language()->addToStack('content_cat_name_' . $this->id . '_desc');
}
}
+24 -19
View File
@@ -12,71 +12,76 @@
namespace IPS\cms\Selector;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Node\Model;
use IPS\Node\Permissions;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Databases Model
*/
class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
class Databases extends Model implements Permissions
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons = array();
protected static array $multitons = array();
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'database_';
public static string $databasePrefix = 'database_';
/**
* @brief [ActiveRecord] ID Database Table
*/
public static $databaseTable = 'cms_databases';
public static ?string $databaseTable = 'cms_databases';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'database_key', 'database_page_id' );
protected static array $databaseIdFields = array( 'database_key', 'database_page_id' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
public static $databaseColumnOrder = 'id';
public static ?string $databaseColumnOrder = 'id';
/**
* @brief [Node] Sortable?
*/
public static $nodeSortable = FALSE;
public static bool $nodeSortable = FALSE;
/**
* @brief [Node] Node Title
*/
public static $nodeTitle = '';
public static string $nodeTitle = '';
/**
* @brief [Node] Subnode class
*/
public static $subnodeClass = 'IPS\cms\Selector\Categories';
public static ?string $subnodeClass = 'IPS\cms\Selector\Categories';
/**
* @brief The map of permission columns
*/
public static $permissionMap = array(
public static array $permissionMap = array(
'view' => 'view',
'read' => 2,
'add' => 3,
@@ -89,25 +94,25 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* @brief [Node] App for permission index
*/
public static $permApp = 'cms';
public static ?string $permApp = 'cms';
/**
* @brief [Node] Type for permission index
*/
public static $permType = 'databases';
public static ?string $permType = 'databases';
/**
* @brief [Node] Prefix string that is automatically prepended to permission matrix language strings
*/
public static $permissionLangPrefix = 'perm_content_';
public static string $permissionLangPrefix = 'perm_content_';
/**
* @brief [Node] Title prefix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}" as the key
*/
public static $titleLangPrefix = 'content_db_';
public static ?string $titleLangPrefix = 'content_db_';
/**
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = 'desc';
public static ?string $descriptionLangSuffix = 'desc';
}
@@ -12,62 +12,68 @@
namespace IPS\cms\Templates;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Db;
use IPS\Http\Url\Friendly;
use IPS\Patterns\ActiveRecord;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Template Model
*/
class _Container extends \IPS\Patterns\ActiveRecord
class Container extends ActiveRecord
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons = array();
protected static array $multitons = array();
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'container_';
public static string $databasePrefix = 'container_';
/**
* @brief [ActiveRecord] ID Database Table
*/
public static $databaseTable = 'cms_containers';
public static ?string $databaseTable = 'cms_containers';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'container_key' );
protected static array $databaseIdFields = array( 'container_key' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief Have fetched all?
*/
protected static $gotAll = FALSE;
protected static bool $gotAll = FALSE;
/**
* Return all containers
*
* @return array
*/
public static function containers()
public static function containers() : array
{
if ( ! static::$gotAll )
{
foreach( \IPS\Db::i()->select( '*', static::$databaseTable ) as $container )
foreach( Db::i()->select( '*', static::$databaseTable ) as $container )
{
static::$multitons[ $container['container_id'] ] = static::constructFromData( $container );
}
@@ -84,7 +90,7 @@ class _Container extends \IPS\Patterns\ActiveRecord
* @param string $type Type of container (template_block, page, etc)
* @return array of Container objects
*/
public static function getByType( $type )
public static function getByType( string $type ) : array
{
$return = array();
static::containers();
@@ -109,18 +115,20 @@ class _Container extends \IPS\Patterns\ActiveRecord
* Add a new container
*
* @param array $container Template Data
* @return object \IPS\cms\Templates
* @return static
*/
public static function add( $container )
public static function add( array $container ) : static
{
$newContainer = new static;
$newContainer->_new = TRUE;
$newContainer->name = $container['name'];
$newContainer->type = $container['type'];
$newContainer->save();
/* Create a unique key */
if ( empty( $newContainer->key ) )
{
$newContainer->key = 'template__' . \IPS\Http\Url\Friendly::seoTitle( $newContainer->name ) . '.' . $newContainer->id;
$newContainer->key = 'template__' . Friendly::seoTitle( $newContainer->name ) . '.' . $newContainer->id;
$newContainer->save();
}
+173 -151
View File
@@ -13,11 +13,39 @@ namespace IPS\cms;
/* To prevent PHP errors (extending class does not exist) revealing path */
use BadMethodCallException;
use Exception;
use InvalidArgumentException;
use IPS\cms\Pages\Page;
use IPS\Data\Store;
use IPS\Db;
use IPS\Db\Select;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\CheckboxSet;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Http\Url\Internal;
use IPS\Member;
use IPS\Patterns\ActiveRecord;
use IPS\Theme;
use OutOfRangeException;
use SimpleXMLElement;
use UnderflowException;
use UnexpectedValueException;
use XMLReader;
use XMLWriter;
use function count;
use function defined;
use function in_array;
use function is_array;
use function is_numeric;
use function strtolower;
use function substr;
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' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
@@ -32,47 +60,47 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*
* Updating templates: Update where user_edited is FALSE and user_created is FALSE OR user_created is TRUE and original_group is in master groups
*/
class _Templates extends \IPS\Patterns\ActiveRecord
class Templates extends ActiveRecord
{
/**
* @brief [ActiveRecord] Multiton Store
*/
protected static $multitons;
protected static array $multitons;
/**
* @brief [ActiveRecord] Database Prefix
*/
public static $databasePrefix = 'template_';
public static string $databasePrefix = 'template_';
/**
* @brief [ActiveRecord] ID Database Table
*/
public static $databaseTable = 'cms_templates';
public static ?string $databaseTable = 'cms_templates';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static $databaseColumnId = 'id';
public static string $databaseColumnId = 'id';
/**
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'template_key', 'template_id' );
protected static array $databaseIdFields = array( 'template_key', 'template_id' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
protected static array $multitonMap = array();
/**
* @brief Master groups
*/
protected static $masterGroups = NULL;
protected static ?array $masterGroups = NULL;
/**
* @brief Master templates
*/
protected static $masterTemplates = NULL;
protected static ?array $masterTemplates = NULL;
/**
* @brief Retusn all types
@@ -110,7 +138,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
const RETURN_ONLY_TEMPLATE = 64;
/**
* @brief Return just contents of cms_templates ignoring IN_DEV and DESIGNERS' MODE
* @brief Return just contents of cms_templates ignoring IN_DEV
*/
const RETURN_DATABASE_ONLY = 128;
@@ -122,8 +150,8 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/**
* @brief Default database template group names
*/
public static $databaseDefaults = array(
'featured' => 'category_articles',
public static array $databaseDefaults = array(
'index' => 'category_articles',
'form' => 'form',
'display' => 'display',
'listing' => 'listing',
@@ -136,9 +164,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @param string $group Group to load templates from
* @return void
*/
public static function fixTemplateTags( $group )
public static function fixTemplateTags( string $group ) : void
{
$templates = iterator_to_array( \IPS\Db::i()->select( '*', 'cms_templates', array( array( 'template_group=?', $group ) ) )->setKeyField('template_title') );
$templates = iterator_to_array( Db::i()->select( '*', 'cms_templates', array( array( 'template_group=?', $group ) ) )->setKeyField('template_title') );
foreach( $templates as $template )
{
@@ -167,18 +195,18 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( isset( $options['app'] ) and $options['app'] == 'cms' and isset( $options['location'] ) and $options['location'] == 'database' and isset( $options['group'] ) and $options['group'] != $template['template_original_group'] )
{
if ( \in_array( $value, array_keys( $templates ) ) )
if ( in_array( $value, array_keys( $templates ) ) )
{
$options['group'] = $group;
$replace = '{template="' . $value . '" app="' . $options['app'] . '" location="' . $options['location'] . '" group="' . $options['group'] . '" params="' . ( isset($options['params']) ? $options['params'] : NULL ) . '"}';
$replace = '{template="' . $value . '" app="' . $options['app'] . '" location="' . $options['location'] . '" group="' . $options['group'] . '" params="' . ( $options['params'] ?? NULL ) . '"}';
$save['template_content'] = str_replace( $matches[$index][0], $replace, $template['template_content'] );
}
}
if ( \count( $save ) )
if ( count( $save ) )
{
\IPS\Db::i()->update( 'cms_templates', $save, array( 'template_id=?', $template['template_id'] ) );
Db::i()->update( 'cms_templates', $save, array( 'template_id=?', $template['template_id'] ) );
}
}
}
@@ -191,24 +219,24 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* Overloaded so we can force loading by key by default but still retain the template_id field as the primary key so
* save still updates the primary ID.
*
* @see \IPS\Db::build
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @see Db::build
* @param int|string|null $id ID
* @param string|null $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @return static
* @throws \InvalidArgumentException
* @throws \OutOfRangeException
* @return ActiveRecord|static
* @throws InvalidArgumentException
* @throws OutOfRangeException
*/
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
public static function load( int|string|null $id, string $idField=NULL, mixed $extraWhereClause=NULL ): ActiveRecord|static
{
if ( !\is_numeric( $id ) and $idField === NULL )
if ( !is_numeric( $id ) and $idField === NULL )
{
$idField = 'template_key';
}
if ( !\is_numeric( $id ) and ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) )
if ( !is_numeric( $id ) and \IPS\IN_DEV )
{
$templates = \IPS\cms\Theme::i()->getRawTemplates( NULL, NULL, NULL, \IPS\cms\Theme::RETURN_AS_OBJECT );
$templates = Theme::i()->getAllTemplates( '', '', '', \IPS\cms\Theme::RETURN_AS_OBJECT );
if ( isset( $templates[ $id ] ) )
{
@@ -220,7 +248,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
return parent::load( $id, $idField, $extraWhereClause );
}
catch( \OutOfRangeException $ex )
catch( OutOfRangeException $ex )
{
throw $ex;
}
@@ -232,7 +260,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @param string $name Group name from the database
* @return string
*/
public static function readableGroupName( $name )
public static function readableGroupName( string $name ) : string
{
if ( $name === 'js' )
{
@@ -256,7 +284,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
if ( static::$masterTemplates === NULL )
{
static::$masterTemplates = iterator_to_array( \IPS\Db::i()->select( '*, MD5( CONCAT( template_location, \'.\', template_group, \'.\', template_title ) ) as bit_key', 'cms_templates', array( 'template_master=1' ) )->setKeyField( 'bit_key' ) );
static::$masterTemplates = iterator_to_array( Db::i()->select( '*, MD5( CONCAT( template_location, \'.\', template_group, \'.\', template_title ) ) as bit_key', 'cms_templates', array( 'template_master=1' ) )->setKeyField( 'bit_key' ) );
}
return static::$masterTemplates;
@@ -265,9 +293,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/**
* Get the master template version of this template
*
* @return \IPS\cms\Templates|NULL
* @return Templates|null
*/
public function getMasterOfThis()
public function getMasterOfThis(): ?Templates
{
if ( $this->master )
{
@@ -275,12 +303,13 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
$key = md5( $this->location . '.'. $this->original_group . '.' . $this->title );
if ( \in_array( $key, array_keys( static::getMasterTemplates() ) ) )
if ( in_array( $key, array_keys( static::getMasterTemplates() ) ) )
{
/* @var static Templates */
return static::constructFromData( static::$masterTemplates[ $key ] );
}
return NULL;
return null;
}
/**
@@ -294,7 +323,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( static::$masterGroups === NULL )
{
static::$masterGroups = array();
foreach( \IPS\Db::i()->select( 'template_group', static::$databaseTable, array( 'template_master=1 and template_original_group=template_group' ), 'template_group ASC', NULL, 'template_group' ) as $template )
foreach( Db::i()->select( 'template_group', static::$databaseTable, array( 'template_master=1 and template_original_group=template_group' ), 'template_group ASC', NULL, 'template_group' ) as $template )
{
static::$masterGroups[ $template ] = $template;
}
@@ -306,32 +335,28 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/**
* Get all template group
*
* @param int|constant $returnType Determines the content returned
* @param string $returnType Determines the content returned
* @return array
*/
public static function getGroups( $returnType=1 )
public static function getGroups( string $returnType ): array
{
$where = array();
$return = array();
$locations = NULL;
if ( \is_string( $returnType ) )
switch( $returnType )
{
switch( $returnType )
{
case 'all':
$returnType = self::RETURN_ALL;
case 'all':
$returnType = self::RETURN_ALL;
break;
case 'block':
$returnType = self::RETURN_BLOCK;
case 'block':
$returnType = self::RETURN_BLOCK;
break;
case 'page':
$returnType = self::RETURN_PAGE;
case 'page':
$returnType = self::RETURN_PAGE;
break;
case 'database':
$returnType = self::RETURN_DATABASE;
case 'database':
$returnType = self::RETURN_DATABASE;
break;
}
}
if ( $returnType & self::RETURN_ALL )
@@ -357,15 +382,15 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$locations[] = 'database';
}
if ( ! \count( $locations ) )
if ( ! count( $locations ) )
{
throw new \UnexpectedValueException();
throw new UnexpectedValueException();
}
$where[] = array( "template_location IN ('" . implode( "','", $locations ) . "')" );
}
foreach( \IPS\Db::i()->select( 'template_group', static::$databaseTable, $where, 'template_group ASC', NULL, 'template_group' ) as $template )
foreach( Db::i()->select( 'template_group', static::$databaseTable, $where, 'template_group ASC', NULL, 'template_group' ) as $template )
{
$return[ $template ] = $template;
}
@@ -376,16 +401,15 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/**
* Get all templates
*
* @param int|constant $returnType Determines the content returned
* @param int $returnType Determines the content returned
* @return array
*/
public static function getTemplates( $returnType=1 )
public static function getTemplates( int $returnType=1 ): array
{
$where = array();
$return = array();
$locations = NULL;
if ( ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) AND ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) AND ! ( $returnType & self::RETURN_DATABASE_ONLY ) )
if ( ( \IPS\IN_DEV ) AND ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) AND ! ( $returnType & self::RETURN_DATABASE_ONLY ) )
{
$flags = \IPS\cms\Theme::RETURN_AS_OBJECT;
@@ -419,10 +443,10 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
}
$return = \IPS\cms\Theme::i()->getRawTemplates( 'cms', NULL, NULL, $flags );
$return = Theme::i()->getAllTemplates( 'cms', '', '', $flags );
}
if ( ! ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) OR ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) OR ( $returnType & self::RETURN_DATABASE_ONLY ) )
if ( ! ( \IPS\IN_DEV ) OR ( $returnType & self::RETURN_DATABASE_AND_IN_DEV ) OR ( $returnType & self::RETURN_DATABASE_ONLY ) )
{
if ( $returnType & self::RETURN_ALL )
{
@@ -459,15 +483,15 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$locations[] = 'database';
}
if ( !\count( $locations ) )
if ( !count( $locations ) )
{
throw new \UnexpectedValueException();
throw new UnexpectedValueException();
}
$where[] = array( "template_location IN ('" . implode( "','", $locations ) . "')" );
}
foreach ( \IPS\Db::i()->select( '*', static::$databaseTable, $where, 'template_user_edited DESC' ) as $template )
foreach ( Db::i()->select( '*', static::$databaseTable, $where, 'template_user_edited DESC' ) as $template )
{
/* user_edited version is returned first, so only add to the array if the key isn't already in $return */
if ( !isset( $return[ $template['template_key'] ] ) )
@@ -487,14 +511,14 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @param int|string $id ID
* @param string $idField The database column that the $id parameter pertains to
* @param mixed $extraWhereClause Additional where clause(s)
* @return \IPS\Db\Select
* @return Select
*/
protected static function constructLoadQuery( $id, $idField, $extraWhereClause )
protected static function constructLoadQuery( int|string $id, string $idField, mixed $extraWhereClause ): Select
{
$where = array( array( $idField . '=?', $id ) );
if( $extraWhereClause !== NULL )
{
if ( !\is_array( $extraWhereClause ) or !\is_array( $extraWhereClause[0] ) )
if ( !is_array( $extraWhereClause ) or !is_array( $extraWhereClause[0] ) )
{
$extraWhereClause = array( $extraWhereClause );
}
@@ -510,13 +534,13 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @param array $templates Template data from the database
* @return array
*/
public static function buildTree( $templates )
public static function buildTree( array $templates ) : array
{
$return = array();
foreach( $templates as $id => $template )
{
if ( ! ( $template->location === 'database' and ! \IPS\Member::loggedIn()->hasAcpRestriction( 'cms', 'databases', 'databases_use' ) ) )
if ( ! ( $template->location === 'database' and ! Member::loggedIn()->hasAcpRestriction( 'cms', 'databases', 'databases_use' ) ) )
{
$return[$template->location][$template->group][$template->key] = $template;
}
@@ -529,9 +553,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* Add a new template
*
* @param array $template Template Data
* @return object \IPS\cms\Templates
* @return static
*/
public static function add( $template )
public static function add( array $template ) : static
{
$newTemplate = new static;
@@ -548,7 +572,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$newTemplate->save();
/* Create a unique key */
$newTemplate->key = $newTemplate->location . '_' . \IPS\Http\Url\Friendly::seoTitle( $newTemplate->title ) . '_' . $newTemplate->id;
$newTemplate->key = $newTemplate->location . '_' . Friendly::seoTitle( $newTemplate->title ) . '_' . $newTemplate->id;
/* Make sure there's no double __ in there */
foreach( array( 'group', 'title', 'key' ) as $field )
@@ -569,18 +593,18 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return void
*/
public static function deleteCompiledFiles()
public static function deleteCompiledFiles() : void
{
foreach( \IPS\Db::i()->select( '*', 'cms_templates', array( 'template_file_object IS NOT NULL' ) ) as $template )
foreach( Db::i()->select( '*', 'cms_templates', array( 'template_file_object IS NOT NULL' ) ) as $template )
{
try
{
\IPS\File::get( 'core_Theme', $template['template_file_object'] )->delete();
File::get( 'core_Theme', $template['template_file_object'] )->delete();
}
catch( \Exception $ex ) { }
catch( Exception $ex ) { }
}
\IPS\Db::i()->update( 'cms_templates', array( 'template_file_object' => NULL ) );
Db::i()->update( 'cms_templates', array( 'template_file_object' => NULL ) );
}
/**
@@ -588,15 +612,15 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @param bool $appPluginBuild TRUE to customise form for app/plugin builds
* @param array $preSelected Array of default values for checkboxSet
* @return \IPS\Helpers\Form
* @return Form
*/
public static function exportForm( bool $appPluginBuild=FALSE, array $preSelected=array() ): \IPS\Helpers\Form
public static function exportForm( bool $appPluginBuild=FALSE, array $preSelected=array() ): Form
{
$form = new \IPS\Helpers\Form( 'form', $appPluginBuild ? 'save' : 'next' );
$form = new Form( 'form', $appPluginBuild ? 'save' : 'next' );
if( !$appPluginBuild )
{
$form->addMessage( 'cms_templates_export_description', 'ipsMessage ipsMessage_information' );
$form->addButton( 'cms_templates_export_return', 'link', \IPS\Http\Url::internal( "app=cms&module=pages&controller=templates" ) );
$form->addButton( 'cms_templates_export_return', 'link', Url::internal( "app=cms&module=pages&controller=templates" ) );
}
else
{
@@ -605,9 +629,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$templates = [];
foreach( \IPS\cms\Templates::getTemplates( \IPS\cms\Templates::RETURN_DATABASE_ONLY + \IPS\cms\Templates::RETURN_ALL ) as $template )
foreach(Templates::getTemplates( Templates::RETURN_DATABASE_ONLY + Templates::RETURN_ALL ) as $template )
{
$title = \IPS\cms\Templates::readableGroupName( $template->group );
$title = Templates::readableGroupName( $template->group );
if ( $template->location === 'database' )
{
@@ -626,7 +650,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
foreach( $templates as $location => $data )
{
$defaultValue = $preSelected[ 'templates_' . $location ] ?? FALSE;
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'templates_' . $location, $defaultValue, FALSE, array( 'options' => $data ) ) );
$form->add( new CheckboxSet( 'templates_' . $location, $defaultValue, FALSE, array( 'options' => $data ) ) );
}
return $form;
@@ -636,23 +660,23 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* Export requested templates as XML
*
* @param array $values Checkbox set values
* @return \XMLWriter|null
* @return XMLWriter|null
*/
public static function exportAsXml( array $values ):? \XMLWriter
public static function exportAsXml( array $values ):? XMLWriter
{
$templates = array();
foreach( \IPS\cms\Templates::getTemplates( \IPS\cms\Templates::RETURN_DATABASE_ONLY + \IPS\cms\Templates::RETURN_ALL ) as $template )
foreach(Templates::getTemplates( Templates::RETURN_DATABASE_ONLY + Templates::RETURN_ALL ) as $template )
{
$templates[ $template->location ][ $template->group ] = $template->title;
}
$exportTemplates = array();
foreach ( $templates as $location => $data )
foreach ( $templates as $location => $outerData )
{
if ( isset( $values[ 'templates_' . $location ] ) and \count( $values[ 'templates_' . $location ] ) )
if ( isset( $values[ 'templates_' . $location ] ) and count( $values[ 'templates_' . $location ] ) )
{
if ( $location === 'database' )
{
$tmp = \IPS\cms\Theme::i()->getRawTemplates( 'cms', array( 'database' ), array_values( $values[ 'templates_' . $location ] ), \IPS\cms\Theme::RETURN_DATABASE_ONLY + \IPS\cms\Theme::RETURN_ALL );
$tmp = \IPS\cms\Theme::i()->getAllTemplates( 'cms', array( 'database' ), array_values( $values[ 'templates_' . $location ] ), \IPS\cms\Theme::RETURN_DATABASE_ONLY + Theme::RETURN_ALL );
foreach ( $tmp['cms'] as $loc => $data )
{
@@ -670,7 +694,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
else
{
$tmp = \IPS\cms\Theme::i()->getRawTemplates( 'cms', array( $location ), NULL, \IPS\cms\Theme::RETURN_DATABASE_ONLY + \IPS\cms\Theme::RETURN_ALL );
$tmp = \IPS\cms\Theme::i()->getAllTemplates( 'cms', array( $location ), [], \IPS\cms\Theme::RETURN_DATABASE_ONLY + Theme::RETURN_ALL );
foreach ( $tmp['cms'] as $loc => $data )
{
@@ -680,7 +704,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
foreach ( $tdata as $tkey => $tkdata )
{
if ( \in_array( $tkey, $values[ 'templates_' . $location ] ) )
if ( in_array( $tkey, $values[ 'templates_' . $location ] ) )
{
$exportTemplates['cms'][ $location ][ $key ][ $tkey ] = $tkdata;
}
@@ -692,10 +716,10 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
}
if ( \count( $exportTemplates ) )
if ( count( $exportTemplates ) )
{
/* Init */
$xml = new \XMLWriter;
$xml = new XMLWriter;
$xml->openMemory();
$xml->setIndent( TRUE );
$xml->startDocument( '1.0', 'UTF-8' );
@@ -716,7 +740,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
foreach ( $templateData as $k => $v )
{
if ( !\in_array( \substr( $k, 9 ), array( 'content', 'params' ) ) )
if ( !in_array( substr( $k, 9 ), array( 'content', 'params' ) ) )
{
$xml->startAttribute( $k );
$xml->text( $v );
@@ -730,7 +754,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( isset( $templateData[ $field ] ) )
{
$xml->startElement( $field );
if ( preg_match( '/<|>|&/', $templateData[ $field ] ) )
if ( preg_match( '/[<>&]/', $templateData[ $field ] ) )
{
$xml->writeCData( str_replace( ']]>', ']]]]><![CDATA[>', $templateData[ $field ] ) );
}
@@ -763,7 +787,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return boolean
*/
public function isSuitableForCustomWrapper()
public function isSuitableForCustomWrapper(): bool
{
if ( $this->location == 'page' and preg_match( '#<html([^>]+?)?>#', $this->content ) )
{
@@ -781,7 +805,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return boolean
*/
public function isSuitableForBuilderWrapper()
public function isSuitableForBuilderWrapper() : bool
{
if ( $this->location == 'page' and mb_stristr( $this->content, '{template="widgetContainer"' ) )
{
@@ -812,13 +836,11 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/**
* Import User Template XML
*
* @param string $filePath Path to XML file
* @param string $fileContents XML Contents as string
* @throws \UnexpectedValueException
* @throws \BadMethodCallException
* @return \IPS\Http\Url\Internal|null
* @param string|null $filePath Path to XML file
* @param string|null $fileContents XML Contents as string
* @return Internal|null
*/
public static function importUserTemplateXml( string $filePath=NULL, string $fileContents=NULL ):? \IPS\Http\Url\Internal
public static function importUserTemplateXml( string $filePath=NULL, string $fileContents=NULL ):? Internal
{
/* Open XML file */
if( $filePath )
@@ -831,28 +853,28 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if( !$xml->xml( $fileContents, NULL, LIBXML_NONET ) )
{
throw \UnexpectedValueException('bad_xml_string');
throw new UnexpectedValueException('bad_xml_string');
}
}
else
{
throw new \BadMethodCallException('path_or_data_missing');
throw new BadMethodCallException('path_or_data_missing');
}
$conflictKey = md5( microtime() );
$templates = \IPS\cms\Theme::i()->getRawTemplates( 'cms', NULL, NULL, \IPS\cms\Theme::RETURN_DATABASE_ONLY + \IPS\cms\Theme::RETURN_ALL );
$templates = Theme::i()->getAllTemplates( 'cms', NULL, NULL, \IPS\cms\Theme::RETURN_DATABASE_ONLY + Theme::RETURN_ALL );
if ( ! @$xml->read() )
{
throw new \UnexpectedValueException('xml_file_bad_format');
throw new UnexpectedValueException('xml_file_bad_format');
}
$xml->read();
while ( $xml->read() )
{
if ( $xml->name === 'template' )
{
$node = new \SimpleXMLElement( $xml->readOuterXML() );
$node = new SimpleXMLElement( $xml->readOuterXML() );
$attrs = array();
foreach( $node->attributes() as $k => $v )
@@ -864,7 +886,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
foreach( $node->children() as $k => $v )
{
$tryJson = json_decode( $v, TRUE );
$attrs[ $k ] = ( $tryJson ) ? $tryJson : (string) $v;
$attrs[ $k ] = ( $tryJson ) ?: (string) $v;
}
@@ -889,7 +911,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( $exists['template_master'] )
{
/* This is a master template bit, so it's totes fine to create a new one, as it will 'overload' this one */
$obj = new \IPS\cms\Templates;
$obj = new Templates;
$obj->location = $attrs['template_location'];
$obj->group = $attrs['template_group'];
$obj->title = $attrs['template_title'];
@@ -909,7 +931,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( $exists['template_user_edited'] )
{
/* User has edited this, so we should just log a conflict... */
\IPS\Db::i()->insert( 'cms_template_conflicts', array(
Db::i()->insert( 'cms_template_conflicts', array(
'conflict_key' => $conflictKey,
'conflict_item_id' => $exists['template_id'],
'conflict_type' => $attrs['template_type'],
@@ -927,18 +949,18 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/* Not edited, so just update */
try
{
$template = \IPS\cms\Templates::load( $exists['template_key'], 'template_key' );
$template = Templates::load( $exists['template_key'], 'template_key' );
$template->params = $attrs['template_params'];
$template->content = $attrs['template_content'];
$template->save();
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
}
else
{
/* This is an overloaded master template, so just update */
$template = \IPS\cms\Templates::load( $exists['template_key'], 'template_key' );
$template = Templates::load( $exists['template_key'], 'template_key' );
$template->user_edited = 1;
$template->params = $attrs['template_params'];
$template->content = $attrs['template_content'];
@@ -949,7 +971,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( $exists === NULL )
{
$obj = new \IPS\cms\Templates;
$obj = new Templates;
$obj->location = $attrs['template_location'];
$obj->group = $attrs['template_group'];
$obj->title = $attrs['template_title'];
@@ -968,9 +990,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
try
{
$check = \IPS\Db::i()->select( 'template_id', 'cms_templates', array( 'template_key=?', $attrs['template_key'] ) )->first();
$check = Db::i()->select( 'template_id', 'cms_templates', array( 'template_key=?', $attrs['template_key'] ) )->first();
if ( $check['template_master'] == 1 and \in_array( $attrs['template_group'], \IPS\cms\Templates::getMasterGroups() ) )
if ( $check['template_master'] == 1 and in_array( $attrs['template_group'], Templates::getMasterGroups() ) )
{
/* it's OK to overload this with a user edited version */
$obj->user_created = 0;
@@ -979,14 +1001,14 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$key = $attrs['template_key'];
}
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* It doesn't exist! */
$key = $attrs['template_key'];
}
}
$obj->key = ( $key ) ? $key : $obj->location . \IPS\Http\Url\Friendly::seoTitle( $obj->group ) . '_' . \IPS\Http\Url\Friendly::seoTitle( $obj->title ) . '_' . $obj->id;
$obj->key = ( $key ) ?: $obj->location . Friendly::seoTitle( $obj->group ) . '_' . Friendly::seoTitle( $obj->title ) . '_' . $obj->id;
$obj->save();
}
}
@@ -996,10 +1018,10 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
/* Check for conflicts */
$conflicts = \IPS\Db::i()->select( 'count(*)', 'cms_template_conflicts', array( 'conflict_key=?', $conflictKey ) )->first();
$conflicts = Db::i()->select( 'count(*)', 'cms_template_conflicts', array( 'conflict_key=?', $conflictKey ) )->first();
if( $conflicts )
{
return \IPS\Http\Url::internal( 'app=cms&module=pages&controller=templates&do=conflicts&key=' . $conflictKey );
return Url::internal( 'app=cms&module=pages&controller=templates&do=conflicts&key=' . $conflictKey );
}
return NULL;
@@ -1014,7 +1036,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @param boolean $update If updating, files written as master templates
* @return bool Rows imported (true) or none imported (false)
*/
public static function importXml( $file, $offset=NULL, $limit=NULL, $update=TRUE )
public static function importXml( string $file, ?int $offset=NULL, ?int $limit=NULL, bool $update=TRUE ) : bool
{
$i = 0;
$worked = false;
@@ -1026,8 +1048,8 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
if ( $update === TRUE )
{
\IPS\Db::i()->delete( 'cms_templates', array( 'template_master=1' ) );
\IPS\cms\Theme::deleteCompiledTemplate( 'cms' );
Db::i()->delete( 'cms_templates', array( 'template_master=1' ) );
Theme::deleteCompiledTemplate( 'cms' );
}
}
@@ -1037,7 +1059,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
while( $xml->read() )
{
if( $xml->nodeType != \XMLReader::ELEMENT )
if( $xml->nodeType != XMLReader::ELEMENT )
{
continue;
}
@@ -1067,7 +1089,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$save[ 'template_' . $field ] = $xml->getAttribute( 'template_' . $field );
}
\IPS\Db::i()->insert( 'cms_templates', $save );
Db::i()->insert( 'cms_templates', $save );
$worked = true;
}
@@ -1091,10 +1113,10 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return void
*/
public static function updateAllInheritedMasterTemplates()
public static function updateAllInheritedMasterTemplates() : void
{
/* Update existing template bits */
foreach( \IPS\Db::i()->select( '*', 'cms_templates', array( 'template_master=0 and template_user_created=1 and template_user_edited=0' ) ) as $key => $template )
foreach( Db::i()->select( '*', 'cms_templates', array( 'template_master=0 and template_user_created=1 and template_user_edited=0' ) ) as $key => $template )
{
$obj = static::constructFromData( $template );
@@ -1114,14 +1136,14 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$masterByGroup[ $template['template_group'] ][ $template['template_title'] ] = $template;
}
$customGroups = iterator_to_array( \IPS\Db::i()->select( 'template_group, MIN(template_original_group) as template_original_group', static::$databaseTable, array( 'template_master=0 and template_original_group != template_group' ), 'template_group ASC', NULL, 'template_group' )->setKeyField('template_group')->setValueField('template_original_group') );
$customGroups = iterator_to_array( Db::i()->select( 'template_group, MIN(template_original_group) as template_original_group', static::$databaseTable, array( 'template_master=0 and template_original_group != template_group' ), 'template_group ASC', NULL, 'template_group' )->setKeyField('template_group')->setValueField('template_original_group') );
foreach( $customGroups as $customGroup => $masterGroup )
{
if ( $masterGroup )
{
$groupByGroup = array();
foreach ( \IPS\Db::i()->select( '*', static::$databaseTable, array('template_group=?', $customGroup) ) as $bit )
foreach ( Db::i()->select( '*', static::$databaseTable, array('template_group=?', $customGroup) ) as $bit )
{
$groupByGroup[$bit['template_title']] = $bit;
}
@@ -1129,7 +1151,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
/* Now, see if we're missing any */
foreach( $masterByGroup[ $masterGroup ] as $title => $template )
{
if ( ! \in_array( $title, array_keys( $groupByGroup ) ) )
if ( ! in_array( $title, array_keys( $groupByGroup ) ) )
{
$copy = $template;
foreach( array( 'template_id', 'template_file_object', 'bit_key' ) as $field )
@@ -1142,7 +1164,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$copy['template_group'] = $customGroup;
$copy['template_key'] = $copy['template_location'] . '_' . $customGroup . '_' . $title;
\IPS\Db::i()->insert( 'cms_templates', $copy );
Db::i()->insert( 'cms_templates', $copy );
}
}
}
@@ -1155,13 +1177,13 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return void
*/
public function delete()
public function delete(): void
{
\IPS\cms\Theme::deleteCompiledTemplate( 'cms', $this->location, $this->group );
Theme::deleteCompiledTemplate( 'cms', $this->location, $this->group );
if ( $this->user_created )
{
\IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_created=?', $this->key, 1 ) );
Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_created=?', $this->key, 1 ) );
if ( isset( static::$multitons[ $this->id ] ) )
{
@@ -1182,7 +1204,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
{
if ( $this->user_edited )
{
\IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_edited=?', $this->key, 1 ) );
Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_edited=?', $this->key, 1 ) );
if ( isset( static::$multitons[ $this->id ] ) )
{
@@ -1201,7 +1223,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
else
{
throw new \OutOfRangeException('CANNOT_DELETE');
throw new OutOfRangeException('CANNOT_DELETE');
}
}
}
@@ -1211,9 +1233,9 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return string
*/
public function get__inherited()
public function get__inherited() : string
{
if ( $this->user_created and ! \in_array( $this->original_group, static::getMasterGroups() ) )
if ( $this->user_created and ! in_array( $this->original_group, static::getMasterGroups() ) )
{
return 'custom';
}
@@ -1234,7 +1256,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return string
*/
public function get__file_object()
public function get__file_object(): string
{
if ( ! $this->file_object )
{
@@ -1244,12 +1266,12 @@ class _Templates extends \IPS\Patterns\ActiveRecord
if ( $this->type != 'js' and ( mb_stristr( $this->content, "{block=" ) or mb_stristr( $this->content, "{{if" ) or mb_stristr( $this->content, "{media=" ) ) )
{
$functionName = 'css_' . mt_rand();
\IPS\Theme::makeProcessFunction( str_replace( '\\', '\\\\', $content ), $functionName );
Theme::makeProcessFunction( str_replace( '\\', '\\\\', $content ), $functionName );
$functionName = "IPS\Theme\\{$functionName}";
$content = $functionName();
}
$this->file_object = (string) \IPS\File::create( 'cms_Pages', $this->title, $content ?: ' ', 'page_objects', TRUE );
$this->file_object = (string) File::create( 'cms_Pages', $this->title, $content ?: ' ', 'page_objects', TRUE );
parent::save(); # Go to parent save to prevent $this->save() from wiping file objects
}
@@ -1261,22 +1283,22 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*
* @return void
*/
public function save()
public function save(): void
{
/* Trash file object if appropriate */
if ( $this->file_object )
{
try
{
\IPS\File::get( 'cms_Pages', $this->file_object )->delete();
File::get( 'cms_Pages', $this->file_object )->delete();
}
catch ( \Exception $e )
catch ( Exception $e )
{
/* Just to be sure nothing is throw, we don't care too much if it's not deleted */
}
/* Trash all cached page file objects too */
\IPS\cms\Pages\Page::deleteCachedIncludes( $this->file_object );
Page::deleteCachedIncludes( $this->file_object );
$this->file_object = NULL;
}
@@ -1284,7 +1306,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
parent::save();
/* Clear store */
$key = \strtolower( 'template_cms_' . \IPS\cms\Theme::makeBuiltTemplateLookupHash( 'cms', $this->location, $this->group ) . '_' . $this->group );
unset( \IPS\Data\Store::i()->$key );
$key = strtolower( 'template_cms_' . Theme::makeBuiltTemplateLookupHash( 'cms', $this->location, $this->group ) . '_' . $this->group );
unset( Store::i()->$key );
}
}
@@ -12,23 +12,34 @@
namespace IPS\cms\Theme\Advanced;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DirectoryIterator;
use IPS\cms\Theme as SystemTheme;
use IPS\Data\Store;
use IPS\Db;
use IPS\Theme\Dev\Theme as DevTheme;
use IPS\Theme\Setup\Theme as SetupTheme;
use function count;
use function defined;
use function strtolower;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Designer's Mode Theme
*/
class _Theme extends \IPS\cms\Theme
class Theme extends SystemTheme
{
/**
* Get currently logged in member's theme
*
* @return static
* @return static|SetupTheme|DevTheme
*/
public static function i()
public static function i() : static|SetupTheme|DevTheme
{
return new self;
}
@@ -38,9 +49,9 @@ class _Theme extends \IPS\cms\Theme
*
* @return void
*/
public static function export()
public static function export() : void
{
foreach( \IPS\Db::i()->select(
foreach( Db::i()->select(
"*, MD5( CONCAT(template_location, ',', template_group, ',', template_title) ) as bit_key",
'cms_templates',
NULL,
@@ -56,20 +67,20 @@ class _Theme extends \IPS\cms\Theme
*
* @return void
*/
public static function import()
public static function import() : void
{
$seen = array();
foreach( array( 'block', 'page', 'database', 'js', 'css' ) as $location )
{
$templates = iterator_to_array( \IPS\Db::i()->select(
$templates = iterator_to_array( Db::i()->select(
"*, MD5( CONCAT(template_location, ',', template_group, ',', template_title) ) as bit_key",
'cms_templates',
array( 'template_location=?', $location ),
'template_user_edited ASC' /* Ensure we get edited versions, not the master version if one exists */
)->setKeyField('bit_key') );
$master = iterator_to_array( \IPS\Db::i()->select(
$master = iterator_to_array( Db::i()->select(
"*, MD5( CONCAT(template_location, ',', template_group, ',', template_title) ) as bit_key",
'cms_templates',
array( 'template_master=1 and template_location=?', $location )
@@ -79,7 +90,7 @@ class _Theme extends \IPS\cms\Theme
if ( is_dir( $path ) )
{
foreach( new \DirectoryIterator( $path ) as $group )
foreach( new DirectoryIterator( $path ) as $group )
{
if ( $group->isDot() || mb_substr( $group->getFilename(), 0, 1 ) === '.' )
{
@@ -90,7 +101,7 @@ class _Theme extends \IPS\cms\Theme
{
$seen = array();
foreach( new \DirectoryIterator( $path . '/' . $group->getFilename() ) as $file )
foreach( new DirectoryIterator( $path . '/' . $group->getFilename() ) as $file )
{
if ( $file->isDot() || ( mb_substr( $file->getFilename(), -6 ) !== '.phtml' and mb_substr( $file->getFilename(), -3 ) !== '.js' and mb_substr( $file->getFilename(), -4 ) !== '.css' ) )
{
@@ -136,7 +147,7 @@ class _Theme extends \IPS\cms\Theme
if ( ! $templates[ $key ]['template_master'] )
{
$added = TRUE;
\IPS\Db::i()->update( 'cms_templates', array(
Db::i()->update( 'cms_templates', array(
'template_content' => $html,
'template_params' => ( isset($params[1]) ) ? $params[1] : '',
'template_file_object' => NULL
@@ -158,7 +169,7 @@ class _Theme extends \IPS\cms\Theme
}
/* New template */
\IPS\Db::i()->insert( 'cms_templates', array(
Db::i()->insert( 'cms_templates', array(
'template_key' => $location . '_' . $group->getFilename() . '_' . $title,
'template_title' => $title,
'template_desc' => '',
@@ -176,19 +187,19 @@ class _Theme extends \IPS\cms\Theme
}
/* remove compiled version */
$key = \strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( 'cms', $location, $group->getFilename() ) . '_' . static::cleanGroupName( $group->getFilename() ) );
$key = strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( 'cms', $location, $group->getFilename() ) . '_' . static::cleanGroupName( $group->getFilename() ) );
if ( isset( \IPS\Data\Store::i()->$key ) )
if ( isset( Store::i()->$key ) )
{
unset(\IPS\Data\Store::i()->$key);
unset(Store::i()->$key);
}
}
}
/* Remove any templates we've not imported for this location/group */
if ( \count( $seen ) )
if ( count( $seen ) )
{
\IPS\Db::i()->delete( 'cms_templates', array('template_master=0 and template_location=? and template_group=? and ' . \IPS\Db::i()->in( 'template_title', $seen, TRUE ), $location, $group->getFilename() ) );
Db::i()->delete( 'cms_templates', array('template_master=0 and template_location=? and template_group=? and ' . Db::i()->in( 'template_title', $seen, TRUE ), $location, $group->getFilename() ) );
}
}
}
@@ -202,7 +213,7 @@ class _Theme extends \IPS\cms\Theme
* @param string|null $path Path or Filename
* @return string
*/
protected static function _getHtmlPath( $app, $location=null, $path=null )
protected static function _getHtmlPath( string $app, ?string $location=null, ?string $path=null ) : string
{
return rtrim( \IPS\ROOT_PATH . "/themes/cms/{$location}/{$path}", '/' ) . '/';
}
+35 -31
View File
@@ -12,31 +12,40 @@
namespace IPS\cms\Theme;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use BadMethodCallException;
use IPS\cms\Theme;
use IPS\Db;
use IPS\Theme\Template as ThemeTemplate;
use UnderflowException;
use function defined;
use function function_exists;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Magic Template Class for IN_DEV mode
*/
class _Template extends \IPS\Theme\Template
class Template extends ThemeTemplate
{
/**
* @brief Source Folder
*/
public $sourceFolder = NULL;
public ?string $sourceFolder = NULL;
/**
* Extract the data object from the param tag
* @note We always assume the tag is in the format of <ips:template parameters="" data="" />
*
* @param string $tag <ips:template> tag
* @param string $key Data key to fetch
* @return null|array
* @param string $tag <ips:template> tag
* @param string $key Data key to fetch
* @return string|null
*/
public static function extractDataFromTag( $tag, $key="data" )
public static function extractDataFromTag( string $tag, string $key="data" ): ?string
{
if ( ! preg_match( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', $tag, $matches ) )
{
@@ -55,10 +64,10 @@ class _Template extends \IPS\Theme\Template
* Extract the data object from the param tag
* @note We always assume the tag is in the format of <ips:template parameters="" data="" />
*
* @param string $tag <ips:template> tag
* @return null|array
* @param string $tag <ips:template> tag
* @return null|string
*/
public static function extractParamsFromTag( $tag )
public static function extractParamsFromTag( string $tag ): ?string
{
if ( ! preg_match( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', $tag, $matches ) )
{
@@ -76,18 +85,14 @@ class _Template extends \IPS\Theme\Template
* @param string $templateName Template Name
* @return void
*/
public function __construct( $app, $templateLocation, $templateName )
public function __construct( string $app, string $templateLocation, string $templateName )
{
parent::__construct( $app, $templateLocation, $templateName );
$this->app = $app;
$this->templateLocation = $templateLocation;
$this->templateName = $templateName;
if ( \IPS\Theme::designersModeEnabled() )
{
$this->sourceFolder = \IPS\ROOT_PATH . "/themes/cms/{$templateLocation}/{$templateName}/";
}
else if ( \IPS\IN_DEV )
if ( \IPS\IN_DEV )
{
$this->sourceFolder = \IPS\ROOT_PATH . "/applications/{$app}/dev/html/{$templateLocation}/{$templateName}/";
}
@@ -96,36 +101,35 @@ class _Template extends \IPS\Theme\Template
/**
* Magic Method: Call Template Bit
*
* @param string $bit Template Bit Name
* @param array $params Parameters
* @return string
* @param string $bit Template Bit Name
* @param array $params Parameters
*/
public function __call( $bit, $params )
public function __call( string $bit, array $params )
{
if ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() )
if ( \IPS\IN_DEV )
{
/* What are we calling this? */
$functionName = "theme_{$this->app}_{$this->templateLocation}_{$this->templateName}_{$bit}";
/* If it doesn't exist, build it */
if( !\function_exists( 'IPS\\Theme\\'.$functionName ) )
if( !function_exists( 'IPS\\Theme\\'.$functionName ) )
{
/* Find the file */
$file = $this->sourceFolder . $bit . '.phtml';
/* Get the content */
if ( $file === NULL or !file_exists( $file ) )
if ( !file_exists( $file ) )
{
/* Try the database */
try
{
$template = \IPS\Db::i()->select( '*', 'cms_templates', array( 'template_location=? and LOWER(template_group)=? and template_title=?', $this->templateLocation, $this->templateName, $bit ) )->first();
$template = Db::i()->select( '*', 'cms_templates', array( 'template_location=? and LOWER(template_group)=? and template_title=?', $this->templateLocation, $this->templateName, $bit ) )->first();
\IPS\cms\Theme::makeProcessFunction( $template['template_content'], $functionName, $template['template_params'], TRUE );
Theme::makeProcessFunction( $template['template_content'], $functionName, $template['template_params'], TRUE );
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
throw new \BadMethodCallException( 'NO_TEMPLATE_FILE - ' . $file );
throw new BadMethodCallException( 'NO_TEMPLATE_FILE - ' . $file );
}
}
else
@@ -136,14 +140,14 @@ class _Template extends \IPS\Theme\Template
/* Parse the header tag */
if ( !preg_match( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', $output, $matches ) )
{
throw new \BadMethodCallException( 'NO_HEADER - ' . $file );
throw new BadMethodCallException( 'NO_HEADER - ' . $file );
}
/* Strip it */
$output = preg_replace( '/^<ips:template parameters="(.+?)?"([^>]+?)>(\r\n?|\n)/', '', $output );
/* Make it into a lovely function */
\IPS\cms\Theme::makeProcessFunction( $output, $functionName, ( isset( $matches[1] ) ? $matches[1] : '' ), TRUE );
Theme::makeProcessFunction( $output, $functionName, ( $matches[1] ?? '' ), TRUE );
}
}
+136 -159
View File
@@ -12,29 +12,59 @@
namespace IPS\cms;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DirectoryIterator;
use DomainException;
use ErrorException;
use IPS\Data\Store;
use IPS\Db;
use IPS\Dispatcher;
use IPS\Http\Url\Friendly;
use IPS\Log;
use IPS\Login;
use IPS\Member;
use IPS\Theme as SystemTheme;
use IPS\Theme\Dev\Template;
use IPS\Theme\Dev\Theme as DevTheme;
use IPS\Theme\Setup\Theme as SetupTheme;
use ParseError;
use RuntimeException;
use UnexpectedValueException;
use function count;
use function defined;
use function file_put_contents;
use function in_array;
use function is_array;
use function is_string;
use function strtolower;
use const IPS\IPS_FILE_PERMISSION;
use const IPS\IPS_FOLDER_PERMISSION;
use const IPS\IN_DEV;
use const IPS\ROOT_PATH;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* IN_DEV Skin set
*/
class _Theme extends \IPS\Theme
class Theme extends SystemTheme
{
/**
* @brief Template Classes
*/
protected $templates;
protected ?array $templates = null;
/**
* @brief [SkinSets] Templates already loaded and evald via getTemplate()
*/
public static $calledTemplates = array();
public static array $calledTemplates = array();
/**
* @brief Return type for getRawTemplates/getRawCss: Uses DB if not IN_DEV, otherwise uses disk .phtml look up
* @brief Return type for getAllTemplates/getRawCss: Uses DB if not IN_DEV, otherwise uses disk .phtml look up
*/
const RETURN_AS_OBJECT = 32;
@@ -69,71 +99,43 @@ class _Theme extends \IPS\Theme
const RETURN_BLOCK = 2048;
/**
* @brief Return just contents of cms_templates ignoring IN_DEV and DESIGNERS' MODE
* @brief Return just contents of cms_templates ignoring IN_DEV
*/
const RETURN_DATABASE_ONLY = 4096;
/**
* Get currently logged in member's theme
*
* @return \IPS\Theme
* @return static|DevTheme|SystemTheme
*/
public static function i()
public static function i(): static|SetupTheme|DevTheme
{
if ( \IPS\Theme::designersModeEnabled() )
{
return new \IPS\cms\Theme\Advanced\Theme;
}
else
{
return new self;
}
return new self;
}
/**
* Imports templates from the /dev directories.
*
* @param string $location Location (database, block)
* @param string $location Location (database, block)
* @return void
*/
public static function importInDev( $location )
public static function importFromFiles( string $location ): void
{
/* Clear out existing template bits */
\IPS\Db::i()->delete( 'cms_templates', array( 'template_master=1 and template_user_created=0 and template_user_edited=0 AND template_location=?', $location ) );
Db::i()->delete( 'cms_templates', array( 'template_master=1 and template_user_created=0 and template_user_edited=0 AND template_location=?', $location ) );
static::importLocation( $location );
}
/**
* Write IN_DEV files
*
* @param boolean $force TRUE to rewrite templates, FALSE to check if exists first
* @return void
*/
public static function writeInDev( $force=FALSE )
{
foreach( \IPS\Db::i()->select( '*', 'cms_templates', array( 'template_master=?', 1 ) ) as $template )
{
try
{
static::writeTemplate( $template, $force );
}
catch( \RuntimeException $ex )
{
throw new \RuntimeException( $ex->getMessage() );
}
}
}
/**
* Imports templates from the /dev directories.
*
* @param string $location Location (database, block)
* @param string $location Location (database, block)
* @return array
*/
public static function importLocation( $location )
public static function importLocation( string $location ): array
{
$master = iterator_to_array( \IPS\Db::i()->select(
$master = iterator_to_array( Db::i()->select(
"*, MD5( CONCAT(template_location, ',', template_group, ',', template_title) ) as bit_key",
'cms_templates',
array( 'template_master=1 and template_user_created=0 and template_user_edited=0 AND template_location=?', $location )
@@ -144,7 +146,7 @@ class _Theme extends \IPS\Theme
if ( is_dir( $path ) )
{
foreach( new \DirectoryIterator( $path ) as $group )
foreach( new DirectoryIterator( $path ) as $group )
{
if ( $group->isDot() || mb_substr( $group->getFilename(), 0, 1 ) === '.' || $group->getFilename() == 'index.html' )
{
@@ -153,7 +155,7 @@ class _Theme extends \IPS\Theme
if ( $group->isDir() )
{
foreach( new \DirectoryIterator( $path . '/' . $group->getFilename() ) as $file )
foreach( new DirectoryIterator( $path . '/' . $group->getFilename() ) as $file )
{
if ( $file->isDot() || mb_substr( $file->getFilename(), -6 ) !== '.phtml')
{
@@ -175,7 +177,7 @@ class _Theme extends \IPS\Theme
if ( isset( $master[ $key ] ) )
{
if( \IPS\Login::compareHashes( md5( trim( $master[ $key ]['template_content'] ) ), md5( trim( $html ) ) ) )
if( Login::compareHashes( md5( trim( $master[ $key ]['template_content'] ) ), md5( trim( $html ) ) ) )
{
continue;
}
@@ -184,14 +186,14 @@ class _Theme extends \IPS\Theme
$seen[ $group->getFilename() ] = $title;
/* remove compiled version */
$key = \strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( 'cms', $location, $group->getFilename() ) . '_' . static::cleanGroupName( $group->getFilename() ) );
$key = strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( 'cms', $location, $group->getFilename() ) . '_' . static::cleanGroupName( $group->getFilename() ) );
if ( isset( \IPS\Data\Store::i()->$key ) )
if ( isset( Store::i()->$key ) )
{
unset(\IPS\Data\Store::i()->$key);
unset(Store::i()->$key);
}
\IPS\Db::i()->insert( 'cms_templates', array(
Db::i()->insert( 'cms_templates', array(
'template_key' => $location . '_' . $group->getFilename() . '_' . $title,
'template_title' => $title,
'template_desc' => '',
@@ -217,30 +219,30 @@ class _Theme extends \IPS\Theme
* @param array $template Template to write
* @param boolean $force Force overwrite
* @return void
* @throws \RuntimeException
* @throws RuntimeException
*/
public static function writeTemplate( $template, $force=FALSE )
public static function writeTemplate( array $template, bool $force=FALSE ) : void
{
$path = static::_getHtmlPath('cms');
if ( ! is_dir( $path ) )
{
if ( ! mkdir( $path, \IPS\IPS_FOLDER_PERMISSION, TRUE ) )
if ( ! mkdir( $path, IPS_FOLDER_PERMISSION, TRUE ) )
{
throw new \DomainException();
throw new DomainException();
}
}
if ( ! is_dir( $path . '/' . $template['template_location'] ) )
{
mkdir( $path . '/' . $template['template_location'] );
@chmod( $path . '/' . $template['template_location'], \IPS\IPS_FOLDER_PERMISSION );
@chmod( $path . '/' . $template['template_location'], IPS_FOLDER_PERMISSION );
}
if ( ! is_dir( $path . '/' . $template['template_location'] . '/' . $template['template_group'] ) )
{
mkdir( $path . '/' . $template['template_location'] . '/' . $template['template_group'] );
@chmod( $path . '/' . $template['template_location'] . '/' . $template['template_group'], \IPS\IPS_FOLDER_PERMISSION );
@chmod( $path . '/' . $template['template_location'] . '/' . $template['template_group'], IPS_FOLDER_PERMISSION );
}
$fileName = ( $template['template_type'] === 'template' ) ? $template['template_title'] . '.phtml' : $template['template_title'];
@@ -256,13 +258,13 @@ class _Theme extends \IPS\Theme
$write .= $template['template_content'];
if ( @\file_put_contents( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName, $write ) === FALSE )
if ( @file_put_contents( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName, $write ) === FALSE )
{
throw new \RuntimeException( \IPS\Member::loggedIn()->language()->addToStack( 'content_theme_dev_cannot_write_template', FALSE, array( 'sprintf' => array( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName ) ) ) );
throw new RuntimeException( Member::loggedIn()->language()->addToStack( 'content_theme_dev_cannot_write_template', FALSE, array( 'sprintf' => array( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName ) ) ) );
}
else
{
@chmod( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName, \IPS\IPS_FILE_PERMISSION );
@chmod( $path . '/' . $template['template_location'] . '/' . $template['template_group'] . '/' . $fileName, IPS_FILE_PERMISSION );
}
}
}
@@ -270,31 +272,31 @@ class _Theme extends \IPS\Theme
/**
* Get raw templates. Raw means HTML logic and variables are still in {{format}}
*
* @param string|array $app Template app (e.g. core, forum)
* @param string|array $location Template location (e.g. admin,global,front)
* @param string|array $group Template group (e.g. login, share)
* @param int|constant $returnType Determines the content returned
* @param boolean $returnThisSetOnly Returns rows unique to this set only
* @param array|string $app Template app (e.g. core, forum)
* @param array|string $location Template location (e.g. admin,global,front)
* @param array|string $group Template group (e.g. login, share)
* @param int|null $returnType Determines the content returned
* @param boolean $returnThisSetOnly Returns rows unique to this set only
* @return array
*/
public function getRawTemplates( $app=array(), $location=array(), $group=array(), $returnType=null, $returnThisSetOnly=false )
public function getAllTemplates( array|string $app=array(), array|string $location=array(), array|string $group=array(), int $returnType=null, bool $returnThisSetOnly=false ): array
{
$returnType = ( $returnType === null ) ? self::RETURN_ALL : $returnType;
$app = ( \is_string( $app ) AND ! empty( $app ) ) ? array( $app ) : $app;
$location = ( \is_string( $location ) AND ! empty( $location ) ) ? array( $location ) : $location;
$group = ( \is_string( $group ) AND ! empty( $group ) ) ? array( $group ) : $group;
$app = ( is_string( $app ) AND ! empty( $app ) ) ? array( $app ) : $app;
$location = ( is_string( $location ) AND ! empty( $location ) ) ? array( $location ) : $location;
$group = ( is_string( $group ) AND ! empty( $group ) ) ? array( $group ) : $group;
$where = array();
$templates = array();
if ( ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() ) AND ! ( $returnType & static::RETURN_DATABASE_ONLY ) )
if ( ( IN_DEV ) AND ! ( $returnType & static::RETURN_DATABASE_ONLY ) )
{
$fixedLocations = array( 'admin', 'front', 'global' );
$results = array();
$seenKeys = array();
foreach( new \DirectoryIterator( static::_getHtmlPath('cms') ) as $location )
foreach( new DirectoryIterator( static::_getHtmlPath('cms') ) as $location )
{
if ( ! \in_array( $location->getFilename(), $fixedLocations ) AND $location->isDir() AND mb_substr( $location->getFilename(), 0, 1 ) !== '.' )
if ( ! in_array( $location->getFilename(), $fixedLocations ) AND $location->isDir() AND mb_substr( $location->getFilename(), 0, 1 ) !== '.' )
{
$allowedLocations = array();
if ( $returnType & static::RETURN_ONLY_TEMPLATE )
@@ -329,31 +331,31 @@ class _Theme extends \IPS\Theme
}
}
if ( \count( $allowedLocations ) and ! \in_array( $location->getFilename(), $allowedLocations ) )
if ( count( $allowedLocations ) and ! in_array( $location->getFilename(), $allowedLocations ) )
{
continue;
}
foreach( new \DirectoryIterator( static::_getHtmlPath( 'cms', $location->getFilename() ) ) as $file )
foreach( new DirectoryIterator( static::_getHtmlPath( 'cms', $location->getFilename() ) ) as $file )
{
if ( $file->isDir() AND mb_substr( $file->getFilename(), 0, 1 ) !== '.' )
{
if ( $group === NULL or ! \count( $group ) or ( \in_array( $file->getFilename(), $group ) ) )
if ( empty( $group ) or ! count( $group ) or ( in_array( $file->getFilename(), $group ) ) )
{
foreach( new \DirectoryIterator( static::_getHtmlPath( 'cms', $location->getFilename(), $file->getFilename() ) ) as $template )
foreach( new DirectoryIterator( static::_getHtmlPath( 'cms', $location->getFilename(), $file->getFilename() ) ) as $template )
{
if ( ! $template->isDir() AND ( mb_substr( $template->getFilename(), -6 ) === '.phtml' or mb_substr( $template->getFilename(), -4 ) === '.css' or mb_substr( $template->getFilename(), -3 ) === '.js' ) )
{
$title = str_replace( ".phtml", "", $template->getFilename() );
$contents = file_get_contents( static::_getHtmlPath( 'cms', $location->getFilename(), $file->getFilename() ) . '/' . $template->getFilename() );
$key = \IPS\cms\Theme\Template::extractDataFromTag( $contents, 'key' );
$key = $key ? $key : \IPS\Http\Url\Friendly::seoTitle( $file->getFilename() . '_' . $title );
$ogroup = \IPS\cms\Theme\Template::extractDataFromTag( $contents, 'original_group' );
$params = \IPS\cms\Theme\Template::extractParamsFromTag( $contents );
$key = Theme\Template::extractDataFromTag( $contents, 'key' );
$key = $key ?: Friendly::seoTitle( $file->getFilename() . '_' . $title );
$ogroup = Theme\Template::extractDataFromTag( $contents, 'original_group' );
$params = Theme\Template::extractParamsFromTag( $contents );
$container = NULL;
if ( \in_array( $key, $seenKeys ) )
if ( in_array( $key, $seenKeys ) )
{
$key .= filemtime( static::_getHtmlPath( 'cms', $location->getFilename(), $file->getFilename() ) . '/' . $template->getFilename() ) . mt_rand();
}
@@ -364,7 +366,7 @@ class _Theme extends \IPS\Theme
if ( $returnType & static::RETURN_AS_OBJECT )
{
$object = new \IPS\cms\Templates;
$object = new Templates;
$object->key = $key;
$object->title = $title;
$object->desc = NULL;
@@ -372,7 +374,7 @@ class _Theme extends \IPS\Theme
$object->content = $contents;
$object->location = $location->getFilename();
$object->group = $file->getFilename();
$object->original_group = isset( $ogroup ) ? $ogroup : $object->group;
$object->original_group = $ogroup ?? $object->group;
$object->user_created = 0;
$object->user_edited = 0;
$object->params = $params;
@@ -389,7 +391,7 @@ class _Theme extends \IPS\Theme
'template_content' => $contents,
'template_location' => $location->getFilename(),
'template_group' => $file->getFilename(),
'template_original_group' => isset( $ogroup ) ? $ogroup : $file->getFilename(),
'template_original_group' => $ogroup ?? $file->getFilename(),
'template_user_created' => 0,
'template_user_edited' => 0,
'template_params' => $params,
@@ -416,12 +418,12 @@ class _Theme extends \IPS\Theme
}
else
{
if ( \is_array( $location ) AND \count( $location ) )
if ( is_array( $location ) AND count( $location ) )
{
$where[] = "template_location IN ('" . implode( "','", $location ) . "')";
}
if ( \is_array( $group ) AND \count( $group ) )
if ( is_array( $group ) AND count( $group ) )
{
$where[] = "template_group IN ('" . implode( "','", $group ) . "')";
}
@@ -443,7 +445,7 @@ class _Theme extends \IPS\Theme
$originalTemplateNames = array();
$groupMap = array();
foreach( \IPS\Db::i()->select( '*', 'cms_templates', implode( " AND ", $where ), 'template_location, template_group, template_key, template_user_edited ASC' ) as $row )
foreach( Db::i()->select( '*', 'cms_templates', implode( " AND ", $where ), 'template_location, template_group, template_key, template_user_edited ASC' ) as $row )
{
$rawTemplates[] = $row;
$templateNames[ $row['template_original_group'] ][] = $row['template_title'];
@@ -455,9 +457,9 @@ class _Theme extends \IPS\Theme
}
}
if ( \count( $originalGroups ) )
if ( count( $originalGroups ) )
{
foreach( \IPS\Db::i()->select( '*', 'cms_templates', array( array( 'template_original_group = template_group '), array( \IPS\Db::i()->in( 'template_original_group', $originalGroups ) ) ) ) as $row )
foreach( Db::i()->select( '*', 'cms_templates', array( array( 'template_original_group = template_group '), array( Db::i()->in( 'template_original_group', $originalGroups ) ) ) ) as $row )
{
$originalTemplates[ $row['template_group'] . '_' . $row['template_title'] ] = $row;
$originalTemplateNames[ $row['template_group'] ][] = $row['template_title'];
@@ -471,7 +473,7 @@ class _Theme extends \IPS\Theme
{
foreach( $names as $name )
{
if ( ! \in_array( $name, $templateNames[ $group ] ) )
if ( ! in_array( $name, $templateNames[ $group ] ) )
{
$rawTemplates[] = array_merge( $originalTemplates[ $group . '_' . $name ], array( 'template_group' => $groupMap[ $group ]) );
}
@@ -538,37 +540,37 @@ class _Theme extends \IPS\Theme
/**
* Get a template
*
* @param string $group Template Group
* @param string $app Application key (NULL for current application)
* @param string $location Template Location (NULL for current template location)
* @return \IPS\Theme\Template
* @param string $group Template Group
* @param string|null $app Application key (NULL for current application)
* @param string|null $location Template Location (NULL for current template location)
* @return mixed
*/
public function getTemplate( $group, $app=NULL, $location=NULL )
public function getTemplate( string $group, string $app=NULL, string $location=NULL ): mixed
{
/* Do we have an application? */
if( $app === NULL )
{
$app = \IPS\Dispatcher::i()->application->directory;
$app = Dispatcher::i()->application->directory;
}
/* How about a template location? */
if( $location === NULL )
{
$location = \IPS\Dispatcher::i()->controllerLocation;
$location = Dispatcher::i()->controllerLocation;
}
/* Get template */
if ( \IPS\IN_DEV or \IPS\Theme::designersModeEnabled() )
if ( IN_DEV )
{
if ( ! isset( $this->templates[ $app ][ $location ][ $group ] ) )
{
if ( $app === 'cms' AND ! \in_array( $location, array( 'admin', 'front', 'global' ) ) )
if ( $app === 'cms' AND ! in_array( $location, array( 'admin', 'front', 'global' ) ) )
{
$this->templates[ $app ][ $location ][ $group ] = new \IPS\cms\Theme\Template( $app, $location, $group );
$this->templates[ $app ][ $location ][ $group ] = new Template( $app, $location, $group );
}
else
{
$this->templates[ $app ][ $location ][ $group ] = \IPS\Theme::i()->getTemplate( $group, $app, $location );
$this->templates[ $app ][ $location ][ $group ] = Theme::i()->getTemplate( $group, $app, $location );
}
}
@@ -579,42 +581,42 @@ class _Theme extends \IPS\Theme
/* Group is saved clean */
$group = static::cleanGroupName( $group );
if ( ( $app !== 'cms' ) OR ( $app === 'cms' AND \in_array( $location, array( 'admin', 'front', 'global' ) ) ) )
if ( ( $app !== 'cms' ) OR ( in_array( $location, array( 'admin', 'front', 'global' ) ) ) )
{
return \IPS\Theme::i()->getTemplate( $group, $app, $location );
return SystemTheme::i()->getTemplate( $group, $app, $location );
}
$key = \strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
$key = strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
/* Still here */
if ( !\in_array( $key, array_keys( static::$calledTemplates ) ) )
if ( !in_array( $key, array_keys( static::$calledTemplates ) ) )
{
/* If we don't have a compiled template, do that now */
if ( !isset( \IPS\Data\Store::i()->$key ) )
if ( !isset( Store::i()->$key ) )
{
$this->compileTemplates( $app, $location, $group );
}
/* Still no key? */
if ( ! isset( \IPS\Data\Store::i()->$key ) )
if ( ! isset( Store::i()->$key ) )
{
\IPS\Log::log( "Template store key: {$key} missing ({$app}, {$location}, {$group})", "template_store_missing" );
Log::log( "Template store key: {$key} missing ({$app}, {$location}, {$group})", "template_store_missing" );
throw new \ErrorException('template_store_missing ' . $key);
throw new ErrorException('template_store_missing ' . $key);
}
/* Load compiled template */
$compiledGroup = \IPS\Data\Store::i()->$key;
$compiledGroup = Store::i()->$key;
try
{
if ( @eval( $compiledGroup ) === FALSE )
{
throw new \UnexpectedValueException;
throw new UnexpectedValueException;
}
}
catch ( \ParseError $e )
catch ( ParseError $e )
{
throw new \UnexpectedValueException;
throw new UnexpectedValueException;
}
/* Hooks */
@@ -633,14 +635,14 @@ class _Theme extends \IPS\Theme
* Build Templates ready for non IN_DEV use
* This fetches all templates in a group, converts HTML logic into ready to eval PHP and stores as a single PHP class per template group
*
* @param string|array $app Templates app (e.g. core, forum)
* @param string|array $location Templates location (e.g. admin,global,front)
* @param string|array $group Templates group (e.g. forms, members)
* @return void
* @param array|string|null $app Templates app (e.g. core, forum)
* @param array|string|null $location Templates location (e.g. admin,global,front)
* @param array|string|null $group Templates group (e.g. forms, members)
* @return bool
*/
public function compileTemplates( $app=null, $location=null, $group=null )
public function compileTemplates( array|string $app=null, array|string $location=null, array|string $group=null ): bool
{
$templates = $this->getRawTemplates( $app, $location, static::cleanGroupName( $group ) );
$templates = $this->getAllTemplates( $app, $location, static::cleanGroupName( $group ) );
foreach( $templates as $templateApp => $v )
{
@@ -648,37 +650,10 @@ class _Theme extends \IPS\Theme
{
foreach( $templates[ $templateApp ][ $location ] as $group => $bits )
{
/* Any template hooks? */
$templateHooks = array();
if( isset( \IPS\IPS::$hooks[ "\IPS\Theme\class_{$app}_{$location}_{$group}" ] ) AND \IPS\RECOVERY_MODE === FALSE )
{
foreach ( \IPS\IPS::$hooks[ "\IPS\Theme\class_{$app}_{$location}_{$group}" ] as $k => $data )
{
if ( !class_exists( "IPS\Theme\hook{$k}", FALSE ) )
{
try
{
if ( @eval( "namespace IPS\Theme;\n\n" . str_replace( ' extends _HOOK_CLASS_', '', file_get_contents( \IPS\ROOT_PATH . '/' . $data['file'] ) ) ) !== FALSE )
{
$class = "IPS\Theme\hook{$data['class']}";
$templateHooks = array_merge( $templateHooks, $class::hookData() );
}
}
catch ( \ParseError $e ) {}
}
}
}
/* Build all the functions */
$functions = array();
foreach( $templates[ $templateApp ][ $location ][ $group ] as $name => $data )
{
if ( isset( $templateHooks[ $name ] ) )
{
$data['template_content'] = static::themeHooks( $data['template_content'], $templateHooks[ $name ] );
}
$functions[ $name ] = static::compileTemplate( $data['template_content'], $data['template_title'], $data['template_params'], true, false, $app, $location, $group );
}
@@ -696,27 +671,29 @@ EOF;
EOF;
/* Store it */
$key = \strtolower( 'template_cms_' . static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
$key = strtolower( 'template_cms_' . static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
\IPS\Data\Store::i()->$key = $template;
Store::i()->$key = $template;
}
}
}
return TRUE;
}
/**
* Delete compiled templates
* Removes compiled templates bits for all themes that match the arguments
*
* @param string $app Application Directory (core, forums, etc)
* @param string|null $location Template location (front, admin, global, etc)
* @param string|null $group Template group (forms, messaging, etc)
* @param int|null $themeId Limit to a specific theme (and children)
* @param string|null $app Application Directory (core, forums, etc)
* @param array|string|null $location Template location (front, admin, global, etc)
* @param array|string|null $group Template group (forms, messaging, etc)
* @param int|null $themeId Limit to a specific theme (and children)
* @return void
*/
public static function deleteCompiledTemplate( $app=null, $location=null, $group=null, $themeId=null )
public static function deleteCompiledTemplate( string $app=null, array|string|null $location=null, array|string|null $group=null, int $themeId=null ): void
{
$templates = \IPS\cms\Theme::i()->getRawTemplates( $app, $location, $group );
$templates = Theme::i()->getAllTemplates( $app, $location, $group );
foreach( $templates as $templateApp => $v )
{
@@ -724,11 +701,11 @@ EOF;
{
foreach( $templates[ $templateApp ][ $location ] as $group => $bits )
{
$key = \strtolower( 'template_cms_' . static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
$key = strtolower( 'template_cms_' . static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
if ( isset( \IPS\Data\Store::i()->$key ) )
if ( isset( Store::i()->$key ) )
{
unset( \IPS\Data\Store::i()->$key );
unset( Store::i()->$key );
}
}
}
@@ -744,9 +721,9 @@ EOF;
* @param string|null $path Path or Filename
* @return string
*/
protected static function _getHtmlPath( $app, $location=null, $path=null )
protected static function _getHtmlPath( string $app, ?string $location=null, ?string $path=null ) : string
{
return rtrim( \IPS\ROOT_PATH . "/applications/{$app}/dev/html/{$location}/{$path}", '/' ) . '/';
return rtrim( ROOT_PATH . "/applications/{$app}/dev/html/{$location}/{$path}", '/' ) . '/';
}
}
+84 -65
View File
@@ -12,59 +12,37 @@
namespace IPS\cms;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Store;
use IPS\Db;
use IPS\Widget as SystemWidget;
use IPS\Widget\Area;
use function count;
use function defined;
use function in_array;
use function is_array;
use function json_decode;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* CMS Widgets
*/
class _Widget extends \IPS\Widget
class Widget extends SystemWidget
{
/**
* Fetch the configuration for this unqiue ID. Looks in active tables and trash. When a widget is moved, saveOrder is called twice,
* once to remove the widget from column A and again to add it to column B. We store the widget removed from column A into the trash
* table.
*
* @param string $uniqueId Widget's unique ID
* @return array
*/
public static function getConfiguration( $uniqueId )
{
foreach( \IPS\Db::i()->select( '*', 'cms_page_widget_areas' ) as $item )
{
$widgets = json_decode( $item['area_widgets'], TRUE );
if( \is_array( $widgets ) )
{
foreach( $widgets as $widget )
{
if ( $widget['unique'] == $uniqueId )
{
if ( isset( $widget['configuration'] ) )
{
return $widget['configuration'];
}
}
}
}
}
/* Still here? rummage in the trash */
return parent::getConfiguration( $uniqueId );
}
/**
* Delete caches. We need a different name from the parent class otherwise the Pages app hook will get stuck in infinite recursion
*
* @param String $key Widget key
* @param String $app Parent application
* @param String $plugin Parent plugin
* @param String|null $key Widget key
* @param String|null $app Parent application
* @param String|null $plugin Parent plugin
* @return void
*/
static public function deleteCachesForBlocks( $key=NULL, $app=NULL, $plugin=NULL )
static public function deleteCachesForBlocks( string $key=NULL, string $app=NULL, string $plugin=NULL ) : void
{
/* Delete any custom block caches relevant to this plug in */
if ( $key OR $app )
@@ -82,23 +60,36 @@ class _Widget extends \IPS\Widget
}
$blocks = array();
foreach( \IPS\Db::i()->select( '*', 'cms_blocks', $where ) as $row )
foreach( Db::i()->select( '*', 'cms_blocks', $where ) as $row )
{
$blocks[ $row['block_key'] ] = $row;
}
if ( \count( $blocks ) )
if ( count( $blocks ) )
{
$uniqueIds = array();
foreach( \IPS\Db::i()->select( '*', 'cms_page_widget_areas' ) as $item )
foreach( Db::i()->select( '*', 'cms_page_widget_areas' ) as $item )
{
$widgets = json_decode( $item['area_widgets'], TRUE );
if( $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 ) )
{
continue;
}
foreach( $widgets as $widget )
{
if ( ( isset( $widget['app'] ) and $widget['app'] === 'cms' ) and $widget['key'] === 'Blocks' and isset( $widget['unique'] ) and isset( $widget['configuration'] ) and isset( $widget['configuration']['cms_widget_custom_block'] ) )
{
if ( \in_array( $widget['configuration']['cms_widget_custom_block'], array_keys( $blocks ) ) )
if ( in_array( $widget['configuration']['cms_widget_custom_block'], array_keys( $blocks ) ) )
{
$uniqueIds[] = $widget['unique'];
}
@@ -106,15 +97,28 @@ class _Widget extends \IPS\Widget
}
}
foreach( \IPS\Db::i()->select( '*', 'core_widget_areas' ) as $item )
foreach( Db::i()->select( '*', 'core_widget_areas' ) as $item )
{
$widgets = json_decode( $item['widgets'], TRUE );
if( $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 ) )
{
continue;
}
foreach( $widgets as $widget )
{
if ( ( isset( $widget['app'] ) and $widget['app'] === 'cms' ) and $widget['key'] === 'Blocks' and isset( $widget['unique'] ) and isset( $widget['configuration'] ) and isset( $widget['configuration']['cms_widget_custom_block'] ) )
{
if ( \in_array( $widget['configuration']['cms_widget_custom_block'], array_keys( $blocks ) ) )
if ( in_array( $widget['configuration']['cms_widget_custom_block'], array_keys( $blocks ) ) )
{
$uniqueIds[] = $widget['unique'];
}
@@ -122,15 +126,15 @@ class _Widget extends \IPS\Widget
}
}
if ( \count( $uniqueIds ) )
if ( count( $uniqueIds ) )
{
$widgetRow = \IPS\Db::i()->select( '*', 'core_widgets', array( '`key`=? and app=?', 'Blocks', 'cms' ) )->first();
$widgetRow = Db::i()->select( '*', 'core_widgets', array( '`key`=? and app=?', 'Blocks', 'cms' ) )->first();
if ( ! empty( $widgetRow['caches'] ) )
{
$caches = json_decode( $widgetRow['caches'], TRUE );
if ( \is_array( $caches ) )
if ( is_array( $caches ) )
{
$save = $caches;
foreach( $caches as $key => $time )
@@ -139,9 +143,9 @@ class _Widget extends \IPS\Widget
{
if ( mb_stristr( $key, 'widget_Blocks_' . $id ) )
{
if ( isset( \IPS\Data\Store::i()->$key ) )
if ( isset( Store::i()->$key ) )
{
unset( \IPS\Data\Store::i()->$key );
unset( Store::i()->$key );
}
unset( $save[ $key ] );
@@ -149,10 +153,10 @@ class _Widget extends \IPS\Widget
}
}
if ( \count( $save ) !== \count( $caches ) )
if ( count( $save ) !== count( $caches ) )
{
\IPS\Db::i()->update( 'core_widgets', array( 'caches' => ( \count( $save ) ? json_encode( $save ) : NULL ) ), array( 'id=?', $widgetRow['id'] ) );
unset( \IPS\Data\Store::i()->widgets );
Db::i()->update( 'core_widgets', array( 'caches' => ( count( $save ) ? json_encode( $save ) : NULL ) ), array( 'id=?', $widgetRow['id'] ) );
unset( Store::i()->widgets );
}
}
}
@@ -166,25 +170,40 @@ class _Widget extends \IPS\Widget
*
* @return array
*/
public static function getUniqueIds()
public static function getUniqueIds(): array
{
$uniqueIds = parent::getUniqueIds();
foreach ( \IPS\Db::i()->select( '*', 'cms_page_widget_areas' ) as $row )
foreach ( Db::i()->select( '*', 'cms_page_widget_areas' ) as $row )
{
$data = json_decode( $row['area_widgets'], TRUE );
if ( is_countable( $data ) AND \count( $data ) )
if( $row['area_widgets'] )
{
foreach( $data as $widget )
$data = json_decode( $row['area_widgets'], TRUE );
if ( is_countable( $data ) AND count( $data ) )
{
if ( isset( $widget['unique'] ) )
{
foreach( $data as $widget )
{
if ( isset( $widget['unique'] ) )
{
$uniqueIds[] = $widget['unique'];
}
}
}
}
if( $row['area_tree'] )
{
$area = new Area( json_decode( $row['area_tree'], true ), $row['area_area'] );
foreach( $area->getAllWidgets() as $widget )
{
if( isset( $widget['unique'] ) )
{
$uniqueIds[] = $widget['unique'];
}
}
}
}
return $uniqueIds;
return array_unique( $uniqueIds );
}
}