Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+189 -28
View File
@@ -12,7 +12,7 @@
namespace IPS\blog;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -68,16 +68,31 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
*/
public static $descriptionLangSuffix = '_desc';
/**
* @brief [Node] Parent Node ID Database Column
*/
public static $parentNodeColumnId = 'category_id';
/**
* @brief [Node] Moderator Permission
*/
public static $modPerm = 'blogs';
/**
* @brief [Node] Maximum results to display at a time in any node helper form elements. Useful for user-submitted node types when there may be a lot. NULL for no limit.
*/
public static $maxFormHelperResults = 2000;
/**
* @brief Content Item Class
*/
public static $contentItemClass = 'IPS\blog\Entry';
/**
* @brief Category
*/
protected $category;
/**
* @brief Icon
@@ -152,16 +167,55 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
return FALSE;
}
/**
* Check permissions on any node
*
* For example - can be used to check if the user has
* permission to create content in any node to determine
* if there should be a "Submit" button
*
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
* @param \IPS\Member|\IPS\Member\Group|NULL $member The member or group to check (NULL for currently logged in member)
* @param array $where Additional WHERE clause
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
* @return bool
* @throws \OutOfBoundsException If $permission does not exist in static::$permissionMap
*/
public static function canOnAny( $permission, $member=NULL, $where = array(), $considerPostBeforeRegistering = TRUE )
{
$member = $member ?: \IPS\Member::loggedIn();
/* Posts per day - we need to do this independently here, because Blogs do not implement the Permissions interface */
if ( \in_array( $permission, array( 'add', 'reply' ) ) )
{
$checkPostsPerDay = TRUE;
if ( isset( static::$contentItemClass ) )
{
$contentClass = static::$contentItemClass;
$checkPostsPerDay = $contentClass::$checkPostsPerDay;
}
if ( $checkPostsPerDay === TRUE AND $member->checkPostsPerDay() === FALSE )
{
return FALSE;
}
}
/* This will always return true, but we always want to bubble up anyway */
return parent::canOnAny( $permission, $member, $where, $considerPostBeforeRegistering );
}
/**
* Check permissions
*
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
* @param \IPS\Member|\IPS\Member\Group|NULL $member The member or group to check (NULL for currently logged in member)
* @param mixed $permission A key which has a value in static::$permissionMap['view'] matching a column ID in core_permission_index
* @param \IPS\Member|\IPS\Member\Group|NULL $member The member or group to check (NULL for currently logged in member)
* @param bool $considerPostBeforeRegistering If TRUE, and $member is a guest, will return TRUE if "Post Before Registering" feature is enabled
* @return bool
* @throws \OutOfBoundsException If $permission does not exist in static::$permissionMap
*/
public function can( $permission, $member=NULL )
public function can( $permission, $member=NULL, $considerPostBeforeRegistering = TRUE )
{
/* Load member */
if ( $member === NULL )
@@ -169,6 +223,18 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
$member = \IPS\Member::loggedIn();
}
/* If the member has hit their posting limits, then just stop here. */
switch( $permission )
{
case 'add':
case 'reply':
if ( $member->checkPostsPerDay() === FALSE )
{
return FALSE;
}
break;
}
if ( $club = $this->club() )
{
switch ( $permission )
@@ -227,7 +293,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
}
}
return parent::can( $permission, $member );
return parent::can( $permission, $member, $considerPostBeforeRegistering );
}
/**
@@ -314,7 +380,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
foreach ( \IPS\Db::i()->select( 'data', 'core_queue', array( 'app=? AND `key`=?', 'core', 'DeleteOrMoveContent' ) ) as $row )
{
$data = json_decode( $row, TRUE );
if ( $data['class'] === get_class( $this ) and $data['id'] == $this->_id )
if ( $data['class'] === \get_class( $this ) and $data['id'] == $this->_id )
{
return FALSE;
}
@@ -327,12 +393,23 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
* [Node] Add/Edit Form
*
* @param \IPS\Helpers\Form $form The form
* @param bool $public Whether this is a public form or not
* @return void
*/
public function form( &$form, $public=FALSE )
{
if( $public )
{
$form->addTab( 'blog_settings' );
if ( !$this->id OR \IPS\Member::loggedIn()->modPermission('can_mod_blogs') )
{
$form->add( new \IPS\Helpers\Form\Node( 'blog_category_id', $this->id ? $this->category_id : NULL, TRUE, array(
'class' => '\IPS\blog\Category',
'subnodes' => FALSE,
), NULL, NULL, NULL, 'blog_category_id' ) );
}
$form->add( new \IPS\Helpers\Form\Text( 'blog_name', $this->id ? $this->_title : NULL, TRUE, array(), NULL, NULL, NULL, 'blog_name' ) );
$form->add( new \IPS\Helpers\Form\Editor( 'blog_desc', $this->id ? $this->description : NULL, FALSE, array( 'app' => 'blog', 'key' => 'Blogs', 'autoSaveKey' => ( $this->id ? "blogs-blog-{$this->id}" : "blogs-new-blog" ), 'attachIds' => $this->id ? array( $this->id, NULL, 'description' ) : NULL, 'minimize' => 'blog_desc_placeholder' ), NULL, NULL, NULL, 'blog_desc_wrap' ) );
@@ -345,7 +422,13 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
}
else
{
$form->addHeader( 'blog_settings' );
if( $this->id )
{
$form->add( new \IPS\Helpers\Form\Node( 'blog_category_id', $this->category_id, TRUE, array(
'class' => '\IPS\blog\Category',
'subnodes' => FALSE,
), NULL, NULL, NULL, 'blog_category_id' ) );
}
$groups = array();
foreach ( \IPS\Member\Group::groups() as $k => $v )
@@ -370,14 +453,14 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
{
if ( \IPS\Request::i()->blog_type === 'member' )
{
if( !is_object( $member ) or !$member->member_id )
if( !\is_object( $member ) or !$member->member_id )
{
throw new \InvalidArgumentException( 'no_blog_author_selected' );
}
}
},
NULL, NULL, 'blog_member_id' ) );
$form->add( new \IPS\Helpers\Form\Select( 'blog_groupblog_ids', $this->id ? explode( ',', $this->groupblog_ids ) : array(), FALSE, array( 'options' => $groups, 'multiple' => TRUE ), NULL, NULL, NULL, 'blog_groupblog_ids' ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'blog_groupblog_name', NULL, FALSE, array( 'app' => 'blog', 'key' => ( $this->id ? "blogs_groupblog_name_{$this->id}" : NULL ) ), function( $value ) {
if ( \IPS\Request::i()->blog_type === 'group' )
@@ -458,6 +541,13 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
{
$form->add( new \IPS\Helpers\Form\YesNo( 'blog_enable_rss', $this->id ? $this->settings['allowrss'] : TRUE ) );
}
/* Categories */
if ( $public and $this->id )
{
$form->addTab( 'blog_entry_categories' );
$form->addHtml( \IPS\Theme::i()->getTemplate( 'view', 'blog', 'front' )->manageCategories( $this ) );
}
}
/**
@@ -468,7 +558,13 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
*/
public function formatFormValues( $values )
{
if( isset( $values['blog_type'] ) and $values['blog_type'] == 'member' and isset( $values['blog_member_id'] ) and is_object( $values['blog_member_id'] ) )
if ( isset( $values['blog_category_id'] ) and $values[ 'blog_category_id' ] )
{
$values[ 'blog_category_id' ] = \intval( $values[ 'blog_category_id' ]->id );
}
if( isset( $values['blog_type'] ) and $values['blog_type'] == 'member' and isset( $values['blog_member_id'] ) and \is_object( $values['blog_member_id'] ) )
{
$values['blog_member_id']->create_menu = NULL;
$values['blog_member_id']->save();
@@ -521,6 +617,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
if ( !$this->id )
{
$this->category_id = $values[ 'blog_category_id' ];
$this->save();
if( $values['blog_member_id'] )
@@ -544,7 +641,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
if ( $fieldKey === 'blog_name' )
{
$values['seo_name'] = \IPS\Http\Url\Friendly::seoTitle( ( is_array( $values[ $fieldKey ] ) ) ? $values[ $fieldKey ][ \IPS\Lang::defaultLanguage() ] : $values[ $fieldKey ] );
$values['seo_name'] = \IPS\Http\Url\Friendly::seoTitle( ( \is_array( $values[ $fieldKey ] ) ) ? $values[ $fieldKey ][ \IPS\Lang::defaultLanguage() ] : $values[ $fieldKey ] );
}
unset( $values[ $fieldKey ] );
@@ -563,7 +660,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
{
try
{
$feed = \IPS\blog\Blog\Feed::constructFromData( \IPS\Db::i()->select( '*', 'blog_rss_import', array( 'rss_blog_id=?', $this->id ) )->first() );
$feed = \IPS\core\Rss\Import::constructFromData( \IPS\Db::i()->select( '*', 'core_rss_import', array( 'rss_import_class=? AND rss_import_node_id=?', 'IPS\\blog\\Entry', $this->id ) )->first() );
}
catch ( \UnderflowException $e )
{
@@ -684,7 +781,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
public function get_seo_name()
{
/* If we have the seo_name, just return it - don't query the language string */
if( $this->_data['seo_name'] )
if( isset( $this->_data['seo_name'] ) and $this->_data['seo_name'] )
{
return $this->_data['seo_name'];
}
@@ -796,7 +893,7 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
$memberIds[] = $member;
}
if( count( $memberIds ) )
if( \count( $memberIds ) )
{
foreach( \IPS\Db::i()->select( '*', 'core_members', 'member_id IN(' . implode( ',', $memberIds ) . ')' ) as $member )
{
@@ -1000,6 +1097,23 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
return array( \IPS\Theme::i()->getTemplate( 'browse', 'blog' ), 'rows' );
}
/**
* Save Changed Columns
*
* @return void
*/
public function save()
{
$new = $this->_new;
parent::save();
if ( $new )
{
\IPS\Api\Webhook::fire( 'blogBlog_create', $this, $this->webhookFilters() );
}
}
/**
* [ActiveRecord] Delete Record
*
@@ -1008,7 +1122,12 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
public function delete()
{
/* Delete RSS Imports */
\IPS\Db::i()->delete( 'blog_rss_import', array( "rss_blog_id=?", $this->_id ) );
try
{
$import = \IPS\core\Rss\Import::constructFromData( \IPS\Db::i()->select( '*', 'core_rss_import', array( "rss_import_class=? AND rss_import_node_id=?", 'IPS\\blog\\Entry', $this->_id ) )->first() );
$import->delete();
}
catch( \UnderflowException $e ) { }
/* Delete Language Strings */
foreach ( array( 'blog_groupblog_name' => "blogs_groupblog_name_{$this->id}" ) as $fieldKey => $langKey )
@@ -1088,15 +1207,16 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
*
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
* @apiresponse string description Description
* @apiresponse \IPS\Member owner If the blog is owned by a single member, the member
* @apiresponse [\IPS\Member\Group] groups If the blog is owned by groups, an array of the groups
* @apiresponse bool pinned If the blog is pinned
* @apiresponse int entries Number of entries
* @apiresponse int comments Number of comments
* @apiresponse string url URL
* @apiresponse int id ID number
* @apiresponse string name Name
* @apiresponse string description Description
* @apiresponse \IPS\Member owner If the blog is owned by a single member, the member
* @apiresponse [\IPS\Member\Group] groups If the blog is owned by groups, an array of the groups
* @apiresponse bool pinned If the blog is pinned
* @apiresponse int entries Number of entries
* @apiresponse int comments Number of comments
* @apiresponse string url URL
* @apiresponse \IPS\blog\Category|NULL category Category, or NULL for club blogs
*/
public function apiOutput( \IPS\Member $authorizedMember = NULL )
{
@@ -1113,17 +1233,26 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
}
}
return array(
$return = array(
'id' => $this->id,
'name' => $this->_title,
'description' => \IPS\Member::loggedIn()->language()->addToStack("blogs_blog_{$this->id}_desc"),
'description' => $this->member_id ? \IPS\Text\Parser::removeLazyLoad( $this->desc ) : \IPS\Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->_id . static::$descriptionLangSuffix , NULL, array( 'removeLazyLoad' => TRUE ) ),
'owner' => $this->member_id ? $this->owner()->apiOutput( $authorizedMember ) : null,
'groups' => $groups,
'pinned' => (bool) $this->pinned,
'entries' => $this->count_entries,
'comments' => $this->count_comments,
'url' => (string) $this->url()
'url' => (string) $this->url(),
'category' => NULL
);
try
{
$return['category'] = $this->category()->apiOutput();
}
catch( \OutOfRangeException $e ){}
return $return;
}
/**
@@ -1234,7 +1363,39 @@ class _Blog extends \IPS\Node\Model implements \IPS\Node\Ratings, \IPS\Content\E
}
$where[] = array( \IPS\Db::i()->in( 'status', $statuses ) );
return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'blog_blogs', $where )->join( 'core_clubs_memberships', array( 'club_id=blog_club_id AND member_id=?', $member->member_id ) ), get_called_class() );
return iterator_to_array( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'blog_blogs', $where )->join( 'core_clubs_memberships', array( 'club_id=blog_club_id AND member_id=?', $member->member_id ) ), \get_called_class() ) );
}
}
/**
* Get category
*
* @return \IPS\Node\Model
* @throws \OutOfRangeException|\BadMethodCallException
*/
public function category()
{
if( !$this->category_id )
{
throw new \OutOfRangeException;
}
if ( $this->category === NULL )
{
$this->category = \IPS\blog\Category::load( $this->category_id );
}
return $this->category;
}
/**
* Get last comment time
*
* @note This should return the last comment time for this node only, not for children nodes
* @return \IPS\DateTime|NULL
*/
public function getLastCommentTime()
{
return $this->last_edate ? \IPS\DateTime::ts( $this->last_edate ) : NULL;
}
}