Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+81 -13
View File
@@ -2,9 +2,10 @@
/**
* @brief Block Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 19 Feb 2014
* @version SVN_VERSION_NUMBER
*/
@@ -47,6 +48,11 @@ class _Block extends \IPS\Node\Model implements \IPS\Node\Permissions
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('block_key');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
@@ -239,7 +245,7 @@ class _Block extends \IPS\Node\Model implements \IPS\Node\Permissions
unset( \IPS\Data\Store::i()->$functionName );
/* Still here */
if ( ! in_array( $functionName, array_keys( self::$calledTemplates ) ) )
if ( ! in_array( $functionName, array_keys( static::$calledTemplates ) ) )
{
if ( ! isset( \IPS\Data\Store::i()->$functionName ) )
{
@@ -252,8 +258,10 @@ class _Block extends \IPS\Node\Model implements \IPS\Node\Permissions
{
try
{
$template = \IPS\cms\Templates::load( $this->template );
return call_user_func_array( array( \IPS\cms\Theme::i()->getTemplate( $template->group, 'cms', $template->location ), $template->title ), $args );
$template = \IPS\cms\Templates::load( $this->template );
$object = \IPS\cms\Theme::i()->getTemplate( $template->group, 'cms', $template->location );
$title = $template->title;
return $object->$title( ...$args );
}
catch( \OutOfRangeException $ex )
{
@@ -280,10 +288,10 @@ EOF;
$class = "\IPS\Theme\\class_{$functionName}";
/* Init */
self::$calledTemplates[ $functionName ] = new $class();
static::$calledTemplates[ $functionName ] = new $class();
}
return call_user_func_array( array( self::$calledTemplates[ $functionName ], 'run' ), $args );
return static::$calledTemplates[ $functionName ]->run( ...$args );
}
/**
@@ -623,7 +631,12 @@ EOF;
}
else
{
$form->add( new \IPS\Helpers\Form\Codemirror( 'block_content', $this->content, FALSE, array(), NULL, NULL, NULL, 'block_content' ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'block_content', htmlentities( $this->content, \IPS\HTMLENTITIES, 'UTF-8', TRUE ), FALSE, array(), function( $val ) {
if ( mb_strpos( $val, '{block="' . \IPS\Request::i()->block_key . '"}' ) !== FALSE )
{
throw new \DomainException('block_content_recursive_error');
}
}, NULL, NULL, 'block_content' ) );
}
if ( $block_type === 'plugin' and ! $this->id )
@@ -653,6 +666,12 @@ EOF;
}
$form->hiddenValues['template_params'] = ( $this->id ) ? $this->template_params : '';
/* If we are editing, we can save and reload */
if( $this->id )
{
$form->canSaveAndReload = true;
}
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'templates/view.css', 'cms', 'admin' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'blocks/form.css', 'cms', 'admin' ) );
@@ -682,6 +701,10 @@ EOF;
}
$config = array();
if( isset( $values['language_key'] ) )
{
$config['language_key'] = $values['language_key'];
}
if( isset( $values['block_name'] ) )
{
@@ -704,11 +727,12 @@ EOF;
{
if ( is_array( $values['block_name'] ) )
{
$values['block_key'] = \IPS\Http\Url::seoTitle( $values['block_name'][ key( $values['block_name'] ) ] );
reset( $values['block_name'] );
$values['block_key'] = \IPS\Http\Url\Friendly::seoTitle( $values['block_name'][ key( $values['block_name'] ) ] );
}
else
{
$values['block_key'] = \IPS\Http\Url::seoTitle( $values['block_name'] );
$values['block_key'] = \IPS\Http\Url\Friendly::seoTitle( $values['block_name'] );
}
/* Now test it */
@@ -758,6 +782,11 @@ EOF;
{
$theValue[ $eachKey ] = $eachValue;
}
elseif ( $eachKey === 'start' or $eachKey === 'end' )
{
/* date ranges */
$theValue[ $eachKey ] = $eachValue;
}
else
{
$theValue[] = $eachValue;
@@ -816,7 +845,7 @@ EOF;
$templateArray = array(
'key' => 'template_' . $templateBit['template_name'] . '.' . uniqid(),
'title' => str_replace( '-', '_', \IPS\Http\Url::seoTitle( $values['block_save_as_template_name'] ? $values['block_save_as_template_name'] : $templateBit['template_name'] . '_' . \IPS\Member::loggedIn()->language()->get('copy_noun') ) ),
'title' => str_replace( '-', '_', \IPS\Http\Url\Friendly::seoTitle( $values['block_save_as_template_name'] ? $values['block_save_as_template_name'] : $templateBit['template_name'] . '_' . \IPS\Member::loggedIn()->language()->get('copy_noun') ) ),
'desc' => null,
'content' => $values['block_content'],
'location' => 'block',
@@ -836,7 +865,7 @@ EOF;
$templateArray = array(
'key' => 'template_' . $template->name . '.' . uniqid(),
'title' => str_replace( '-', '_', \IPS\Http\Url::seoTitle( $values['block_save_as_template_name'] ? $values['block_save_as_template_name'] : $template->name . '_' . \IPS\Member::loggedIn()->language()->get('copy_noun') ) ),
'title' => str_replace( '-', '_', \IPS\Http\Url\Friendly::seoTitle( $values['block_save_as_template_name'] ? $values['block_save_as_template_name'] : $template->name . '_' . \IPS\Member::loggedIn()->language()->get('copy_noun') ) ),
'desc' => null,
'content' => $values['block_content'],
'location' => 'block',
@@ -950,4 +979,43 @@ EOF;
throw new \OutOfRangeException;
}
/**
* [ActiveRecord] Duplicate
*
* @return void
*/
public function __clone()
{
parent::__clone();
if( $this->skipCloneDuplication === TRUE )
{
return;
}
/* Copy language bits to make them unique */
$config = $this->_plugin_config;
$wordKey = 'widget_title_' . md5( uniqid() );
if ( isset( $config['language_key'] ) )
{
try
{
foreach( \IPS\Db::i()->select( '*', 'core_sys_lang_words', array( 'word_key=?', $config['language_key'] ) ) as $row )
{
unset( $row['word_id'] );
$row['word_key'] = $wordKey;
\IPS\Db::i()->insert( 'core_sys_lang_words', $row );
}
}
catch( \Exception $ex ) { }
$config['language_key'] = $wordKey;
$this->plugin_config = json_encode( $config );
}
$this->key .= '_' . uniqid();
$this->save();
}
}
@@ -2,9 +2,10 @@
/**
* @brief Block Container Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 19 Feb 2014
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Container extends \IPS\Node\Model
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array('container_key');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
@@ -2,9 +2,10 @@
/**
* @brief Categories Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 8 April 2014
* @version SVN_VERSION_NUMBER
*/
@@ -58,6 +59,11 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected static $databaseIdFields = array('category_furl_name', 'category_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -146,7 +152,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* @brief [Node] Prefix string that is automatically prepended to permission matrix language strings
*/
public static $permissionLangPrefix = 'perm_content_';
public static $permissionLangPrefix = 'perm_content_category_';
/**
* @brief [Page] Loaded pages from paths
@@ -187,7 +193,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
catch ( \OutOfRangeException $e )
{
/* May contain a record name */
$where = ( $databaseId === NULL ) ? array( '? LIKE CONCAT( category_full_path, \'%\')', $path ) : array( 'category_database_id=? AND ? LIKE CONCAT( category_full_path, \'%\')', $databaseId, $path );
$where = ( $databaseId === NULL ) ? array( '? LIKE CONCAT( TRIM(TRAILING \'/\' FROM category_full_path), \'/\', \'%\')', rtrim( $path, '/' ) . '/' ) : array( 'category_database_id=? AND ? LIKE CONCAT( TRIM(TRAILING \'/\' FROM category_full_path), \'/\', \'%\')', $databaseId, rtrim( $path, '/' ) . '/' );
foreach(
\IPS\Db::i()->select(
@@ -253,6 +259,8 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static function roots( $permissionCheck='view', $member=NULL, $where=array(), $databaseId=null )
{
$permissionCheck = ( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation === 'admin' ) ? NULL : $permissionCheck;
if ( $databaseId === NULL and static::$customDatabaseId !== NULL )
{
$databaseId = static::$customDatabaseId;
@@ -266,6 +274,23 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return parent::roots( $permissionCheck, $member, $where );
}
/**
* [Node] Fetch Child 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 bool $subnodes Include subnodes? NULL to *only* check subnodes
* @param array|NULL $skip Children IDs to skip
* @param mixed $_where Additional WHERE clause
* @return array
*/
public function children( $permissionCheck='view', $member=NULL, $subnodes=TRUE, $skip=null, $_where=array() )
{
$permissionCheck = ( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation === 'admin' ) ? NULL : $permissionCheck;
return parent::children( $permissionCheck, $member, $subnodes, $skip, $_where );
}
/**
* Resets a category path
*
@@ -335,9 +360,16 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
public function __clone()
{
parent::__clone();
if( $this->skipCloneDuplication === TRUE )
{
return;
}
$this->furl_name .= '_' . $this->id;
$this->save();
$this->setFullPath();
}
/**
@@ -401,37 +433,31 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return true;
}
if ( \IPS\Request::i()->category_parent_id == 0 and \IPS\cms\Categories::isFurlCollision( $val ) )
{
throw new \InvalidArgumentException('content_cat_furl_collision');
}
try
{
if ( \IPS\Request::i()->category_parent_id == 0 and \IPS\cms\Categories::isFurlCollision( $val ) )
{
throw new \InvalidArgumentException('content_cat_furl_collision');
}
$cat = \IPS\cms\Categories::load( $val, 'category_furl_name' );
/* We have a cat, is it in a different database? */
if ( $cat->database_id != \IPS\Request::i()->database_id )
{
return true;
}
if ( isset( \IPS\Request::i()->id ) )
{
if ( $cat->id != \IPS\Request::i()->id )
{
throw new \InvalidArgumentException('content_cat_furl_not_unique');
}
}
else
$cat = \IPS\Db::i()->select( '*', 'cms_database_categories', array( 'category_database_id=? and category_parent_id=? and category_furl_name=?', \IPS\Request::i()->database_id, \IPS\Request::i()->category_parent_id, $val ) )->first();
}
catch( \UnderflowException $ex )
{
/* Nuffink matches */
return true;
}
if ( isset( \IPS\Request::i()->id ) )
{
if ( $cat['category_id'] != \IPS\Request::i()->id )
{
throw new \InvalidArgumentException('content_cat_furl_not_unique');
}
}
catch ( \OutOfRangeException $e )
else
{
/* Key is OK as select failed */
return true;
throw new \InvalidArgumentException('content_cat_furl_not_unique');
}
return true;
@@ -462,6 +488,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
$form->add( new \IPS\Helpers\Form\YesNo( 'category_show_records', $this->id ? $this->show_records : TRUE, FALSE, array(), NULL, NULL, NULL, 'category_show_records' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'category_allow_rating', $this->allow_rating, FALSE, array(), NULL, NULL, NULL, 'category_allow_rating' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'category_has_perms', $this->has_perms, FALSE, array(), NULL, NULL, NULL, 'category_has_perms' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'category_can_view_others', $this->id ? $this->can_view_others : TRUE, FALSE, array(), NULL, NULL, NULL, 'category_can_view_others' ) );
$form->addHeader('cms_categories_header_display');
$templatesList = array( 0 => \IPS\Member::loggedIn()->language()->addToStack('cms_categories_use_database') );
@@ -525,7 +552,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
}
), function( $val )
{
if ( ! $val and \IPS\Request::i()->database_forum_record_checkbox )
if ( ! $val and \IPS\Request::i()->category_forum_override and \IPS\Request::i()->database_forum_record_checkbox )
{
throw new \InvalidArgumentException('cms_database_no_forum_selected');
}
@@ -586,13 +613,13 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( ! $this->id )
{
$this->_updatePaths = TRUE;
$this->save();
}
if ( isset( $values['category_name'] ) AND is_array( $values['category_name'] ) )
{
$name = current( $values['category_name'] );
reset( $values['category_name'] );
$name = $values['category_name'][ \IPS\Lang::defaultLanguage() ];
}
else if( isset( $values['category_name'] ) )
{
@@ -617,7 +644,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
try
{
$cat = \IPS\cms\Categories::load( \IPS\Http\Url::seoTitle( $name ), 'category_furl_name' );
$cat = \IPS\cms\Categories::load( \IPS\Http\Url\Friendly::seoTitle( $name ), 'category_furl_name' );
/* We have a cat, is it in a different database? */
if ( $cat->database_id != \IPS\Request::i()->database_id )
@@ -638,23 +665,23 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
$ok = TRUE;
}
if ( \IPS\Request::i()->category_parent_id == 0 and \IPS\cms\Categories::isFurlCollision( \IPS\Http\Url::seoTitle( $name ) ) )
if ( \IPS\Request::i()->category_parent_id == 0 and \IPS\cms\Categories::isFurlCollision( \IPS\Http\Url\Friendly::seoTitle( $name ) ) )
{
$ok = FALSE;
}
if ( $ok === TRUE )
{
$values['furl_name'] = \IPS\Http\Url::seoTitle( $name );
$values['furl_name'] = \IPS\Http\Url\Friendly::seoTitle( $name );
}
else
{
$values['furl_name'] = $this->id . '_' . \IPS\Http\Url::seoTitle( $name );
$values['furl_name'] = $this->id . '_' . \IPS\Http\Url\Friendly::seoTitle( $name );
}
}
else if( isset( $values['category_furl_name'] ) )
{
$values['furl_name'] = \IPS\Http\Url::seoTitle( $values['category_furl_name'] );
$values['furl_name'] = \IPS\Http\Url\Friendly::seoTitle( $values['category_furl_name'] );
}
if( array_key_exists( 'category_furl_name_choice', $values ) )
@@ -782,6 +809,17 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Db::i()->delete( 'core_tags', array( 'tag_aap_lookup=?', $aap ) );
\IPS\Db::i()->delete( 'core_tags_perms', array( 'tag_perm_aap_lookup=?', $aap ) );
/* Delete category follows */
\IPS\Db::i()->delete( 'core_follow', array( "follow_app=? AND follow_area=? AND follow_rel_id=?", static::$permApp, static::$permType . $this->database()->id, $this->_id ) );
if( $this->hasChildren() )
{
foreach ( $this->children( NULL, NULL, TRUE ) as $child )
{
$child->delete();
}
}
parent::delete();
}
@@ -794,7 +832,17 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
* @brief Cached title
*/
protected $_catTitle = NULL;
/**
* @brief Cached title for strip tags version
*/
protected $_stripTagsCatTitle = NULL;
/**
* @brief Cached title for strip tags version
*/
protected $_catTitleLangKey = NULL;
/**
* @brief Last comment time
*/
@@ -954,7 +1002,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if( $this->_url === NULL )
{
if ( \IPS\cms\Pages\Page::$currentPage )
if ( \IPS\cms\Pages\Page::$currentPage and static::database()->page_id == \IPS\cms\Pages\Page::$currentPage->id )
{
$pagePath = \IPS\cms\Pages\Page::$currentPage->full_path;
}
@@ -990,7 +1038,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*
* @param array $indexData Data from the search index
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @return \IPS\Http\Url
*/
public static function urlFromIndexData( $indexData, $itemData, $containerData )
@@ -1014,6 +1062,47 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return \IPS\Http\Url::internal( "app=cms&module=pages&controller=page&path=" . $recordClass::$pagePath, 'front', 'content_page_path', '' );
}
}
/**
* Get title 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 array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @return \IPS\Http\Url
*/
public static function titleFromIndexData( $indexData, $itemData, $containerData )
{
$recordClass = $indexData['index_class'];
if ( in_array( 'IPS\Content\Comment', class_parents( $recordClass ) ) )
{
$recordClass = $recordClass::$itemClass;
}
if ( $recordClass::database()->use_categories )
{
return \IPS\Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $indexData['index_container_id'] );
}
else
{
return $recordClass::database()->_title;
}
}
/**
* Get Page Title for use in <title> tag
*
* @return string
*/
public function pageTitle()
{
if ( $this->page_title )
{
return $this->page_title;
}
return $this->database()->pageTitle();
}
/**
* Get title of category
@@ -1045,6 +1134,68 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return parent::get__title();
}
}
/**
* [Node] Get Title language key, not added to a language stack
*
* @return string|null
*/
protected function get__titleLanguageKey()
{
/* 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 ( ! $this->_catTitleLangKey )
{
try
{
$page = \IPS\cms\Pages\Page::loadByDatabaseId( $this->database_id );
$this->_catTitleLangKey = $page->_titleLanguageKey;
}
catch( \OutOfRangeException $e )
{
$this->_catTitleLangKey = parent::get__titleLanguageKey();
}
}
return $this->_catTitleLangKey;
}
else
{
return parent::get__titleLanguageKey();
}
}
/**
* Node titles can contain HTML. Apparently.
*
* @return string
*/
public function get__stripTagsTitle()
{
/* 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 ( ! $this->_stripTagsCatTitle )
{
try
{
$page = \IPS\cms\Pages\Page::loadByDatabaseId( $this->database_id );
$this->_stripTagsCatTitle = $page->_stripTagsTitle;
}
catch( \OutOfRangeException $e )
{
$this->_stripTagsCatTitle = parent::get__stripTagsTitle();
}
}
return $this->_stripTagsCatTitle;
}
else
{
return parent::get__stripTagsTitle();
}
}
/**
* [Node] Get Description
@@ -1068,6 +1219,11 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected function get__items()
{
if ( ! $this->can_view_others and !\IPS\Member::loggedIn()->modPermission( 'can_content_view_others_records' ) )
{
return \IPS\Db::i()->select('count(*)', 'cms_custom_database_' . $this->database_id, array("record_future_date=0 AND category_id=? AND record_approved=1 AND member_id=?", $this->id, \IPS\Member::loggedIn()->member_id) )->first();
}
return (int) $this->records;
}
@@ -1282,11 +1438,24 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if ( static::$latestRecordAdded->record_comments AND ( $database->_comment_bump & \IPS\cms\Databases::BUMP_ON_COMMENT ) )
{
try
if ( static::$latestRecordAdded->useForumComments() )
{
$comment = $commentClass::constructFromData( \IPS\Db::i()->select( '*', 'cms_database_comments', array( 'comment_record_id=? AND comment_approved=1', \IPS\Db::i()->select( 'primary_id_field', 'cms_custom_database_' . $this->database_id, array( 'category_id=? AND record_approved=1', $this->id ), 'record_last_comment DESC', 1 )->first() ), 'comment_date DESC', 1 )->first() );
$syncRecord = static::$latestRecordAdded;
try
{
$comment = $syncRecord->comments( 1, 0, 'date', 'desc', NULL, FALSE );
}
catch( \Exception $e ) { }
}
else
{
try
{
$comment = $commentClass::constructFromData( \IPS\Db::i()->select( '*', 'cms_database_comments', array( 'comment_record_id=? AND comment_approved=1', \IPS\Db::i()->select( 'primary_id_field', 'cms_custom_database_' . $this->database_id, array( 'category_id=? AND record_approved=1', $this->id ), 'record_last_comment DESC', 1 )->first() ), 'comment_date DESC', 1 )->first() );
}
catch( \UnderflowException $e ) { }
}
catch( \UnderflowException $e ) { }
}
if ( $comment and ( static::$latestRecordAdded->record_last_comment > $comment->mapped('date') ) )
@@ -1312,9 +1481,9 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->last_record_date = $comment->mapped('date');
$this->last_record_member = intval( $comment->author()->member_id );
$this->last_record_name = $comment->author()->member_id ? $comment->author()->name : NULL;
$this->last_record_seo_name = \IPS\Http\Url::seoTitle( $this->last_poster_name );
$this->last_title = $comment->item()->mapped('title');
$this->last_seo_title = \IPS\Http\Url::seoTitle( $this->last_title );
$this->last_record_seo_name = \IPS\Http\Url\Friendly::seoTitle( $this->last_poster_name );
$this->last_title = mb_substr( $comment->item()->mapped('title'), 0, 255 );
$this->last_seo_title = \IPS\Http\Url\Friendly::seoTitle( $this->last_title );
$this->last_record_id = $comment->item()->_id;
}
else if ( static::$latestRecordAdded !== NULL )
@@ -1322,9 +1491,8 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->last_record_date = static::$latestRecordAdded->record_saved;
$this->last_record_member = static::$latestRecordAdded->member_id;
$this->last_record_name = static::$latestRecordAdded->member_id ? static::$latestRecordAdded->record_last_comment_name : NULL;
$this->last_record_seo_name = static::$latestRecordAdded->member_id ? static::$latestRecordAdded->record_last_comment_seo : NULL;
$this->last_title = static::$latestRecordAdded->_title;
$this->last_seo_title = \IPS\Http\Url::seoTitle( static::$latestRecordAdded->_title );
$this->last_title = mb_substr( static::$latestRecordAdded->_title, 0, 255 );
$this->last_seo_title = \IPS\Http\Url\Friendly::seoTitle( mb_substr( static::$latestRecordAdded->_title, 0, 255 ) );
$this->last_record_id = static::$latestRecordAdded->_id;
}
@@ -1343,6 +1511,20 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function getLastCommentTime()
{
if( !$this->can_view_others and !\IPS\Member::loggedIn()->modPermission( 'can_content_view_others_records' ) )
{
try
{
$select = \IPS\Db::i()->select('record_last_comment', 'cms_custom_database_' . $this->database_id, array("record_future_date=0 AND category_id=? AND record_approved=1 AND member_id=?", $this->id, \IPS\Member::loggedIn()->member_id), 'record_last_comment DESC', 1 )->first();
}
catch ( \UnderflowException $e )
{
return NULL;
}
return $select ? \IPS\DateTime::ts( $select ) : NULL;
}
return $this->last_record_date ? \IPS\DateTime::ts( $this->last_record_date ) : NULL;
}
@@ -1354,27 +1536,37 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
public function lastPost()
{
$result = NULL;
$RecordsClass = static::$contentItemClass;
if( $this->last_record_date )
/* This category does not allow you to see records from other users... */
if( $this->can_view_others or \IPS\Member::loggedIn()->modPermission( 'can_content_view_others_records' ) )
{
try
{
$RecordsClass = static::$contentItemClass;
$result = array(
'author' => \IPS\Member::load( $this->last_record_member ),
'record_url' => $RecordsClass::load( $this->last_record_id )->url(),
'record_title' => $this->last_title,
'date' => $this->last_record_date
);
if ( $this->last_record_date )
{
try
{
$result = array( 'author' => \IPS\Member::load( $this->last_record_member ), 'record_url' => $RecordsClass::load( $this->last_record_id )->url(), 'record_title' => $this->last_title, 'date' => $this->last_record_date );
if( !$this->last_record_member AND $this->last_record_name )
if ( !$this->last_record_member AND $this->last_record_name )
{
$result[ 'author' ]->name = $this->last_record_name;
}
}
catch ( \OutOfRangeException $e )
{
$result['author']->name = $this->last_record_name;
}
}
catch ( \OutOfRangeException $e ){}
}
else
{
try
{
$record = $RecordsClass::constructFromData( \IPS\Db::i()->select('*', 'cms_custom_database_' . $this->database_id, array("record_future_date=0 AND category_id=? AND record_approved=1 AND member_id=?", $this->id, \IPS\Member::loggedIn()->member_id), 'record_last_comment DESC', 1 )->first() );
$result = array( 'author' => $record->author(), 'record_url' => $record->url(), 'record_title' => $record->_title, 'date' => $record->record_last_comment );
}
catch ( \Exception $e )
{
}
}
foreach( $this->children() as $child )
@@ -1418,7 +1610,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( ! $parent->furl_name )
{
$parent->furl_name = \IPS\Http\Url::seoTitle( $parent->name );
$parent->furl_name = \IPS\Http\Url\Friendly::seoTitle( $parent->name );
}
$parentId = $parent->parent_id;
@@ -1457,7 +1649,63 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return ( $can === FALSE ) ? FALSE : (boolean) $this->has_perms;
}
/**
* Get which permission keys can access all records in a category which
* can normally only show records to the author
*
* @return array
*/
public function permissionsThatCanAccessAllRecords()
{
$normal = $this->searchIndexPermissions();
$return = array();
$members = array();
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $moderator )
{
if ( $moderator['perms'] === '*' or in_array( 'can_content_view_others_records', explode( ',', $moderator['perms'] ) ) )
{
if( $moderator['type'] === 'g' )
{
$return[] = $moderator['id'];
}
else
{
$members[] = "m{$moderator['id']}";
}
}
}
$return = ( $normal == '*' ) ? array_unique( $return ) : array_intersect( explode( ',', $normal ), array_unique( $return ) );
if( count( $members ) )
{
$return = array_merge( $return, $members );
}
return $return;
}
/**
* Update search index permissions
*
* @return void
*/
protected function updateSearchIndexPermissions()
{
if ( $this->can_view_others )
{
return parent::updateSearchIndexPermissions();
}
else
{
$permissions = implode( ',', $this->permissionsThatCanAccessAllRecords() );
\IPS\Content\Search\Index::i()->massUpdate( 'IPS\cms\Records' . static::database()->_id, $this->_id, NULL, $permissions, NULL, NULL, NULL, NULL, NULL, TRUE );
\IPS\Content\Search\Index::i()->massUpdate( 'IPS\cms\Records\Comment' . static::database()->_id, $this->_id, NULL, $permissions, NULL, NULL, NULL, NULL, NULL, TRUE );
}
}
/**
* Get the filter cookie for this category
*
@@ -1497,7 +1745,17 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
}
else
{
$cookie[ $this->id ] = $values;
/* We only want to include ones where we have actually specified values to filter on */
$toSave = array();
foreach( $values AS $key => $data )
{
if ( count( $data ) )
{
$toSave[$key] = $data;
}
}
$cookie[ $this->id ] = $toSave;
}
\IPS\Request::i()->setCookie( 'cms_filters', json_encode( $cookie ), \IPS\DateTime::create()->add( new \DateInterval( 'P7D' ) ) );
@@ -1512,4 +1770,14 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
{
return $this->meta_description ?: parent::metaDescription();
}
/**
* [Node] Get meta title
*
* @return string
*/
public function metaTitle()
{
return $this->page_title ?: parent::metaTitle();
}
}
@@ -2,9 +2,10 @@
/**
* @brief Abstract class that Controllers should extend
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 16 April 2014
* @version SVN_VERSION_NUMBER
*/
+204 -42
View File
@@ -2,9 +2,10 @@
/**
* @brief Databases Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 31 March 2014
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected static $databaseIdFields = array( 'database_key', 'database_page_id' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -147,6 +153,11 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
)
);
/**
* @brief Page title
*/
protected static $pageTitle = NULL;
/**
* Return all databases
*
@@ -156,15 +167,10 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if ( ! static::$gotAll )
{
if ( ! isset( \IPS\Data\Store::i()->cms_databases ) )
{
\IPS\Data\Store::i()->cms_databases = iterator_to_array( \IPS\Db::i()->select( '*', static::$databaseTable )->setKeyField('database_id') );
}
/* Avoid using SHOW TABLES LIKE / checkForTable() */
try
{
foreach( \IPS\Data\Store::i()->cms_databases as $db )
foreach( static::getDatabaseDataFromStore() as $db )
{
$id = $db[ static::$databasePrefix . static::$databaseColumnId ];
static::$multitons[ $id ] = static::constructFromData( $db );
@@ -177,7 +183,31 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
return static::$multitons;
}
/**
* Returns database data from the store
*
* @return array
*/
public static function getDatabaseDataFromStore()
{
if ( ! isset( \IPS\Data\Store::i()->cms_databases ) )
{
\IPS\Data\Store::i()->cms_databases = iterator_to_array(
\IPS\Db::i()->select(
static::$databaseTable . '.*, core_permission_index.perm_id, core_permission_index.perm_view, core_permission_index.perm_2, core_permission_index.perm_3, core_permission_index.perm_4, core_permission_index.perm_5, core_permission_index.perm_6, core_permission_index.perm_7',
static::$databaseTable
)->join(
'core_permission_index',
array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnId, static::$permApp, static::$permType )
)
->setKeyField('database_id')
);
}
return \IPS\Data\Store::i()->cms_databases;
}
/**
* Can we promote stuff to Pages?
*
@@ -652,7 +682,33 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
{
$this->_data['featured_settings'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
* Get the title of the page when using a database
*
* @return string
*/
public function pageTitle()
{
if ( static::$pageTitle === NULL )
{
if ( $this->use_as_page_title )
{
static::$pageTitle = $this->_title;
}
else
{
try
{
static::$pageTitle = \IPS\cms\Pages\Page::load( $this->page_id )->getHtmlTitle();
}
catch( \Exception $e ) { }
}
}
return static::$pageTitle;
}
/**
* Check permissions
*
@@ -692,39 +748,22 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->langLoaded = TRUE;
return;
}
if ( ! $this->langLoaded )
{
\IPS\Member::loggedIn()->language()->words['__indefart_content_record_comments_title_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( '__indefart_content_record_comments_title' );
\IPS\Member::loggedIn()->language()->words['__indefart_content_record_reviews_title_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( '__indefart_content_record_reviews_title' );
\IPS\Member::loggedIn()->language()->words['content_record_comments_title_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_record_comment_title', FALSE, array( 'sprintf' => array( $this->recordWord( 1, TRUE ) ) ) );
\IPS\Member::loggedIn()->language()->words['content_record_reviews_title_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_record_review_title', FALSE, array( 'sprintf' => array( $this->recordWord( 1, TRUE ) ) ) );
\IPS\Member::loggedIn()->language()->words['content_record_comments_title_' . $this->id . '_pl' ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_record_comments_title', FALSE, array( 'sprintf' => array( $this->recordWord( 1, TRUE ) ) ) );
\IPS\Member::loggedIn()->language()->words['content_record_reviews_title_' . $this->id . '_pl' ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_record_reviews_title', FALSE, array( 'sprintf' => array( $this->recordWord( 1, TRUE ) ) ) );
\IPS\Member::loggedIn()->language()->words['content_db_lang_su_' . $this->id . '_pl' ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_db_lang_pu_' . $this->id );
\IPS\Member::loggedIn()->language()->words['content_db_lang_sl_' . $this->id . '_pl_lc' ] = \IPS\Member::loggedIn()->language()->addToStack( 'content_db_lang_pl_' . $this->id );
$fieldsClass = '\IPS\cms\Fields' . $this->id;
$customFields = $fieldsClass::databaseFieldIds();
foreach ( $customFields AS $id )
{
\IPS\Member::loggedIn()->language()->words['sort_field_' . $id] = \IPS\Member::loggedIn()->language()->addToStack( 'content_field_' . $id );
}
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' )
{
/* Moderator tools */
\IPS\Member::loggedIn()->language()->words['modperms__core_Content_cms_Records' . $this->id ] = $this->_title;
\IPS\Member::loggedIn()->language()->words['cms' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack('categories');
/* Editor Areas */
\IPS\Member::loggedIn()->language()->words['editor__cms_Records' . $this->id ] = $this->_title;
foreach( array( 'pin', 'unpin', 'feature', 'unfeature', 'edit', 'hide', 'unhide', 'view_hidden', 'future_publish', 'view_future', 'move', 'lock', 'unlock', 'reply_to_locked', 'delete' ) as $lang )
{
\IPS\Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_sl_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord( 1 ) ) ) );
\IPS\Member::loggedIn()->language()->words['can_' . $lang . '_content_db_lang_su_' . $this->id ] = \IPS\Member::loggedIn()->language()->addToStack( 'can_' . $lang . '_record', FALSE, array( 'sprintf' => array( $this->recordWord( 1, TRUE ) ) ) );
if ( in_array( $lang, array( 'edit', 'hide', 'unhide', 'view_hidden', 'delete' ) ) )
{
@@ -748,8 +787,16 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function recordWord( $number = 2, $upper = FALSE )
{
$case = $upper ? 'u' : 'l';
return $number == 1 ? \IPS\Member::loggedIn()->language()->addToStack("content_db_lang_s{$case}_{$this->id}") : \IPS\Member::loggedIn()->language()->addToStack("content_db_lang_p{$case}_{$this->id}");
if ( \IPS\Application::appIsEnabled('cms') )
{
return \IPS\Member::loggedIn()->language()->recordWord( $number, $upper, $this->id );
}
else
{
/* If the Pages app is disabled, just load a generic phrase */
$key = "content_database_noun_" . ( $number > 1 ? "p" : "s" ) . ( $upper ? "u" : "l" );
return \IPS\Member::loggedIn()->language()->addToStack( $key );
}
}
/**
@@ -759,12 +806,39 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function save()
{
/* If we are enabling search, we will need to index that content */
$rebuildSearchIndex = ( !$this->_new AND isset( $this->changed['search'] ) AND $this->changed['search'] );
parent::save();
/* Clear cache */
if ( isset( \IPS\Data\Store::i()->cms_databases ) )
{
unset( \IPS\Data\Store::i()->cms_databases );
}
parent::save();
/* If this database isn't searchable, make sure its content is not in the search index */
if( !$this->search AND !$this->_new )
{
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records' . $this->id );
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Comment' . $this->id );
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Review' . $this->id );
/* If there are any bg tasks to rebuild index, clear them */
foreach( \IPS\Db::i()->select( '*', 'core_queue', array( '`key`=?', 'RebuildSearchIndex' ) ) as $queue )
{
$details = json_decode( $queue['data'], true );
if( isset( $details['class'] ) AND $details['class'] == 'IPS\cms\Records' . $this->id )
{
\IPS\Db::i()->delete( 'core_queue', array( 'id=?', $queue['id'] ) );
}
}
}
elseif( $rebuildSearchIndex )
{
\IPS\Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\cms\Records' . $this->id ), 5, TRUE );
}
}
/**
@@ -795,20 +869,22 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
/* Delete comments */
\IPS\Db::i()->delete( 'cms_database_comments', array( 'comment_database_id=?', $this->id ) );
/* Delete from search index */
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=?', "IPS\cms\Records\Comment" . $this->id ) );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=?', "IPS\cms\Records\Review" . $this->id ) );
/* Delete from view counter */
\IPS\Db::i()->delete( 'core_view_updates', array( 'classname=? ', 'IPS\cms\Records' . $this->id ) );
/* Delete records */
\IPS\Db::i()->dropTable( 'cms_custom_database_' . $this->id, TRUE );
/* Delete revisions */
\IPS\Db::i()->delete( 'cms_database_revisions', array( 'revision_database_id=?', $this->id ) );
/* Remove any reciprocal linking */
\IPS\Db::i()->delete( 'cms_database_fields_reciprocal_map', array( 'map_foreign_database_id=? or map_origin_database_id=?', $this->id, $this->id ) );
/* Delete notifications */
$memberIds = array();
foreach( \IPS\DB::i()->select( 'member', 'core_notifications', array( 'item_class=? ', 'IPS\cms\Records' . $this->id ) ) as $member )
foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'item_class=? ', 'IPS\cms\Records' . $this->id ) ) as $member )
{
$memberIds[ $member ] = $member;
}
@@ -823,6 +899,8 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
/* Remove from search */
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records' . $this->id );
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Comment' . $this->id );
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\cms\Records\Review' . $this->id );
/* Delete custom languages */
\IPS\Lang::deleteCustom( 'cms', "content_db_" . $this->id );
@@ -834,16 +912,20 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Lang::deleteCustom( 'cms', "content_db_lang_ia_" . $this->id );
\IPS\Lang::deleteCustom( 'cms', "content_db_lang_sl_" . $this->id . '_pl' );
\IPS\Lang::deleteCustom( 'cms', "__indefart_content_db_lang_sl_" . $this->id );
\IPS\Lang::deleteCustom( 'cms', "__defart_content_db_lang_sl_" . $this->id );
\IPS\Lang::deleteCustom( 'cms', "cms_create_menu_records_" . $this->id );
\IPS\Lang::deleteCustom( 'cms', "cms_records" . $this->id . '_pl' );
\IPS\Lang::deleteCustom( 'cms', "module__cms_records" . $this->id );
/* Unclaim attachments */
\IPS\File::unclaimAttachments( 'content_Records_' . $this->id );
\IPS\File::unclaimAttachments( 'cms_Records_' . $this->id );
/* Remove widgets */
$this->removeWidgets();
/* Best remove the stored data */
unset( \IPS\Data\Store::i()->database_reciprocal_links );
parent::delete();
}
@@ -890,6 +972,12 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
{
parent::setPermissions( $insert, $matrix );
/* Clear cache */
if ( isset( \IPS\Data\Store::i()->cms_databases ) )
{
unset( \IPS\Data\Store::i()->cms_databases );
}
/* Clone these permissions to all categories that do not have permissions */
$class = '\IPS\cms\Categories' . $this->id;
foreach( $class::roots( NULL, NULL, array(), $this->id ) as $category )
@@ -936,5 +1024,79 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
}
return $this->_numberOfCategories;
}
/**
* Determines if any fields from other databases are linking to items in this database via the Relational field
*
* @param int $databaseId The ID of the database
* @return boolean
*/
public static function hasReciprocalLinking( $databaseId )
{
if ( isset( \IPS\Data\Store::i()->database_reciprocal_links ) )
{
$values = \IPS\Data\Store::i()->database_reciprocal_links;
}
else
{
$values = array();
foreach( static::databases() as $database )
{
$fieldsClass = 'IPS\cms\Fields' . $database->_id;
foreach( $fieldsClass::data() as $field )
{
if ( $field->type === 'Item' )
{
$extra = $field->extra;
if ( ! empty( $extra['database'] ) )
{
$values[ $database->_id ] = $extra;
}
}
}
\IPS\Data\Store::i()->database_reciprocal_links = $values;
}
}
if ( is_array( $values ) )
{
foreach( $values as $id => $data )
{
if ( $data['database'] == $databaseId and $data['crosslink'] )
{
return TRUE;
}
}
}
return FALSE;
}
/**
* Rebuild the reciprocal linking maps across all databases
*
* @return void
*/
public static function rebuildReciprocalLinkMaps()
{
/* Ensure the SPL are loaded from /cms/Application.php as this may be called by a task or upgrade module */
\IPS\Application::load('cms');
\IPS\Db::i()->delete( 'cms_database_fields_reciprocal_map' );
foreach( static::databases() as $database )
{
$fieldsClass = 'IPS\cms\Fields' . $database->_id;
foreach( $fieldsClass::data() as $field )
{
if ( $field->type === 'Item' )
{
\IPS\Task::queue( 'cms', 'RebuildReciprocalMaps', array( 'database' => $database->_id, 'field' => $field->id ), 2, array( 'field' ) );
}
}
}
}
}
@@ -2,9 +2,10 @@
/**
* @brief Database Dispatcher
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 16 April 2013
* @version SVN_VERSION_NUMBER
*/
@@ -61,7 +62,7 @@ class _Dispatcher extends \IPS\Dispatcher
/**
* @brief Output to return
*/
public $output = NULL;
public $output = "";
/**
* Set Database ID
@@ -206,7 +207,6 @@ class _Dispatcher extends \IPS\Dispatcher
try
{
$record = $recordClass::loadFromSlug( $slug );
\IPS\Output::i()->redirect( $record->url(), NULL, 301 );
}
catch ( \OutOfRangeException $ex )
@@ -216,7 +216,7 @@ class _Dispatcher extends \IPS\Dispatcher
{
$record = $recordClass::loadFromSlugHistory( $slug );
\IPS\Output::i()->redirect( $record->url(), NULL, 301 );
$this->_redirectToCorrectUrl( $record->url() );
}
catch ( \OutOfRangeException $ex )
{
@@ -239,7 +239,7 @@ class _Dispatcher extends \IPS\Dispatcher
/* Find the record */
try
{
$record = $recordClass::loadFromSlug( $whatsLeft );
$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;
@@ -251,7 +251,7 @@ class _Dispatcher extends \IPS\Dispatcher
{
$record = $recordClass::loadFromSlugHistory( $whatsLeft );
\IPS\Output::i()->redirect( $record->url(), NULL, 301 );
$this->_redirectToCorrectUrl( $record->url() );
}
catch( \OutOfRangeException $ex )
{
@@ -259,6 +259,12 @@ class _Dispatcher extends \IPS\Dispatcher
}
}
/* Make sure the URL is correct, for instance, it could have moved categories */
if ( $database->use_categories AND $this->categoryId != $record->category_id )
{
$this->_redirectToCorrectUrl( $record->url() );
}
$this->module = 'record';
}
else
@@ -295,6 +301,34 @@ class _Dispatcher extends \IPS\Dispatcher
return $this->finish();
}
/**
* Redirect to the "correct" URL (for example if the category slug is incorrect)
* while retaining any query string parameters in the request URL
* For example, an embed to a record might be example.com/records/cat-1/record/?do=embed
* 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
* @return void
*/
protected function _redirectToCorrectUrl( $correctUrl )
{
$paramsToSet = array();
foreach ( \IPS\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 ) )
{
$correctUrl = $correctUrl->setQueryString( $paramsToSet );
}
\IPS\Output::i()->redirect( $correctUrl, NULL, 301 );
}
/**
* Finish
*
@@ -302,6 +336,6 @@ class _Dispatcher extends \IPS\Dispatcher
*/
public function finish()
{
return $this->output;
return (string) $this->output;
}
}
File diff suppressed because it is too large. Load diff
@@ -2,9 +2,10 @@
/**
* @brief Soundcloud input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 11 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -56,6 +57,11 @@ class _Soundcloud extends \IPS\Helpers\Form\Text
*/
public static function displayValue( $value, $customField )
{
if( !$value )
{
return '';
}
$params = $customField->extra;
if ( ! isset( $params['width'] ) )
+8 -2
View File
@@ -2,9 +2,10 @@
/**
* @brief Spotify input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 11 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -56,6 +57,11 @@ class _Spotify extends \IPS\Helpers\Form\Text
*/
public static function displayValue( $value, $customField )
{
if( !$value )
{
return '';
}
$params = $customField->extra;
if ( ! isset( $params['width'] ) )
+8 -2
View File
@@ -2,9 +2,10 @@
/**
* @brief Youtube input class for Form Builder
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 11 Mar 2013
* @version SVN_VERSION_NUMBER
*/
@@ -56,6 +57,11 @@ class _Youtube extends \IPS\Helpers\Form\Text
*/
public static function displayValue( $value, $customField )
{
if( !$value )
{
return '';
}
$url = new \IPS\Http\Url( $value );
if ( isset( $url->queryString['v'] ) )
+10 -4
View File
@@ -2,9 +2,10 @@
/**
* @brief Folder Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 07 July 2015
* @version SVN_VERSION_NUMBER
*/
@@ -72,6 +73,11 @@ class _Folder extends \IPS\Node\Model
*/
protected static $databaseIdFields = array('media_folder_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -200,7 +206,7 @@ class _Folder extends \IPS\Node\Model
{
try
{
$test = \IPS\cms\Media\Folder::load( \IPS\Http\Url::seoTitle( $val ), 'media_folder_name' );
$test = \IPS\cms\Media\Folder::load( \IPS\Http\Url\Friendly::seoTitle( $val ), 'media_folder_name' );
if ( ! empty( \IPS\Request::i()->id ) and $test->id != \IPS\Request::i()->id )
{
@@ -264,7 +270,7 @@ class _Folder extends \IPS\Node\Model
if( isset( $values['media_folder_name'] ) )
{
$values['name'] = \IPS\Http\Url::seoTitle( $values['media_folder_name'] );
$values['name'] = \IPS\Http\Url\Friendly::seoTitle( $values['media_folder_name'] );
unset( $values['media_folder_name'] );
}
+16 -6
View File
@@ -2,9 +2,10 @@
/**
* @brief Media Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 15 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Media extends \IPS\Node\Model
*/
protected static $databaseIdFields = array('media_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent Node ID Database Column
*/
@@ -387,9 +393,13 @@ class _Media extends \IPS\Node\Model
chmod( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ), \IPS\IPS_FOLDER_PERMISSION );
}
}
\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 );
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 ) { }
}
}
@@ -474,7 +484,7 @@ class _Media extends \IPS\Node\Model
$contents = \file_get_contents( $dir->getRealPath() );
/* Create */
$seen[] = static::createMedia( trim( str_replace( \IPS\ROOT_PATH . '/themes/cms/media', '', $dir->getRealPath() ), '/' ), $contents );
$seen[] = static::createMedia( trim( str_replace( str_replace( '\\', '/', \IPS\ROOT_PATH ) . '/themes/cms/media', '', str_replace( '\\', '/', $dir->getRealPath() ) ), '/' ), $contents );
}
}
}
+110 -6
View File
@@ -2,9 +2,10 @@
/**
* @brief Folder Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 15 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -72,6 +73,11 @@ class _Folder extends \IPS\Node\Model
*/
protected static $databaseIdFields = array('folder_name', 'folder_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -120,6 +126,16 @@ class _Folder extends \IPS\Node\Model
return $this->name;
}
/**
* Node titles can contain HTML. Apparently.
*
* @return string
*/
public function get__stripTagsTitle()
{
return $this->name;
}
/**
* Get sortable name
*
@@ -181,7 +197,20 @@ class _Folder extends \IPS\Node\Model
if ( isset( $buttons['delete'] ) )
{
$return['delete'] = $buttons['delete'];
if ( $this->getItemCount() )
{
$return['delete'] = array(
'icon' => 'trash-o',
'title' => 'empty',
'link' => $url->setQueryString( array( 'do' => 'delete', 'id' => $this->_id, 'deleteNode' => 0 ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('empty') ),
'hotkey'=> 'd'
);
}
else
{
$return['delete'] = $buttons['delete'];
}
}
return $return;
@@ -200,7 +229,23 @@ class _Folder extends \IPS\Node\Model
{
try
{
$test = \IPS\cms\Pages\Folder::load( \IPS\Http\Url::seoTitle( $val ), 'folder_name' );
$testPage = \IPS\cms\Pages\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 )
{
/* 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');
}
}
catch ( \OutOfRangeException $e )
{
/* Nothing with the same name, so that's proper good that is */
}
try
{
$test = \IPS\cms\Pages\Folder::load( \IPS\Http\Url\Friendly::seoTitle( $val ), 'folder_name' );
if ( ! empty( \IPS\Request::i()->id ) and $test->id != \IPS\Request::i()->id )
{
@@ -210,7 +255,7 @@ class _Folder extends \IPS\Node\Model
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( \IPS\Http\Url::seoTitle( $val ) ) )
if ( \IPS\Request::i()->folder_parent_id == 0 AND \IPS\cms\Pages\Page::isFurlCollision( \IPS\Http\Url\Friendly::seoTitle( $val ) ) )
{
throw new \InvalidArgumentException('content_folder_name_furl_collision');
}
@@ -260,7 +305,7 @@ class _Folder extends \IPS\Node\Model
if( isset( $values['folder_name'] ) )
{
$values['name'] = \IPS\Http\Url::seoTitle( $values['folder_name'] );
$values['name'] = \IPS\Http\Url\Friendly::seoTitle( $values['folder_name'] );
unset( $values['folder_name'] );
}
@@ -322,6 +367,65 @@ class _Folder extends \IPS\Node\Model
parent::save();
}
/**
* Retrieve item count (if applicable) for a node.
*
* @return int|bool
*/
public function getItemCount()
{
return (int) \IPS\Db::i()->select( 'COUNT(*)', 'cms_pages', array( 'page_folder_id=?', $this->id ) )->first();
}
/**
* 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
*/
public function deleteOrMoveForm( $showMoveToChildren=FALSE )
{
$hasContent = $this->getItemCount();
$form = new \IPS\Helpers\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 )
{
return \IPS\Request::i()->id != $node->id;
} ) ) );
}
return $form;
}
/**
* Handle submissions of form to delete or move content
*
* @param array $values Values from form
* @param bool $deleteWhenDone Delete the node when done?
* @return void
*/
public function deleteOrMoveFormSubmit( $values, $deleteWhenDone=TRUE )
{
if ( $values['cms_move_pages'] )
{
$folderId = $values['cms_move_pages']->id;
}
else
{
$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 ) );
/* Delete it */
\IPS\Session::i()->log( 'acplog__node_deleted', array( $this->title => TRUE, $this->titleForLog() => FALSE ) );
$this->delete();
}
/**
* Resets the stored path
*
+460 -69
View File
@@ -2,9 +2,10 @@
/**
* @brief Page Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 15 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected static $databaseIdFields = array('page_seo_name', 'page_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -181,7 +187,7 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static function loadFromUrl( \IPS\Http\Url $url )
{
$qs = array_merge( $url->queryString, $url->getFriendlyUrlData() );
$qs = array_merge( $url->queryString, $url->hiddenQueryString );
if ( isset( $qs['id'] ) )
{
@@ -196,14 +202,15 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
}
else if ( isset( $qs['path'] ) )
{
if ( method_exists( get_called_class(), 'loadAndCheckPerms' ) )
$return = static::load( $qs['path'], 'page_full_path' );
if ( method_exists( $return, 'can' ) )
{
return static::loadAndCheckPerms( $qs['path'], 'page_full_path' );
}
else
{
return static::load( $qs['path'], 'page_full_path' );
if ( !$return->can( 'view' ) )
{
throw new \OutOfRangeException;
}
}
return $return;
}
throw new \InvalidArgumentException;
@@ -288,10 +295,17 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
/* Nope - try a folder */
try
{
$class = static::$parentNodeClass;
$folder = $class::load( $path, 'folder_path' );
static::$loadedPagesFromPath[ $path ] = static::getDefaultPage( $folder->id );
if ( $path )
{
$class = static::$parentNodeClass;
$folder = $class::load( $path, 'folder_path' );
static::$loadedPagesFromPath[ $path ] = static::getDefaultPage( $folder->id );
}
else
{
static::$loadedPagesFromPath[ $path ] = static::getDefaultPage( 0 );
}
}
catch ( \OutOfRangeException $e )
{
@@ -323,12 +337,25 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
break;
}
}
/* Still here? It's possible this is a legacy URL that starts with "page" - last ditch effort */
if ( static::$loadedPagesFromPath[ $path ] === NULL AND mb_substr( $path, 0, 5 ) === 'page/' )
{
$pathWithoutPage = str_replace( 'page/', '', $path );
try
{
/* Pass back recursively so we don't have to duplicate all of the checks again */
static::$loadedPagesFromPath[ $path ] = static::loadFromPath( $pathWithoutPage );
}
catch( \OutOfRangeException $e ) {}
}
}
}
}
}
}
if ( static::$loadedPagesFromPath[ $path ] === NULL )
{
throw new \OutOfRangeException;
@@ -360,14 +387,12 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
foreach( \IPS\Db::i()->select( '*', 'cms_url_store', array( 'store_type=? and ? LIKE CONCAT( store_path, \'%\') OR store_path=?', 'page', $slug, $slug ) ) as $item )
{
$url = static::load( $item['store_current_id'] )->url();
$url->data['path'] = '/' . trim( str_replace( $item['store_path'], trim( $url->data['path'], '/' ), $slug ), '/' );
$url = $url->setPath( '/' . trim( str_replace( $item['store_path'], trim( $url->data['path'], '/' ), $slug ), '/' ) );
if ( $queryString !== NULL )
{
$url->data['query'] = $queryString;
$url = $url->setQueryString( $queryString );
}
return \IPS\Http\Url::createFromArray( $url->data );
return $url;
}
/* Still here? Ok, now we may have changed the folder name at some point, so lets look for that */
@@ -515,13 +540,20 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
$page = static::load( \IPS\Settings::i()->cms_error_page );
$page->content = str_replace( '{error_message}', $message, $page->content );
$page->content = str_replace( '{error_code}', $code, $page->content );
/* Pages are compiled and cached, which we do not want for the error page as the {error_*} tags are saved with their text content */
$functionName = 'content_pages_' . $page->id;
if ( isset( \IPS\Data\Store::i()->$functionName ) )
{
unset( \IPS\Data\Store::i()->$functionName );
}
$page->output( $title, $httpStatusCode, $httpHeaders );
}
catch( \Exception $ex )
{
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, NULL ), array( 'app' => \IPS\Dispatcher::i()->application ? \IPS\Dispatcher::i()->application->directory : NULL, 'module' => \IPS\Dispatcher::i()->module ? \IPS\Dispatcher::i()->module->key : NULL, 'controller' => \IPS\Dispatcher::i()->controller ) ), $httpStatusCode, 'text/html', $httpHeaders, FALSE, FALSE );
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, NULL, \IPS\Member::loggedIn() ), array( 'app' => \IPS\Dispatcher::i()->application ? \IPS\Dispatcher::i()->application->directory : NULL, 'module' => \IPS\Dispatcher::i()->module ? \IPS\Dispatcher::i()->module->key : NULL, 'controller' => \IPS\Dispatcher::i()->controller ) ), $httpStatusCode, 'text/html', $httpHeaders, FALSE, FALSE );
}
}
@@ -549,9 +581,32 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if ( empty( $val ) )
{
$val = \IPS\Http\Url::seoTitle( $val );
$val = \IPS\Http\Url\Friendly::seoTitle( $val );
}
/* We cannot have a page name the same as a folder name in this folder */
try
{
$testFolder = \IPS\cms\Pages\Folder::load( $val, 'folder_name' );
/* Ok, we have a folder, but is it on the same tree as us ?*/
if ( intval( \IPS\Request::i()->page_folder_id ) == $testFolder->parent_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_pages');
}
}
catch ( \OutOfRangeException $e )
{
/* Nothing with the same name, so that's proper good that is */
}
/* If we hit here, we don't have an existing name so that's good */
if ( ! \IPS\Request::i()->page_folder_id and \IPS\cms\Pages\Page::isFurlCollision( $val ) )
{
throw new \InvalidArgumentException('content_folder_name_furl_collision_pages_app');
}
try
{
$test = \IPS\cms\Pages\Page::load( $val, 'page_seo_name' );
@@ -572,18 +627,14 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
}
}
catch ( \OutOfRangeException $e )
catch ( \OutOfRangeException $e )
{
/* If we hit here, we don't have an existing name so that's good */
if ( ! \IPS\Request::i()->page_folder_id and \IPS\cms\Pages\Page::isFurlCollision( $val ) )
{
throw new \InvalidArgumentException('content_folder_name_furl_collision');
}
/* An exception means we don't have a match, so that is good */
}
}, NULL, NULL, 'page_seo_name' );
$return['page_folder_id'] = new \IPS\Helpers\Form\Node( 'page_folder_id', ( $item ? intval( $item->folder_id ) : ( ( isset( \IPS\Request::i()->parent ) and \IPS\Request::i()->parent ) ? \IPS\Request::i()->parent : 0 ) ), FALSE, array(
'class' => '\IPS\cms\Pages\Folder',
'class' => 'IPS\cms\Pages\Folder',
'zeroVal' => 'node_no_parent',
'subnodes' => false
), NULL, NULL, NULL, 'page_folder_id' );
@@ -705,6 +756,35 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
throw new \LogicException('cms_page_error_bad_syntax');
}
/* New page? quick check to see if we added a DB tag, and if so, make sure it's not being used on another page. */
if ( ! isset( \IPS\Request::i()->id ) )
{
preg_match( '#{database="([^"]+?)"#', $val, $matches );
if ( isset( $matches[1] ) )
{
try
{
if ( is_numeric( $matches[1] ) )
{
$database = \IPS\cms\Databases::load( intval( $matches[1] ) );
}
else
{
$database = \IPS\cms\Databases::load( $matches[1], 'database_key' );
}
if ( $database->page_id )
{
throw new \LogicException('cms_err_db_in_use_other_page');
}
}
catch( \OutOfRangeException $ex )
{
throw new \LogicException('cms_err_db_does_not_exist');
}
}
}
}, NULL, NULL, 'page_content' );
}
@@ -752,7 +832,13 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
$path = trim( $path , '/');
$bits = explode( '/', $path );
$folder = $bits[0];
/* Ensure we cannot have a structure that starts with core/interface as we have this partial URL blacklisted in \IPS\Text\Parser::safeIframeRegexp() */
if ( mb_substr( $path, 0, 15 ) == 'core/interface/' )
{
return TRUE;
}
$defaultApplication = \IPS\Db::i()->select( 'app_directory', 'core_applications', 'app_default=1' )->first();
foreach( \IPS\Application::applications() as $key => $app )
@@ -792,6 +878,21 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
}
}
}
/* Still here? Some apps use very loose matching, like calendar looks for {id}-{?} which may conflict with a page with a filename of 123-foo */
try
{
$url = \IPS\Http\Url::createFromString( \IPS\Http\Url::baseUrl() . $path );
if ( $url and $url instanceof \IPS\Http\Url\Friendly and $url->seoTemplate !== 'content_page_path' )
{
return TRUE;
}
}
catch( \Exception $ex )
{
/* If we get an error, then it cannot be a legitimate link */
}
return FALSE;
}
@@ -814,6 +915,18 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static function buildPageUrlStore()
{
/* This fails because hooks are not installed when this is attempted to build via admin/install */
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation === 'setup' )
{
return;
}
/* This also fails if we're installing via ACP */
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation === 'admin' and isset( \IPS\Request::i()->do ) and \IPS\Request::i()->do == 'install' )
{
return;
}
$store = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'cms_pages' ), 'IPS\cms\Pages\Page' ) as $page )
{
@@ -839,7 +952,6 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
return \IPS\Data\Store::i()->pages_page_urls;
}
/**
* Set JS/CSS include keys
*
@@ -1031,16 +1143,30 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
$functionName = 'content_pages_' . $this->id;
if ( ! isset( \IPS\Data\Store::i()->$functionName ) )
{
\IPS\Data\Store::i()->$functionName = \IPS\Theme::compileTemplate( $this->content, $functionName, null, true );
if ( \IPS\Theme::i()->designersModeEnabled() and file_exists( \IPS\ROOT_PATH . '/themes/cms/pages/' . $this->full_path ) )
{
$contents = @file_get_contents( \IPS\ROOT_PATH . '/themes/cms/pages/' . $this->full_path );
$contents = preg_replace( '#^<ips:pages.+?/>(\r\n?|\n)#', '', $contents );
\IPS\Theme::runProcessFunction( \IPS\Theme::compileTemplate( $contents, $functionName, null, true ), $functionName );
}
else
{
if ( ! isset( \IPS\Data\Store::i()->$functionName ) )
{
\IPS\Data\Store::i()->$functionName = \IPS\Theme::compileTemplate( $this->content, $functionName, null, true );
}
\IPS\Theme::runProcessFunction( \IPS\Data\Store::i()->$functionName, $functionName );
}
\IPS\Theme::runProcessFunction( \IPS\Data\Store::i()->$functionName, $functionName );
return call_user_func( 'IPS\\Theme\\'. $functionName );
}
/**
* @brief Cached widgets
*/
protected $cachedWidgets = NULL;
/**
* Return the blocks for this page
*
@@ -1048,10 +1174,16 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function getWidgets()
{
$widgets = array();
$return = array();
$dbWidgets = array();
$areas = array();
if( $this->cachedWidgets !== NULL )
{
return $this->cachedWidgets;
}
$this->cachedWidgets = array();
$widgets = array();
$dbWidgets = array();
$areas = array();
foreach( \IPS\Db::i()->select( '*', 'cms_page_widget_areas', array( 'area_page_id=?', $this->id ) ) as $k => $widgetMain )
{
@@ -1104,17 +1236,17 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Output::i()->sidebar['widgets'][ $areaKey ][] = $_widget;
}
$return[ $areaKey ][] = $_widget;
$this->cachedWidgets[ $areaKey ][] = $_widget;
}
catch ( \Exception $e )
{
\IPS\Log::i( LOG_ERR )->write( $e->getMessage() );
\IPS\Log::debug( $e, 'pages_widgets' );
}
}
}
}
return $return;
return $this->cachedWidgets;
}
/**
@@ -1212,7 +1344,6 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( ! $this->id )
{
$form->hiddenValues['page_type'] = $pageType = \IPS\Request::i()->page_type;
}
else
{
@@ -1301,7 +1432,10 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
}
}
$form->addButton( 'save_and_reload', 'submit', null, 'ipsButton ipsButton_primary', array( 'name' => 'save_and_reload', 'value' => 1 ) );
if( $this->id )
{
$form->canSaveAndReload = true;
}
\IPS\Output::i()->title = $this->id ? \IPS\Member::loggedIn()->language()->addToStack('content_editing_page', NULL, array( 'sprintf' => array( $this->_title ) ) ) : \IPS\Member::loggedIn()->language()->addToStack('content_add_page');
}
@@ -1335,7 +1469,7 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
$bits = explode( '.', $values['page_seo_name'] );
foreach( $bits as $i => $v )
{
$bits[ $i ] = \IPS\Http\Url::seoTitle( $v );
$bits[ $i ] = \IPS\Http\Url\Friendly::seoTitle( $v );
}
$values['page_seo_name'] = implode( '.', $bits );
@@ -1390,23 +1524,19 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if( $values['page_add_to_menu'] )
{
$values = \IPS\cms\extensions\core\FrontNavigation\Pages::parseConfiguration( $values, $this->id );
$config = array( 'menu_content_page' => $this->id );
$permission = $values['menu_manager_access'] == '*' ? '*' : implode( ',', $values['menu_manager_access'] );
foreach( array( 'menu_title_page_type', 'menu_title_page' ) as $field )
if ( $values['menu_manager_access_type'] === 0 )
{
if ( isset( $values[ $field ] ) )
{
$config[ $field ] = $values[ $field ];
}
$permission = '';
}
$save = array(
'app' => 'cms',
'extension' => 'Pages',
'config' => json_encode( $config ),
'config' => '',
'parent' => $values['menu_parent'],
'permissions' => $values['menu_manager_access'] == '*' ? '*' : implode( ',', $values['menu_manager_access'] )
'permissions' => $permission
);
try
@@ -1418,7 +1548,21 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
$save['position'] = 1;
}
\IPS\Db::i()->insert( 'core_menu', $save );
$id = \IPS\Db::i()->insert( 'core_menu', $save );
$values = \IPS\cms\extensions\core\FrontNavigation\Pages::parseConfiguration( $values, $id );
$config = array( 'menu_content_page' => $this->id );
foreach( array( 'menu_title_page_type', 'menu_title_page' ) as $field )
{
if ( isset( $values[ $field ] ) )
{
$config[ $field ] = $values[ $field ];
}
}
\IPS\Db::i()->update( 'core_menu', array( 'config' => json_encode( $config ) ), array( 'id=?', $id ) );
unset( \IPS\Data\Store::i()->frontNavigation );
}
@@ -1530,9 +1674,19 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
if( $this->_url === NULL )
{
if ( \IPS\Application::load('cms')->default AND $this->default AND ! $this->folder_id )
if ( ( \IPS\Application::load('cms')->default OR \IPS\Settings::i()->cms_use_different_gateway ) AND $this->default AND ! $this->folder_id )
{
$this->_url = \IPS\Http\Url::internal( '', 'front' );
/* Are we using the gateway file? */
if ( \IPS\Settings::i()->cms_use_different_gateway )
{
/* Yes, work out the proper URL. */
$this->_url = \IPS\Http\Url::createFromString( \IPS\Settings::i()->cms_root_page_url, TRUE );
}
else
{
/* No - that's easy */
$this->_url = \IPS\Http\Url::internal( '', 'front' );
}
}
else
{
@@ -1611,7 +1765,7 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( isset( $seen['database'] ) )
{
/* Yep, is it the same ID? */
if ( $seen['database']['value'] != $database->id )
if ( $seen['database']['value'] != $database->id AND $seen['database']['value'] != $database->key )
{
/* There's been a change.. */
try
@@ -1805,8 +1959,13 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
}
unset( \IPS\Data\Store::i()->frontNavigation );
return parent::delete();
parent::delete();
if ( $this->full_path and \IPS\Theme::designersModeEnabled() )
{
static::cleanUpDesignersModeFiles();
}
}
/**
@@ -1878,6 +2037,11 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
parent::save();
static::buildPageUrlStore();
if ( $this->full_path and \IPS\Theme::designersModeEnabled() )
{
static::exportDesignersMode( $this->id );
}
}
/**
@@ -1897,8 +2061,8 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function setAsDefault()
{
\IPS\DB::i()->update( 'cms_pages', array( 'page_default' => 0 ), array( 'page_folder_id=?', $this->folder_id ) );
\IPS\DB::i()->update( 'cms_pages', array( 'page_default' => 1 ), array( 'page_id=?', $this->id ) );
\IPS\Db::i()->update( 'cms_pages', array( 'page_default' => 0 ), array( 'page_folder_id=?', $this->folder_id ) );
\IPS\Db::i()->update( 'cms_pages', array( 'page_default' => 1 ), array( 'page_id=?', $this->id ) );
static::buildPageUrlStore();
}
@@ -1918,8 +2082,9 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* Displays a page
*
* @param int $httpStatusCode HTTP Status Code
* @param array $httpHeaders Additional HTTP Headers
* @param string|NULL $title The Page title
* @param int $httpStatusCode HTTP Status Code
* @param array $httpHeaders Additional HTTP Headers
* @throws \ErrorException
* @return void
*/
@@ -1956,7 +2121,7 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
/* Populate \IPS\Output::i()->sidebar['widgets'] sidebar/header/footer widgets */
$this->getWidgets();
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('pages')->globalWrap( $nav, $this->getHtmlContent(), $this );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'pages', 'cms' )->globalWrap( $nav, $this->getHtmlContent(), $this );
}
\IPS\Output::i()->sidebar['enabled'] = true;
@@ -1972,6 +2137,12 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
{
\IPS\Output::i()->metaTags['description'] = $this->meta_description;
}
/* If this is a default page, we may be accessing this from the folder only */
if ( $this->default )
{
\IPS\Output::i()->linkTags['canonical'] = (string) $this->url();
}
/* Can only disable sidebar if HTML page */
if ( ! $this->show_sidebar and $this->type === 'html' )
@@ -2050,4 +2221,224 @@ class _Page extends \IPS\Node\Model implements \IPS\Node\Permissions
\IPS\Output::i()->sendOutput( $this->getHtmlContent(), ( $httpStatusCode ? $httpStatusCode : 200 ), $this->getContentType(), ( $httpHeaders ? $httpHeaders : \IPS\Output::i()->httpHeaders ) );
}
}
/**
* Write HTML pages to disk for designer's mode
*
* @param NULL|int $pageId Single page to export
* @return void
*/
public static function exportDesignersMode( $pageId=NULL)
{
$where = array( array( 'page_type=?', 'html' ) );
$seen = array();
if ( $pageId )
{
$where[] = array( 'page_id=?', $pageId );
}
foreach( \IPS\Db::i()->select( '*', 'cms_pages', $where ) as $page )
{
/* We could use recursive mode but it wouldn't correctly chmod the intermediate dirs */
$bits = explode( '/', "/themes/cms/pages/" . $page['page_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 );
}
}
$headers = array();
foreach( array( 'page_seo_name', 'page_ipb_wrapper', 'page_title') as $field )
{
$headers[ $field ] = $field . '="' . str_replace( '"', '\\"', $page[ $field ] ) . '"';
}
$header = "<ips:pages " . implode( " ", $headers ) . " />";
try
{
\file_put_contents( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ) . '/' . $filename, $header . "\n" . $page['page_content'] );
@chmod( \IPS\ROOT_PATH . '/' . trim( $dir, '/' ) . '/' . $filename, \IPS\IPS_FILE_PERMISSION );
}
catch( \RuntimeException $e ) { }
}
/* Clear out any older designer mode files */
if ( ! $pageId )
{
static::cleanUpDesignersModeFiles();
}
}
/**
* Remove any old designer mode files
*
* @return void
*/
public static function cleanUpDesignersModeFiles()
{
$diskPaths = array();
static::getCurrentDesignersModeFilePaths( $diskPaths, \IPS\ROOT_PATH . "/themes/cms/pages/" );
$databasePaths = array();
foreach( \IPS\Db::i()->select( '*', 'cms_pages', array( 'page_type=?', 'html' ) ) as $page )
{
$databasePaths[] = $page['page_full_path'];
}
if ( count( $diskPaths ) )
{
foreach( $diskPaths as $path )
{
if ( ! in_array( $path, $databasePaths ) )
{
@unlink( \IPS\ROOT_PATH . "/themes/cms/pages/" . $path );
}
}
}
}
/**
* Get existing designer's mode page paths
*
* @param array $paths Array of processed page paths
* @param string $path Path to look into
* @return void
*/
public static function getCurrentDesignersModeFilePaths( &$paths, $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::getCurrentDesignersModeFilePaths( $paths, $dir->getRealPath() );
}
else
{
$paths[] = trim( str_replace( \IPS\ROOT_PATH . '/themes/cms/pages/', '', $dir->getRealPath() ), '/' );
}
}
}
}
/**
* Import media from disk for designer's mode
*
* @return void
*/
public static function importDesignersMode()
{
$path = \IPS\ROOT_PATH . '/themes/cms/pages';
$seen = array();
/* Grab folder data */
if ( is_dir( $path ) )
{
static::importDesignersModeRecurse( $seen, $path );
}
\IPS\Db::i()->delete( 'cms_pages', array( 'page_type=? and ' . \IPS\Db::i()->in( 'page_id', $seen, TRUE ), 'html' ) );
}
/**
* Import media from disk for designer's mode recursive method
*
* @param array $seen Array of processed page IDs
* @param string $path Path to look into
* @return void
*/
public static function importDesignersModeRecurse( &$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::importDesignersModeRecurse( $seen, $dir->getRealPath() );
}
else
{
$folders = \IPS\cms\Pages\Folder::roots();
$contents = \file_get_contents( $dir->getRealPath() );
$params = array();
/* Parse the header tag */
preg_match( '#^<ips:pages(.+?)/>(\r\n?|\n)#', $contents, $params );
/* Strip it */
$contents = ( isset($params[0]) ) ? str_replace( $params[0], '', $contents ) : $contents;
/* Get fields */
preg_match_all( '#([a-z\-_]+?)="(.+?)"#', $params[1], $data, PREG_SET_ORDER );
$fields = array();
foreach( $data as $id => $matches )
{
$fields[ $data[ $id ][1] ] = $data[ $id ][2];
}
/* Existing page, or a new one? */
$fullPath = trim( str_replace( str_replace( '\\', '/', \IPS\ROOT_PATH ) . '/themes/cms/pages', '', str_replace( '\\', '/', $dir->getRealPath() ) ), '/' );
$bits = explode( '/', $fullPath );
$fileName = array_pop( $bits );
$path = implode( '/', $bits );
try
{
$page = \IPS\cms\Pages\Page::load( $fullPath, 'page_full_path' );
$page->content = $contents;
$page->save();
}
catch( \OutOfRangeException $e )
{
$folderId = 0;
foreach( $folders as $folder )
{
if ( $folder->path == $path )
{
$folderId = $folder->id;
}
}
/* Doesn't exist, so this is new */
$page = new \IPS\cms\Pages\Page;
$page->content = $contents;
$page->full_path = $fullPath;
$page->seo_name = $fileName;
$page->type = 'html';
$page->title = ( isset( $fields['page_title'] ) ) ? $fields['page_title'] : $fileName;
$page->ipb_wrapper = ( isset( $fields['page_ipb_wrapper'] ) ) ? $fields['page_ipb_wrapper'] : 1;
$page->folder_id = $folderId;
$page->save();
\IPS\Lang::saveCustom( 'cms', "cms_page_" . $page->id, $page->title );
}
$seen[] = $page->id;
}
}
}
}
}
+49 -4
View File
@@ -2,10 +2,10 @@
/**
* @brief Post Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Content
* @since 8 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -103,6 +103,11 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\ReportCenter
*/
public static $hideLogKey = 'ccs-records';
/**
* @brief Store generated search index titles for efficiency
*/
protected static $searchIndexTitles = array();
/**
* Return custom where for SQL delete
*
@@ -113,7 +118,45 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\ReportCenter
{
return array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=?', $id ), array( static::$databasePrefix . 'database_id=?', static::$customDatabaseId ) );
}
/**
* 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
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL )
{
/* 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 );
}
/**
* Do stuff after creating (abstracted as comments and reviews need to do different things)
*
@@ -164,7 +207,7 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\ReportCenter
* @param string|NULL $action Action
* @return \IPS\Http\Url
*/
public function url( $action=NULL )
public function url( $action='find' )
{
$url = parent::url( $action );
@@ -172,6 +215,8 @@ class _Comment extends \IPS\Content\Comment implements \IPS\Content\ReportCenter
{
$url = $url->setQueryString( 'd', static::$customDatabaseId );
}
$url = $url->setQueryString( 'tab', 'comments' );
return $url;
}
@@ -2,9 +2,10 @@
/**
* @brief Forum Post Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 8 April 2014
* @version SVN_VERSION_NUMBER
*/
@@ -126,7 +127,7 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
$comment->delete();
}
/**
/**
* Edit Comment Contents - Note: does not add edit log
*
* @param string $newContent New content
@@ -138,6 +139,18 @@ class _CommentTopicSync extends \IPS\cms\Records\Comment
$comment->editContents( $newContent );
}
/**
* Report
*
* @param string $reportContent Report content message from member
* @return \\IPS\core\Reports\Report
* @throws \UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
*/
public function report( $reportContent )
{
$comment = \IPS\forums\Topic\Post::load( $this->pid );
return $comment->report( $reportContent );
}
/**
* Addition where needed for fetching comments
File diff suppressed because it is too large. Load diff
+23 -11
View File
@@ -2,10 +2,10 @@
/**
* @brief Post Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @subpackage Board
* @package IPS Community Suite
* @subpackage Content
* @since 8 Jan 2014
* @version SVN_VERSION_NUMBER
*/
@@ -107,17 +107,19 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\ReportCenter,
/**
* 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 \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)
* @return static
* @throws \InvalidArgumentException
*/
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL )
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
{
$review = call_user_func_array( 'parent::create', func_get_args() );
@@ -179,4 +181,14 @@ class _Review extends \IPS\Content\Review implements \IPS\Content\ReportCenter,
$commentIdColumn = static::$databaseColumnId;
return array( $this->item()->$idColumn, $this->$commentIdColumn, static::$customDatabaseId . '-review' );
}
/**
* Addition where needed for fetching comments
*
* @return array|NULL
*/
public static function commentWhere()
{
return array( 'review_database_id=?', static::$customDatabaseId );
}
}
+5 -15
View File
@@ -2,9 +2,10 @@
/**
* @brief Revisions Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 29 April 2014
* @version SVN_VERSION_NUMBER
*/
@@ -99,25 +100,14 @@ class _Revisions extends \IPS\Patterns\ActiveRecord
$customFields = $fieldsClass::data( 'view' );
$conflicts = array();
require_once \IPS\ROOT_PATH . "/system/3rd_party/Diff/class.Diff.php";
/* Build up our data set */
foreach( $customFields as $id => $field )
{
$key = 'field_' . $field->id;
$conflicts[ $key ] = array( 'revision' => $this->get( $key ), 'record' => $record->$key, 'field' => $field, 'diff' => NULL );
if ( $record->$key AND $this->get( $key ) )
if( $justChanged === FALSE OR !\IPS\Login::compareHashes( md5( $record->$key ), md5( $this->get( $key ) ) ) )
{
if ( md5( $record->$key ) != md5( $this->get( $key ) ) )
{
$conflicts[ $key ]['diff'] = \Diff::toTable( \Diff::compare( $this->get( $key ), $record->$key ) );
}
else if ( $justChanged !== FALSE )
{
unset( $conflicts[ $key ] );
}
$conflicts[ md5( uniqid() ) ] = array( 'original' => $this->get( $key ), 'current' => $record->$key, 'field' => $field );
}
}
@@ -2,9 +2,10 @@
/**
* @brief Categories Selector Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 20 July 2015
* @version SVN_VERSION_NUMBER
*/
@@ -58,6 +59,11 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected static $databaseIdFields = array('category_furl_name', 'category_full_path');
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -2,9 +2,10 @@
/**
* @brief Databases Selector Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 20 July 2015
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Databases extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
protected static $databaseIdFields = array( 'database_key', 'database_page_id' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief [Node] Parent ID Database Column
*/
@@ -2,9 +2,10 @@
/**
* @brief Templates Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 25 Feb 2014
* @version SVN_VERSION_NUMBER
*/
@@ -48,6 +49,11 @@ class _Container extends \IPS\Patterns\ActiveRecord
*/
protected static $databaseIdFields = array( 'container_key' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief Have fetched all?
*/
@@ -115,7 +121,7 @@ class _Container extends \IPS\Patterns\ActiveRecord
/* Create a unique key */
if ( empty( $newContainer->key ) )
{
$newContainer->key = 'template__' . \IPS\Http\Url::seoTitle( $newContainer->name ) . '.' . $newContainer->id;
$newContainer->key = 'template__' . \IPS\Http\Url\Friendly::seoTitle( $newContainer->name ) . '.' . $newContainer->id;
$newContainer->save();
}
@@ -2,9 +2,10 @@
/**
* @brief Templates Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 25 Feb 2014
* @version SVN_VERSION_NUMBER
*/
@@ -47,6 +48,11 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @brief [ActiveRecord] Database ID Fields
*/
protected static $databaseIdFields = array( 'template_key', 'template_id' );
/**
* @brief [ActiveRecord] Multiton Map
*/
protected static $multitonMap = array();
/**
* @brief Retusn all types
@@ -92,7 +98,74 @@ class _Templates extends \IPS\Patterns\ActiveRecord
* @brief Return both IN_DEV and database templates
*/
const RETURN_DATABASE_AND_IN_DEV = 256;
/**
* @brief Default database template group names
*/
public static $databaseDefaults = array(
'featured' => 'category_articles',
'form' => 'form',
'display' => 'display',
'listing' => 'listing',
'categories' => 'category_index'
);
/**
* Ensure that the template is calling the correct groups
*
* @param string $group Group to load templates from
* @return void
*/
public static function fixTemplateTags( $group )
{
$templates = iterator_to_array( \IPS\Db::i()->select( '*', 'cms_templates', array( array( 'template_group=?', $group ) ) )->setKeyField('template_title') );
foreach( $templates as $template )
{
$save = array();
/* Make sure template tags call the correct group */
if ( mb_stristr( $template['template_content'], '{template' ) )
{
preg_match_all( '/\{([a-z]+?=([\'"]).+?\\2 ?+)}/', $template['template_content'], $matches, PREG_SET_ORDER );
/* Work out the plugin and the values to pass */
foreach( $matches as $index => $array )
{
preg_match_all( '/(.+?)=' . $array[ 2 ] . '(.+?)' . $array[ 2 ] . '\s?/', $array[ 1 ], $submatches );
$plugin = array_shift( $submatches[ 1 ] );
if ( $plugin == 'template' )
{
$value = array_shift( $submatches[ 2 ] );
$options = array();
foreach ( $submatches[ 1 ] as $k => $v )
{
$options[ $v ] = $submatches[ 2 ][ $k ];
}
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 ) ) )
{
$options['group'] = $group;
$replace = '{template="' . $value . '" app="' . $options['app'] . '" location="' . $options['location'] . '" group="' . $options['group'] . '" params="' . ( isset($options['params']) ? $options['params'] : NULL ) . '"}';
$save['template_content'] = str_replace( $matches[$index][0], $replace, $template['template_content'] );
}
}
if ( count( $save ) )
{
\IPS\Db::i()->update( 'cms_templates', $save, array( 'template_id=?', $template['template_id'] ) );
}
}
}
}
}
}
/**
* Load Record
* Overloaded so we can force loading by key by default but still retain the template_id field as the primary key so
@@ -395,7 +468,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$newTemplate->save();
/* Create a unique key */
$newTemplate->key = $newTemplate->location . '_' . \IPS\Http\Url::seoTitle( $newTemplate->title ) . '_' . $newTemplate->id;
$newTemplate->key = $newTemplate->location . '_' . \IPS\Http\Url\Friendly::seoTitle( $newTemplate->title ) . '_' . $newTemplate->id;
/* Make sure there's no double __ in there */
foreach( array( 'group', 'title', 'key' ) as $field )
@@ -490,7 +563,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
}
/* Open XML file */
$xml = new \XMLReader;
$xml = new \IPS\Xml\XMLReader;
$xml->open( $file );
$xml->read();
@@ -548,13 +621,15 @@ class _Templates extends \IPS\Patterns\ActiveRecord
*/
public function delete()
{
if ( $this->user_added )
\IPS\cms\Theme::deleteCompiledTemplate( 'cms', $this->location, $this->group );
if ( $this->user_created )
{
\IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_added=?', $this->key, 1 ) );
if ( isset( static::$multitons[ $this->key ] ) )
\IPS\Db::i()->delete( 'cms_templates', array( 'template_key=? AND template_user_created=?', $this->key, 1 ) );
if ( isset( static::$multitons[ $this->id ] ) )
{
unset( static::$multitons[ $this->key ] );
unset( static::$multitons[ $this->id ] );
}
}
else
@@ -562,10 +637,10 @@ 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 ) );
if ( isset( static::$multitons[ $this->key ] ) )
if ( isset( static::$multitons[ $this->id ] ) )
{
unset( static::$multitons[ $this->key ] );
unset( static::$multitons[ $this->id ] );
}
}
else
@@ -606,7 +681,7 @@ class _Templates extends \IPS\Patterns\ActiveRecord
$content = $this->content;
/* Build on demand */
if ( mb_stristr( $this->content, "{block=" ) )
if ( $this->type != 'js' and ( mb_stristr( $this->content, "{block=" ) or mb_stristr( $this->content, "{{if" ) ) )
{
$functionName = 'css_' . uniqid();
\IPS\Theme::makeProcessFunction( str_replace( '\\', '\\\\', $content ), $functionName );
@@ -2,9 +2,10 @@
/**
* @brief Designer's Mode Theme
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 28 Nov 2014
* @version SVN_VERSION_NUMBER
*/
@@ -133,22 +134,21 @@ class _Theme extends \IPS\cms\Theme
if ( isset( $templates[ $key ] ) )
{
if ( md5( trim( $templates[ $key ]['template_content'] ) ) == md5( trim( $html ) ) and 1==2 )
if( \IPS\Login::compareHashes( md5( trim( $html ) ), md5( trim( $templates[ $key ]['template_content'] ) ) ) )
{
/* No change */
continue;
}
else
/* Update */
if ( ! $templates[ $key ]['template_master'] )
{
/* Update */
if ( ! $templates[ $key ]['template_master'] )
{
$added = TRUE;
\IPS\Db::i()->update( 'cms_templates', array(
'template_content' => $html,
'template_params' => ( isset($params[1]) ) ? $params[1] : '',
'template_file_object' => NULL
), array( 'template_id=?', $templates[ $key ]['template_id'] ) );
}
$added = TRUE;
\IPS\Db::i()->update( 'cms_templates', array(
'template_content' => $html,
'template_params' => ( isset($params[1]) ) ? $params[1] : '',
'template_file_object' => NULL
), array( 'template_id=?', $templates[ $key ]['template_id'] ) );
}
}
+3 -2
View File
@@ -2,9 +2,10 @@
/**
* @brief Magic Template Class for IN_DEV mode
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 18 Feb 2013
* @version SVN_VERSION_NUMBER
*/
+71 -24
View File
@@ -2,9 +2,10 @@
/**
* @brief IN_DEV Skin Set
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 16 Apr 2013
* @version SVN_VERSION_NUMBER
*/
@@ -175,7 +176,7 @@ class _Theme extends \IPS\Theme
if ( isset( $master[ $key ] ) )
{
if ( md5( trim( $master[ $key ]['template_content'] ) ) == md5( trim( $html ) ) )
if( \IPS\Login::compareHashes( md5( trim( $master[ $key ]['template_content'] ) ), md5( trim( $html ) ) ) )
{
continue;
}
@@ -342,7 +343,7 @@ class _Theme extends \IPS\Theme
$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::seoTitle( $file->getFilename() . '_' . $title );
$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 );
$container = NULL;
@@ -429,16 +430,52 @@ class _Theme extends \IPS\Theme
{
$where[] = "template_type='js'";
}
foreach(
\IPS\Db::i()->select(
'*',
'cms_templates',
implode( " AND ", $where ),
'template_location, template_group, template_key, template_user_edited ASC'
)
as $row
) {
$templateNames = array();
$rawTemplates = array();
$originalGroups = array();
$originalTemplates = array();
$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 )
{
$rawTemplates[] = $row;
$templateNames[ $row['template_original_group'] ][] = $row['template_title'];
$groupMap[ $row['template_original_group'] ] = $row['template_group'];
if ( $row['template_original_group'] )
{
$originalGroups[ $row['template_original_group'] ] = $row['template_original_group'];
}
}
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 )
{
$originalTemplates[ $row['template_group'] . '_' . $row['template_title'] ] = $row;
$originalTemplateNames[ $row['template_group'] ][] = $row['template_title'];
}
}
/* Now try and see if we can merge in missing templates */
foreach( $originalTemplateNames as $group => $names )
{
if ( isset( $templateNames[ $group ] ) )
{
foreach( $names as $name )
{
if ( ! in_array( $name, $templateNames[ $group ] ) )
{
$rawTemplates[] = array_merge( $originalTemplates[ $group . '_' . $name ], array( 'template_group' => $groupMap[ $group ]) );
}
}
}
}
foreach( $rawTemplates as $row )
{
$row['TemplateKey'] = $row['template_app'] . '_' . $row['template_location'] . '_' . $row['template_group'] . '_' . $row['template_key'];
$row['jsDataKey'] = str_replace( '.', '--', $row['TemplateKey'] );
$row['template_app'] = 'cms';
@@ -545,7 +582,7 @@ class _Theme extends \IPS\Theme
$key = \strtolower( 'template_cms_' .static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) );
/* Still here */
if ( !in_array( $key, array_keys( self::$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 ) )
@@ -556,15 +593,21 @@ class _Theme extends \IPS\Theme
/* Still no key? */
if ( ! isset( \IPS\Data\Store::i()->$key ) )
{
\IPS\Log::i( LOG_WARNING )->write( "Template store key: {$key} missing ({$app}, {$location}, {$group})", "template_store_missing" );
\IPS\Log::log( "Template store key: {$key} missing ({$app}, {$location}, {$group})", "template_store_missing" );
throw new \ErrorException('template_store_missing ' . $key);
}
/* Load compiled template */
$compiledGroup = \IPS\Data\Store::i()->$key;
if ( @eval( $compiledGroup ) === FALSE )
try
{
if ( @eval( $compiledGroup ) === FALSE )
{
throw new \UnexpectedValueException;
}
}
catch ( \ParseError $e )
{
throw new \UnexpectedValueException;
}
@@ -574,10 +617,10 @@ class _Theme extends \IPS\Theme
$class = "\IPS\Theme\\{$class}";
/* Init */
self::$calledTemplates[ $key ] = new $class();
static::$calledTemplates[ $key ] = new $class();
}
return self::$calledTemplates[ $key ];
return static::$calledTemplates[ $key ];
}
}
@@ -603,17 +646,21 @@ class _Theme extends \IPS\Theme
/* Any template hooks? */
$templateHooks = array();
if( isset( \IPS\IPS::$hooks[ "\IPS\Theme\class_{$app}_{$location}_{$group}" ] ) )
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 ) )
{
if ( @eval( "namespace IPS\Theme;\n\n" . str_replace( ' extends _HOOK_CLASS_', '', file_get_contents( \IPS\ROOT_PATH . '/' . $data['file'] ) ) ) !== FALSE )
try
{
$class = "IPS\Theme\hook{$data['class']}";
$templateHooks = array_merge( $templateHooks, $class::hookData() );
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 ) {}
}
}
}
+37 -6
View File
@@ -2,9 +2,10 @@
/**
* @brief CMS Widgets
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Content
* @since 13 Oct 2014
* @version SVN_VERSION_NUMBER
*/
@@ -37,13 +38,16 @@ class _Widget extends \IPS\Widget
{
$widgets = json_decode( $item['area_widgets'], TRUE );
foreach( $widgets as $widget )
if( is_array( $widgets ) )
{
if ( $widget['unique'] == $uniqueId )
foreach( $widgets as $widget )
{
if ( isset( $widget['configuration'] ) )
if ( $widget['unique'] == $uniqueId )
{
return $widget['configuration'];
if ( isset( $widget['configuration'] ) )
{
return $widget['configuration'];
}
}
}
}
@@ -141,4 +145,31 @@ class _Widget extends \IPS\Widget
}
}
}
/**
* Return unique IDs in use
*
* @return array
*/
public static function getUniqueIds()
{
$uniqueIds = parent::getUniqueIds();
foreach ( \IPS\Db::i()->select( '*', 'cms_page_widget_areas' ) as $row )
{
$data = json_decode( $row['area_widgets'], TRUE );
if ( count( $data ) )
{
foreach( $data as $widget )
{
if ( isset( $widget['unique'] ) )
{
$uniqueIds[] = $widget['unique'];
}
}
}
}
return $uniqueIds;
}
}