Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

@@ -12,6 +12,9 @@
namespace IPS\cms;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Content\Comment;
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
@@ -109,6 +112,11 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
* @breif Used by the dataLayer, overwritten by \IPS\cms\Application
*/
public static $containerType = 'pages_database_category';
/**
* @brief SEO Title Column
*/
public static string $seoTitleColumn = 'furl_name';
/**
* @brief [Node] ACP Restrictions
@@ -331,7 +339,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
$path = trim( $path , '/');
$bits = explode( '/', $path );
$root = $bits[0];
/* _ is here due to IP.Board 3.x using that to denote the articles database (eg. articles.html/_/category/record-r1/), which we need to redirect from for records and articles. */
if ( \in_array( $root, array( 'submit', '_' ) ) )
{
@@ -612,6 +620,34 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
), NULL, NULL, NULL, 'category_fields' ) );
}
}
/**
* See if a furl name is taken within a database
*
* @param string $furl The default FURL (of a category)
* @param int $databaseId The database
* @param int|null $excludeId Optional: an id to exclude from the query
*
* @return bool
*/
public static function furlNameIsTaken( string $furl, int $databaseId, ?int $excludeId=null ) : bool
{
$where = array( ['category_database_id=?', $databaseId ] );
if ( $excludeId )
{
$where[] = [ 'category_id <> ?', $excludeId ];
}
foreach ( (new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'cms_database_categories', $where ), self::class )) as $cat )
{
if ( ( !empty( $cat->furl_name ) and $cat->furl_name == $furl ) or ( empty( $cat->furl_name ) AND !empty( $cat->name ) AND \IPS\Http\Url\Friendly::seoTitle( $cat->name ) == $furl ) )
{
return true;
}
}
return false;
}
/**
* [Node] Format form values from add/edit form for save
@@ -667,44 +703,14 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( isset( $name ) AND empty( $values['category_furl_name'] ) )
{
$ok = FALSE;
try
{
$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 )
{
$ok = TRUE;
}
if ( isset( \IPS\Request::i()->id ) )
{
if ( $cat->id == \IPS\Request::i()->id )
{
$ok = TRUE;
}
}
}
catch ( \OutOfRangeException $e )
{
$ok = TRUE;
}
$ok = static::furlNameIsTaken( \IPS\Http\Url\Friendly::seoTitle( $name ), $this->database_id, $this->id ?: null );
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\Friendly::seoTitle( $name );
}
else
{
$values['furl_name'] = $this->id . '_' . \IPS\Http\Url\Friendly::seoTitle( $name );
}
$values['furl_name'] = $ok ? \IPS\Http\Url\Friendly::seoTitle( $name ) : $this->id . '_' . \IPS\Http\Url\Friendly::seoTitle( $name );
}
else if( isset( $values['category_furl_name'] ) )
{
@@ -962,8 +968,8 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function permissions()
{
/* Let the ACP/Setup use normal permissions or it'll get messy */
if ( !\IPS\Dispatcher::hasInstance() OR \IPS\Dispatcher::i()->controllerLocation !== 'front' )
/* Let the ACP/Setup use normal permissions or it'll get messy. Also use normal permissions for club categories */
if ( !\IPS\Dispatcher::hasInstance() OR \IPS\Dispatcher::i()->controllerLocation !== 'front' OR $this->isClubCategory() )
{
$this->_permsMashed = true;
return parent::permissions();
@@ -1147,7 +1153,7 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
if ( $recordClass::database()->use_categories )
{
return \IPS\Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $indexData['index_container_id'], NULL, $escape ? array( 'escape' => $escape ) : array() );
return parent::titleFromIndexData( $indexData, $itemData, $containerData, $escape );
}
else
{
@@ -1170,6 +1176,20 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return $this->database()->pageTitle();
}
/**
* Get the club of this category, or null if there is no club
*
* @return \IPS\Member\Club|null
*/
public function get__club() : \IPS\Member\Club|null
{
if ( $this->isClubCategory() )
{
return $this->club();
}
return null;
}
/**
* Get title of category
*
@@ -1178,8 +1198,8 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
protected function get__title()
{
/* If the DB is in a page, and we're not using categories, then return the page title, not the category title for continuity */
$database = \IPS\cms\Databases::load( $this->database_id );
if ( ! $database->use_categories )
$database = static::database();
if ( ! $database->use_categories and !$this->_club )
{
if ( ! $this->_catTitle )
{
@@ -1454,14 +1474,15 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return static::database()->template_display;
}
/**
* Set last comment
*
* @param \IPS\Content\Comment $comment The latest comment or NULL to work it out
* @return void
* @param Comment|null $comment The latest comment or NULL to work it out
* @param Item|null $updatedItem We sometimes run setLastComment() when an item has been edited, if so, that item will be here
* @return void
*/
public function setLastComment( \IPS\Content\Comment $comment=NULL )
public function _setLastComment( \IPS\Content\Comment $comment=NULL, \IPS\Content\Item $updatedItem=NULL )
{
$database = \IPS\cms\Databases::load( $this->database_id );
@@ -1715,6 +1736,16 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public function canManagePermissions()
{
/* For club categories, we don't want the permissions to be managed via the ACP */
if ( $this->isClubCategory() and !( \IPS\Dispatcher::i() instanceof \IPS\Dispatcher\Front ) )
{
return false;
}
else if ( $this->isClubCategory() )
{
return $this->can( 'add' );
}
$can = parent::canManagePermissions();
return ( $can === FALSE ) ? FALSE : (boolean) $this->has_perms;
@@ -1864,6 +1895,90 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
{
$_member = $member ?: \IPS\Member::loggedIn();
if ( $club = $this->_club )
{
if ( \in_array( $permission, [ 'add', 'edit', 'delete', 'reply', 'review' ] ) and $_member instanceof \IPS\Member )
{
if( \in_array( $club->memberStatus( $_member ), [
\IPS\Member\Club::STATUS_MEMBER,
\IPS\Member\Club::STATUS_LEADER,
\IPS\Member\Club::STATUS_MODERATOR
] ) or $_member->modPermission('can_access_all_clubs' ) )
{
return parent::can( $permission, $_member, $considerPostBeforeRegistering );
}
return false;
}
if ( \in_array( $permission, ['view', 'read' ] ) )
{
/* Check the database permission first */
if( !static::database()->can( $permission, $_member ) )
{
return false;
}
/* Site moderators can see everything */
if ( $_member instanceof \IPS\Member and $_member->modPermission('can_access_all_clubs') )
{
return TRUE;
}
/* If it's not approved, only moderators and the person who created it can see it */
if ( \IPS\Settings::i()->clubs_require_approval and !$club->approved and $_member instanceof \IPS\Member )
{
return ( $_member->modPermission('can_access_all_clubs') or ( $club->owner AND $_member->member_id == $club->owner->member_id ) );
}
/* Owner or leader? */
if ( $_member instanceof \IPS\Member and ( $_member->member_id === $club->owner->member_id OR $club->memberStatus( $_member ) === \IPS\Member\Club::STATUS_LEADER ) )
{
return TRUE;
}
$viewGroups = explode( ',', $this->club_view_permission );
/* Can literally anyone, guest included, view this and the club? */
if ( $this->club_category_meta_index and $club->type !== \IPS\Member\Club::TYPE_PRIVATE and \in_array( 'nonmember', $viewGroups ) )
{
return true;
}
/* Nonmembers */
if ( \in_array( 'nonmember', $viewGroups ) and $_member instanceof \IPS\Member and $_member->member_id )
{
return true;
}
/* Moderators? */
if ( \in_array( 'moderator', $viewGroups ) AND $club->memberStatus( $_member ) === \IPS\Member\Club::STATUS_MODERATOR )
{
return TRUE;
}
/* Members */
if (
( \mb_strpos( '*', $this->club_view_permission ) !== false or \in_array( 'member', $viewGroups ) ) and
\in_array(
$club->memberStatus( $_member ),
[
\IPS\Member\Club::STATUS_MEMBER,
\IPS\Member\Club::STATUS_INVITED,
\IPS\Member\Club::STATUS_INVITED_BYPASSING_PAYMENT,
\IPS\Member\Club::STATUS_EXPIRED_MODERATOR
]
)
)
{
return TRUE;
}
return false;
}
}
if ( !$_member->member_id and !$this->can_view_others )
{
return FALSE;
@@ -1917,4 +2032,24 @@ class _Categories extends \IPS\Node\Model implements \IPS\Node\Permissions
return $this->_dataLayerProperties;
}
/**
* See if this category belongs to a club
* @return bool
*/
public function isClubCategory() : bool
{
try
{
$database = \IPS\cms\Databases::load( $this->database_id );
$class = '\IPS\cms\Categories' . $this->database_id;
if ( $database->allow_club_categories and \IPS\IPS::classUsesTrait( $class, 'IPS\Content\ClubContainer' ) )
{
return true;
}
}
catch ( \OutOfBoundsException|\OutOfRangeException ) { }
return false;
}
}