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

@@ -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();
}
}