Version 5.0.0 beta 4

This commit is contained in:
Neo committed 2025-12-19 16:57:00 -08:00
1 parent e86153356a
commit 2f24f9de48
276 files changed
+8614 -6044

No files matched your search

+1 -1
View File
@@ -165,7 +165,7 @@ class ItemController extends Controller
/* Sort */
$supportedSortBy = array( 'date', 'title' );
if( $class::$databaseColumnMap['updated'] )
if( isset($class::$databaseColumnMap['updated'] ) )
{
$supportedSortBy[] = 'updated';
}
+84 -27
View File
@@ -29,6 +29,7 @@ use IPS\DateTime;
use IPS\Db;
use IPS\Db\Exception;
use IPS\Db\Select;
use IPS\Dispatcher\Front;
use IPS\Events\Event;
use IPS\File;
use IPS\Helpers\Form\Captcha;
@@ -39,6 +40,7 @@ use IPS\IPS;
use IPS\Member;
use IPS\Node\Model;
use IPS\Notification;
use IPS\Output;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Platform\Bridge;
use IPS\Redis;
@@ -57,6 +59,7 @@ use function in_array;
use function intval;
use function is_array;
use function substr;
use function ucfirst;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -151,27 +154,31 @@ abstract class Comment extends Content
break;
case 'approved':
if ( $first ) // If this is the first post within an item, don't mark it hidden, otherwise the count of unapproved comments/items will include both the comment and the item when really only the item is hidden
if( IPS::classUsesTrait( $obj, Hideable::class ) )
{
$val = TRUE;
}
elseif ( $hiddenStatus === NULL )
{
$permissionCheckFunction = in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) ? 'canReview' : 'canComment';
if ( !$member->member_id and !$item->$permissionCheckFunction( $member, FALSE ) )
if ( $first ) // If this is the first post within an item, don't mark it hidden, otherwise the count of unapproved comments/items will include both the comment and the item when really only the item is hidden
{
$val = -3;
$val = TRUE;
}
elseif ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
elseif ( $hiddenStatus === NULL )
{
$val = $item->moderateNewReviews( $member ) ? 0 : 1;
}
else
{
$val = $item->moderateNewComments( $member ) ? 0 : 1;
$permissionCheckFunction = in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) ? 'canReview' : 'canComment';
if ( !$member->member_id and !$item->$permissionCheckFunction( $member, FALSE ) )
{
$val = -3;
}
elseif ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
$val = $item->moderateNewReviews( $member ) ? 0 : 1;
}
else
{
$val = $item->moderateNewComments( $member ) ? 0 : 1;
}
}
}
else
if( $val === null or !IPS::classUsesTrait( $obj, Hideable::class ) )
{
switch ( $hiddenStatus )
{
@@ -189,11 +196,15 @@ abstract class Comment extends Content
break;
case 'hidden':
if( !IPS::classUsesTrait( $obj, Hideable::class ) )
{
$val = $hiddenStatus;
}
if ( $first )
{
$val = FALSE; // If this is the first post within an item, don't mark it hidden, otherwise the count of unapproved comments/items will include both the comment and the item when really only the item is hidden
}
elseif ( $item->approvedButHidden() )
elseif ( IPS::classUsesTrait( $item, Hideable::class ) and $item->approvedButHidden() )
{
$val = 2;
}
@@ -602,6 +613,49 @@ abstract class Comment extends Content
) );
}
/**
* @var array Shareable URLs
*/
protected static $shareableUrls = array();
/**
* Get a shareable URL
*
* @return Url
*/
public function shareableUrl( string $type='comment' ): Url
{
$currentPage = Front::i()->currentPage ?? 1;
$idColumn = static::$databaseColumnId;
$item = $this->item();
$itemIdColumn = $item::$databaseColumnId;
/* Avoid building a single item's URL multiple times, such as when viewing a topic */
if ( isset( static::$shareableUrls[ $type ][ $item->$itemIdColumn ] ) )
{
$url = static::$shareableUrls[ $type ][ $item->$itemIdColumn ];
}
else
{
$url = $item->url()->setPage( 'page', $currentPage );
/* if we can do both comments and reviews, just return the findCommment url as we have tabs to navigate anyway */
if ( isset( $item::$reviewClass ) and isset( $item::$commentClass ) )
{
if ( method_exists( $item, 'commentReviewTabs' ) )
{
if ( count( $item->commentReviewTabs() ) > 1 )
{
$url = $url->setQueryString( ['tab' => $type . 's'] );
}
}
}
static::$shareableUrls[ $type ][ $item->$itemIdColumn ] = $url;
}
return $url->setFragment( 'find' . ucfirst( $type ) . '-' . $this->$idColumn );
}
/**
* Get containing item
*
@@ -1868,28 +1922,31 @@ abstract class Comment extends Content
}
}
if( $this->mapped('first') )
if ( ! Output::i()->reduceLinks() )
{
$share = new ContentMenuLink( url: $this->item()->url('share' ), languageString: Member::loggedIn()->language()->addToStack( $contentType === 'Comment' ? 'share_this_post' : 'share_this_review' ) );
}
else
{
if ( $contentType === 'Comment' )
if ( $this->mapped( 'first' ) )
{
$share = new ContentMenuLink( url: $this->item()->url( )->setQueryString( array( 'do' => 'findComment', 'comment' => $this->$idField )), languageString: Member::loggedIn()->language()->addToStack( 'share_this_post' ) );
$share = new ContentMenuLink( url: $this->item()->url( 'share' ), languageString: Member::loggedIn()->language()->addToStack( $contentType === 'Comment' ? 'share_this_post' : 'share_this_review' ) );
}
else
{
$share = new ContentMenuLink( url: $this->item()->url( )->setQueryString( array( 'do' => 'findReview', 'review' => $this->$idField )), languageString: Member::loggedIn()->language()->addToStack( 'share_this_review' ) );
if ( $contentType === 'Comment' )
{
$share = new ContentMenuLink( url: $this->shareableUrl(), languageString: Member::loggedIn()->language()->addToStack( 'share_this_post' ) );
}
else
{
$share = new ContentMenuLink( url: $this->shareableUrl('review'), languageString: Member::loggedIn()->language()->addToStack( 'share_this_review' ) );
}
}
}
$share->opensDialog( title: Member::loggedIn()->language()->addToStack('share'), size: 'narrow', contentSelector: '#elShare' . ucfirst( static::$moderationMenuIdPrefix ) . $this->$idField . '_menu' )
$share->opensDialog( title: Member::loggedIn()->language()->addToStack('share'), size: 'narrow', contentSelector: '#elShare' . ucfirst( static::$moderationMenuIdPrefix ) . $this->$idField . '_menu' )
->addAttribute( 'data-role', 'share' . $contentType )
->addAttribute( 'id', 'elShare' . ucfirst( static::$moderationMenuIdPrefix ) . $this->$idField )
->addAttribute( 'rel', 'nofollow' );
$menu->add( $share );
$menu->add( $share );
}
if( method_exists( $this, 'canRecognize') )
{
+12 -4
View File
@@ -879,11 +879,12 @@ abstract class Content extends ActiveRecord
*
* @param string $reportContent Report content message from member
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report)
* @param Member|null $member Member making the report (or NULL for loggedIn())
* @param Member|null $member Member making the report (or NULL for loggedIn())
* @param array $guestDetails Details for a guest report
* @return Report
* @throws UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
*/
public function report( string $reportContent, int $reportType=1, Member|null $member=null ): Report
public function report( string $reportContent, int $reportType=1, Member|null $member=null, array $guestDetails=array() ): Report
{
$member = ( $member ) ?: Member::loggedIn();
@@ -936,9 +937,16 @@ abstract class Content extends ActiveRecord
'report_by' => (int) $member->member_id,
'date_reported' => time(),
'ip_address' => Request::i()->ipAddress(),
'report_type' => $member->member_id ? $reportType : 0
'report_type' => ( ! $member->member_id and Settings::i()->automoderation_enabled ) ? 0 : $reportType
);
if ( count( $guestDetails ) and isset( $guestDetails['email'] ) )
{
/* Guest name can be optional */
$reportInsert['guest_name'] = isset( $guestDetails['name'] ) ? $guestDetails['name'] : '';
$reportInsert['guest_email'] = $guestDetails['email'];
}
$insertID = Db::i()->insert( 'core_rc_reports', $reportInsert );
$reportInsert['id'] = $insertID;
+71 -14
View File
@@ -19,8 +19,11 @@ use ErrorException;
use Exception;
use InvalidArgumentException;
use IPS\Api\Webhook;
use IPS\cloud\Application as CloudApplication;
use IPS\Content\Search\SearchContent;
use IPS\core\Feature;
use IPS\Helpers\Form\Email;
use IPS\Helpers\Form\Select;
use IPS\Helpers\Form\TextArea;
use IPS\Helpers\Form\Upload;
use IPS\Helpers\Form\Url as FormUrl;
@@ -398,7 +401,12 @@ class Controller extends CoverPhotoController
}
Output::i()->sidebar['contextual'] .= $item->ui( 'sidebar' );
if( Application::appIsEnabled( 'cloud' ) )
{
Output::setCacheTime( CloudApplication::getCacheDateForPage( $item->mapped('updated'), ( ( $item::$application === 'forums' and $item->isArchived() ) ? 'archived' : 'content' ) ) );
}
/* Return */
return $item;
}
@@ -1470,7 +1478,26 @@ class Controller extends CoverPhotoController
}
if ( $values = $form->values() )
{
$report = $item->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0 );
$guestDetails = [];
if ( ! Member::loggedIn()->member_id and Settings::i()->report_capture_guest_details )
{
$guestDetails = [ 'name' => $values['report_guest_name'], 'email' => $values['report_guest_email'] ];
/* Has this guest already reported this? */
try
{
$index = Db::i()->select( '*', 'core_rc_index', [ 'class=? and content_id=?', \get_class( $item ), $item->$idColumn ] )->first();
$report = Db::i()->select( '*', 'core_rc_reports', [ 'rid=? and guest_email=?', $index['id'], $values['report_guest_email'] ] )->first();
Output::i()->error( 'report_err_already_reported', '2S136/11', 403, '' );
}
catch( \UnderflowException )
{
/* The query failed, so this is a new report */
}
}
$report = $item->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0, null, $guestDetails );
File::claimAttachments( "report-{$class::$application}-{$class::$module}-{$item->$idColumn}", $report->id );
if ( Request::i()->isAjax() )
{
@@ -3337,7 +3364,7 @@ class Controller extends CoverPhotoController
}
/* Show form */
$form = new Form( NULL, 'report_submit' );
$form = new Form( 'report_submit', 'report_submit' );
$form->class = 'ipsForm--vertical ipsForm--report';
$itemIdColumn = $class::$databaseColumnId;
$idColumn = $comment::$databaseColumnId;
@@ -3355,17 +3382,28 @@ class Controller extends CoverPhotoController
return;
}
catch( Exception $e ) { }
$options = array( Report::TYPE_MESSAGE => Member::loggedIn()->language()->addToStack('report_message_comment') );
foreach( Types::roots() as $type )
{
$options[ $type->id ] = $type->_title;
}
$form->add( new Radio( 'report_type', NULL, FALSE, array( 'options' => $options ) ) );
}
$form->add( new Editor( 'report_message', NULL, FALSE, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", 'minimize' => 'report_message_placeholder' ) ) );
$options = array( Report::TYPE_MESSAGE => Member::loggedIn()->language()->addToStack('report_message_comment') );
foreach( Types::roots() as $type )
{
$options[ $type->id ] = $type->_title;
}
if ( count( $options ) > 1 )
{
$form->add( new Select( 'report_type', null, false, ['options' => $options] ) );
}
if ( ! Member::loggedIn()->member_id and Settings::i()->report_capture_guest_details )
{
/* Guest details */
$form->add( new Text( 'report_guest_name', null, Settings::i()->report_guest_details_name_mandatory ) );
$form->add( new Email( 'report_guest_email', null, true ) );
}
$form->add( new Editor( 'report_message', null, (bool) Settings::i()->report_content_mandatory, array( 'app' => 'core', 'key' => 'Reports', 'autoSaveKey' => "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", 'minimize' => Settings::i()->report_content_mandatory ? 'report_message_placeholder_mandatory' : 'report_message_placeholder' ) ) );
if ( !Request::i()->isAjax() )
{
Member::loggedIn()->language()->words['report_message'] = Member::loggedIn()->language()->addToStack('report_message_fallback');
@@ -3378,7 +3416,26 @@ class Controller extends CoverPhotoController
if ( $values = $form->values() )
{
$report = $comment->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0 );
$guestDetails = [];
if ( ! Member::loggedIn()->member_id and Settings::i()->report_capture_guest_details )
{
$guestDetails = [ 'name' => $values['report_guest_name'], 'email' => $values['report_guest_email'] ];
/* Has this guest already reported this? */
try
{
$index = Db::i()->select( '*', 'core_rc_index', [ 'class=? and content_id=?', \get_class( $comment ), $comment->$idColumn ] )->first();
$report = Db::i()->select( '*', 'core_rc_reports', [ 'rid=? and guest_email=?', $index['id'], $values['report_guest_email'] ] )->first();
Output::i()->error( 'report_err_already_reported', '2S136/11', 403, '' );
}
catch( \UnderflowException )
{
/* The query failed, so this is a new report */
}
}
$report = $comment->report( $values['report_message'], ( isset( $values['report_type'] ) ) ? $values['report_type'] : 0, null, $guestDetails );
File::claimAttachments( "report-{$class::$application}-{$class::$module}-{$item->$itemIdColumn}-{$comment->$idColumn}", $report->id );
if ( Request::i()->isAjax() )
{
+21 -2
View File
@@ -1401,7 +1401,26 @@ abstract class Item extends Content
}
throw new BadMethodCallException;
}
/**
* Get a shareable URL
*
* @param int|null $commentId
* @return Url
*/
public function shareableUrl( int $commentId = NULL ): Url
{
$idColumn = static::$databaseColumnId;
$url = $this->url();
if ( $commentId )
{
$url = $url->setFragment( 'findComment-' . $commentId );
}
return $url;
}
/**
* Get mapped value
*
@@ -7165,7 +7184,7 @@ abstract class Item extends Content
{
$lock->addAttribute( 'data-ipsDialog')
->addAttribute( 'data-ipsDialog-size', 'medium')
->addAttribute( 'data-ipsDialog-title', 'lock')
->addAttribute( 'data-ipsDialog-title', Member::loggedIn()->language()->addToStack( 'lock'))
->addAttribute( 'data-ipsDialog-destructOnClose', 'true');
}
$menu->add( $lock );
+1
View File
@@ -619,6 +619,7 @@ class Tag extends Model
$this->text = sprintf( Member::loggedIn()->language()->get( 'copy_tag_text' ), $this->text );
$this->text_seo = Friendly::seoTitle( $this->text );
$this->totals = null;
parent::__clone();