Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+11 -2
View File
@@ -46,7 +46,16 @@ class _Application extends \IPS\Application
switch ( $rule['archive_field'] )
{
case 'lastpost':
$clause = array( 'last_post' . $rule['archive_value'] . '?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $rule['archive_text'] . mb_strtoupper( $rule['archive_unit'] ) ) )->getTimestamp() );
/* If the data is bad, log and don't throw an error, but don't allow anything to be archived. */
if( !$rule['archive_text'] OR !$rule['archive_unit'] )
{
\IPS\Log::log( 'Forum archiving missing time period or archive unit', 'forum_archive' );
$clause = array( '0=?', '1' );
}
else
{
$clause = array( '(last_post > 0 AND last_post' . $rule['archive_value'] . ' ?)', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . trim( $rule['archive_text'] ) . mb_strtoupper( $rule['archive_unit'] ) ) )->getTimestamp() );
}
break;
case 'forum':
@@ -70,7 +79,7 @@ class _Application extends \IPS\Application
}
else
{
$clause = array( 'poll_state=0 or poll_state IS NULL' );
$clause = array( '(poll_state=0 or poll_state IS NULL)' );
}
break;
+20 -9
View File
@@ -32,6 +32,9 @@ class _forums extends \IPS\Node\Api\NodeController
* GET /forums/forums
* Get list of forums
*
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @note For requests using an OAuth Access Token for a particular member, only forums the authorized user can view will be included
* @return \IPS\Api\PaginatedResponse<IPS\forums\Forum>
*/
public function GETindex()
@@ -45,19 +48,26 @@ class _forums extends \IPS\Node\Api\NodeController
* Get specific forum
*
* @param int $id ID Number
*
* @return \IPS\Api\PaginatedResponse<IPS\forums\Forum>
* @throws 1F363/1 INVALID_ID The forum does not exist or the authorized user does not have permission to view it
* @return \IPS\forums\Forum
*/
public function GETitem( $id )
{
/* Return */
return $this->_view( $id );
try
{
return $this->_view( $id );
}
catch ( \OutOfRangeException $e )
{
throw new \IPS\Api\Exception( 'INVALID_ID', '1F363/1', 404 );
}
}
/**
* POST /forums/forums
* Create a forum
*
* @apiclientonly
* @reqapiparam string title The forum title
* @apiparam string description The forum description
* @apiparam string type normal|qa|redirect|category
@@ -71,13 +81,14 @@ class _forums extends \IPS\Node\Api\NodeController
*/
public function POSTindex()
{
return $this->_create();
return new \IPS\Api\Response( 201, $this->_create()->apiOutput( $this->member ) );
}
/**
* POST /forums/forums/{id}
* Edit a forum
*
* @apiclientonly
* @reqapiparam string title The forum title
* @apiparam string description The forum description
* @apiparam string type normal|qa|redirect|category
@@ -91,17 +102,17 @@ class _forums extends \IPS\Node\Api\NodeController
*/
public function POSTitem( $id )
{
$class = static::$class;
$class = $this->class;
$forum = $class::load( $id );
$forum = $this->_createOrUpdate( $forum );
return $forum;
return new \IPS\Api\Response( 201, $this->_createOrUpdate( $forum )->apiOutput( $this->member ) );
}
/**
* DELETE /forums/forums/{id}
* Delete a forum
*
* @apiclientonly
* @param int $id ID Number
* @return void
*/
+62 -24
View File
@@ -32,6 +32,7 @@ class _posts extends \IPS\Content\Api\CommentController
* GET /forums/posts
* Get list of posts
*
* @note For requests using an OAuth Access Token for a particular member, only posts the authorized user can view will be included
* @apiparam string forums Comma-delimited list of forum IDs
* @apiparam string authors Comma-delimited list of member IDs - if provided, only posts by those members are returned
* @apiparam int hasBestAnswer If 1, only posts from topics with a best answer are returned, if 0 only without
@@ -44,6 +45,7 @@ class _posts extends \IPS\Content\Api\CommentController
* @apiparam string sortBy What to sort by. Can be 'date', 'title' or leave unspecified for ID
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\forums\Topic\Post>
*/
public function GETindex()
@@ -86,14 +88,24 @@ class _posts extends \IPS\Content\Api\CommentController
* View information about a specific post
*
* @param int $id ID Number
* @throws 1F295/4 INVALID_ID The post ID does not exist
* @throws 1F295/4 INVALID_ID The post ID does not exist or the authorized user does not have permission to view it
* @return \IPS\forums\Topic\Post
*/
public function GETitem( $id )
{
try
{
return new \IPS\Api\Response( 200, \IPS\forums\Topic\Post::load( $id )->apiOutput() );
$class = $this->class;
if ( $this->member )
{
$object = $class::loadAndCheckPerms( $id, $this->member );
}
else
{
$object = $class::load( $id );
}
return new \IPS\Api\Response( 200, $object->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -105,16 +117,18 @@ class _posts extends \IPS\Content\Api\CommentController
* POST /forums/posts
* Create a post
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, hidden will only be honoured if the authentictaed user has permission to hide content).
* @reqapiparam int topic The ID number of the topic the post should be created in
* @reqapiparam int author The ID number of the member making the post (0 for guest)
* @reqapiparam int author The ID number of the member making the post (0 for guest). Required for requests made using an API Key or the Client Credentials Grant Type. For requests using an OAuth Access Token for a particular member, that member will always be the author
* @apiparam string author_name If author is 0, the guest name that should be used
* @reqapiparam string post The post content as HTML (e.g. "<p>This is a post.</p>")
* @apiparam datetime date The date/time that should be used for the topic/post post date. If not provided, will use the current date/time
* @apiparam string ip_address The IP address that should be stored for the topic/post. If not provided, will use the IP address from the API request
* @reqapiparam string post The post content as HTML (e.g. "<p>This is a post.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam datetime date The date/time that should be used for the topic/post post date. If not provided, will use the current date/time. Ignored for requests using an OAuth Access Token for a particular member
* @apiparam string ip_address The IP address that should be stored for the topic/post. If not provided, will use the IP address from the API request. Ignored for requests using an OAuth Access Token for a particular member
* @apiparam int hidden 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @throws 1F295/1 NO_TOPIC The forum ID does not exist
* @throws 1F295/1 NO_TOPIC The topic ID does not exist
* @throws 1F295/2 NO_AUTHOR The author ID does not exist
* @throws 1F295/3 NO_POST No post was supplied
* @throws 2F294/A NO_PERMISSION The authorized user does not have permission to reply to that topic
* @return \IPS\forums\Topic\Post
*/
public function POSTindex()
@@ -130,18 +144,29 @@ class _posts extends \IPS\Content\Api\CommentController
}
/* Get author */
if ( \IPS\Request::i()->author )
if ( $this->member )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
if ( !$topic->canComment( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1F295/2', 403 );
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F294/A', 403 );
}
$author = $this->member;
}
else
{
$author = new \IPS\Member;
$author->name = \IPS\Request::i()->author_name;
if ( \IPS\Request::i()->author )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1F295/2', 403 );
}
}
else
{
$author = new \IPS\Member;
$author->name = \IPS\Request::i()->author_name;
}
}
/* Check we have a post */
@@ -158,16 +183,17 @@ class _posts extends \IPS\Content\Api\CommentController
* POST /forums/posts/{id}
* Edit a post
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, hidden will only be honoured if the authentictaed user has permission to hide content).
* @param int $id ID Number
* @apiparam int author The ID number of the member making the post (0 for guest)
* @apiparam int author The ID number of the member making the post (0 for guest). Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string author_name If author is 0, the guest name that should be used
* @apiparam string post The post content as HTML (e.g. "<p>This is a post.</p>")
* @apiparam int hidden 1/0 indicating if the topic should be hidden
* @throws 2F295/6 INVALID_ID The post ID does not exist
* @throws 2F295/7 NO_AUTHOR The author ID does not exist
* @throws 2F295/7 NO_AUTHOR The author ID does not exist
* @apiparam string post The post content as HTML (e.g. "<p>This is a post.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam int hidden 1/0 indicating if the topic should be hidden
* @throws 2F295/6 INVALID_ID The post ID does not exist or the authorized user does not have permission to view it
* @throws 2F295/7 NO_AUTHOR The author ID does not exist
* @throws 1F295/8 CANNOT_HIDE_FIRST_POST You cannot hide or unhide the first post in a topic. Hide/unhide the topic itself instead.
* @throws 1F295/9 CANNOT_AUTHOR_FIRST_POST You cannot change the author for the first post in a topic. Change the author on the topic itself instead.
* @throws 2F295/A NO_PERMISSION The authorized user does not have permission to edit the post
* @return \IPS\forums\Topic\Post
*/
public function POSTitem( $id )
@@ -176,6 +202,14 @@ class _posts extends \IPS\Content\Api\CommentController
{
/* Load */
$post = \IPS\forums\Topic\Post::load( $id );
if ( $this->member and !$post->canView( $this->member ) )
{
throw new \OutOfRangeException;
}
if ( $this->member and !$post->canEdit( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F295/A', 403 );
}
/* Check */
if ( $post->isFirst() )
@@ -213,20 +247,24 @@ class _posts extends \IPS\Content\Api\CommentController
* @param int $id ID Number
* @throws 1F295/5 INVALID_ID The post ID does not exist
* @throws 1F295/B CANNOT_DELETE_FIRST_POST You cannot delete the first post in a topic. Delete the topic itself instead.
* @throws 2F295/B NO_PERMISSION The authorized user does not have permission to delete the post
* @return void
*/
public function DELETEitem( $id )
{
try
{
$post = \IPS\forums\Topic\Post::load( $id );
if ( $post->isFirst() )
$class = $this->class;
$object = $class::load( $id );
if ( $object->isFirst() )
{
throw new \IPS\Api\Exception( 'CANNOT_DELETE_FIRST_POST', '1F295/B', 403 );
}
$post->delete();
if ( $this->member and !$object->canDelete( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F295/B', 403 );
}
$object->delete();
return new \IPS\Api\Response( 200, NULL );
}
+71 -32
View File
@@ -32,6 +32,7 @@ class _topics extends \IPS\Content\Api\ItemController
* GET /forums/topics
* Get list of topics
*
* @note For requests using an OAuth Access Token for a particular member, only topics the authorized user can view will be included
* @apiparam string forums Comma-delimited list of forum IDs
* @apiparam string authors Comma-delimited list of member IDs - if provided, only topics started by those members are returned
* @apiparam int hasBestAnswer If 1, only topics with a best answer are returned, if 0 only without
@@ -44,6 +45,7 @@ class _topics extends \IPS\Content\Api\ItemController
* @apiparam string sortBy What to sort by. Can be 'date', 'title' or leave unspecified for ID
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @apiparam int perPage Number of results per page - defaults to 25
* @return \IPS\Api\PaginatedResponse<IPS\forums\Topic>
*/
public function GETindex()
@@ -109,7 +111,8 @@ class _topics extends \IPS\Content\Api\ItemController
* @apiparam int hidden If 1, only posts which are hidden are returned, if 0 only not hidden
* @apiparam string sortDir Sort direction. Can be 'asc' or 'desc' - defaults to 'asc'
* @apiparam int page Page number
* @throws 1F294/1 INVALID_ID The topic ID does not exist
* @apiparam int perPage Number of results per page - defaults to 25
* @throws 1F294/1 INVALID_ID The topic ID does not exist or the authorized user does not have permission to view it
* @return \IPS\Api\PaginatedResponse<IPS\forums\Topic\Post>
*/
public function GETitem_posts( $id )
@@ -128,9 +131,10 @@ class _topics extends \IPS\Content\Api\ItemController
* Create or update topic
*
* @param \IPS\Content\Item $item The item
* @param string $type add or edit
* @return \IPS\Content\Item
*/
protected function _createOrUpdate( \IPS\Content\Item $item )
protected function _createOrUpdate( \IPS\Content\Item $item, $type='add' )
{
/* Open/Close time */
if ( \IPS\Request::i()->open_time )
@@ -143,32 +147,34 @@ class _topics extends \IPS\Content\Api\ItemController
}
/* Pass up */
return parent::_createOrUpdate( $item );
return parent::_createOrUpdate( $item, $type );
}
/**
* POST /forums/topics
* Create a topic
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, locked will only be honoured if the authentictaed user has permission to lock topics).
* @reqapiparam int forum The ID number of the forum the topic should be created in
* @reqapiparam int author The ID number of the member creating the topic (0 for guest)
* @apiparam int author The ID number of the member creating the topic (0 for guest). Required for requests made using an API Key or the Client Credentials Grant Type. For requests using an OAuth Access Token for a particular member, that member will always be the author
* @apiparam string author_name If author is 0, the guest name that should be used
* @reqapiparam string title The topic title
* @reqapiparam string post The post content as HTML (e.g. "<p>This is a post.</p>")
* @reqapiparam string post The post content as HTML (e.g. "<p>This is a post.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam string prefix Prefix tag
* @apiparam string tags Comma-separated list of tags (do not include prefix)
* @apiparam datetime date The date/time that should be used for the topic/post post date. If not provided, will use the current date/time
* @apiparam string ip_address The IP address that should be stored for the topic/post. If not provided, will use the IP address from the API request
* @apiparam datetime date The date/time that should be used for the topic/post post date. If not provided, will use the current date/time. Ignored for requests using an OAuth Access Token for a particular member
* @apiparam string ip_address The IP address that should be stored for the topic/post. If not provided, will use the IP address from the API request. Ignored for requests using an OAuth Access Token for a particular member
* @apiparam int locked 1/0 indicating if the topic should be locked
* @apiparam datetime open_time When the topic should be unlocked from
* @apiparam datetime close_time When the topic should be locked from
* @apiparam int hidden 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @apiparam int pinned 1/0 indicating if the topic should be pinned
* @apiparam int featured 1/0 indicating if the topic should be featured
* @throws 1F294/2 NO_FORUM The forum ID does not exist
* @throws 1F294/3 NO_AUTHOR The author ID does not exist
* @throws 1F294/5 NO_TITLE No title was supplied
* @throws 1F294/4 NO_POST No post was supplied
* @throws 1F294/2 NO_FORUM The forum ID does not exist
* @throws 1F294/3 NO_AUTHOR The author ID does not exist
* @throws 1F294/5 NO_TITLE No title was supplied
* @throws 1F294/4 NO_POST No post was supplied
* @throws 2F294/9 NO_PERMISSION The authorized user does not have permission to create a topic in that forum
* @return \IPS\forums\Topic
*/
public function POSTindex()
@@ -184,18 +190,29 @@ class _topics extends \IPS\Content\Api\ItemController
}
/* Get author */
if ( \IPS\Request::i()->author )
if ( $this->member )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
if ( !$forum->can( 'add', $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1F294/3', 400 );
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F294/9', 403 );
}
$author = $this->member;
}
else
{
$author = new \IPS\Member;
$author->name = \IPS\Request::i()->author_name;
if ( \IPS\Request::i()->author )
{
$author = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$author->member_id )
{
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1F294/3', 400 );
}
}
else
{
$author = new \IPS\Member;
$author->name = \IPS\Request::i()->author_name;
}
}
/* Check we have a title and a post */
@@ -209,31 +226,33 @@ class _topics extends \IPS\Content\Api\ItemController
}
/* Do it */
return new \IPS\Api\Response( 201, $this->_create( $forum, $author )->apiOutput() );
return new \IPS\Api\Response( 201, $this->_create( $forum, $author )->apiOutput( $this->member ) );
}
/**
* POST /forums/topics/{id}
* Edit a topic
*
* @note For requests using an OAuth Access Token for a particular member, any parameters the user doesn't have permission to use are ignored (for example, locked will only be honoured if the authentictaed user has permission to lock topics).
* @apiparam int forum The ID number of the forum the topic should be created in
* @apiparam int author The ID number of the member creating the topic (0 for guest)
* @apiparam int author The ID number of the member creating the topic (0 for guest). Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string author_name If author is 0, the guest name that should be used
* @apiparam string title The topic title
* @apiparam string post The post content as HTML (e.g. "<p>This is a post.</p>")
* @apiparam string post The post content as HTML (e.g. "<p>This is a post.</p>"). Will be sanatized for requests using an OAuth Access Token for a particular member; will be saved unaltered for requests made using an API Key or the Client Credentials Grant Type.
* @apiparam string prefix Prefix tag
* @apiparam string tags Comma-separated list of tags (do not include prefix)
* @apiparam datetime date The date/time that should be used for the topic/post post date. If not provided, will use the current date/time
* @apiparam string ip_address The IP address that should be stored for the topic/post. If not provided, will use the IP address from the API request
* @apiparam datetime date The date/time that should be used for the topic/post post date. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam string ip_address The IP address that should be stored for the topic/post. Ignored for requests using an OAuth Access Token for a particular member.
* @apiparam int locked 1/0 indicating if the topic should be locked
* @apiparam datetime open_time When the topic should be unlocked from
* @apiparam datetime close_time When the topic should be locked from
* @apiparam int hidden 1/0 indicating if the topic should be hidden
* @apiparam int pinned 1/0 indicating if the topic should be pinned
* @apiparam int featured 1/0 indicating if the topic should be featured
* @throws 2F294/6 INVALID_ID The topic ID does not exist
* @throws 1F294/7 NO_FORUM The forum ID does not exist
* @throws 1F294/8 NO_AUTHOR The author ID does not exist
* @throws 2F294/6 INVALID_ID The topic ID does not exist or the authorized user does not have permission to view it
* @throws 1F294/7 NO_FORUM The forum ID does not exist or the authorized user does not have permission to post in it
* @throws 1F294/8 NO_AUTHOR The author ID does not exist
* @throws 2F294/A NO_PERMISSION The authorized user does not have permission to edit the topic
* @return \IPS\forums\Topic
*/
public function POSTitem( $id )
@@ -241,13 +260,26 @@ class _topics extends \IPS\Content\Api\ItemController
try
{
$topic = \IPS\forums\Topic::load( $id );
if ( $this->member and !$topic->can( 'read', $this->member ) )
{
throw new \OutOfRangeException;
}
if ( $this->member and !$topic->canEdit( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F294/A', 403 );
}
/* New forum */
if ( isset( \IPS\Request::i()->forum ) and \IPS\Request::i()->forum != $topic->forum_id )
if ( isset( \IPS\Request::i()->forum ) and \IPS\Request::i()->forum != $topic->forum_id and ( !$this->member or $topic->canMove( $this->member ) ) )
{
try
{
$newForum = \IPS\forums\Forum::load( \IPS\Request::i()->forum );
if ( $this->member and !$newForum->can( 'add', $this->member ) )
{
throw new \OutOfRangeException;
}
$topic->move( $newForum );
}
catch ( \OutOfRangeException $e )
@@ -257,7 +289,7 @@ class _topics extends \IPS\Content\Api\ItemController
}
/* New author */
if ( isset( \IPS\Request::i()->author ) )
if ( !$this->member and isset( \IPS\Request::i()->author ) )
{
/* Just renaming the guest */
if ( !$topic->starter_id and ( !isset( \IPS\Request::i()->author ) or !\IPS\Request::i()->author ) and isset( \IPS\Request::i()->author_name ) )
@@ -291,11 +323,11 @@ class _topics extends \IPS\Content\Api\ItemController
}
/* Everything else */
$this->_createOrUpdate( $topic );
$this->_createOrUpdate( $topic, 'edit' );
/* Save and return */
$topic->save();
return new \IPS\Api\Response( 200, $topic->apiOutput() );
return new \IPS\Api\Response( 200, $topic->apiOutput( $this->member ) );
}
catch ( \OutOfRangeException $e )
{
@@ -307,15 +339,22 @@ class _topics extends \IPS\Content\Api\ItemController
* DELETE /forums/topics/{id}
* Delete a topic
*
* @param int $id ID Number
* @throws 1F294/5 INVALID_ID The topic ID does not exist
* @param int $id ID Number
* @throws 1F294/5 INVALID_ID The topic ID does not exist
* @throws 2F294/B NO_PERMISSION The authorized user does not have permission to delete the topic
* @return void
*/
public function DELETEitem( $id )
{
try
{
\IPS\forums\Topic::load( $id )->delete();
$item = \IPS\forums\Topic::load( $id );
if ( $this->member and !$item->canDelete( $this->member ) )
{
throw new \IPS\Api\Exception( 'NO_PERMISSION', '2F294/B', 404 );
}
$item->delete();
return new \IPS\Api\Response( 200, NULL );
}
+2 -2
View File
@@ -19,7 +19,7 @@
</div>
</td>
</tr>]]></template_content_html><template_data>$comment, $email</template_data><template_content_plaintext><![CDATA[{{if $comment->item()->isQuestion()}}{$email->language->addToStack("x_answered_a_question", FALSE, array( 'sprintf' => array( $comment->author()->name ) ) )}{{else}}{$email->language->addToStack("x_replied_to_a_topic", FALSE, array( 'sprintf' => array( $comment->author()->name ) ) )}{{endif}}: {$comment->item()->mapped('title')} ({$comment->url()})
]]></template_content_plaintext></template><template><template_app>forums</template_app><template_name>digests__item</template_name><template_content_html><![CDATA[<tr style='border-bottom: 1px solid #eeeeee'>
]]></template_content_plaintext><template_pinned>0</template_pinned></template><template><template_app>forums</template_app><template_name>digests__item</template_name><template_content_html><![CDATA[<tr style='border-bottom: 1px solid #eeeeee'>
<td dir='{dir}' width='40' align='center' valign='top' class=' '>
<img src='{$topic->author()->get_photo( true, true )}' width='32' height='32' style='border: 1px solid #000; vertical-align: middle;'>
</td>
@@ -60,4 +60,4 @@
</p>
</td>
</tr>]]></template_content_html><template_data>$topic, $email</template_data><template_content_plaintext><![CDATA[{{if $topic->isQuestion()}}{$email->language->addToStack("x_asked_new_question", FALSE, array( 'sprintf' => array( $topic->author()->name, $topic->container()->_title ) ) )}{{else}}{$email->language->addToStack("x_created_topic_in", FALSE, array( 'sprintf' => array( $topic->author()->name, $topic->container()->_title ) ) )}{{endif}}: {$topic->title} ({$topic->url()})
]]></template_content_plaintext></template></emails>
]]></template_content_plaintext><template_pinned>0</template_pinned></template></emails>
+4
View File
@@ -33,6 +33,10 @@
"real": "app=forums&module=forums&controller=forums",
"verify": "\\IPS\\forums\\Forum"
},
"forums_clubs": {
"friendly": "forum/clubs",
"real": "app=forums&module=forums&controller=forums&do=clubs"
},
"topic_copy": {
"friendly": "topic/{#id}-{?}/copy",
"real": "app=cms&module=database&controller=topic"
+31 -36
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<javascript app="forums">
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.flow.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050"><![CDATA[/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.flow.js" javascript_type="controller" javascript_version="103005" javascript_position="1000100"><![CDATA[/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -29,20 +29,22 @@
/* Populate forum IDs */
var _ids = ips.utils.url.getParam( 'forumId' );
var self = this;
if ( ! _.isUndefined( _ids ) ) {
/* Check URL */
this._ids = decodeURIComponent( ips.utils.url.getParam( 'forumId' ) ).split(',');
this._ids = decodeURIComponent( _ids ).split(',');
} else if( ! _.isUndefined( ips.utils.cookie.get('forums_flowIds') ) ) {
/* Check local storage */
var _ids = ips.utils.cookie.get('forums_flowIds').split(',');
var self = this;
if( _.isObject( _ids ) && _.size( _ids ) ){
_.each( _ids, function (val, key) {
if ( val ) {
self.childSelect( val );
}
});
}
this._ids = ips.utils.cookie.get('forums_flowIds').split(',');
}
if( _.isObject( this._ids ) && _.size( this._ids ) ){
_.each( this._ids, function (val, key) {
if ( val ) {
self.childSelect( val, true );
}
});
}
this._button = $('body').find('[data-role="fluidForumMobileDesc"]');
@@ -64,7 +66,7 @@
if ( _.indexOf( this._ids, id ) == -1 ) {
/* Does not exist, so add it */
this.childSelect( id );
this.childSelect( id, false );
} else {
/* Exists, so remove it */
@@ -73,7 +75,7 @@
this._updateButtonText();
$( document ).trigger( 'updateTableURL', { forumId: this._ids.join(',') } );
$( document ).trigger( 'updateTableURL', { forumId: _.uniq( _.values( this._ids ) ).join(',') } );
},
/**
@@ -88,32 +90,25 @@
* Select a child
*
*/
childSelect: function(id) {
childSelect: function(id, skipChildren) {
Debug.log("Select child: " + id );
this._ids.push( id );
var node = this.scope.find('[data-node-id="' + id + '"]');
node.addClass('cForumMiniList_selected');
var parentId = node.attr('data-parent-id');
this.updateCookie();
/* Now unselect any parents */
if ( id != parentId ) {
this.parentUnselect( parentId );
}
/* Mark any children, but we do not need to keep individual row markings */
if ( node.attr('data-has-children') || id == parentId ) {
node.closest('li').addClass('cForumMiniList_categorySelected');
if ( skipChildren === false && ( node.attr('data-has-children') || id == parentId ) ) {
var self = this;
_.each( this.scope.find('[data-parent-id="' + id + '"]'), function( v, k ) {
var _cId = $(v).attr('data-node-id');
if ( _cId != id ) {
self.childUnselect( _cId );
self.childSelect( _cId, false );
}
} );
}
this.updateCookie();
},
/**
@@ -124,7 +119,6 @@
Debug.log("UNselect child: " + id );
/* Remove marking */
this.scope.find('[data-node-id="' + id + '"]').removeClass('cForumMiniList_selected');
this.scope.find('[data-node-id="' + id + '"]').closest('li').removeClass('cForumMiniList_categorySelected');
/* Remove from local storage and id stack */
this._ids = _.without( this._ids, id );
@@ -138,6 +132,9 @@
self.childUnselect( _cId );
}
} );
/* And we always need to unselect the category */
this.parentUnselect( this.scope.find('[data-node-id="' + id + '"]').closest('[data-category]').find('[data-node-id]').first().attr('data-node-id') );
},
/**
@@ -148,7 +145,6 @@
Debug.log("Select parent: " + parentId );
/* Mark category and children as selected */
this.scope.find('[data-node-id="' + parentId + '"]').addClass('cForumMiniList_selected');
this.scope.find('[data-node-id="' + parentId + '"]').closest('li').addClass('cForumMiniList_categorySelected');
/* Remove children from the arrays as PHP handles this */
var self = this;
@@ -170,7 +166,6 @@
/* Unselect parent as marked */
var node = this.scope.find('[data-node-id="' + parentId + '"]');
node.removeClass('cForumMiniList_selected');
node.closest('li').removeClass('cForumMiniList_categorySelected');
this._ids = _.without( this._ids, parentId );
this.updateCookie();
@@ -207,7 +202,7 @@
_updateButtonText: function () {
var blobs = this.scope.find('.cForumMiniList_blob');
var selectedBlobRows = blobs.filter( function () {
if( $( this ).closest('.cForumMiniList_selected, .cForumMiniList_categorySelected').length ){
if( $( this ).closest('.cForumMiniList_selected').length ){
return true;
}
return false;
@@ -225,7 +220,7 @@
}
});
}(jQuery, _));]]></file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.forumList.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050"><![CDATA[/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.forumList.js" javascript_type="controller" javascript_version="103005" javascript_position="1000100"><![CDATA[/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -292,7 +287,7 @@
}
});
}(jQuery, _));]]></file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.forumPage.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050">/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.forumPage.js" javascript_type="controller" javascript_version="103005" javascript_position="1000100">/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -351,7 +346,7 @@
}
});
}(jQuery, _));</file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.hovercard.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050">/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/forum" javascript_name="ips.forum.hovercard.js" javascript_type="controller" javascript_version="103005" javascript_position="1000100">/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -401,7 +396,7 @@
}
});
}(jQuery, _));</file>
<file javascript_app="forums" javascript_location="admin" javascript_path="controllers/settings" javascript_name="ips.settings.archiveRules.js" javascript_type="controller" javascript_version="102002" javascript_position="1000050"><![CDATA[/**
<file javascript_app="forums" javascript_location="admin" javascript_path="controllers/settings" javascript_name="ips.settings.archiveRules.js" javascript_type="controller" javascript_version="103005" javascript_position="1000050"><![CDATA[/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -466,7 +461,7 @@
});
}(jQuery, _));
]]></file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.answers.js" javascript_type="controller" javascript_version="102002" javascript_position="1000100"><![CDATA[/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.answers.js" javascript_type="controller" javascript_version="103005" javascript_position="1000050"><![CDATA[/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -564,7 +559,7 @@
}
});
}(jQuery, _));]]></file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.reply.js" javascript_type="controller" javascript_version="102002" javascript_position="1000100">/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.reply.js" javascript_type="controller" javascript_version="103005" javascript_position="1000050">/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
@@ -593,7 +588,7 @@
}
});
}(jQuery, _));</file>
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.view.js" javascript_type="controller" javascript_version="102002" javascript_position="1000100">/**
<file javascript_app="forums" javascript_location="front" javascript_path="controllers/topic" javascript_name="ips.topic.view.js" javascript_type="controller" javascript_version="103005" javascript_position="1000050">/**
* Invision Community
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
*
+9 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<language>
<app key="forums" version="102002">
<app key="forums" version="103005">
<word key="__app_forums" js="0">Forums</word>
<word key="frontnavigation_forums" js="0">Forums</word>
<word key="__indefart_post" js="0">a post in a topic</word>
@@ -63,7 +63,7 @@
<word key="can_unhide_topic" js="0">Can unhide topics/questions?</word>
<word key="can_view_hidden_topic" js="0">Can view hidden topics/questions?</word>
<word key="can_move_topic" js="0">Can move topics/questions?</word>
<word key="can_move_topic_desc" js="0">Will be able to move topics/questions only between the forums they have permission to moderate, not to any other forum.</word>
<word key="can_move_topic_desc" js="0">Will be able to move topics/questions only between the forums they can create new content within.</word>
<word key="can_lock_topic" js="0">Can lock topics/questions?</word>
<word key="can_unlock_topic" js="0">Can unlock topics/questions?</word>
<word key="can_reply_to_locked_topic" js="0">Can reply to locked topics/questions?</word>
@@ -218,7 +218,6 @@
<word key="archive_topic_state" js="0">Locked/Unlocked</word>
<word key="unlocked" js="0">Unlocked</word>
<word key="archive_topic_approved" js="0">Hidden/Unhidden</word>
<word key="unhidden" js="0">Unhidden</word>
<word key="archive_topic_poll" js="0">Poll</word>
<word key="topic_has_poll" js="0">Has poll</word>
<word key="topic_does_not_have_poll" js="0">Does not have poll</word>
@@ -502,6 +501,7 @@
<word key="topic_unlocks_at_short" js="0">Unlocks %s</word>
<word key="topic_locks_at" js="0">Topic will be automatically locked %s at %s</word>
<word key="topic_locks_at_short" js="0">Locks %s</word>
<word key="club_node_forums" js="0">Club Forums</word>
<word key="saved_actions" js="0">Saved Actions</word>
<word key="saved_actions_sg" js="0">Saved Action</word>
<word key="mm_title" js="0">Name</word>
@@ -563,8 +563,14 @@
<word key="ad_fluid_index_view_repeat_suffix" js="0">times</word>
<word key="stats_new_topics" js="0">New Topics</word>
<word key="stats_new_posts" js="0">New Posts</word>
<word key="stats_topics_tab_total" js="0">All New Topics</word>
<word key="stats_topics_tab_byforum" js="0">New Topics By Forum</word>
<word key="stats_topics_title" js="0">New topics by time period</word>
<word key="stats_topics_title_byforum" js="0">New topics by forum</word>
<word key="stats_posts_tab_total" js="0">All New Posts</word>
<word key="stats_posts_tab_byforum" js="0">New Posts By Forum</word>
<word key="stats_posts_title" js="0">New posts by time period</word>
<word key="stats_posts_title_byforum" js="0">New posts by forum</word>
<word key="widget_feed_container_topic" js="0">Forums</word>
<word key="block_topicFeed_title" js="0">Topics</word>
<word key="block_postFeed_title" js="0">Posts</word>
+123 -97
View File
@@ -17,29 +17,29 @@
"zerofill": false
},
"topics": {
"name": "topics",
"type": "MEDIUMINT",
"length": 6,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
"binary": false,
"comment": "",
"decimals": null,
"default": "0",
"length": 10,
"name": "topics",
"type": "INT",
"unsigned": true,
"values": [],
"zerofill": false
},
"posts": {
"name": "posts",
"type": "MEDIUMINT",
"length": 6,
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
@@ -129,18 +129,18 @@
"zerofill": false
},
"last_title": {
"allow_null": true,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": null,
"length": 255,
"name": "last_title",
"type": "VARCHAR",
"length": 255,
"decimals": null,
"values": [],
"allow_null": true,
"default": null,
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
},
"last_id": {
"allow_null": true,
@@ -325,32 +325,32 @@
"zerofill": false
},
"queued_topics": {
"allow_null": false,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": "0",
"length": 6,
"name": "queued_topics",
"type": "MEDIUMINT",
"unsigned": false,
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"zerofill": false
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
},
"queued_posts": {
"allow_null": false,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": "0",
"length": 6,
"name": "queued_posts",
"type": "MEDIUMINT",
"unsigned": false,
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"zerofill": false
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
},
"forum_allow_rating": {
"allow_null": false,
@@ -367,32 +367,32 @@
"zerofill": false
},
"min_posts_post": {
"allow_null": false,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": "0",
"length": 10,
"name": "min_posts_post",
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
},
"min_posts_view": {
"allow_null": false,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": "0",
"length": 10,
"name": "min_posts_view",
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
},
"can_view_others": {
"allow_null": false,
@@ -591,46 +591,46 @@
"zerofill": false
},
"icon": {
"allow_null": true,
"auto_increment": false,
"binary": false,
"comment": "",
"decimals": null,
"default": null,
"length": 0,
"name": "icon",
"type": "TEXT",
"length": null,
"decimals": null,
"values": [],
"allow_null": true,
"default": null,
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
},
"club_id": {
"allow_null": true,
"auto_increment": false,
"binary": false,
"comment": "The club ID if this forum belongs to a club, or NULL",
"decimals": null,
"default": null,
"length": 20,
"name": "club_id",
"type": "BIGINT",
"length": 20,
"decimals": null,
"values": null,
"allow_null": true,
"default": null,
"comment": "The club ID if this forum belongs to a club, or NULL",
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
},
"feature_color": {
"allow_null": true,
"auto_increment": false,
"binary": false,
"comment": "Feature color",
"decimals": null,
"default": null,
"length": 15,
"name": "feature_color",
"type": "VARCHAR",
"length": 15,
"decimals": null,
"values": null,
"allow_null": true,
"default": null,
"comment": "Feature color",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
"values": [],
"zerofill": false
}
},
"indexes": {
@@ -647,16 +647,28 @@
"position": {
"type": "key",
"name": "position",
"length": [
null,
null
],
"columns": [
"parent_id",
"position"
]
},
"min_posts_view": {
"type": "key",
"name": "min_posts_view",
"columns": [
"min_posts_view"
],
"length": [
null,
null
]
}
},
"collation": "utf8mb4_unicode_ci",
"engine": "InnoDB",
"inserts": [
{
"id": 1,
@@ -748,7 +760,9 @@
"qa_rate_answers": "*",
"icon": null
}
]
],
"comment": "",
"reporting": "count"
},
"forums_topics": {
"name": "forums_topics",
@@ -1451,7 +1465,9 @@
"featured": "0",
"question_rating": "0"
}
]
],
"comment": "",
"reporting": "count"
},
"forums_posts": {
"name": "forums_posts",
@@ -1845,7 +1861,6 @@
"post_date": "<%TIME%>",
"post": "<p>Welcome to your new Invision Community!<\/p> <br \/><p> Congratulations on your purchase of our software and setting up your community. Please take some time and read through the Getting Started Guide and Administrator Documentation. The Getting Started Guide will walk you through some of the necessary steps to setting up an IP.Board and starting your community. The Administrator Documentation takes you through the details of the capabilities of IP.Board.<\/p> <br \/><p> You can remove this message, topic, forum or even category at any time.<\/p> <br \/><p><a href='https:\/\/remoteservices.invisionpower.com\/docs\/getting-started' rel='external'>Go to the documentation now...<\/a><br \/><p>Nulled by IPBMafia.ru<\/p>",
"queued": "0",
"queued": "0",
"topic_id": "1",
"new_topic": "1",
"edit_name": "",
@@ -1858,7 +1873,9 @@
"post_field_t1": null,
"post_field_t2": null
}
]
],
"comment": "",
"reporting": "count"
},
"forums_answer_ratings": {
"name": "forums_answer_ratings",
@@ -1993,7 +2010,9 @@
"member"
]
}
}
},
"comment": "",
"reporting": "count"
},
"forums_question_ratings": {
"name": "forums_question_ratings",
@@ -2114,7 +2133,9 @@
null
]
}
}
},
"comment": "",
"reporting": "count"
},
"forums_archive_rules": {
"name": "forums_archive_rules",
@@ -2259,7 +2280,8 @@
"archive_unit": "",
"archive_skip": 0
}
]
],
"reporting": "count"
},
"forums_archive_posts": {
"name": "forums_archive_posts",
@@ -2833,7 +2855,9 @@
"mm_id"
]
}
}
},
"comment": "",
"reporting": "count"
},
"forums_rss_import": {
"name": "forums_rss_import",
@@ -3100,7 +3124,9 @@
"rss_import_last_import"
]
}
}
},
"comment": "",
"reporting": "count"
},
"forums_rss_imported": {
"name": "forums_rss_imported",
+36 -18
View File
@@ -1,74 +1,92 @@
[
{
"key": "archive_on",
"default": "0"
"default": "0",
"report": "full"
},
{
"key": "archive_remote_sql_database",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_remote_sql_host",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_remote_sql_pass",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_remote_sql_user",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_sql_port",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_sql_socket",
"default": ""
"default": "",
"report": "none"
},
{
"key": "archive_sql_tbl_prefix",
"default": ""
"default": "",
"report": "none"
},
{
"key": "forums_answers_downvote",
"default": "1"
"default": "1",
"report": "full"
},
{
"key": "forums_default_view",
"default": "table"
"default": "table",
"report": "full"
},
{
"key": "forums_default_view_choose",
"default": "0"
"default": "0",
"report": "full"
},
{
"key": "forums_fluid_pinned",
"default": "1"
"default": "1",
"report": "full"
},
{
"key": "forums_new_questions",
"default": "0"
"default": "0",
"report": "full"
},
{
"key": "forums_popular_now",
"default": "{\"posts\":\"10\",\"minutes\":\"60\"}"
"default": "{\"posts\":\"10\",\"minutes\":\"60\"}",
"report": "bool"
},
{
"key": "forums_posts_per_page",
"default": "25"
"default": "25",
"report": "full"
},
{
"key": "forums_questions_downvote",
"default": "0"
"default": "0",
"report": "full"
},
{
"key": "forums_rss",
"default": "1"
"default": "1",
"report": "full"
},
{
"key": "forums_topics_per_page",
"default": "25"
"default": "25",
"report": "full"
}
]
+289 -125
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<theme name="Default" author_name="Invision Power Services, Inc" author_url="https://www.invisioncommunity.com">
<field sc_key="forum_layout" sc_type="Select" sc_multiple="0" sc_updated="1494930618" sc_app="forums" sc_default="traditional" sc_tab_key="custom" sc_show_in_vse="1" sc_title="themesetting_forum_layout" sc_order="5" sc_condition="">[{&quot;key&quot;:&quot;grid&quot;,&quot;value&quot;:&quot;Grid View&quot;},{&quot;key&quot;:&quot;traditional&quot;,&quot;value&quot;:&quot;Traditional List&quot;}]</field>
<template template_group="feeds" template_name="importPreview" template_data="$feed" template_location="admin" template_app="forums"><![CDATA[<form method="post">
<input type="hidden" name="continue" value="1">
<ol class='ipsDataList ipsClear'>
@@ -19,6 +18,31 @@
<button type="submit" class="ipsButton ipsButton_primary">{lang="continue"}</button>
</div>
</form>]]></template>
<template template_group="forums" template_name="clubForums" template_data="" template_location="front" template_app="forums"><![CDATA[
<div class="ipsPageHeader ipsClearfix">
<header class='ipsSpacer_bottom'>
<h1 class="ipsType_pageTitle">{lang="club_node_forums"}</h1>
</header>
</div>
<div class='ipsList_reset cForumList ipsBox ipsSpacer_bottom' data-controller='forums.front.forum.forumList' data-baseURL=''>
<h2 class='ipsType_sectionTitle ipsType_reset'>{lang="subforums_header_category"}</h2>
{{if \IPS\forums\Forum::getMemberView() === 'grid'}}
<div class='ipsAreaBackground ipsPad' data-role="forums">
<div class='ipsGrid ipsGrid_collapsePhone' data-ipsGrid data-ipsGrid-minItemSize='300' data-ipsGrid-maxItemSize='400' data-ipsGrid-equalHeights='row'>
{{foreach \IPS\forums\Forum::clubNodes() as $childforum}}
{template="forumGridItem" group="index" app="forums" params="$childforum"}
{{endforeach}}
</div>
</div>
{{else}}
<ol class="ipsDataList ipsDataList_zebra ipsDataList_large ipsAreaBackground_reset">
{{foreach \IPS\forums\Forum::clubNodes() as $childforum}}
{template="forumRow" group="index" app="forums" params="$childforum, TRUE"}
{{endforeach}}
</ol>
{{endif}}
</div>]]></template>
<template template_group="forums" template_name="forumButtons" template_data="$forum" template_location="front" template_app="forums"><![CDATA[{{if $forum->can('add')}}
<li class='ipsToolList_primaryAction'>
{{if $forum->forums_bitoptions['bw_enable_answers']}}
@@ -75,7 +99,7 @@
</div>
{{if $forum->children()}}
<div class='ipsList_reset cForumList ipsBox ipsSpacer_bottom' data-controller='forums.front.forum.forumList' data-baseURL=''>
<div class='ipsList_reset cForumList ipsBox ipsSpacer_bottom' data-controller='core.global.core.table, forums.front.forum.forumList' data-baseURL=''>
<h2 class='ipsType_sectionTitle ipsType_reset'>{{if $forum->sub_can_post}}{lang="subforums_header"}{{else}}{lang="subforums_header_category"}{{endif}}</h2>
{{if \IPS\forums\Forum::getMemberView() === 'grid'}}
<div class='ipsAreaBackground ipsPad' data-role="forums">
@@ -249,7 +273,7 @@
<ul class="ipsMenu ipsMenu_auto ipsMenu_withStem ipsMenu_selectable ipsHide" id="elSortByMenu_{$table->uniqueId}_menu">
{{$custom = TRUE;}}
{{foreach $table->sortOptions as $k => $col}}
<li class="ipsMenu_item {{if $col === $table->getSortByColumn()}}{{$custom = FALSE;}}ipsMenu_itemChecked{{endif}}" data-ipsMenuValue="{$col}" data-sortDirection='{{if $col == 'title'}}asc{{else}}desc{{endif}}'><a href="{$table->baseUrl->setQueryString( array( 'filter' => $table->filter, 'sortby' => $col, 'sortdirection' => ( $col == 'title' ) ? 'asc' : 'desc', 'page' => '1' ) )}">{lang="{$table->langPrefix}sort_{$k}"}</a></li>
<li class="ipsMenu_item {{if $col === $table->getSortByColumn()}}{{$custom = FALSE;}}ipsMenu_itemChecked{{endif}}" data-ipsMenuValue="{$col}" data-sortDirection='{$table->getSortDirection( $col )}'><a href="{$table->baseUrl->setQueryString( array( 'filter' => $table->filter, 'sortby' => $col, 'sortdirection' => $table->getSortDirection( $col ), 'page' => '1' ) )}">{lang="{$table->langPrefix}sort_{$k}"}</a></li>
{{endforeach}}
{{if $table->advancedSearch}}
<li class="ipsMenu_item {{if $custom}}ipsMenu_itemChecked{{endif}}" data-noSelect="true">
@@ -398,7 +422,7 @@
{{endif}}
</div>
<div class='ipsDataItem_main'>
<h4 class='ipsDataItem_title ipsType_break'>
<h4 class='ipsDataItem_title ipsType_break ipsContained_container'>
<span class='ipsType_break ipsContained'>
{{if $question->locked()}}
<i class='fa fa-lock' data-ipsTooltip title='{lang="topic_locked"}'></i>
@@ -547,8 +571,15 @@
{{$rowIds[] = $row->$idField;}}
{{endforeach}}
{{if count( $rows )}}
{{$rowCount=0;}}
{{foreach $rows as $row}}
{{$idField = $row::$databaseColumnId;}}
{{if $rowCount == 1 AND $advertisement = \IPS\core\Advertisement::loadByLocation( 'ad_forum_listing' )}}
<li class="ipsDataItem">
{$advertisement|raw}
</li>
{{endif}}
{{$rowCount++;}}
{{$idField = $row::$databaseColumnId;}}
{{if $row->mapped('moved_to')}}
{{if $movedTo = $row->movedTo()}}
<li class="ipsDataItem">
@@ -716,7 +747,7 @@
{{if $tabData[1]->item()->unread() }}
&middot; <a href='{$tabData[1]->item()->url('markRead')->csrf()}' title='{lang="mark_topic_read"}' data-action='markTopicRead'><i class='fa fa-check'></i> {lang="mark_topic_read"}</a>
{{endif}}
{{if $tabData[1]->canReport()}}
{{if $tabData[1]->canReportOrRevoke() === TRUE}}
&middot; <a href='{$tabData[1]->url('report')}' data-ipsDialog data-ipsDialog-remoteSubmit data-ipsDialog-size='medium' data-ipsDialog-flashMessage='{lang="report_submit_success"}' data-ipsDialog-title="{lang="report_post"}" data-action='reportComment' title='{lang="report_post"}'><span class='ipsResponsive_showPhone ipsResponsive_inline'><i class='fa fa-flag'></i></span><span class='ipsResponsive_hidePhone ipsResponsive_inline'>{lang="report_post"}</span></a>
{{endif}}
</p>
@@ -812,6 +843,10 @@
{{elseif !$row->locked() && $row->topic_close_time && $row->topic_close_time > time()}}
<span><strong class='ipsType_small ipsType_noBreak' data-ipsTooltip title='{lang="topic_locks_at" sprintf="\IPS\DateTime::ts( $row->topic_close_time )->relative(), \IPS\DateTime::ts( $row->topic_close_time )->localeTime( FALSE )"}'><i class='fa fa-clock-o'></i> {lang="topic_locks_at_short" sprintf="\IPS\DateTime::ts($row->topic_close_time)->relative(1)"}</strong>&nbsp;&nbsp;</span>
{{endif}}
{{if $row->mapped('poll')}}
<span><span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_neutral" data-ipsTooltip title='{lang="topic_has_poll"}'><i class='fa fa-question'></i></span></span>
{{endif}}
{{if $row->mapped('pinned') || $row->mapped('featured') || $row->hidden() === -1 || $row->hidden() === 1}}
{{if $row->hidden() === -1}}
@@ -926,8 +961,8 @@
</div>]]></template>
<template template_group="global" template_name="embedPost" template_data="$comment, $item, $url" template_location="front" template_app="forums"><![CDATA[
<div data-embedInfo-maxSize='500' class='ipsRichEmbed'>
{template="embedHeader" group="embed" app="core" params="$comment, \IPS\Member::loggedIn()->language()->addToStack( 'x_replied_to_a_topic', FALSE, array( 'sprintf' => array( $comment->author()->name ) ) ), $comment->mapped('date')"}
<div class='ipsPad'>
{template="embedHeader" group="embed" app="core" params="$comment, \IPS\Member::loggedIn()->language()->addToStack( 'x_replied_to_a_topic', FALSE, array( 'sprintf' => array( $comment->author()->name ) ) ), $comment->mapped('date'), $url"}
<div class='ipsPad_double'>
<div class='ipsRichEmbed_originalItem ipsAreaBackground_reset ipsPad ipsSpacer_bottom ipsType_blendLinks'>
<div>
{template="embedOriginalItem" group="embed" app="core" params="$item, TRUE"}
@@ -938,7 +973,7 @@
{$comment->truncated(TRUE)|raw}
</div>
{{if \IPS\IPS::classUsesTrait( $comment, 'IPS\Content\Reactable' ) and count( $comment->reactions() ) and settings.reputation_enabled}}
{{if settings.reputation_enabled and \IPS\IPS::classUsesTrait( $comment, 'IPS\Content\Reactable' ) and count( $comment->reactions() )}}
<ul class='ipsList_inline ipsSpacer_top ipsSpacer_half'>
<li>
{template="reactionOverview" app="core" group="global" params="$comment, TRUE, 'small'"}
@@ -949,19 +984,19 @@
</div>]]></template>
<template template_group="global" template_name="embedTopic" template_data="$item, $url" template_location="front" template_app="forums"><![CDATA[
<div data-embedInfo-maxSize='500' class='ipsRichEmbed'>
{template="embedHeader" group="embed" app="core" params="$item, \IPS\Member::loggedIn()->language()->addToStack( 'x_created_topic_in', FALSE, array( 'sprintf' => array( $item->author()->name, $item->container()->_title ) ) ), $item->mapped('date')"}
{{if count( $item->contentImages() )}}
{{$attachType = key( $item->contentImages()[0] );}}
{{$firstPhoto = \IPS\File::get( $attachType, $item->contentImages()[0][ $attachType ] );}}
{template="embedHeader" group="embed" app="core" params="$item, \IPS\Member::loggedIn()->language()->addToStack( 'x_created_topic_in', FALSE, array( 'sprintf' => array( $item->author()->name, $item->container()->_title ) ) ), $item->mapped('date'), $url"}
{{if $contentImage = $item->contentImages()}}
{{$attachType = key( $contentImage[0] );}}
{{$firstPhoto = \IPS\File::get( $attachType, $contentImage[0][ $attachType ] );}}
<div class='ipsRichEmbed_masthead ipsRichEmbed_mastheadBg ipsType_center'>
<a href='{$item->url()}' style='background-image: url( "{expression="str_replace( array( '(', ')' ), array( '\(', '\)' ), $firstPhoto->url )"}" )'>
<a href='{$url}' style='background-image: url( "{expression="str_replace( array( '(', ')' ), array( '\(', '\)' ), $firstPhoto->url )"}" )'>
<img src='{$firstPhoto->url}' class='ipsHide' alt=''>
</a>
</div>
{{endif}}
<div class='ipsPad'>
<div class='ipsPad_double'>
<h3 class='ipsRichEmbed_itemTitle ipsTruncate ipsTruncate_line ipsType_blendLinks'>
<a href='{$item->url()}' title="{$item->mapped('title')}">{$item->mapped('title')}</a>
<a href='{$url}' title="{$item->mapped('title')}">{$item->mapped('title')}</a>
</h3>
<div class='ipsType_richText ipsType_medium ipsSpacer_top ipsSpacer_half' data-truncate='3'>
{$item->truncated(TRUE)|raw}
@@ -1055,6 +1090,9 @@
<span><span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_positive" data-ipsTooltip title='{lang="featured"}'><i class='fa fa-star'></i></span></span>
{{endif}}
{{endif}}
{{if $topic->prefix()}}
<span>{template="prefix" group="global" app="core" params="$topic->prefix( TRUE ), $topic->prefix()"}</span>
{{endif}}
<span class='ipsType_break ipsContained'>
<a href='{$topic->url()}' {{if $topic->canView()}}data-ipsHover data-ipsHover-target='{$topic->url()->setQueryString('preview', 1)}' data-ipsHover-timeout='1.5' {{endif}}>
@@ -1085,6 +1123,10 @@
{{endif}}
{{endif}}
</ul>
{{if count( $topic->tags() )}}
&nbsp;&nbsp;
{template="tags" group="global" app="core" params="$topic->tags(), true, true"}
{{endif}}
</div>
<ul class='ipsDataItem_stats'>
{{if $topic->isQuestion()}}
@@ -1157,21 +1199,21 @@
<ul class='ipsButton_split'>
{{if $chooseable == '*' OR in_array('table', $chooseable )}}
<li>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=table" seoTemplate="forums"}" class='ipsButton {{if $chosen == 'table'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_table"}'>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=table" seoTemplate="forums" csrf="true"}" class='ipsButton {{if $chosen == 'table'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_table"}'>
<i class='fa fa-align-justify'></i>
</a>
</li>
{{endif}}
{{if $chooseable == '*' OR in_array('grid', $chooseable )}}
<li>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=grid" seoTemplate="forums"}" class='ipsButton {{if $chosen == 'grid'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_grid"}'>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=grid" seoTemplate="forums" csrf="true"}" class='ipsButton {{if $chosen == 'grid'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_grid"}'>
<i class='fa fa-th-large'></i>
</a>
</li>
{{endif}}
{{if $chooseable == '*' OR in_array('fluid', $chooseable )}}
<li>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=fluid" seoTemplate="forums"}" class='ipsButton {{if $chosen == 'fluid'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_fluid"}'>
<a href="{url="app=forums&module=forums&controller=index&do=setMethod&method=fluid" seoTemplate="forums" csrf="true"}" class='ipsButton {{if $chosen == 'fluid'}}ipsButton_primary{{else}}ipsButton_link{{endif}} ipsButton_narrow ipsButton_medium' data-ipsTooltip data-ipsTooltip-safe title='{lang="forums_default_view_fluid"}'>
<i class='fa fa-th-list'></i>
</a>
</li>
@@ -1181,6 +1223,7 @@
{{endif}}]]></template>
<template template_group="index" template_name="forumGridItem" template_data="$forum" template_location="front" template_app="forums"><![CDATA[{{if $forum->can('view')}}
{{$lastPost = $forum->lastPost();}}
{{$club = $forum->club();}}
<div class="ipsDataItem ipsGrid_span4 ipsAreaBackground_reset cForumGrid {{if \IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}cForumGrid_unread ipsDataItem_unread{{endif}} ipsClearfix" data-forumID="{$forum->_id}">
<div class='ipsPhotoPanel ipsPhotoPanel_mini ipsClearfix ipsPad cForumGrid_forumInfo'>
@@ -1188,8 +1231,10 @@
{{if !$forum->redirect_on AND \IPS\forums\Topic::containerUnread( $forum ) AND \IPS\Member::loggedIn()->member_id}}
<a href="{$forum->url()->setQueryString( 'do', 'markRead' )->csrf()}" data-action='markAsRead'>
{{endif}}
{{if $forum->icon}}
<img src="{file="$forum->icon" extension="forums_Icons"}" class='ipsItemStatus ipsItemStatus_custom {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{if $club}}
<img src="{{if $club->profile_photo}}{file="$club->profile_photo" extension="core_Clubs"}{{else}}{resource="default_club.png" app="core" location="global"}{{endif}}" alt='' class='ipsItemStatus ipsItemStatus_large {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{elseif $forum->icon}}
<img src="{file="$forum->icon" extension="forums_Icons"}" alt='' class='ipsItemStatus ipsItemStatus_custom {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{else}}
{{if $forum->redirect_on}}
<span class='ipsItemStatus ipsItemStatus_large cForumIcon_redirect {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}' {template="formattedInlineStyle" params="$forum" app="core" group="global" location="front"}>
@@ -1222,7 +1267,7 @@
{{if $forum->password && !$forum->loggedInMemberHasPasswordAccess()}}
<a href="{$forum->url()->setQueryString( 'passForm', '1' )}" data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title='{lang="forum_requires_password" sprintf="$forum->_title"}'>{$forum->_title}</a>
{{else}}
<a href="{$forum->url()}">{$forum->_title}</a>
<a href="{$forum->url()}">{{if $club}}{lang="club_node" sprintf="$club->name, $forum->_title"}{{else}}{$forum->_title}{{endif}}</a>
{{endif}}
</h3>
{{if !$forum->redirect_on}}
@@ -1299,13 +1344,16 @@
{{endif}}]]></template>
<template template_group="index" template_name="forumRow" template_data="$forum, $isSubForum=FALSE, $table=NULL" template_location="front" template_app="forums"><![CDATA[{{if $forum->can('view')}}
{{$lastPost = $forum->lastPost();}}
{{$club = $forum->club();}}
<li class="cForumRow ipsDataItem ipsDataItem_responsivePhoto {{if \IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsDataItem_unread{{endif}} ipsClearfix" data-forumID="{$forum->_id}">
<div class="ipsDataItem_icon ipsDataItem_category">
{{if !$forum->redirect_on}}
{{if \IPS\forums\Topic::containerUnread( $forum ) AND \IPS\Member::loggedIn()->member_id}}<a href="{{if $isSubForum}}{$forum->url()->setQueryString( array( 'do' => 'markRead', 'return' => $forum->parent_id ) )->csrf()}{{else}}{$forum->url()->setQueryString( 'do', 'markRead' )->csrf()}{{endif}}" data-action='markAsRead' title='{lang="mark_forum_read"}' data-ipsTooltip>{{endif}}
{{endif}}
{{if $forum->icon}}
<img src="{file="$forum->icon" extension="forums_Icons"}" class='ipsItemStatus ipsItemStatus_custom {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{if $club}}
<img src="{{if $club->profile_photo}}{file="$club->profile_photo" extension="core_Clubs"}{{else}}{resource="default_club.png" app="core" location="global"}{{endif}}" alt='' class='ipsItemStatus ipsItemStatus_large {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{elseif $forum->icon}}
<img src="{file="$forum->icon" extension="forums_Icons"}" alt='' class='ipsItemStatus ipsItemStatus_custom {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}'>
{{else}}
{{if $forum->redirect_on}}
<span class='ipsItemStatus ipsItemStatus_large cForumIcon_redirect {{if !\IPS\forums\Topic::containerUnread( $forum ) && !$forum->redirect_on}}ipsItemStatus_read{{endif}}' {template="formattedInlineStyle" params="$forum" app="core" group="global" location="front"}>
@@ -1338,7 +1386,7 @@
{{if $forum->password && !$forum->loggedInMemberHasPasswordAccess()}}
<a href="{$forum->url()->setQueryString( 'passForm', '1' )}" data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title='{lang="forum_requires_password" sprintf="$forum->_title"}'>{$forum->_title}</a>
{{else}}
<a href="{$forum->url()}">{$forum->_title}</a>
<a href="{$forum->url()}">{{if $club}}{lang="club_node" sprintf="$club->name, $forum->_title"}{{else}}{$forum->_title}{{endif}}</a>
{{endif}}
{{if $forum->redirect_on}}
&nbsp;&nbsp;<span class='ipsType_light ipsType_medium'>({lang="redirect_hits" pluralize="$forum->redirect_hits"})</span>
@@ -1412,7 +1460,7 @@
{template=&quot;forumRow&quot; group=&quot;index&quot; app=&quot;forums&quot; params=&quot;$forum, FALSE, $table&quot;}
{{endforeach}}</template>
<template template_group="index" template_name="index" template_data="" template_location="front" template_app="forums"><![CDATA[<div class='ipsPageHeader ipsClearfix ipsSpacer_bottom cForumHeader'>
{{if \IPS\Member::loggedIn()->member_id}}
{{if \IPS\forums\Forum::canOnAny( 'add' ) }}
<ul class='ipsToolList ipsToolList_horizontal ipsClearfix ipsPos_right'>
<li class='ipsToolList_primaryAction ipsResponsive_hidePhone'>
<a class="ipsButton ipsButton_medium ipsButton_important" href="{url="app=forums&module=forums&controller=forums&do=add" seoTemplate="topic_non_forum_add_button"}" data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title='{lang="select_forum"}'>{lang="start_new_topic"}</a>
@@ -1457,6 +1505,29 @@
</li>
{{endif}}
{{endforeach}}
{{if settings.club_nodes_in_apps and $clubForums = \IPS\forums\Forum::clubNodes()}}
<li data-categoryID='clubs' class='cForumRow ipsBox ipsSpacer_bottom'>
<h2 class="ipsType_sectionTitle ipsType_reset cForumTitle">
<a href='#' class='ipsPos_right ipsJS_show ipsType_noUnderline cForumToggle' data-action='toggleCategory' data-ipsTooltip title='{lang="toggle_this_category"}'></a>
<a href='{url="app=forums&module=forums&controller=forums&do=clubs" seoTemplate="forums_clubs"}'>{lang="club_node_forums"}</a>
</h2>
{{if \IPS\forums\Forum::getMemberView() === 'grid'}}
<div class='ipsAreaBackground ipsPad' data-role="forums">
<div class='ipsGrid ipsGrid_collapsePhone' data-ipsGrid data-ipsGrid-minItemSize='250' data-ipsGrid-maxItemSize='500' data-ipsGrid-equalHeights='row'>
{{foreach $clubForums as $forum}}
{template="forumGridItem" group="index" app="forums" params="$forum"}
{{endforeach}}
</div>
</div>
{{else}}
<ol class="ipsDataList ipsDataList_large ipsDataList_zebra ipsAreaBackground_reset" data-role="forums">
{{foreach $clubForums as $forum}}
{template="forumRow" group="index" app="forums" params="$forum"}
{{endforeach}}
</ol>
{{endif}}
</li>
{{endif}}
</ol>
</section>]]></template>
<template template_group="index" template_name="indexButtons" template_data="$showViewButtons=TRUE, $showFilterButton=FALSE" template_location="front" template_app="forums"><![CDATA[
@@ -1466,9 +1537,11 @@
<span data-role='fluidForumMobileDesc'>{lang="forums_simple_filter_by"}</span> <i class='fa fa-angle-down'></i></a>
</li>
{{endif}}
{{if \IPS\forums\Forum::canOnAny('add')}}
<li class='ipsToolList_primaryAction ipsResponsive_hideTablet'>
<a class="ipsButton ipsButton_medium ipsButton_important ipsButton_fullWidth" href="{url="app=forums&module=forums&controller=forums&do=add"}" data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title='{lang="select_forum"}'>{lang="start_new_topic"}</a>
</li>
{{endif}}
{{if $showViewButtons}}
{template="viewChange" group="global" app="forums" params=""}
@@ -1508,7 +1581,7 @@
<ul class="ipsMenu ipsMenu_auto ipsMenu_withStem ipsMenu_selectable ipsHide" id="elSortByMenu_{$table->uniqueId}_menu">
{{$custom = TRUE;}}
{{foreach $table->sortOptions as $k => $col}}
<li class="ipsMenu_item {{if $col === $table->getSortByColumn()}}{{$custom = FALSE;}}ipsMenu_itemChecked{{endif}}" data-ipsMenuValue="{$col}" data-sortDirection='{{if $col == 'title'}}asc{{else}}desc{{endif}}'><a href="{$table->baseUrl->setQueryString( array( 'filter' => $table->filter, 'sortby' => $col, 'sortdirection' => ( $col == 'title' ) ? 'asc' : 'desc', 'page' => '1' ) )}">{lang="{$table->langPrefix}sort_{$k}"}</a></li>
<li class="ipsMenu_item {{if $col === $table->getSortByColumn()}}{{$custom = FALSE;}}ipsMenu_itemChecked{{endif}}" data-ipsMenuValue="{$col}" data-sortDirection='{$table->getSortDirection( $col )}'><a href="{$table->baseUrl->setQueryString( array( 'filter' => $table->filter, 'sortby' => $col, 'sortdirection' => $table->getSortDirection( $col ), 'page' => '1' ) )}">{lang="{$table->langPrefix}sort_{$k}"}</a></li>
{{endforeach}}
{{if $table->advancedSearch}}
<li class="ipsMenu_item {{if $custom}}ipsMenu_itemChecked{{endif}}" data-noSelect="true">
@@ -1713,6 +1786,10 @@
{{elseif !$row->locked() && $row->topic_close_time && $row->topic_close_time > time()}}
<span><strong class='ipsType_small ipsType_noBreak' data-ipsTooltip title='{lang="topic_locks_at" sprintf="\IPS\DateTime::ts( $row->topic_close_time )->relative(), \IPS\DateTime::ts( $row->topic_close_time )->localeTime( FALSE )"}'><i class='fa fa-clock-o'></i> {lang="topic_locks_at_short" sprintf="\IPS\DateTime::ts($row->topic_close_time)->relative(1)"}</strong>&nbsp;&nbsp;</span>
{{endif}}
{{if $row->mapped('poll')}}
<span><span class="ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_neutral" data-ipsTooltip title='{lang="topic_has_poll"}'><i class='fa fa-question'></i></span></span>
{{endif}}
{{if $row->mapped('pinned') || $row->mapped('featured') || $row->hidden() === -1 || $row->hidden() === 1}}
{{if $row->hidden() === -1}}
@@ -1748,7 +1825,7 @@
<span>
{lang="byline_itemprop" htmlsprintf="$row->author()->link()"}
</span>{datetime="$row->mapped('date')" lowercase="true"}
{lang="in"} <a href="{$row->container()->url()}">{$row->container()->_formattedTitle|raw}</a>
{lang="in"} <a href="{$row->container()->url()}">{{if $club = $row->container()->club()}}{lang="club_node" sprintf="$club->name, $row->container()->_title"}{{else}}{$row->container()->_formattedTitle|raw}{{endif}}</a>
{{if count( $row->tags() )}}
&nbsp;&nbsp;
@@ -1805,7 +1882,7 @@
{{endforeach}}
{{endif}}]]></template>
<template template_group="index" template_name="simplifiedView" template_data="$table" template_location="front" template_app="forums"><![CDATA[<div class="ipsPageHeader ipsClearfix ipsSpacer_bottom cForumHeader">
{{if \IPS\Member::loggedIn()->member_id}}
{{if \IPS\forums\Forum::canOnAny( 'add' )}}
<ul class='ipsToolList ipsToolList_horizontal ipsClearfix ipsPos_right'>
<li class='ipsToolList_primaryAction ipsResponsive_hidePhone'>
<a class="ipsButton ipsButton_medium ipsButton_important" href="{url="app=forums&module=forums&controller=forums&do=add" seoTemplate="topic_non_forum_add_button"}" data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title='{lang="select_forum"}'>{lang="start_new_topic"}</a>
@@ -1825,28 +1902,54 @@
{{endif}}
{$table|raw}]]></template>
<template template_group="index" template_name="simplifiedViewSidebar" template_data="$forumIds, $parentIds" template_location="front" template_app="forums"><![CDATA[<div data-controller="forums.front.forum.flow" class='ipsBox cForumMiniList_wrapper' id='elFluidFormFilters'>
<template template_group="index" template_name="simplifiedViewSidebar" template_data="$forumIds" template_location="front" template_app="forums"><![CDATA[<div data-controller="forums.front.forum.flow" class='ipsBox cForumMiniList_wrapper' id='elFluidFormFilters'>
<div class='ipsSideMenu'>
{{if count( \IPS\forums\Forum::roots() ) === 1}}
<ul class='ipsSideMenu_list cForumMiniList cForumMiniList_singleRoot'>
{{foreach \IPS\forums\Forum::roots() as $category}}
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $parentIds, $category, 0"}
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $category, 0"}
{{endforeach}}
</ul>
{{else}}
<ul class='ipsSideMenu_list cForumMiniList cForumMiniList_multiRoot'>
{{foreach \IPS\forums\Forum::roots() as $category}}
{{if $category->hasChildren()}}
<li class="{{if in_array( $category->_id, $parentIds )}}cForumMiniList_categorySelected{{endif}}">
<a href="{$category->url()}" data-parent-id="{$category->_id}" data-node-id="{$category->_id}" class='{{if in_array( $category->_id, $parentIds )}}cForumMiniList_selected{{endif}} ipsSideMenu_item ipsTruncate ipsTruncate_line'>{$category->_title}</a>
<li class="" data-category>
<a href="{$category->url()}" data-parent-id="{$category->_id}" data-node-id="{$category->_id}" class='ipsSideMenu_item ipsTruncate ipsTruncate_line'>{$category->_title}</a>
{{if $category->hasChildren()}}
<ul class='ipsSideMenu_list cForumMiniList'>
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $parentIds, $category, 0"}
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $category, 0"}
</ul>
{{endif}}
</li>
{{endif}}
{{endforeach}}
{{if settings.club_nodes_in_apps and $clubForums = \IPS\forums\Forum::clubNodes()}}
<li class="{{if in_array( 'clubs', $parentIds )}}cForumMiniList_categorySelected{{endif}}">
<a href="{url="app=forums&module=forums&controller=index&forumId=clubs" seoTemplate="forums"}" data-parent-id="clubs" data-node-id="clubs" class='{{if in_array( 'clubs', $parentIds )}}cForumMiniList_selected{{endif}} ipsSideMenu_item ipsTruncate ipsTruncate_line'>{lang="club_node_forums"}</a>
<ul class='ipsSideMenu_list cForumMiniList'>
{{foreach $clubForums as $idx => $forum}}
{{$lastPost = $forum->lastPost();}}
{{$unread = \IPS\forums\Topic::containerUnread( $forum );}}
{{$children = $forum->children();}}
{{if ! $forum->redirect_on and $forum->can('read') }}
<li class="{{if $children}}cForumMiniList_category{{endif}} {{if in_array( $forum->_id, $parentIds )}}cForumMiniList_categorySelected{{endif}}">
<a href="{$forum->url()}" data-parent-id="clubs" data-node-id="{$forum->_id}" data-has-children="{{if $children}}1{{else}}0{{endif}}" class='{{if in_array( $forum->_id, $forumIds )}}cForumMiniList_selected{{endif}} ipsSideMenu_item {{if !$unread}}cForumMiniList_unread{{endif}}'>
<span class='cForumMiniList_blob' {{if $forum->feature_color}}style="background-color: {$forum->feature_color}; color: {$forum->_featureTextColor};"{{endif}} {{if $lastPost['date']}}title="{lang="forum_simple_view_last_post"} {datetime="$lastPost['date']"}" data-ipsTooltip data-ipsTooltip-safe{{endif}}>
<span></span>
<i class='fa fa-check'></i>
</span>
<span class='cForumMiniList_title ipsTruncate ipsTruncate_line'>
{{if $unread}}<strong>{{endif}}{lang="club_node" sprintf="$forum->club()->name, $forum->_title"}{{if $unread}}</strong>{{endif}}
</span>
<span class='ipsType_small ipsType_light cForumMiniList_count'>{expression="\IPS\forums\Topic::contentCount( $forum )"}</span>
</a>
</li>
{{endif}}
{{endforeach}}
</ul>
</li>
{{endif}}
</ul>
{{endif}}
</div>
@@ -1855,14 +1958,14 @@
<a href='#' class='ipsButton ipsButton_fullWidth ipsButton_primary ipsButton_medium' data-action='dialogClose'>{lang="done_forum_filtering"}</a>
</div>
</div>]]></template>
<template template_group="index" template_name="simplifiedViewSidebar_children" template_data="$forumIds, $parentIds, $parent, $depth" template_location="front" template_app="forums"><![CDATA[{{if $parent->hasChildren()and $depth < 5}}
<template template_group="index" template_name="simplifiedViewSidebar_children" template_data="$forumIds, $parent, $depth" template_location="front" template_app="forums"><![CDATA[{{if $parent->hasChildren()and $depth < 5}}
{{foreach $parent->children() as $idx => $forum}}
{{$lastPost = $forum->lastPost();}}
{{$unread = \IPS\forums\Topic::containerUnread( $forum );}}
{{$children = $forum->children();}}
{{if ! $forum->redirect_on and $forum->can('read') }}
<li class="{{if $children}}cForumMiniList_category{{endif}} {{if in_array( $forum->_id, $parentIds )}}cForumMiniList_categorySelected{{endif}}">
<a href="{$forum->url()}" data-parent-id="{$parent->_id}" data-node-id="{$forum->_id}" data-has-children="{{if $children}}1{{else}}0{{endif}}" class='{{if in_array( $forum->_id, $forumIds )}}cForumMiniList_selected{{endif}} ipsSideMenu_item {{if !$unread}}cForumMiniList_unread{{endif}}'>
{{if ! $forum->redirect_on and $forum->can('read') or !$forum->sub_can_post }}
<li class="{{if $children}}cForumMiniList_category{{endif}}">
<a href="{$forum->url()}" data-parent-id="{$parent->_id}" data-node-id="{$forum->_id}" data-has-children="{{if $children}}1{{else}}0{{endif}}" class='ipsSideMenu_item {{if !$unread}}cForumMiniList_unread{{endif}}'>
<span class='cForumMiniList_blob' {{if $forum->feature_color}}style="background-color: {$forum->feature_color}; color: {$forum->_featureTextColor};"{{endif}} {{if $lastPost['date']}}title="{lang="forum_simple_view_last_post"} {datetime="$lastPost['date']"}" data-ipsTooltip data-ipsTooltip-safe{{endif}}>
<span></span>
<i class='fa fa-check'></i>
@@ -1874,7 +1977,7 @@
</a>
{{if $children}}
<ul class='ipsSideMenu_list cForumMiniList'>
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $parentIds, $forum, $depth+1"}
{template="simplifiedViewSidebar_children" app="forums" location="front" group="index" params="$forumIds, $forum, $depth+1"}
</ul>
{{endif}}
</li>
@@ -1920,7 +2023,7 @@
<input type="checkbox" name="{$name}[3]" id='el{$name}_3' data-control="unlimited" {{if isset( $value[3] ) and $value[3]}}checked{{endif}}>
<span></span>
</span> <label for='el{$name}_3' class='ipsField_unlimited'>{{if mb_strpos( $name, 'not' )}}{lang="no_restriction"}{{else}}{lang="any_time"}{{endif}}</label>]]></template>
<template template_group="settings" template_name="popularNow" template_data="$name, $value" template_location="admin" template_app="forums"><![CDATA[<input type="number" name="{$name}[posts]" value="{$value['posts']}" class="ipsField_short"> {lang="posts_in_the_last"} <input type="number" name="{$name}[minutes]" value="{$value['minutes']}" class="ipsField_short"> {lang="minutes"} {lang="or"}
<template template_group="settings" template_name="popularNow" template_data="$name, $value, $popularNowMaximum" template_location="admin" template_app="forums"><![CDATA[<input type="number" name="{$name}[posts]" value="{$value['posts']}" class="ipsField_short"> {lang="posts_in_the_last"} <input type="number" name="{$name}[minutes]" value="{$value['minutes']}" max="{$popularNowMaximum}" class="ipsField_short"> {lang="minutes"} {lang="or"}
<span class='ipsCustomInput'>
<input type="checkbox" name="{$name}[never]" id='el{$name}_never' data-control="unlimited" {{if !$value['posts']}}checked{{endif}}>
<span></span>
@@ -1942,14 +2045,14 @@
{{endif}}]]></template>
<template template_group="submit" template_name="createTopicForm" template_data="$forum, $hasModOptions, $topic, $id, $action, $elements, $hiddenValues, $actionButtons, $uploadField, $class='', $attributes=array(), $sidebar=NULL, $form=NULL, $errorTabs=array()" template_location="front" template_app="forums"><![CDATA[{{$modOptions = array( 'topic_create_state', 'create_topic_locked', 'create_topic_pinned', 'create_topic_hidden', 'create_topic_featured', 'topic_open_time', 'topic_close_time');}}
<form accept-charset='utf-8' class="ipsForm {$class}" action="{$action}" method="post" {{if $uploadField}}enctype="multipart/form-data"{{endif}} data-ipsForm>
<form accept-charset='utf-8' class="ipsForm {$class}" action="{$action}" method="post" {{if $uploadField}}enctype="multipart/form-data"{{endif}} data-ipsForm data-ipsFormSubmit>
<input type="hidden" name="{$id}_submitted" value="1">
{{foreach $hiddenValues as $k => $v}}
<input type="hidden" name="{$k}" value="{$v}">
{{endforeach}}
{{if $uploadField}}
<input type="hidden" name="MAX_FILE_SIZE" value="{$uploadField}">
<input type="hidden" name="plupload" value="{expression="md5( uniqid() )"}">
<input type="hidden" name="plupload" value="{expression="md5( mt_rand() )"}">
{{endif}}
{{if $form->error}}
@@ -2065,7 +2168,7 @@
</div>]]></template>
<template template_group="topics" template_name="post" template_data="$item, $comment, $editorName, $app, $type, $class=''" template_location="front" template_app="forums"><![CDATA[{{$idField = $comment::$databaseColumnId;}}
<div id='comment-{$comment->$idField}_wrap' data-controller='core.front.core.comment' data-commentApp='{$app}' data-commentType='{$type}' data-commentID="{$comment->$idField}" data-quoteData='{expression="json_encode( array('userid' => $comment->author()->member_id, 'username' => $comment->author()->name, 'timestamp' => $comment->mapped('date'), 'contentapp' => $comment::$application, 'contenttype' => $type, 'contentid' => $item->tid, 'contentclass' => $class, 'contentcommentid' => $comment->$idField) )"}' class='ipsComment_content ipsType_medium {{if $comment->hidden()}}ipsModerated{{endif}} ipsFaded_withHover'>
{{if ( settings.reputation_highlight and $comment->reactionCount() >= settings.reputation_highlight and settings.reputation_enabled ) OR $comment->isFeatured()}}
{{if ( settings.reputation_enabled and settings.reputation_highlight and $comment->reactionCount() >= settings.reputation_highlight ) OR $comment->isFeatured()}}
{{if $comment->isFeatured()}}
<strong class='ipsComment_popularFlag' data-ipsTooltip title='{lang="this_is_a_featured_post"}'><i class='fa fa-star'></i></strong>
{{else}}
@@ -2076,7 +2179,7 @@
<div class='ipsComment_meta ipsType_light'>
<div class='ipsPos_right ipsType_light ipsType_reset ipsFaded ipsFaded_more ipsType_blendLinks'>
<ul class='ipsList_inline ipsComment_tools'>
{{if $comment->canReport()}}
{{if $comment->canReportOrRevoke() === TRUE}}
<li><a href='{$comment->url('report')}' data-ipsDialog data-ipsDialog-remoteSubmit data-ipsDialog-size='medium' data-ipsDialog-flashMessage='{lang="report_submit_success"}' data-ipsDialog-title="{lang="report_post"}" data-action='reportComment' title='{lang="report_content"}' class='ipsFaded ipsFaded_more'><span class='ipsResponsive_showPhone ipsResponsive_inline'><i class='fa fa-flag'></i></span><span class='ipsResponsive_hidePhone ipsResponsive_inline'>{lang="report_post"}</span></a></li>
{{endif}}
{{if \IPS\Member::loggedIn()->modPermission('can_use_ip_tools') and \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'core', 'modcp' ) )}}
@@ -2184,7 +2287,7 @@
<li><a href='{$comment->url('edit')}' data-action='editComment'>{lang="edit"}</a></li>
{{endif}}
{{endif}}
{{if ( ! $comment->mapped('first') and $comment->canPromoteToSocialMedia() ) || $comment->canDelete() || $comment->canHide() || $comment->canUnhide() || $comment->canSplit() || ( $comment->hidden() == -2 AND \IPS\Member::loggedIn()->modPermission('can_manage_deleted_content') )}}
{{if !$comment->mapped('first') and ( $comment->canPromoteToSocialMedia() || $comment->canDelete() || $comment->canHide() || $comment->canUnhide() || $comment->canSplit() || $item->canFeatureComment() || $item->canUnfeatureComment() || ( $comment->hidden() == -2 AND \IPS\Member::loggedIn()->modPermission('can_manage_deleted_content') ) )}}
<li>
<a href='#elControls_{$comment->$idField}_menu' id='elControls_{$comment->$idField}' data-ipsMenu data-ipsMenu-appendTo='#comment-{$comment->$idField}_wrap'>{lang="options"} &nbsp;<i class='fa fa-caret-down'></i></a>
<ul id='elControls_{$comment->$idField}_menu' class='ipsMenu ipsMenu_narrow ipsHide'>
@@ -2264,16 +2367,16 @@
{{endif}}
<a id='comment-{$comment->$idField}'></a>
<article {{if $comment->author()->hasHighlightedReplies()}}data-memberGroup="{$comment->author()->member_group_id}" {{endif}} id='elComment_{$comment->$idField}' class='cPost ipsBox {{if $otherClasses}}{$otherClasses}{{endif}} ipsComment {{if ( settings.reputation_highlight and $comment->reactionCount() >= settings.reputation_highlight and settings.reputation_enabled ) OR $comment->isFeatured()}}ipsComment_popular{{endif}} ipsComment_parent ipsClearfix ipsClear ipsColumns ipsColumns_noSpacing ipsColumns_collapsePhone {{if $comment->author()->hasHighlightedReplies()}}ipsComment_highlighted{{endif}}{{if $comment->isIgnored()}}ipsHide{{endif}} {{if $comment->hidden() OR $item->hidden() === -2}}ipsModerated{{endif}}'>
<article {{if $comment->author()->hasHighlightedReplies()}}data-memberGroup="{$comment->author()->member_group_id}" {{endif}} id='elComment_{$comment->$idField}' class='cPost ipsBox {{if $otherClasses}}{$otherClasses}{{endif}} ipsComment {{if ( settings.reputation_enabled and settings.reputation_highlight and $comment->reactionCount() >= settings.reputation_highlight ) OR $comment->isFeatured()}}ipsComment_popular{{endif}} ipsComment_parent ipsClearfix ipsClear ipsColumns ipsColumns_noSpacing ipsColumns_collapsePhone {{if $comment->author()->hasHighlightedReplies()}}ipsComment_highlighted{{endif}}{{if $comment->isIgnored()}}ipsHide{{endif}} {{if $comment->hidden() OR $item->hidden() === -2}}ipsModerated{{endif}}'>
{{if $item->isQuestion() and !$comment->new_topic}}
{template="postRating" group="topics" app="forums" params="$item, $comment, $votes"}
{{endif}}
<div class='cAuthorPane cAuthorPane_mobile ipsResponsive_showPhone ipsResponsive_block'>
<span class='ipsType_sectionHead cAuthorPane_author ipsResponsive_showPhone ipsResponsive_inlineBlock ipsType_break ipsType_blendLinks ipsTruncate ipsTruncate_line'>
<h3 class='ipsType_sectionHead cAuthorPane_author ipsResponsive_showPhone ipsResponsive_inlineBlock ipsType_break ipsType_blendLinks ipsTruncate ipsTruncate_line'>
{template="userLink" group="global" app="core" params="$comment->author(), $comment->warningRef(), $comment->author()->group['g_id']"}
<span class='ipsResponsive_showPhone ipsResponsive_inline'>&nbsp;&nbsp;{template="reputationBadge" group="global" app="core" params="$comment->author()"}</span>
</span>
</h3>
<div class='cAuthorPane_photo'>
{template="userPhoto" app="core" group="global" params="$comment->author(), 'large', $comment->warningRef()"}
</div>
@@ -2402,12 +2505,12 @@
<li><span data-role="voteCount" data-voteCount="{expression="intval( $topic->question_rating )"}" class='cAnswerRating {{if isset( $topicVotes[ \IPS\Member::loggedIn()->member_id ] ) && $topicVotes[ \IPS\Member::loggedIn()->member_id ] === 1}}ipsType_positive{{elseif isset( $topicVotes[ \IPS\Member::loggedIn()->member_id ] ) && $topicVotes[ \IPS\Member::loggedIn()->member_id ] === -1}}ipsType_negative{{endif}}'>{expression="intval( $topic->question_rating )"}</span></li>
{{if $topic->canVote() && \IPS\Settings::i()->forums_answers_downvote}}
{{if $topic->canVote() && \IPS\Settings::i()->forums_questions_downvote}}
<li>
<a href='{$topic->url()->setQueryString( array( 'do' => 'rateQuestion', 'rating' => -1 ) )->csrf()}' class='{{if !$topic->canVote(-1)}}ipsHide{{endif}} cAnswerRate cAnswerRate_down {{if isset( $topicVotes[ \IPS\Member::loggedIn()->member_id ] ) && $topicVotes[ \IPS\Member::loggedIn()->member_id ] === -1}}ipsType_negative{{endif}}' title='{lang="vote_question_down"}' data-ipsTooltip><i class='fa fa-caret-down'></i></a>
<span class='{{if $topic->canVote(-1)}}ipsHide{{endif}} cAnswerRate cAnswerRate_down {{if isset( $topicVotes[ \IPS\Member::loggedIn()->member_id ] ) && $topicVotes[ \IPS\Member::loggedIn()->member_id ] === -1}}ipsType_negative{{endif}}'><i class='fa fa-caret-down'></i></span>
</li>
{{elseif \IPS\Settings::i()->forums_answers_downvote}}
{{elseif \IPS\Settings::i()->forums_questions_downvote}}
<li>
<span class='cAnswerRate cAnswerRate_down cAnswerRate_noPermission' {{if !\IPS\Member::loggedIn()->member_id}}data-ipsTooltip title='{lang="sign_in_rate_question"}'{{endif}}><i class='fa fa-caret-down'></i></span>
</li>
@@ -2606,17 +2709,34 @@
{$poll|raw}
<br>
{{endif}}
<div data-controller='core.front.core.commentFeed,forums.front.topic.view, core.front.core.ignoredComments' {{if settings.auto_polling_enabled}}data-autoPoll{{endif}} data-baseURL='{$topic->url()}' {{if $topic->isLastPage() and !$topic->isQuestion()}}data-lastPage{{endif}} data-feedID='topic-{$topic->tid}' class='cTopic ipsClear ipsSpacer_top'>
<div data-controller='core.front.core.commentFeed,forums.front.topic.view, core.front.core.ignoredComments' {{if settings.auto_polling_enabled}}data-autoPoll{{endif}} data-baseURL='{$topic->url()}' {{if $topic->isLastPage()}}data-lastPage{{endif}} data-feedID='topic-{$topic->tid}' class='cTopic ipsClear ipsSpacer_top'>
{{if $topic->isQuestion() && $question}}
<div class='ipsAreaBackground ipsPad'>
<h2 class='ipsType_sectionHead ipsType_reset ipsSpacer_bottom'>{lang="question_title"}</h2>
{template="postContainer" group="topics" app="forums" params="$topic, $question, $votes, 'cPostQuestion'"}
</div>
<div class='ipsBox ipsSpacer_top'>
<div class='ipsSpacer_both'>
<h2 class='ipsType_sectionTitle ipsType_reset ipsType_medium ipsHide'>{lang="answer_count" pluralize="( $topic->posts ) ? $topic->posts - 1 : 0"}</h2>
<div class="ipsButtonBar ipsPad_half ipsClearfix ipsClear">
<div class="ipsClearfix ipsClear">
<ul class="ipsPos_right ipsButtonRow ipsClearfix">
{{if ( count( $topic->commentMultimodActions() ) && ( $topic->posts > 1 OR $topic->mapped('unapproved_comments') > 0 OR $topic->mapped('hidden_comments') > 0 ) ) || $pagination}}
<li>
<a class="ipsJS_show" href="#elCheck_menu" id="elCheck" title='{lang="select_rows_tooltip"}' data-ipsTooltip data-ipsAutoCheck data-ipsAutoCheck-context="#elPostFeed" data-ipsMenu data-ipsMenu-activeClass="ipsButtonRow_active">
<span class="cAutoCheckIcon ipsType_medium"><i class="fa fa-square-o"></i></span> <i class="fa fa-caret-down"></i>
<span class='ipsNotificationCount' data-role='autoCheckCount'>0</span>
</a>
<ul class="ipsMenu ipsMenu_auto ipsMenu_withStem ipsHide" id="elCheck_menu">
<li class="ipsMenu_title">{lang="select_rows"}</li>
<li class="ipsMenu_item" data-ipsMenuValue="all"><a href="#">{lang="all"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="none"><a href="#">{lang="none"}</a></li>
<li class="ipsMenu_sep"><hr></li>
<li class="ipsMenu_item" data-ipsMenuValue="hidden"><a href="#">{lang="hidden"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="unhidden"><a href="#">{lang="unhidden"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="unapproved"><a href="#">{lang="unapproved"}</a></li>
</ul>
</li>
{{endif}}
<li>
<a href='{$topic->url()}' id="elSortBy_answers" {{if !isset( request.sortby )}}class='ipsButtonRow_active{{endif}}'>{lang="sort_by_answers"}</a>
</li>
@@ -2628,17 +2748,40 @@
{$pagination|raw}
{{endif}}
</div>
</div>
{{else}}
{{if $pagination}}
<div class="ipsClearfix ipsClear ipsSpacer_bottom">
{$pagination|raw}
</div>
{{endif}}
{{if ( count( $topic->commentMultimodActions() ) && ( $topic->posts > 1 OR $topic->mapped('unapproved_comments') > 0 OR $topic->mapped('hidden_comments') > 0 ) ) || $pagination}}
<div class="ipsClearfix ipsClear ipsSpacer_bottom">
{{if count( $topic->commentMultimodActions() )}}
<ul class="ipsButtonRow ipsPos_right ipsClearfix">
<li>
<a class="ipsJS_show" href="#elCheck_menu" id="elCheck" title='{lang="select_rows_tooltip"}' data-ipsTooltip data-ipsAutoCheck data-ipsAutoCheck-context="#elPostFeed" data-ipsMenu data-ipsMenu-activeClass="ipsButtonRow_active">
<span class="cAutoCheckIcon ipsType_medium"><i class="fa fa-square-o"></i></span> <i class="fa fa-caret-down"></i>
<span class='ipsNotificationCount' data-role='autoCheckCount'>0</span>
</a>
<ul class="ipsMenu ipsMenu_auto ipsMenu_withStem ipsHide" id="elCheck_menu">
<li class="ipsMenu_title">{lang="select_rows"}</li>
<li class="ipsMenu_item" data-ipsMenuValue="all"><a href="#">{lang="all"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="none"><a href="#">{lang="none"}</a></li>
<li class="ipsMenu_sep"><hr></li>
<li class="ipsMenu_item" data-ipsMenuValue="hidden"><a href="#">{lang="hidden"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="unhidden"><a href="#">{lang="unhidden"}</a></li>
<li class="ipsMenu_item" data-ipsMenuValue="unapproved"><a href="#">{lang="unapproved"}</a></li>
</ul>
</li>
</ul>
{{endif}}
{{if $pagination}}
{$pagination|raw}
{{endif}}
</div>
{{endif}}
{{endif}}
{template="featuredComments" group="global" app="core" params="$topic->featuredComments(), $topic->url()->setQueryString( 'recommended', 'comments' ), 'recommended_posts', 'post_lc'"}
<div data-role='commentFeed' data-controller='core.front.core.moderation' {{if $topic->isQuestion()}}class='ipsPad ipsAreaBackground'{{endif}}>
<div id="elPostFeed" data-role='commentFeed' data-controller='core.front.core.moderation' {{if $topic->isQuestion()}}class='ipsPad ipsAreaBackground'{{endif}}{{if $topic->isQuestion() AND $topic->topic_answered_pid}} data-topicAnswerID="{$topic->topic_answered_pid}"{{endif}}>
<form action="{$topic->url()->csrf()->setQueryString( 'do', 'multimodComment' )}" method="post" data-ipsPageAction data-role='moderationTools'>
{{$postCount=0; $timeLastRead = $topic->timeLastRead(); $lined = FALSE;}}
{{if count( $comments )}}
@@ -2665,16 +2808,13 @@
</form>
</div>
{{if $topic->isQuestion()}}
</div>
{{endif}}
{{if $pagination}}
<div class="ipsClearfix ipsClear ipsSpacer_top">
<div class='ipsSpacer_top ipsClearfix ipsClear'>
{$pagination|raw}
</div>
{{endif}}
{{if $topic->commentForm() || $topic->locked() || \IPS\Member::loggedIn()->restrict_post || \IPS\Member::loggedIn()->members_bitoptions['unacknowledged_warnings']}}
{{if $topic->commentForm() || $topic->locked() || \IPS\Member::loggedIn()->restrict_post || \IPS\Member::loggedIn()->members_bitoptions['unacknowledged_warnings'] || !\IPS\Member::loggedIn()->checkPostsPerDay()}}
<a id='replyForm'></a>
<div data-role='replyArea' class='cTopicPostArea ipsAreaBackground ipsPad {{if !$topic->canComment()}}cTopicPostArea_noSize{{endif}} ipsSpacer_top'>
{{if $topic->commentForm()}}
@@ -2691,6 +2831,8 @@
{template="commentUnavailable" group="forms" location="front" app="core" params="'restricted_cannot_comment', \IPS\Member::loggedIn()->warnings(5,NULL,'rpa'), \IPS\Member::loggedIn()->restrict_post"}
{{elseif \IPS\Member::loggedIn()->members_bitoptions['unacknowledged_warnings']}}
{template="commentUnavailable" group="forms" location="front" app="core" params="'unacknowledged_warning_cannot_post', \IPS\Member::loggedIn()->warnings( 1, FALSE )"}
{{elseif !\IPS\Member::loggedIn()->checkPostsPerDay()}}
{template="commentUnavailable" group="forms" location="front" app="core" params="'member_exceeded_posts_per_day'"}
{{endif}}
{{endif}}
</div>
@@ -2706,56 +2848,68 @@
{{endif}}
</div>
{{if $topic->canPin() or $topic->canUnpin() or $topic->canFeature() or $topic->canUnfeature() or $topic->canHide() or $topic->canUnhide() or $topic->canMove() or $topic->canLock() or $topic->canUnlock() or $topic->canDelete() or $topic->availableSavedActions()}}
{{if ( $topic->canPin() or $topic->canUnpin() or $topic->canFeature() or $topic->canUnfeature() or $topic->canHide() or $topic->canUnhide() or $topic->canMove() or $topic->canLock() or $topic->canUnlock() or $topic->canDelete() or $topic->availableSavedActions() or $topic->canMerge() or $topic->canUnarchive() or $topic->canRemoveArchiveExcludeFlag() or \IPS\Member::loggedIn()->modPermission('can_view_moderation_log') ) or ( $topic->hidden() == -2 AND \IPS\Member::loggedIn()->modPermission('can_manage_deleted_content') )}}
<ul class="ipsToolList ipsToolList_horizontal ipsClearfix ipsSpacer_top ipsResponsive_hidePhone">
<li>
<a href='#elTopicActions_menu' id='elTopicActions' class='ipsButton ipsButton_link ipsButton_medium ipsButton_fullWidth' data-ipsMenu>{lang="moderator_actions"} <i class='fa fa-caret-down'></i></a>
<ul id='elTopicActions_menu' class='ipsMenu ipsMenu_auto ipsHide'>
{{if $topic->canFeature()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'feature' ) )}'>{lang="feature"}</a></li>
{{endif}}
{{if $topic->canUnfeature()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unfeature' ) )}' >{lang="unfeature"}</a></li>
{{endif}}
{{if $topic->canPin()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) )}' >{lang="pin"}</a></li>
{{endif}}
{{if $topic->canUnpin()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unpin' ) )}' >{lang="unpin"}</a></li>
{{endif}}
{{if $topic->canHide()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'hide' ) )}' data-ipsDialog data-ipsDialog-title="{lang="hide"}">{lang="hide"}</a></li>
{{endif}}
{{if $topic->canUnhide()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unhide' ) )}' >{{if $topic->hidden() === 1}}{lang="approve"}{{else}}{lang="unhide"}{{endif}}</a></li>
{{endif}}
{{if $topic->canLock()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'lock' ) )}' >{lang="lock"}</a></li>
{{endif}}
{{if $topic->canUnlock()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unlock' ) )}' >{lang="unlock"}</a></li>
{{endif}}
{{if $topic->canMove()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'move' ) )}' data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title="{lang="move"}" >{lang="move"}</a></li>
{{endif}}
{{if $topic->canMerge()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'merge' ) )}' data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title="{lang="merge"}" >{lang="merge"}</a></li>
{{endif}}
{{if $topic->canUnarchive()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'unarchive' ) )}' data-confirm data-confirmSubMessage="{$topic->unarchiveBlurb()}" >{lang="unarchive"}</a></li>
{{endif}}
{{if $topic->canDelete()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'delete' ) )}' data-confirm >{lang="delete"}</a></li>
{{endif}}
{{if !$topic->isArchived() and $topic->availableSavedActions()}}
<li class='ipsMenu_sep'><hr></li>
{{foreach $topic->availableSavedActions() as $action}}
<li class="ipsMenu_item"><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'savedAction', 'action' => $action->_id ) )}' data-confirm>{$action->_title}</a></li>
{{endforeach}}
{{endif}}
{{if \IPS\Member::loggedIn()->modPermission('can_view_moderation_log')}}
<li class='ipsMenu_sep'><hr></li>
<li class="ipsMenu_item"><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'modLog' ) )}' data-ipsDialog data-ipsDialog-title='{lang="moderation_history"}'>{lang="moderation_history"}</a></li>
<a href='#elTopicActionsBottom_menu' id='elTopicActionsBottom' class='ipsButton ipsButton_link ipsButton_medium ipsButton_fullWidth' data-ipsMenu>{lang="moderator_actions"} <i class='fa fa-caret-down'></i></a>
<ul id='elTopicActionsBottom_menu' class='ipsMenu ipsMenu_auto ipsHide'>
{{if \IPS\Member::loggedIn()->modPermission('can_manage_deleted_content') AND $topic->hidden() == -2}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'restore' ) )}' data-confirm data-confirmSubMessage='{lang="restore_as_visible_desc"}'>{lang="restore_as_visible"}</a></li>
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'restoreAsHidden' ) )}' data-confirm data-confirmSubMessage='{lang="restore_as_hidden_desc"}'>{lang="restore_as_hidden"}</a></li>
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'delete', 'immediate' => 1 ) )}' data-confirm data-confirmSubMessage='{lang="delete_immediately_desc"}'>{lang="delete_immediately"}</a></li>
{{else}}
{{if $topic->canFeature()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'feature' ) )}'>{lang="feature"}</a></li>
{{endif}}
{{if $topic->canUnfeature()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unfeature' ) )}' >{lang="unfeature"}</a></li>
{{endif}}
{{if $topic->canPin()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'pin' ) )}' >{lang="pin"}</a></li>
{{endif}}
{{if $topic->canUnpin()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unpin' ) )}' >{lang="unpin"}</a></li>
{{endif}}
{{if $topic->canHide()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'hide' ) )}' data-ipsDialog data-ipsDialog-title="{lang="hide"}">{lang="hide"}</a></li>
{{endif}}
{{if $topic->canUnhide()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unhide' ) )}' >{{if $topic->hidden() === 1}}{lang="approve"}{{else}}{lang="unhide"}{{endif}}</a></li>
{{endif}}
{{if $topic->canLock()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'lock' ) )}' >{lang="lock"}</a></li>
{{endif}}
{{if $topic->canUnlock()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'unlock' ) )}' >{lang="unlock"}</a></li>
{{endif}}
{{if $topic->canMove()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'move' ) )}' data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title="{lang="move"}" >{lang="move"}</a></li>
{{endif}}
{{if $topic->canMerge()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'merge' ) )}' data-ipsDialog data-ipsDialog-size='narrow' data-ipsDialog-title="{lang="merge"}" >{lang="merge"}</a></li>
{{endif}}
{{if $topic->canUnarchive()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'unarchive' ) )}' data-confirm data-confirmSubMessage="{$topic->unarchiveBlurb()}" >{lang="unarchive"}</a></li>
{{endif}}
{{if $topic->canRemoveArchiveExcludeFlag()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'removeArchiveExclude' ) )}' >{lang="remove_archive_exlude"}</a></li>
{{endif}}
{{if $topic->canDelete()}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'moderate', 'action' => 'delete' ) )}' data-confirm >{lang="delete"}</a></li>
{{endif}}
{{if $topic->canOnMessage( 'add' )}}
<li class='ipsMenu_item'><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'messageForm' ) )}' data-ipsDialog data-ipsDialog-title='{lang="add_message"}'>{lang="add_message"}</a></li>
{{endif}}
{{if !$topic->isArchived() and $topic->availableSavedActions()}}
<li class='ipsMenu_sep'><hr></li>
{{foreach $topic->availableSavedActions() as $action}}
<li class="ipsMenu_item"><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'savedAction', 'action' => $action->_id ) )}' data-confirm>{$action->_title}</a></li>
{{endforeach}}
{{endif}}
{{if \IPS\Member::loggedIn()->modPermission('can_view_moderation_log')}}
<li class='ipsMenu_sep'><hr></li>
<li class="ipsMenu_item"><a href='{$topic->url()->csrf()->setQueryString( array( 'do' => 'modLog' ) )}' data-ipsDialog data-ipsDialog-title='{lang="moderation_history"}'>{lang="moderation_history"}</a></li>
{{endif}}
{{endif}}
</ul>
</li>
@@ -2924,7 +3078,7 @@
<div class='ipsPad_half ipsWidget_inner ipsClearfix' data-role='pollContents'>
{$pollForm->customTemplate( array( \IPS\Theme::i()->getTemplate( 'widgets', 'forums', 'front' ), 'pollFormWidget' ), $url )|raw}
</div>
{{elseif !$poll->canVote() or $poll->getVote() or ( request._poll == 'results' and settings.allow_result_view )}}
{{elseif ( $poll->canViewResults() and !$poll->canVote() ) or $poll->getVote() or ( request._poll == 'results' and settings.allow_result_view )}}
<h3 class='ipsWidget_title ipsType_reset'>
<span class='ipsType_break ipsContained'>
{$poll->poll_question}&nbsp;&nbsp;
@@ -2957,10 +3111,20 @@
{{if $poll->canVote() || !member.member_id}}
<hr class='ipsHr'>
{{if $poll->canVote()}}<a href="{$url->setQueryString( '_poll', 'form' )}" title='{lang="show_vote_options"}' class='ipsButton ipsButton_verySmall ipsButton_light' data-action='viewResults'><i class='fa fa-caret-left'></i> {lang="show_vote_options"}</a>{{endif}}
{{if !member.member_id}}{lang="poll_guest" htmlsprintf="\IPS\Http\Url::internal( 'app=core&module=system&controller=login', 'front', 'login', NULL, \IPS\Settings::i()->logins_over_https ), \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register', NULL, \IPS\Settings::i()->logins_over_https )"}{{endif}}
{{if !member.member_id}}{lang="poll_guest" htmlsprintf="\IPS\Http\Url::internal( 'app=core&module=system&controller=login', 'front', 'login' ), \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' )"}{{endif}}
{{endif}}
<a class='ipsPos_right ipsType_light ipsType_small ipsType_break' href="{$url}">{lang="poll_view_topic"}</a>
</div>
{{else}}
<h3 class='ipsWidget_title ipsType_reset'>
<span class='ipsType_break ipsContained'>
{$poll->poll_question}&nbsp;&nbsp;
<span class='ipsType_small ipsType_light'>{lang="poll_num_votes" pluralize="$poll->votes"}</span>
</span>
</h3>
<div class='ipsPad_half ipsWidget_inner ipsClearfix' data-role='pollContents'>
{lang="no_permission_poll"}{{if !\IPS\Member::loggedIn()->member_id}} {lang="poll_guest" htmlsprintf="\IPS\Http\Url::internal( 'app=core&module=system&controller=login', 'front', 'login' ), \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' )"}{{endif}}
</div>
{{endif}}
{{if !isset( \IPS\Request::i()->fetchPoll )}}
</section>
@@ -2995,7 +3159,7 @@
{template="userPhoto" app="core" group="global" params="$comment->author(), 'mini', $comment->warningRef()"}
<div>
<p class='ipsPos_right ipsType_reset'>
{{if $comment->canReport()}}
{{if $comment->canReportOrRevoke() === TRUE}}
<a href='{$comment->url('report')}' data-ipsDialog data-ipsDialog-remoteSubmit data-ipsDialog-size='medium' data-ipsDialog-flashMessage='{lang="report_submit_success"}' data-ipsDialog-title="{lang="report"}" data-action='reportComment' title='{lang="report_content"}'>{lang="report"}</a> &middot;
{{endif}}
@@ -3222,12 +3386,18 @@
@media screen and (min-width: 768px) and (max-width: 979px) {
.cForumQuestion .ipsDataItem_main {
width: 100%;
padding-left: 5px !important;
}
.cForumQuestion .ipsDataItem_main,
.cForumQuestion .cForumQuestion_stat {
display: table-cell;
}
.cForumQuestions .cForumQuestion .cForumQuestion_stat + .ipsDataItem_modCheck {
position: absolute;
top: 40px;
padding: 0 4px;
}
}
/* PHONES */
@@ -4048,17 +4218,14 @@ html[dir="rtl"] .cForumMiniList .cForumMiniList_blob {
margin-top: 1px;
}
.cForumMiniList .cForumMiniList_selected .cForumMiniList_blob,
.cForumMiniList .cForumMiniList_categorySelected .cForumMiniList_blob {
.cForumMiniList .cForumMiniList_selected > .cForumMiniList_blob {
opacity: 1;
}
.cForumMiniList .cForumMiniList_selected .cForumMiniList_blob .fa,
.cForumMiniList .cForumMiniList_categorySelected .cForumMiniList_blob .fa {
.cForumMiniList .cForumMiniList_selected > .cForumMiniList_blob .fa {
display: block;
}
.cForumMiniList .cForumMiniList_selected .cForumMiniList_blob > span,
.cForumMiniList .cForumMiniList_categorySelected .cForumMiniList_blob > span {
.cForumMiniList .cForumMiniList_selected > .cForumMiniList_blob > span {
display: none;
}
@@ -4082,12 +4249,9 @@ html[dir="rtl"] .cForumMiniList .cForumMiniList_blob {
margin-top: -4px;
}
.cForumMiniList.cForumMiniList_multiRoot > li > a.cForumMiniList_selected {
.cForumMiniList > li > a.cForumMiniList_selected {
background: #e5e5e5;
}
.cForumMiniList_categorySelected {
background: #f5f5f5;
}
/* Loading styles */
.cForumFluidTable .ipsDataItem_loading > div > span:last-child:after {
+52 -1
View File
@@ -159,5 +159,56 @@
"101117": "4.2.0 Beta 8",
"102000": "4.2.0",
"102001": "4.2.0",
"102002": "4.2.0"
"102002": "4.2.0",
"102003": "4.2.0",
"102004": "4.2.1",
"102005": "4.2.2 Beta 1",
"102006": "4.2.2 Beta 2",
"102007": "4.2.2",
"102008": "4.2.2",
"102009": "4.2.2",
"102010": "4.2.3 Beta 1",
"102011": "4.2.3",
"102012": "4.2.3",
"102013": "4.2.3",
"102014": "4.2.4 Beta 1",
"102015": "4.2.4 Beta 2",
"102016": "4.2.4",
"102017": "4.2.4",
"102018": "4.2.4",
"102019": "4.2.4",
"102020": "4.2.4",
"102021": "4.2.5 Beta 1",
"102022": "4.2.5 Beta 2",
"102023": "4.2.5 Beta 3",
"102024": "4.2.5",
"102025": "4.2.5",
"102026": "4.2.5",
"102027": "4.2.5",
"102028": "4.2.5",
"102029": "4.2.5",
"102030": "4.2.6 Beta 1",
"102031": "4.2.6 Beta 1",
"102032": "4.2.6 Beta 2",
"102033": "4.2.6 Beta 3",
"102034": "4.2.6",
"102035": "4.2.6",
"102036": "4.2.6",
"102037": "4.2.7 Beta 1",
"102038": "4.2.7 Beta 2",
"102039": "4.2.7 Beta 3",
"102040": "4.2.7 Beta 4",
"102041": "4.2.7 Beta 5",
"102042": "4.2.7",
"102043": "4.2.7",
"102044": "4.2.7",
"102045": "4.2.7",
"102046": "4.2.7",
"102047": "4.2.8",
"102048": "4.2.8",
"103000": "4.3.0 Beta 1",
"103002": "4.3.0 Beta 2",
"103003": "4.3.0 Beta 3",
"103004": "4.3.0 Beta 4",
"103005": "4.3.0"
}
@@ -34,7 +34,8 @@ class _IndexView
return array(
'locations' => array( 'ad_fluid_index_view' => array( 'ad_fluid_index_view_number', 'ad_fluid_index_view_repeat' ) ),
'settings' => array(
new \IPS\Helpers\Form\Number( 'ad_fluid_index_view_number', ( isset( $settings['ad_fluid_index_view_number'] ) ) ? $settings['ad_fluid_index_view_number'] : 0, FALSE, array(), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('ad_fluid_index_view_number_suffix'), 'ad_fluid_index_view_number' ),
/* Some communities may already have a 0 stored for `ad_fluid_index_view_number`, we now need to make sure it's at least 1 */
new \IPS\Helpers\Form\Number( 'ad_fluid_index_view_number', ( isset( $settings['ad_fluid_index_view_number'] ) AND $settings['ad_fluid_index_view_number'] ) ? $settings['ad_fluid_index_view_number'] : 1, FALSE, array( 'min' => 1 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('ad_fluid_index_view_number_suffix'), 'ad_fluid_index_view_number' ),
new \IPS\Helpers\Form\Number( 'ad_fluid_index_view_repeat', ( isset( $settings['ad_fluid_index_view_repeat'] ) ) ? $settings['ad_fluid_index_view_repeat'] : 0, FALSE, array( 'unlimited' => -1 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('ad_fluid_index_view_repeat_suffix'), 'ad_fluid_index_view_repeat' )
)
);
@@ -29,10 +29,10 @@ class _Topic
* @return array
*/
public function getItems()
{
if ( \IPS\forums\Forum::canOnAny( 'add' ) )
{
if ( \IPS\forums\Forum::canOnAny( 'add', NULL, \IPS\Settings::i()->club_nodes_in_apps ? array() : array( array( 'club_id IS NULL' ) ) ) )
{
if ( $theOnlyForum = \IPS\forums\Forum::theOnlyForum() )
if ( !\IPS\Settings::i()->club_nodes_in_apps and $theOnlyForum = \IPS\forums\Forum::theOnlyForum() )
{
return array(
'topic' => array(
@@ -117,7 +117,6 @@ class _forums extends \IPS\Node\Controller
return parent::permissions();
}
protected function form()
{
parent::form();
@@ -43,7 +43,7 @@ class _settings extends \IPS\Dispatcher\Controller
}
$activeTab = \IPS\Request::i()->tab ?: 'settings';
$activeTabContents = call_user_func( array( $this, 'manage' . ucfirst( $activeTab ) ) );
$activeTabContents = call_user_func( array( $this, 'manage' . mb_ucfirst( $activeTab ) ) );
if( \IPS\Request::i()->isAjax() and !isset( \IPS\Request::i()->ajaxValidate ) )
{
@@ -437,12 +437,19 @@ class _settings extends \IPS\Dispatcher\Controller
}
/* Do we need to unarchive some? */
$whereClause = \IPS\Db::i()->compileWhereClause( \IPS\Application::load('forums')->archiveWhere( $rules ) );
$_whereClause = array( 'topic_archive_status=? AND !( ' . $whereClause['clause'] . ')' );
$_whereClause[] = \IPS\forums\Topic::ARCHIVE_DONE;
foreach ( $whereClause['binds'] as $bind )
if( $values['archive_on'] )
{
$_whereClause[] = $bind;
$whereClause = \IPS\Db::i()->compileWhereClause( \IPS\Application::load('forums')->archiveWhere( $rules ) );
$_whereClause = array( 'topic_archive_status=? AND !( ' . $whereClause['clause'] . ')' );
$_whereClause[] = \IPS\forums\Topic::ARCHIVE_DONE;
foreach ( $whereClause['binds'] as $bind )
{
$_whereClause[] = $bind;
}
}
else
{
$_whereClause = array( 'topic_archive_status NOT IN(' . \IPS\forums\Topic::ARCHIVE_NOT . ',' . \IPS\forums\Topic::ARCHIVE_EXCLUDE . ')' );
}
/* Make sure the unarchive task is enabled - it will disable itself if there is no work to do */
@@ -461,7 +468,7 @@ class _settings extends \IPS\Dispatcher\Controller
/* Log and redirect */
\IPS\Session::i()->log( 'acplogs__archive_settings' );
if ( \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', $values['archive_on'] ? $_whereClause : array( 'topic_archive_status=?', \IPS\forums\Topic::ARCHIVE_DONE ) )->first() )
if ( \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', $_whereClause )->first() )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=forums&module=forums&controller=settings&do=unarchive' ) );
}
@@ -590,10 +597,19 @@ class _settings extends \IPS\Dispatcher\Controller
} ) );
$form->addHeader('topic_settings');
$popularNowMaximum = 14400000;
$form->add( new \IPS\Helpers\Form\Custom( 'forums_popular_now', json_decode( \IPS\Settings::i()->forums_popular_now, TRUE ), FALSE, array(
'getHtml' => function( $obj )
'getHtml' => function( $obj ) use ( $popularNowMaximum )
{
return \IPS\Theme::i()->getTemplate('settings')->popularNow( $obj->name, $obj->value );
return \IPS\Theme::i()->getTemplate('settings')->popularNow( $obj->name, $obj->value, $popularNowMaximum );
},
/* If popular now time is greater than 10,000 days */
'validate' => function( $obj ) use ( $popularNowMaximum )
{
if( $obj->value['minutes'] > $popularNowMaximum )
{
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_number_max', FALSE, array( 'sprintf' => array( $popularNowMaximum ) ) ) );
}
}
) ) );
$form->add( new \IPS\Helpers\Form\Number( 'forums_posts_per_page', \IPS\Settings::i()->forums_posts_per_page , TRUE, array( 'min' => 2, 'max' => 250 ) ) );
@@ -31,20 +31,63 @@ class _posts extends \IPS\Dispatcher\Controller
protected function manage()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'posts_manage' );
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=posts" ), 'forums_posts', 'post_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_posts' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_posts_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__forums_stats_posts' );
\IPS\Output::i()->output = (string) $chart;
$tabs = array( 'total' => 'stats_posts_tab_total', 'byforum' => 'stats_posts_tab_byforum' );
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'total';
if ( $activeTab === 'total' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=posts" ), 'forums_posts', 'post_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_posts' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_posts_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
}
else
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=posts&tab=" . $activeTab ), 'forums_posts', 'post_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4,
'chartArea' => array( 'width' => '70%', 'left' => '5%' ),
'height' => 400,
'trendlines' => array( 0 => array( 'type' => 'exponential', 'color' => '#000', 'lineWidth' => 2, 'opacity' => 0.3 ) )
),
'ColumnChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array(),
'byforum'
);
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_posts_title_byforum' );
$chart->availableTypes = array( 'ColumnChart' );
$chart->groupBy = 'forum_id';
$chart->joins = array( array( 'forums_topics', 'forums_topics.tid=forums_posts.topic_id' ) );
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'posts>?', 0 ) ) as $forum )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', TRUE, $forum['id'] );
}
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $chart;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__forums_stats_posts');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $chart, \IPS\Http\Url::internal( "app=forums&module=stats&controller=posts" ) );
}
}
}
@@ -31,20 +31,65 @@ class _topics extends \IPS\Dispatcher\Controller
protected function manage()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'topics_manage' );
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=topics" ), 'forums_topics', 'start_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_topics' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'menu__forums_stats_topics' );
\IPS\Output::i()->output = (string) $chart;
$tabs = array( 'total' => 'stats_topics_tab_total', 'byforum' => 'stats_topics_tab_byforum' );
$activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'total';
if ( $activeTab === 'total' )
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=topics" ), 'forums_topics', 'start_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
),
'AreaChart'
);
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_topics' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
}
else
{
$chart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=forums&module=stats&controller=topics&tab=" . $activeTab ), 'forums_topics', 'start_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4,
'chartArea' => array( 'width' => '70%', 'left' => '5%' ),
'height' => 400,
'trendlines' => array( 0 => array( 'type' => 'exponential', 'color' => '#000', 'lineWidth' => 2, 'opacity' => 0.3 ) )
),
'ColumnChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array(),
'byforum'
);
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title_byforum' );
$chart->availableTypes = array( 'ColumnChart' );
$chart->groupBy = 'forum_id';
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'topics>?', 0 ) ) as $forum )
{
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', TRUE, $forum['id'] );
}
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = (string) $chart;
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__forums_stats_topics');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, (string) $chart, \IPS\Http\Url::internal( "app=forums&module=stats&controller=topics" ) );
}
}
}
@@ -29,7 +29,7 @@ class _forums extends \IPS\Dispatcher\Controller
* @return void
*/
protected function manage()
{
{
$forum = NULL;
try
{
@@ -61,7 +61,7 @@ class _forums extends \IPS\Dispatcher\Controller
/* Password protected */
if ( $form = $forum->passwordForm() )
{
\IPS\Output::i()->title = $forum->_stripTagsTitle;
\IPS\Output::i()->title = $forum->_title;
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forums', 'forums', 'front' ), 'forumPasswordPopup' ), $forum );
return;
}
@@ -320,11 +320,11 @@ class _forums extends \IPS\Dispatcher\Controller
$rssUrl = \IPS\Http\Url::internal( "app=forums&module=forums&controller=forums&id={$forum->_id}&rss=1", 'front', 'forums_rss', array( $forum->name_seo ) );
if ( $forum->forums_bitoptions['bw_enable_answers'] )
{
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_questions', FALSE, array( 'sprintf' => array( $forum->_stripTagsTitle ) ) );
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_questions', FALSE, array( 'escape' => true, 'sprintf' => array( $forum->_title ) ) );
}
else
{
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_topics', FALSE, array( 'sprintf' => array( $forum->_stripTagsTitle ) ) );
$rssTitle = \IPS\Member::loggedIn()->language()->addToStack( 'forum_rss_title_topics', FALSE, array( 'escape' => true, 'sprintf' => array( $forum->_title ) ) );
}
\IPS\Output::i()->rssFeeds[ $rssTitle ] = $rssUrl;
@@ -411,6 +411,28 @@ class _forums extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'forums' )->forumDisplay( $forum, $forumOutput );
}
/**
* Show Club Forums
*
* @return void
*/
public function clubs()
{
if ( !\IPS\Settings::i()->club_nodes_in_apps )
{
\IPS\Output::i()->error( 'node_error', '2F176/4', 404, '' );
}
if ( \IPS\forums\Forum::isSimpleView() )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=forums&module=forums&controller=index&forumId=clubs', 'front', 'forums' ), '', 302 );
}
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack('club_node_forums') );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('club_node_forums');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'forums' )->clubForums();
}
/**
* Add Topic
@@ -454,7 +476,7 @@ class _forums extends \IPS\Dispatcher\Controller
$formTemplate = $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'submit', 'forums' ) ), 'createTopicForm' ), $forum, $hasModOptions, NULL );
if ( \IPS\forums\Topic::moderateNewItems( \IPS\Member::loggedIn() ) )
if ( \IPS\forums\Topic::moderateNewItems( \IPS\Member::loggedIn(), $forum ) )
{
$formTemplate = \IPS\Theme::i()->getTemplate( 'forms', 'core' )->modQueueMessage( \IPS\Member::loggedIn()->warnings( 5, NULL, 'mq' ), \IPS\Member::loggedIn()->mod_posts ) . $formTemplate;
}
@@ -568,7 +590,8 @@ class _forums extends \IPS\Dispatcher\Controller
}
return NULL;
}
},
'clubs' => \IPS\Settings::i()->club_nodes_in_apps
) ) );
if ( $values = $form->values() )
@@ -95,7 +95,14 @@ class _index extends \IPS\Dispatcher\Controller
/* Prepare output */
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_core.js', 'core', 'global' ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'forums' );
if ( \IPS\forums\Forum::isSimpleView() )
{
\IPS\Output::i()->title = ( isset( \IPS\Request::i()->page ) AND \IPS\Request::i()->page > 1 ) ? \IPS\Member::loggedIn()->language()->addToStack( 'title_with_page_number', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'forums' ), \IPS\Request::i()->page ) ) ) : \IPS\Member::loggedIn()->language()->addToStack( 'forums' );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'forums' );
}
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( 'app=forums&module=forums&controller=index', 'front', 'forums' );
\IPS\Output::i()->metaTags['og:title'] = \IPS\Settings::i()->board_name;
\IPS\Output::i()->metaTags['og:type'] = 'website';
@@ -108,9 +115,12 @@ class _index extends \IPS\Dispatcher\Controller
/* Display */
if ( \IPS\forums\Forum::isSimpleView() )
{
$where = array( 'container' => array( array( 'forums_forums.password IS NULL AND forums_forums.can_view_others=1 AND forums_forums.club_id IS NULL' ) ) );
$where = array( 'container' => array( array( 'forums_forums.password IS NULL' ) ) );
if ( !\IPS\Settings::i()->club_nodes_in_apps )
{
$where['container'][] = array( 'forums_forums.club_id IS NULL' );
}
$forumIds = array();
$parentIds = array();
$map = array();
$ids = array();
@@ -130,50 +140,32 @@ class _index extends \IPS\Dispatcher\Controller
{
try
{
/* Panic not, they are all loaded into memory at this point */
$forum = \IPS\forums\Forum::load( $id );
if ( $forum->hasChildren() )
if ( $id == 'clubs' )
{
/* This is a root category, so fetch all children */
$this->getParentIds( $forum, $parentIds );
$forumIds[] = $forum->id;
$parentIds['clubs'] = 'clubs';
foreach ( \IPS\forums\Forum::clubNodes() as $child )
{
$forumIds[ $child->id ] = $child->id;
}
}
else
{
{
/* Panic not, they are all loaded into memory at this point */
$forum = \IPS\forums\Forum::load( $id );
$map[ $forum->parent_id ][] = $forum->_id;
$forumIds[] = $forum->id;
}
}
catch( \Exception $ex ) { }
}
/* Now remove all children that are in the same category */
foreach( $map as $parent => $children )
{
if ( in_array( $parent, $parentIds ) )
{
$forumIds = array_diff( $forumIds, $children );
}
}
$or = array();
if ( count( $forumIds ) )
{
$or[] = \IPS\Db::i()->in( 'forums_forums.id', array_filter( $forumIds ) );
}
if ( count( $parentIds ) )
{
$or[] = \IPS\Db::i()->in( 'forums_forums.parent_id', array_filter( $parentIds ) );
}
if ( count( $or ) )
{
$where['container'][] = array( '(' . implode( ' OR ', $or ) . ')' );
$where['container'][] = array( \IPS\Db::i()->in( 'forums_forums.id', array_filter( $forumIds ) ) );
}
}
/* Simplified view */
$table = new \IPS\Helpers\Table\Content( 'IPS\forums\Topic', \IPS\Http\Url::internal( 'app=forums&module=forums&controller=index', 'front', 'forums' ), $where, NULL, NULL, 'read' );
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'index' ), 'simplifiedForumTable' );
@@ -189,7 +181,7 @@ class _index extends \IPS\Dispatcher\Controller
if( \IPS\forums\Forum::theOnlyForum() === NULL )
{
\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'index' )->simplifiedViewSidebar( $forumIds, $parentIds );
\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'index' )->simplifiedViewSidebar( $forumIds );
}
if ( isset( $where['container'] ) )
@@ -204,32 +196,7 @@ class _index extends \IPS\Dispatcher\Controller
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'index' )->index();
}
}
/**
* Recursively collect parents
*
* @param \IPS\forums\Forum $forum Forum object
* @param array $parentIds ParentIds to use
* @return void
*/
protected function getParentIds( $forum, &$parentIds )
{
if ( $forum->hasChildren() )
{
$parentIds[ $forum->id ] = $forum->id;
foreach( $forum->children() as $child )
{
if ( $child->hasChildren() )
{
$parentIds[ $child->id ] = $child->id;
$this->getParentIds( $child, $parentIds );
}
}
}
}
/**
* Set the viewing method
*
@@ -237,6 +204,8 @@ class _index extends \IPS\Dispatcher\Controller
*/
protected function setMethod()
{
\IPS\Session::i()->csrfCheck();
$method = ( isset( \IPS\Request::i()->method ) ) ? \IPS\Request::i()->method : \IPS\Settings::i()->forums_default_view;
\IPS\Request::i()->setCookie( 'forum_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
@@ -234,27 +234,11 @@ class _topic extends \IPS\Content\Controller
{
\IPS\Output::i()->error( 'no_posts_returned', '2F173/L', 404, '' );
}
/* On pages 2 and more, it will show the first post on that page, and that is fine as the title has - Page 2 in it meaning its a different page */
$metaDescription = $topic->isQuestion() ? $question->post : $current->post;
/* Did we request a specific post? If so, reset descriptions. */
if( isset( $_SESSION['_findComment'] ) )
{
$commentId = $_SESSION['_findComment'];
unset( $_SESSION['_findComment'] );
$metaDescription = $topic->metaDescription( isset( $_SESSION['_findComment'] ) ? NULL : ( $topic->isQuestion() ? $question->content() : $current->content() ) );
try
{
$comment = \IPS\forums\Topic\Post::loadAndCheckPerms( $commentId );
$metaDescription = $comment->content();
}
catch( \Exception $e ){}
}
$metaDescription = strip_tags( $metaDescription );
\IPS\Output::i()->metaTags['description'] = \IPS\Output::i()->metaTags['og:description'] = mb_strlen( $metaDescription ) > 160 ? ( mb_substr( $metaDescription, 0, 157 ) . '...' ) : $metaDescription;
\IPS\Output::i()->metaTags['description'] = \IPS\Output::i()->metaTags['og:description'] = $metaDescription;
/* Mark read */
if( !$topic->isLastPage() )
@@ -303,19 +287,18 @@ class _topic extends \IPS\Content\Controller
'@type' => $topic->isQuestion() ? 'Question' : 'DiscussionForumPosting',
'url' => (string) $topic->url(),
'discussionUrl' => (string) $topic->url(),
'name' => htmlspecialchars( $topic->mapped('title'), ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
'headline' => htmlspecialchars( $topic->mapped('title'), ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
'name' => $topic->mapped('title'),
'headline' => $topic->mapped('title'),
'text' => strip_tags( $topic->isQuestion() ? $question->truncated() : $current->truncated() ),
'dateCreated' => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
'datePublished' => \IPS\DateTime::ts( $topic->start_date )->format( \IPS\DateTime::ISO8601 ),
'pageStart' => 1,
'pageEnd' => $topic->commentPageCount(),
/* Image is required, but we don't have "topic images", so we'll use topic starter's profile photo for now */
'image' => \IPS\Member::load( $topic->starter_id )->get_photo(),
'image' => (string) \IPS\Http\Url::external( \IPS\Member::load( $topic->starter_id )->get_photo() )->setScheme( \IPS\Request::i()->isSecure() ? 'https' : 'http' ),
'author' => array(
'@type' => 'Person',
'name' => htmlspecialchars( $topic->starter_name ?: \IPS\Member::load( $topic->starter_id )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
'url' => (string) \IPS\Member::load( $topic->starter_id )->url(),
'name' => $topic->starter_name ?: \IPS\Member::load( $topic->starter_id )->name,
'image' => \IPS\Member::load( $topic->starter_id )->get_photo()
),
'interactionStatistic' => array(
@@ -337,6 +320,22 @@ class _topic extends \IPS\Content\Controller
)
);
/* Are ratings allowed? */
if( !$topic->isArchived() AND $topic->container()->forum_allow_rating AND $topic->averageRating() )
{
\IPS\Output::i()->jsonLd['topic']['aggregateRating'] = array(
'@type' => 'AggregateRating',
'ratingValue' => $topic->averageRating(),
'ratingCount' => $topic->numberOfRatings(),
);
}
/* Do we have a real author */
if( $topic->starter_id )
{
\IPS\Output::i()->jsonLd['topic']['author']['url'] = (string) \IPS\Member::load( $topic->starter_id )->url();
}
if( $topic->isQuestion() )
{
\IPS\Output::i()->jsonLd['topic']['answerCount'] = $topic->posts ? $topic->posts - 1 : 0;
@@ -357,11 +356,15 @@ class _topic extends \IPS\Content\Controller
'upvoteCount' => $answer->post_field_int,
'author' => array(
'@type' => 'Person',
'name' => htmlspecialchars( $answer->author_name ?: \IPS\Member::load( $answer->author_id )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
'url' => (string) \IPS\Member::load( $answer->author_id )->url(),
'name' => $answer->author_name ?: \IPS\Member::load( $answer->author_id )->name,
'image' => \IPS\Member::load( $answer->author_id )->get_photo()
),
);
if( $answer->author_id )
{
\IPS\Output::i()->jsonLd['topic']['acceptedAnswer']['author']['url'] = (string) \IPS\Member::load( $answer->author_id )->url();
}
}
catch( \OutOfRangeException $e ){}
}
@@ -370,21 +373,28 @@ class _topic extends \IPS\Content\Controller
{
\IPS\Output::i()->jsonLd['topic']['comment'] = array();
$i = 0;
foreach( $comments as $comment )
{
\IPS\Output::i()->jsonLd['topic']['comment'][] = array(
\IPS\Output::i()->jsonLd['topic']['comment'][ $i ] = array(
'@type' => 'Comment',
'url' => (string) $comment->url(),
'author' => array(
'@type' => 'Person',
'name' => htmlspecialchars( $comment->author_name ?: \IPS\Member::load( $comment->author_id )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
'url' => (string) \IPS\Member::load( $comment->author_id )->url(),
'name' => $comment->author_name ?: \IPS\Member::load( $comment->author_id )->name,
'image' => \IPS\Member::load( $comment->author_id )->get_photo()
),
'dateCreated' => \IPS\DateTime::ts( $comment->post_date )->format( \IPS\DateTime::ISO8601 ),
'text' => strip_tags( $comment->truncated() ),
'mainEntityOfPage' => (string) $topic->url(),
);
if( $comment->author_id )
{
\IPS\Output::i()->jsonLd['topic']['comment'][ $i ]['author']['url'] = (string) \IPS\Member::load( $comment->author_id )->url();
}
$i++;
}
}
@@ -445,9 +455,20 @@ class _topic extends \IPS\Content\Controller
\IPS\Member::loggedIn()->language()->words['topic_mainTab'] = \IPS\Member::loggedIn()->language()->addToStack( 'question_mainTab', FALSE );
}
if ( !$topic->canEdit() and !\IPS\Request::i()->form_submitted ) // We check if the form has been submitted to prevent the user loosing their content
// We check if the form has been submitted to prevent the user loosing their content
if ( isset( \IPS\Request::i()->form_submitted ) )
{
\IPS\Output::i()->error( 'edit_no_perm_err', '2F173/E', 403, '' );
if ( ! $topic->couldEdit() )
{
\IPS\Output::i()->error( 'edit_no_perm_err', '2F173/E', 403, '' );
}
}
else
{
if ( ! $topic->canEdit() )
{
\IPS\Output::i()->error( 'edit_no_perm_err', '2F173/E', 403, '' );
}
}
$formElements = $class::formElements( $topic, $forum );
@@ -864,6 +885,77 @@ class _topic extends \IPS\Content\Controller
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'widgets', 'forums', 'front' )->poll( $topic, $poll );
}
/**
* Find a Comment / Review (do=findComment/findReview)
*
* @param string $commentClass The comment/review class
* @param \IPS\Content\Comment $comment The comment/review
* @param \IPS\Content\Item $item The item
* @return void
*/
public function _find( $commentClass, $comment, $item )
{
/* For normal topics (i.e. not questions), we can handle this normally */
if ( !$item->isQuestion() )
{
return parent::_find( $commentClass, $comment, $item );
}
/* Otherwise we need to get the position ordered by votes... */
if ( $item->isArchived() )
{
$where = array( array( 'archive_topic_id=?', $item->tid ) );
if ( !$item->canViewHiddenComments() )
{
$hiddenWhereClause = "(archive_queued != -2 AND archive_queued != -1 AND archive_queued != 1)";
if ( \IPS\Member::loggedIn()->member_id )
{
$where[] = array( "( {$hiddenWhereClause} OR ( archive_queued=1 AND archive_author_id=" . \IPS\Member::loggedIn()->member_id . '))' );
}
else
{
$where[] = array( $hiddenWhereClause );
}
}
$answers = \IPS\Db::i()->select( 'archive_id, @rownum := @rownum + 1 AS position', 'forums_archive_posts', $where, 'archive_is_first DESC, archive_bwoptions DESC, archive_field_int DESC, archive_content_date' )->join( array( '(SELECT @rownum := 0)', 'r' ), NULL, 'JOIN' );
$commentPosition = \IPS\Db::i()->select( 'position', $answers, array( 'archive_id=?', $comment->id ) )->first() - 1;
}
else
{
$where = array( array( 'topic_id=?', $item->tid ) );
if ( !$item->canViewHiddenComments() )
{
$hiddenWhereClause = "(queued != -2 AND queued != -1 AND queued != 1)";
if ( \IPS\Member::loggedIn()->member_id )
{
$where[] = array( "( {$hiddenWhereClause} OR ( queued=1 AND author_id=" . \IPS\Member::loggedIn()->member_id . '))' );
}
else
{
$where[] = array( $hiddenWhereClause );
}
}
$answers = \IPS\Db::i()->select( 'pid, @rownum := @rownum + 1 AS position', 'forums_posts', $where, 'new_topic DESC, post_bwoptions DESC, post_field_int DESC, post_date' )->join( array( '(SELECT @rownum := 0)', 'r' ), NULL, 'JOIN' );
$commentPosition = \IPS\Db::i()->select( 'position', $answers, array( 'pid=?', $comment->pid ) )->first() - 1;
}
/* Now work out what page that makes it */
$url = $item->url();
$perPage = $item::getCommentsPerPage();
$page = ceil( $commentPosition / $perPage );
if ( $page != 1 )
{
$url = $url->setQueryString( 'page', $page );
}
/* And redirect */
$idField = $commentClass::$databaseColumnId;
\IPS\Output::i()->redirect( $url->setFragment( 'comment-' . $comment->$idField ) );
}
/**
* Edit Comment/Review
@@ -914,7 +1006,8 @@ class _topic extends \IPS\Content\Controller
{
try
{
$comment = \IPS\forums\Topic\Post::load( \IPS\Request::i()->comment );
$commentClass = $class::$commentClass;
$comment = $commentClass::load( \IPS\Request::i()->comment );
$topic = \IPS\forums\Topic::load( $comment->topic_id );
\IPS\Output::i()->redirect( $topic->url()->setQueryString( array( 'do' => 'findComment', 'comment' => \IPS\Request::i()->comment ) ), NULL, 301 );
@@ -930,4 +1023,22 @@ class _topic extends \IPS\Content\Controller
\IPS\Output::i()->error( 'node_error', '2F173/I', 404, '' );
}
}
/**
* Form for splitting
*
* @param \IPS\Content\Item $item The item
* @return \IPS\Helpers\Form
*/
protected function _splitForm( \IPS\Content\Item $item )
{
$form = parent::_splitForm( $item );
if ( isset( $form->elements['']['topic_create_state'] ) )
{
unset( $form->elements['']['topic_create_state'] );
}
return $form;
}
}
@@ -104,8 +104,7 @@ class _Upgrade
\IPS\Db::i()->insert( 'core_sys_conf_settings', $insert );
}
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $prefix ), array( 'conf_key=?', 'archive_sql_tbl_prefix' ) );
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->changeValues( array( 'archive_sql_tbl_prefix' => $prefix ) );
}
}
@@ -3,17 +3,13 @@
"method": "dropColumn",
"params": [
"forums_archive_posts",
"archive_show_signature"
[
"archive_show_signature",
"archive_show_emoticons"
]
]
},
"2": {
"method": "dropColumn",
"params": [
"forums_archive_posts",
"archive_show_emoticons"
]
},
"3": {
"method": "dropColumn",
"params": [
"forums_posts",
@@ -0,0 +1,37 @@
<?php
/**
* @brief 4.2.2 Beta 1 Upgrade Code
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 26 Jul 2017
*/
namespace IPS\forums\setup\upg_102005;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* 4.2.2 Beta 1 Upgrade Code
*/
class _Upgrade
{
/**
* Recount containers
*
* @return array If returns TRUE, upgrader will proceed to next step. If it returns any other value, it will set this as the value of the 'extra' GET parameter and rerun this step (useful for loops)
*/
public function finish()
{
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\forums\Forum', 'count' => 0 ), 5, array( 'class' ) );
return TRUE;
}
}
@@ -0,0 +1,47 @@
<?php
/**
* @brief 4.2.6 Beta 1 Upgrade Code
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 06 Oct 2017
*/
namespace IPS\forums\setup\upg_102030;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* 4.2.6 Beta 1 Upgrade Code
*/
class _Upgrade
{
/**
* Fix container counts that may be off due to bug with adding in archived posts count
*
* @return array If returns TRUE, upgrader will proceed to next step. If it returns any other value, it will set this as the value of the 'extra' GET parameter and rerun this step (useful for loops)
*/
public function step1()
{
\IPS\Task::queue( 'core', 'RebuildContainerCounts', array( 'class' => 'IPS\forums\Forum', 'count' => 0 ), 5, array( 'class' ) );
return TRUE;
}
/**
* Custom title for this step
*
* @return string
*/
public function step1CustomTitle()
{
return "Rebuilding forum post counts";
}
}
@@ -0,0 +1,138 @@
{
"1": {
"method": "update",
"params": [
"forums_forums",
{
"posts": 0
},
[
"posts < ?",
0
]
]
},
"2": {
"method": "changeColumn",
"params": [
"forums_forums",
"posts",
{
"name": "posts",
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
}
]
},
"3": {
"method": "update",
"params": [
"forums_forums",
{
"topics": 0
},
[
"topics < ?",
0
]
]
},
"4": {
"method": "changeColumn",
"params": [
"forums_forums",
"topics",
{
"name": "topics",
"type": "INT",
"length": null,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": true,
"zerofill": false,
"auto_increment": false,
"binary": false
}
]
},
"5": {
"method": "update",
"params": [
"forums_forums",
{
"queued_topics": 0
},
[
"queued_topics < ?",
0
]
]
},
"6": {
"method": "changeColumn",
"params": [
"forums_forums",
"queued_topics",
{
"name": "queued_topics",
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
}
]
},
"7": {
"method": "update",
"params": [
"forums_forums",
{
"queued_posts": 0
},
[
"queued_posts < ?",
0
]
]
},
"8": {
"method": "changeColumn",
"params": [
"forums_forums",
"queued_posts",
{
"name": "queued_posts",
"type": "INT",
"length": 10,
"decimals": null,
"values": [],
"allow_null": false,
"default": "0",
"comment": "",
"unsigned": false,
"zerofill": false,
"auto_increment": false,
"binary": false
}
]
}
}
@@ -0,0 +1,31 @@
{
"1": {
"method": "update",
"params": [
"forums_topics",
{
"moved_to": null
},
[
"moved_to=?",
"NULL"
]
]
},
"2": {
"method": "addIndex",
"params": [
"forums_forums",
{
"type": "key",
"name": "min_posts_view",
"columns": [
"min_posts_view"
],
"length": [
null
]
}
]
}
}
+30 -9
View File
@@ -193,22 +193,26 @@ class _Feed extends \IPS\Node\Model
if ( $response->httpResponseCode == 401 )
{
throw new \DomainException('rss_import_auth');
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_auth' ) );
}
$response = $response->decodeXml();
if ( !( $response instanceof \IPS\Xml\Rss ) and !( $response instanceof \IPS\Xml\Atom ) )
{
throw new \DomainException('rss_import_invalid');
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
}
catch ( \IPS\Http\Request\Exception $e )
{
throw new \DomainException( 'form_url_bad' );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'form_url_bad' ) );
}
catch( \RuntimeException $e )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
catch ( \ErrorException $e )
{
throw new \DomainException( 'rss_import_invalid' );
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'rss_import_invalid' ) );
}
$values['title'] = (string) $request->get()->decodeXml()->channel->title;
@@ -274,15 +278,22 @@ class _Feed extends \IPS\Node\Model
$inserts = array();
$request = $request->decodeXml();
if( !( $request instanceof \IPS\Xml\RssOne ) AND !( $request instanceof \IPS\Xml\Rss ) AND !( $request instanceof \IPS\Xml\Atom ) )
if( !( $request instanceof \IPS\Xml\Rss1 ) AND !( $request instanceof \IPS\Xml\Rss ) AND !( $request instanceof \IPS\Xml\Atom ) )
{
throw new \RuntimeException( 'rss_import_invalid' );
}
$post = NULL;
foreach ( $request->articles( $this->id ) as $guid => $article )
{
if ( !in_array( $guid, $previouslyImportedGuids ) )
{
/* Don't post future date content - this breaks things like marking as read */
if( $article['date']->getTimestamp() > time() )
{
$artice['date'] = \IPS\DateTime::create();
}
$topic = \IPS\forums\Topic::createItem( \IPS\Member::load( $this->mid ), NULL, $article['date'], $container, $this->topic_hide );
$topic->title = $this->topic_pre . $article['title'];
if ( !$this->topic_open )
@@ -290,6 +301,13 @@ class _Feed extends \IPS\Node\Model
$topic->state = 'closed';
}
$topic->save();
/* Send notifications */
if ( !$this->topic_hide )
{
$topic->sendNotifications();
}
/* Add to search index */
\IPS\Content\Search\Index::i()->index( $topic );
@@ -347,9 +365,12 @@ class _Feed extends \IPS\Node\Model
$this->last_import = time();
$this->save();
$container->setLastComment();
$container->save();
if ( $post !== NULL )
{
$container->setLastComment( $post );
$container->save();
}
}
/**
+40 -12
View File
@@ -323,8 +323,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
try
{
/* We prefer fetching post, joining topic, etc. but that is not efficient and causes a temp table and filesort against posts table so we'll lean on the cached last_post value for the topic */
$select = \IPS\Db::i()->select( '*', 'forums_topics', array( "forums_topics.forum_id=? AND forums_topics.approved=1 AND forums_topics.state != ?", $this->id, 'link' ), 'forums_topics.last_post DESC', 1 )->first();
/*
* We prefer fetching post, joining topic, etc. but that is not efficient and causes a temp table and filesort against posts table so we'll lean on the cached last_post value for the topic
* We also need to fetch from the write server in the event that something has just been deleted and the comment hasn't been passed
*/
$select = \IPS\Db::i()->select( '*', 'forums_topics', array( "forums_topics.forum_id=? AND forums_topics.approved=1 AND forums_topics.state != ?", $this->id, 'link' ), 'forums_topics.last_post DESC', 1, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
$topic = \IPS\forums\Topic::constructFromData( $select );
if ( $topic->last_poster_id and ! $topic->last_poster_name )
@@ -405,10 +408,11 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
parent::resetCommentCounts();
/* Archiving? */
if ( \IPS\Settings::i()->archive_on )
try
{
$this->_comments += \IPS\forums\Topic\ArchivedPost::db()->select( 'COUNT(*)', 'forums_archive_posts', array( 'archive_forum_id=?', $this->id ) )->first();
}
catch( \Exception $e ){}
}
/**
@@ -759,7 +763,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
return FALSE;
}
if ( in_array( $permission, array( 'add', 'reply' ) ) and $this->min_posts_post and $this->min_posts_post >= $_member->member_posts )
if ( in_array( $permission, array( 'add', 'reply' ) ) and $this->min_posts_post and $this->min_posts_post > $_member->member_posts )
{
return FALSE;
}
@@ -1407,7 +1411,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
$this->save();
}
}
/**
* If there is only one forum (and it isn't a redirect forum or password protected), that forum, or NULL
*
@@ -1415,7 +1419,7 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
*/
public static function theOnlyForum()
{
return static::theOnlyNode( array( 'redirect_url' => FALSE, 'password' => FALSE, 'club_id' => 0 ), FALSE );
return static::theOnlyNode( array( 'redirect_url' => FALSE, 'password' => FALSE ), FALSE );
}
/**
@@ -1641,7 +1645,15 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
public function disabledPermissions()
{
$disabled = array();
$guestGroup = \IPS\Member\Group::load( \IPS\Settings::i()->guest_group );
try
{
$guestGroup = \IPS\Member\Group::load( \IPS\Settings::i()->guest_group );
}
catch( \OutOfRangeException $e )
{
throw new \UnderflowException( 'invalid_guestgroup_admin', 199 );
}
if( $this->sub_can_post and !$this->can_view_others )
{
@@ -1700,13 +1712,14 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
/**
* Get output for API
*
* @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 Forum name
* @apiresponse int topics Number of topics in forum
* @apiresponse string url URL
*/
public function apiOutput()
public function apiOutput( \IPS\Member $authorizedMember = NULL )
{
return array(
'id' => $this->id,
@@ -1744,6 +1757,12 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
{
$this->name_seo = \IPS\Http\Url\Friendly::seoTitle( $values['club_node_name'] );
}
if ( !$this->_id )
{
$this->save();
\IPS\File::claimAttachments( 'forums-new-forum', $this->id, NULL, 'description' );
}
}
/* !Simple view */
@@ -1764,6 +1783,12 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
return false;
}
/* If this was called via CLI (e.g. tasks ran via cron), then use the default */
if( !\IPS\Dispatcher::hasInstance() )
{
return \IPS\Settings::i()->forums_default_view === 'fluid' ? true : false;
}
/* Guests are locked to the admin choice */
if ( ! \IPS\Member::loggedIn()->member_id )
{
@@ -1809,11 +1834,14 @@ class _Forum extends \IPS\Node\Model implements \IPS\Node\Permissions
try
{
$method = \IPS\Db::i()->select( 'method', 'forums_view_method', array( 'member_id=?', \IPS\Member::loggedIn()->member_id ) )->first();
/* Attempt to set the cookie again */
\IPS\Request::i()->setCookie( 'forum_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
}
catch( \UnderFlowException $e ){ }
catch( \UnderFlowException $e )
{
$method = \IPS\Settings::i()->forums_default_view;
}
/* Attempt to set the cookie again */
\IPS\Request::i()->setCookie( 'forum_view', $method, ( new \IPS\DateTime )->add( new \DateInterval( 'P1Y' ) ) );
}
if ( ! $method or ( $chooseable != '*' AND ! in_array( $method, $chooseable ) ) )
@@ -221,10 +221,22 @@ class _SavedAction extends \IPS\Node\Model
{
/* Permission Checks */
$member = $member ?: \IPS\Member::loggedIn();
/* Check the member can use saved actions, and they can moderate in this forum */
if ( !$member->modPermission('can_use_saved_actions') )
{
throw new \DomainException('NO_PERMISSION');
}
if ( $member->modPermission( 'forums' ) !== -1 AND $member->modPermission( 'forums' ) !== TRUE )
{
if ( !is_array( $member->modPermission( 'forums' ) ) or !in_array( $topic->container()->_id, $member->modPermission( 'forums' ) ) )
{
throw new \DomainException('NO_PERMISSION');
}
}
/* Check the action is enabled and allowed for the content item */
if ( !$this->mm_enabled )
{
throw new \DomainException('DISABLED');
@@ -283,7 +295,7 @@ class _SavedAction extends \IPS\Node\Model
/* Reply */
if ( $this->topic_reply )
{
$reply = \IPS\forums\Topic\Post::create( $topic, $this->topic_reply_content, FALSE, NULL, $this->topic_reply_postcount );
$reply = \IPS\forums\Topic\Post::create( $topic, $this->topic_reply_content, FALSE, NULL, isset( $this->topic_reply_postcount ) AND $this->topic_reply_postcount ? TRUE : FALSE );
}
/* Move */
+21 -9
View File
@@ -21,9 +21,9 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
/**
* Post Model
*/
class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \IPS\Content\EditHistory, \IPS\Content\Hideable, \IPS\Content\Shareable, \IPS\Content\Searchable, \IPS\Content\Embeddable
class _Post extends \IPS\Content\Comment implements \IPS\Content\EditHistory, \IPS\Content\Hideable, \IPS\Content\Shareable, \IPS\Content\Searchable, \IPS\Content\Embeddable
{
use \IPS\Content\Reactable;
use \IPS\Content\Reactable, \IPS\Content\Reportable;
/**
* @brief [ActiveRecord] Multiton Store
@@ -169,7 +169,7 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
*/
public static function incrementPostCount( \IPS\Node\Model $container = NULL )
{
return $container and $container->inc_postcount;
return !$container or $container->inc_postcount;
}
/**
@@ -324,6 +324,17 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
return TRUE;
}
/**
* [ActiveRecord] Save
*
* @return void
*/
public function save()
{
parent::save();
$this->item()->clearVotes();
}
/**
* Delete Post
@@ -400,22 +411,23 @@ class _Post extends \IPS\Content\Comment implements \IPS\Content\ReportCenter, \
/**
* Definite Article
*
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang $lang The language to use, or NULL for the language of the currently logged in member
* @return string
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param array $options Options to pass to \IPS\Lang::addToStack
* @return string
*/
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL, $options = array() )
{
$bitOptions = ( $containerData['forums_bitoptions'] instanceof \IPS\Patterns\Bitwise ) ? $containerData['forums_bitoptions'] : new \IPS\Patterns\Bitwise( array( 'forums_bitoptions' => $containerData['forums_bitoptions'] ), \IPS\forums\Forum::$bitOptions['forums_bitoptions'] );
if ( $bitOptions['bw_enable_answers'] )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__defart_answer', FALSE );
return $lang->addToStack( '__defart_answer', FALSE, $options );
}
else
{
return parent::_definiteArticle( $containerData, $lang );
return parent::_definiteArticle( $containerData, $lang, $options );
}
}
+60 -27
View File
@@ -27,7 +27,6 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Content\Tags,
\IPS\Content\Followable,
\IPS\Content\Shareable,
\IPS\Content\ReportCenter,
\IPS\Content\ReadMarkers,
\IPS\Content\Views,
\IPS\Content\Polls, \SplObserver,
@@ -36,6 +35,8 @@ class _Topic extends \IPS\Content\Item implements
\IPS\Content\Embeddable,
\IPS\Content\MetaData
{
use \IPS\Content\Reportable;
const ARCHIVE_NOT = 0;
const ARCHIVE_DONE = 1;
const ARCHIVE_WORKING = 2;
@@ -343,7 +344,7 @@ class _Topic extends \IPS\Content\Item implements
{
$joinContainer = TRUE;
$whereClause = array( '( forums_forums.can_view_others=1 OR forums_topics.starter_id=? )', $member->member_id );
$whereClause = array( '( forums_forums.can_view_others=1 OR forums_topics.starter_id=? )', (int) $member->member_id );
if ( $member->modPermission( 'can_read_all_topics' ) )
{
@@ -364,7 +365,7 @@ class _Topic extends \IPS\Content\Item implements
{
$member = $member ?: \IPS\Member::loggedIn();
$joinContainer = TRUE;
$where['container'][] = array( 'forums_forums.min_posts_view<=?', \IPS\Member::loggedIn()->member_posts );
$where['container'][] = array( 'forums_forums.min_posts_view<=?', $member->member_posts );
}
/* Return */
@@ -689,7 +690,10 @@ class _Topic extends \IPS\Content\Item implements
{
$popularNowInterval = new \DateInterval( 'PT' . $popularNowSettings['minutes'] . 'M' );
$comments = $this->comments( NULL, NULL, 'date', 'desc', NULL, FALSE, \IPS\DateTime::create()->sub( $popularNowInterval ) );
$comments = iterator_to_array( new \IPS\Patterns\ActiveRecordIterator(
\IPS\Db::i()->select( '*', 'forums_posts', array( 'queued IN(0,2) AND post_date >? AND topic_id=?', \IPS\DateTime::create()->sub( $popularNowInterval )->getTimestamp(), $this->tid ), 'post_date DESC', NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER ),
'IPS\\forums\\Topic\\Post'
) );
if ( count( $comments ) >= $popularNowSettings['posts'] )
{
$commentToBasePopularNowTimeOff = array_slice( $comments, ( $popularNowSettings['posts'] - 1 ), 1 );
@@ -866,9 +870,9 @@ class _Topic extends \IPS\Content\Item implements
*/
public static function contentTableTemplate()
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'forums.css', 'forums' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'forums_responsive.css', 'forums' ) );
return array( \IPS\Theme::i()->getTemplate( 'global', 'forums' ), 'rows' );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'forums.css', 'forums', 'front' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'forums_responsive.css', 'forums', 'front' ) );
return array( \IPS\Theme::i()->getTemplate( 'global', 'forums', 'front' ), 'rows' );
}
/**
@@ -922,7 +926,7 @@ class _Topic extends \IPS\Content\Item implements
{
if ( $container and ( $container->preview_posts == 1 or $container->preview_posts == 2 ) and !$member->group['g_avoid_q'] )
{
return TRUE;
return !static::modPermission( 'approve', $member, $container );
}
return parent::moderateNewItems( $member, $container );
@@ -1325,7 +1329,7 @@ class _Topic extends \IPS\Content\Item implements
}
/* Have we already voted? */
if ( $rating !== 0 or !\IPS\Settings::i()->forums_answers_downvote )
if ( $rating !== 0 or !\IPS\Settings::i()->forums_questions_downvote )
{
$ratings = $this->votes();
if ( isset( $ratings[ $member->member_id ] ) and $ratings[ $member->member_id ] === $rating )
@@ -1367,6 +1371,29 @@ class _Topic extends \IPS\Content\Item implements
return $this->votes;
}
/**
* Clear Votes Cache
*
* @return void
* @note This is necessary so that when voting on a question or answer, the cached votes ($votes and $answerVotes) are reloaded properly.
*/
public function clearVotes()
{
$this->votes = NULL;
$this->answerVotes = array();
}
/**
* [ActiveRecord] Save
*
* @return void
*/
public function save()
{
parent::save();
$this->clearVotes();
}
/**
* Indefinite Article
*
@@ -1391,21 +1418,23 @@ class _Topic extends \IPS\Content\Item implements
/**
* Definite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param array $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @param array $options Options to pass to \IPS\Lang::addToStack
* @return string
*/
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL, $options = array() )
{
$bitOptions = ( $containerData['forums_bitoptions'] instanceof \IPS\Patterns\Bitwise ) ? $containerData['forums_bitoptions'] : new \IPS\Patterns\Bitwise( array( 'forums_bitoptions' => $containerData['forums_bitoptions'] ), \IPS\forums\Forum::$bitOptions['forums_bitoptions'] );
if ( $bitOptions['bw_enable_answers'] )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__defart_question', FALSE );
return $lang->addToStack( '__defart_question', FALSE, $options );
}
else
{
return parent::_definiteArticle( $containerData, $lang );
return parent::_definiteArticle( $containerData, $lang, $options );
}
}
@@ -1492,10 +1521,12 @@ class _Topic extends \IPS\Content\Item implements
$time = \IPS\DateTime::ts( $taskData['next_run'] );
$postsToBeUnarchived = \IPS\Db::i()->select( 'SUM(posts) + COUNT(*)', 'forums_topics', array( 'topic_archive_status=?', static::ARCHIVE_RESTORE ) )->first();
if ( $postsToBeUnarchived )
if ( $postsToBeUnarchived AND $postsToBeUnarchived > \IPS\forums\tasks\unarchive::PROCESS_PER_BATCH )
{
$total = $postsToBeUnarchived / \IPS\forums\tasks\unarchive::PROCESS_PER_BATCH;
$interval = new \DateInterval( $taskData['frequency'] );
foreach ( range( 1, $postsToBeUnarchived ) as $i )
foreach ( range( 1, $total ) as $i )
{
$time->add( $interval );
}
@@ -1728,9 +1759,10 @@ class _Topic extends \IPS\Content\Item implements
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
* @param bool|NULL $bypassCache Used in cases where comments may have already been loaded i.e. splitting comments on an item.
* @param bool $includeDeleted Include Deleted Content
* @param bool|NULL $canViewWarn TRUE to include Warning information, NULL to determine automatically based on moderator permissions.
* @return array|NULL|\IPS\Content\Comment If $limit is 1, will return \IPS\Content\Comment or NULL for no results. For any other number, will return an array.
*/
public function comments( $limit=NULL, $offset=NULL, $order='date', $orderDirection='asc', $member=NULL, $includeHiddenComments=NULL, $cutoff=NULL, $extraWhereClause=NULL, $bypassCache=FALSE, $includeDeleted=FALSE )
public function comments( $limit=NULL, $offset=NULL, $order='date', $orderDirection='asc', $member=NULL, $includeHiddenComments=NULL, $cutoff=NULL, $extraWhereClause=NULL, $bypassCache=FALSE, $includeDeleted=FALSE, $canViewWarn=NULL )
{
static $comments = array();
$idField = static::$databaseColumnId;
@@ -1741,7 +1773,7 @@ class _Topic extends \IPS\Content\Item implements
return $comments[ $_hash ];
}
$includeWarnings = NULL;
$includeWarnings = $canViewWarn;
$commentClass = NULL;
if ( $this->isArchived() )
@@ -1855,19 +1887,19 @@ class _Topic extends \IPS\Content\Item implements
/**
* Returns the meta description
*
* @param string|NULL $return Specific description to use (useful for paginated displays to prevent having to run extra queries)
* @return string
* @throws \BadMethodCallException
*/
public function metaDescription()
public function metaDescription( $return = NULL )
{
/* We have to store and restore this for the topic controller */
if( isset( $_SESSION['_findComment'] ) )
{
$_findComment = $_SESSION['_findComment'];
}
$metaDescription = parent::metaDescription();
$metaDescription = parent::metaDescription( $return );
if( isset( $_findComment ) )
{
@@ -1880,12 +1912,13 @@ class _Topic extends \IPS\Content\Item implements
/**
* Get output for API
*
* @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 title Title
* @apiresponse \IPS\forums\Forum forum Forum
* @apiresponse int posts Number of posts
* @apiresponse int views Number of posts
* @apiresponse int views Number of views
* @apiresponse string prefix The prefix tag, if there is one
* @apiresponse [string] tags The tags
* @apiresponse \IPS\forums\Topic\Post firstPost The first post in the topic
@@ -1900,7 +1933,7 @@ class _Topic extends \IPS\Content\Item implements
* @apiresponse string url URL
* @apiresponse float rating Average Rating
*/
public function apiOutput()
public function apiOutput( \IPS\Member $authorizedMember = NULL )
{
$firstPost = $this->comments( 1, 0, 'date', 'asc' );
$lastPost = $this->comments( 1, 0, 'date', 'desc' );
@@ -1908,20 +1941,20 @@ class _Topic extends \IPS\Content\Item implements
return array(
'id' => $this->tid,
'title' => $this->title,
'forum' => $this->container()->apiOutput(),
'forum' => $this->container()->apiOutput( $authorizedMember ),
'posts' => $this->posts,
'views' => $this->views,
'prefix' => $this->prefix(),
'tags' => $this->tags(),
'firstPost' => $firstPost ? $firstPost->apiOutput() : null,
'lastPost' => $lastPost ? $lastPost->apiOutput() : null,
'bestAnswer' => $bestAnswer ? $bestAnswer->apiOutput() : null,
'firstPost' => $firstPost ? $firstPost->apiOutput( $authorizedMember ) : null,
'lastPost' => $lastPost ? $lastPost->apiOutput( $authorizedMember ) : null,
'bestAnswer' => $bestAnswer ? $bestAnswer->apiOutput( $authorizedMember ) : null,
'locked' => (bool) $this->locked(),
'hidden' => (bool) $this->hidden(),
'pinned' => (bool) $this->mapped('pinned'),
'featured' => (bool) $this->mapped('featured'),
'archived' => (bool) $this->isArchived(),
'poll' => $this->poll_state ? \IPS\Poll::load( $this->poll_state )->apiOutput() : null,
'poll' => $this->poll_state ? \IPS\Poll::load( $this->poll_state )->apiOutput( $authorizedMember ) : null,
'url' => (string) $this->url(),
'rating' => $this->averageRating(),
);
+1 -1
View File
@@ -71,7 +71,7 @@ class _poll extends \IPS\Widget
$form->add( new \IPS\Helpers\Form\Item( 'widget_poll_tid', ( isset( $this->configuration['widget_poll_tid'] ) ? $this->configuration['widget_poll_tid'] : NULL ), TRUE, array(
'class' => '\IPS\forums\Topic',
'maxItems' => 1,
'where' => array( array( 'poll_state IS NOT NULL' ) )
'where' => array( array( '(poll_state<>0 AND poll_state IS NOT NULL)' ) )
) ) );
return $form;