Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+302
View File
@@ -0,0 +1,302 @@
<?php
/**
* @brief Base API endpoint for Content Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 8 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Content\Api;
/* 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;
}
/**
* @brief Base API endpoint for Content Comments
*/
class _CommentController extends \IPS\Api\Controller
{
/**
* List
*
* @param array $where Extra WHERE clause
* @return \IPS\Api\PaginatedResponse
*/
protected function _list( $where = array(), $containerParam = 'categories' )
{
$class = $this->class;
$itemClass = $class::$itemClass;
/* Containers */
if ( isset( \IPS\Request::i()->$containerParam ) )
{
$where[] = array( \IPS\Db::i()->in( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'], array_map( 'intval', array_filter( explode( ',', \IPS\Request::i()->$containerParam ) ) ) ) );
}
/* Authors */
if ( isset( \IPS\Request::i()->authors ) )
{
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['author'], array_map( 'intval', array_filter( explode( ',', \IPS\Request::i()->authors ) ) ) ) );
}
/* Pinned? */
if ( isset( \IPS\Request::i()->pinned ) AND in_array( 'IPS\Content\Pinnable', class_implements( $itemClass ) ) )
{
if ( \IPS\Request::i()->pinned )
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['pinned'] . "=1" );
}
else
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['pinned'] . "=0" );
}
}
/* Featured? */
if ( isset( \IPS\Request::i()->featured ) AND in_array( 'IPS\Content\Featurable', class_implements( $itemClass ) ) )
{
if ( \IPS\Request::i()->featured )
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['featured'] . "=1" );
}
else
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['featured'] . "=0" );
}
}
/* Locked? */
if ( isset( \IPS\Request::i()->locked ) AND in_array( 'IPS\Content\Lockable', class_implements( $itemClass ) ) )
{
if ( isset( static::$databaseColumnMap['locked'] ) )
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['locked'] . '=?', intval( \IPS\Request::i()->locked ) );
}
else
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['state'] . '=?', \IPS\Request::i()->locked ? 'closed' : 'open' );
}
}
/* Hidden */
if ( isset( \IPS\Request::i()->hidden ) AND in_array( 'IPS\Content\Hideable', class_implements( $class ) ) )
{
if ( \IPS\Request::i()->hidden )
{
if ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '<>0' );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '<>1' );
}
}
else
{
if ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '=0' );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=1' );
}
}
}
/* Has poll? */
if ( isset( \IPS\Request::i()->hasPoll ) AND in_array( 'IPS\Content\Polls', class_implements( $itemClass ) ) )
{
if ( \IPS\Request::i()->hasPoll )
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['poll'] . ">0" );
}
else
{
$where[] = array( $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['poll'] . "=0" );
}
}
/* Sort */
if ( isset( \IPS\Request::i()->sortBy ) and in_array( \IPS\Request::i()->sortBy, array( 'date' ) ) )
{
$sortBy = $class::$databasePrefix . $class::$databaseColumnMap[ \IPS\Request::i()->sortBy ];
}
if ( isset( \IPS\Request::i()->sortBy ) and in_array( \IPS\Request::i()->sortBy, array( 'title' ) ) )
{
$sortBy = $itemClass::$databasePrefix . $itemClass::$databaseColumnMap[ \IPS\Request::i()->sortBy ];
}
else
{
$sortBy = $class::$databasePrefix . $class::$databaseColumnId;
}
$sortDir = ( isset( \IPS\Request::i()->sortDir ) and in_array( mb_strtolower( \IPS\Request::i()->sortDir ), array( 'asc', 'desc' ) ) ) ? \IPS\Request::i()->sortDir : 'asc';
/* Return */
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', $class::$databaseTable, $where, "{$sortBy} {$sortDir}" )->join( $itemClass::$databaseTable, $itemClass::$databasePrefix . $itemClass::$databaseColumnId . '=' . $class::$databasePrefix . $class::$databaseColumnMap['item'] ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
$class,
\IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->join( $itemClass::$databaseTable, $itemClass::$databasePrefix . $itemClass::$databaseColumnId . '=' . $class::$databasePrefix . $class::$databaseColumnMap['item'] )->first()
);
}
/**
* Create
*
* @param \IPS\Content\Item $item Content Item
* @param \IPS\Member $author Author
* @param string $contentParam The parameter that contains the content body
* @return \IPS\Api\Response
*/
protected function _create( \IPS\Content\Item $item, \IPS\Member $author, $contentParam='content' )
{
return new \IPS\Api\Response( 201, $this->_createComment( $item, $author, $contentParam )->apiOutput() );
}
/**
* Create
*
* @param \IPS\Content\Item $item Content Item
* @param \IPS\Member $author Author
* @param string $contentParam The parameter that contains the content body
* @return \IPS\Api\Response
*/
protected function _createComment( \IPS\Content\Item $item, \IPS\Member $author, $contentParam='content' )
{
/* Work out the date */
$date = \IPS\Request::i()->date ? new \IPS\DateTime( \IPS\Request::i()->date ) : \IPS\DateTime::create();
/* Create post */
$class = $this->class;
if ( in_array( 'IPS\Content\Review', class_parents( $class ) ) )
{
$comment = $class::create( $item, \IPS\Request::i()->$contentParam, FALSE, intval( \IPS\Request::i()->rating ), $author->member_id ? NULL : $author->real_name, $author, $date, \IPS\Request::i()->ip_address ?: \IPS\Request::i()->ipAddress(), isset( \IPS\Request::i()->hidden ) ? \IPS\Request::i()->hidden : NULL );
}
else
{
$comment = $class::create( $item, \IPS\Request::i()->$contentParam, FALSE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, \IPS\Request::i()->ip_address ?: \IPS\Request::i()->ipAddress(), isset( \IPS\Request::i()->hidden ) ? \IPS\Request::i()->hidden : NULL );
}
/* Index */
if ( $item instanceof \IPS\Content\Searchable )
{
if ( $item::$firstCommentRequired and !$comment->isFirst() )
{
if ( in_array( 'IPS\Content\Searchable', class_implements( $class ) ) )
{
\IPS\Content\Search\Index::i()->index( $item->firstComment() );
}
}
else
{
\IPS\Content\Search\Index::i()->index( $item );
}
}
if ( $comment instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->index( $comment );
}
/* Return */
return $comment;
}
/**
* Edit
*
* @param \IPS\Content\Comment $comment The comment
* @param string $contentParam The parameter that contains the content body
* @throws InvalidArgumentException Invalid author
* @return \IPS\Api\Response
*/
protected function _edit( $comment, $contentParam='content' )
{
/* Hidden */
if ( isset( \IPS\Request::i()->hidden ) )
{
if ( \IPS\Request::i()->hidden )
{
$comment->hide( FALSE );
}
else
{
$comment->unhide( FALSE );
}
}
/* Change author */
if ( isset( \IPS\Request::i()->author ) )
{
$authorIdColumn = $comment::$databaseColumnMap['author'];
$authorNameColumn = $comment::$databaseColumnMap['author_name'];
/* Just renaming the guest */
if ( !$comment->$authorIdColumn and ( !isset( \IPS\Request::i()->author ) or !\IPS\Request::i()->author ) and isset( \IPS\Request::i()->author_name ) )
{
$comment->$authorNameColumn = \IPS\Request::i()->author_name;
}
/* Actually changing the author */
else
{
try
{
$member = \IPS\Member::load( \IPS\Request::i()->author );
if ( !$member->member_id )
{
throw new \InvalidArgumentException;
}
$comment->changeAuthor( $member );
}
catch ( \OutOfRangeException $e )
{
throw new \InvalidArgumentException;
}
}
}
/* Post value */
if ( isset( \IPS\Request::i()->$contentParam ) )
{
$contentColumn = $comment::$databaseColumnMap['content'];
$comment->$contentColumn = \IPS\Request::i()->$contentParam;
}
/* Rating */
$ratingChanged = FALSE;
if ( isset( \IPS\Request::i()->rating ) )
{
$ratingChanged = TRUE;
$ratingColumn = $comment::$databaseColumnMap['rating'];
$comment->$ratingColumn = intval( \IPS\Request::i()->rating );
}
/* Save and return */
$comment->save();
/* Recalculate ratings */
if ( $ratingChanged )
{
$itemClass = $comment::$itemClass;
$ratingField = $itemClass::$databaseColumnMap['rating'];
$comment->item()->$ratingField = $comment->item()->averageReviewRating() ?: 0;
$comment->item()->save();
}
/* Return */
return new \IPS\Api\Response( 200, $comment->apiOutput() );
}
}
+413
View File
@@ -0,0 +1,413 @@
<?php
/**
* @brief Base API endpoint for Content Items
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 8 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Content\Api;
/* 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;
}
/**
* @brief Base API endpoint for Content Items
*/
class _ItemController extends \IPS\Api\Controller
{
/**
* List
*
* @param array $where Extra WHERE clause
* @return \IPS\Api\PaginatedResponse
*/
protected function _list( $where = array(), $containerParam = 'categories' )
{
$class = $this->class;
/* Containers */
if ( isset( \IPS\Request::i()->$containerParam ) )
{
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], array_map( 'intval', array_filter( explode( ',', \IPS\Request::i()->$containerParam ) ) ) ) );
}
/* Authors */
if ( isset( \IPS\Request::i()->authors ) )
{
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['author'], array_map( 'intval', array_filter( explode( ',', \IPS\Request::i()->authors ) ) ) ) );
}
/* Pinned? */
if ( isset( \IPS\Request::i()->pinned ) AND in_array( 'IPS\Content\Pinnable', class_implements( $class ) ) )
{
if ( \IPS\Request::i()->pinned )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['pinned'] . "=1" );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['pinned'] . "=0" );
}
}
/* Featured? */
if ( isset( \IPS\Request::i()->featured ) AND in_array( 'IPS\Content\Featurable', class_implements( $class ) ) )
{
if ( \IPS\Request::i()->featured )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['featured'] . "=1" );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['featured'] . "=0" );
}
}
/* Locked? */
if ( isset( \IPS\Request::i()->locked ) AND in_array( 'IPS\Content\Lockable', class_implements( $class ) ) )
{
if ( isset( $class::$databaseColumnMap['locked'] ) )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['locked'] . '=?', intval( \IPS\Request::i()->locked ) );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['state'] . '=?', \IPS\Request::i()->locked ? 'closed' : 'open' );
}
}
/* Hidden */
if ( isset( \IPS\Request::i()->hidden ) AND in_array( 'IPS\Content\Hideable', class_implements( $class ) ) )
{
if ( \IPS\Request::i()->hidden )
{
if ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '<>0' );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '<>1' );
}
}
else
{
if ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '=0' );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=1' );
}
}
}
/* Has poll? */
if ( isset( \IPS\Request::i()->hasPoll ) AND in_array( 'IPS\Content\Polls', class_implements( $class ) ) )
{
if ( \IPS\Request::i()->hasPoll )
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['poll'] . ">0" );
}
else
{
$where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['poll'] . "=0" );
}
}
/* Sort */
if ( isset( \IPS\Request::i()->sortBy ) and in_array( \IPS\Request::i()->sortBy, array( 'date', 'title' ) ) )
{
$sortBy = $class::$databasePrefix . $class::$databaseColumnMap[ \IPS\Request::i()->sortBy ];
}
else
{
$sortBy = $class::$databasePrefix . $class::$databaseColumnId;
}
$sortDir = ( isset( \IPS\Request::i()->sortDir ) and in_array( mb_strtolower( \IPS\Request::i()->sortDir ), array( 'asc', 'desc' ) ) ) ? \IPS\Request::i()->sortDir : 'asc';
/* Return */
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', $class::$databaseTable, $where, "{$sortBy} {$sortDir}" ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
$class,
\IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first()
);
}
/**
* View
*
* @param int $id ID Number
* @return \IPS\Api\Response
*/
protected function _view( $id )
{
$class = $this->class;
return new \IPS\Api\Response( 200, $class::load( $id )->apiOutput() );
}
/**
* Create or update item
*
* @param \IPS\Content\Item $item The item
* @return \IPS\Content\Item
*/
protected function _createOrUpdate( \IPS\Content\Item $item )
{
/* Title */
if ( isset( \IPS\Request::i()->title ) and isset( $item::$databaseColumnMap['title'] ) )
{
$titleColumn = $item::$databaseColumnMap['title'];
$item->$titleColumn = \IPS\Request::i()->title;
}
/* Tags */
if ( ( isset( \IPS\Request::i()->prefix ) or isset( \IPS\Request::i()->tags ) ) and in_array( 'IPS\Content\Tags', class_implements( get_class( $item ) ) ) )
{
$tags = isset( \IPS\Request::i()->tags ) ? array_filter( explode( ',', \IPS\Request::i()->tags ) ) : $item->tags();
if ( isset( \IPS\Request::i()->prefix ) )
{
if ( \IPS\Request::i()->prefix )
{
$tags['prefix'] = \IPS\Request::i()->prefix;
}
}
elseif ( $existingPrefix = $item->prefix() )
{
$tags['prefix'] = $existingPrefix;
}
/* we need to save the item before we set the tags because setTags requires that the item exists */
$idColumn = $item::$databaseColumnId;
if ( !$item->$idColumn )
{
$item->save();
}
$item->setTags( $tags );
}
/* Open/closed */
if ( isset( \IPS\Request::i()->locked ) and in_array( 'IPS\Content\Lockable', class_implements( get_class( $item ) ) ) )
{
if ( isset( $item::$databaseColumnMap['locked'] ) )
{
$lockedColumn = $item::$databaseColumnMap['locked'];
$item->$lockedColumn = intval( \IPS\Request::i()->locked );
}
else
{
$stateColumn = $item::$databaseColumnMap['status'];
$item->$stateColumn = \IPS\Request::i()->locked ? 'closed' : 'open';
}
}
/* Hidden */
if ( isset( \IPS\Request::i()->hidden ) and in_array( 'IPS\Content\Hideable', class_implements( get_class( $item ) ) ) )
{
$idColumn = $item::$databaseColumnId;
if ( \IPS\Request::i()->hidden )
{
if ( $item->$idColumn )
{
$item->hide( FALSE );
}
else
{
if ( isset( $item::$databaseColumnMap['hidden'] ) )
{
$hiddenColumn = $item::$databaseColumnMap['hidden'];
$item->$hiddenColumn = \IPS\Request::i()->hidden;
}
else
{
$approvedColumn = $item::$databaseColumnMap['approved'];
$item->$approvedColumn = ( \IPS\Request::i()->hidden == -1 ) ? -1 : 0;
}
}
}
else
{
if ( $item->$idColumn )
{
$item->unhide( FALSE );
}
else
{
if ( isset( $item::$databaseColumnMap['hidden'] ) )
{
$hiddenColumn = $item::$databaseColumnMap['hidden'];
$item->$hiddenColumn = 0;
}
else
{
$approvedColumn = $item::$databaseColumnMap['approved'];
$item->$approvedColumn = 1;
}
}
}
}
/* Pinned */
if ( isset( \IPS\Request::i()->pinned ) and in_array( 'IPS\Content\Pinnable', class_implements( get_class( $item ) ) ) )
{
$pinnedColumn = $item::$databaseColumnMap['pinned'];
$item->$pinnedColumn = intval( \IPS\Request::i()->pinned );
}
/* Featured */
if ( isset( \IPS\Request::i()->featured ) and in_array( 'IPS\Content\Featurable', class_implements( get_class( $item ) ) ) )
{
$featuredColumn = $item::$databaseColumnMap['featured'];
$item->$featuredColumn = intval( \IPS\Request::i()->featured );
}
/* Update first comment if required */
$field = $item::$databaseColumnMap['first_comment_id'];
$commentClass = $item::$commentClass;
$contentField = $commentClass::$databaseColumnMap['content'];
if ( $item::$firstCommentRequired AND isset( $item->$field ) AND isset( \IPS\Request::i()->$contentField ) )
{
$comment = $commentClass::load( $item->$field );
$comment->$contentField = \IPS\Request::i()->$contentField;
$comment->save();
/* Update Search Index of the first item */
if ( $item instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->index( $comment );
}
}
/* Return */
return $item;
}
/**
* Create
*
* @param \IPS\Node\Model $container Container
* @param \IPS\Member $author Author
* @param string $firstPostParam The parameter which contains the body for the first comment
* @return \IPS\Content\Item
*/
protected function _create( \IPS\Node\Model $container = NULL, \IPS\Member $author, $firstPostParam = 'post' )
{
$class = $this->class;
/* Work out the date */
$date = \IPS\Request::i()->date ? new \IPS\DateTime( \IPS\Request::i()->date ) : \IPS\DateTime::create();
/* Create item */
$item = $class::createItem( $author, \IPS\Request::i()->ip_address ?: \IPS\Request::i()->ipAddress(), $date, $container );
$this->_createOrUpdate( $item );
$item->save();
/* Create post */
if ( $class::$firstCommentRequired )
{
$commentClass = $item::$commentClass;
$post = $commentClass::create( $item, \IPS\Request::i()->$firstPostParam, TRUE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date );
if ( isset( $class::$databaseColumnMap['first_comment_id'] ) )
{
$firstCommentColumn = $class::$databaseColumnMap['first_comment_id'];
$commentIdColumn = $commentClass::$databaseColumnId;
$item->$firstCommentColumn = $post->$commentIdColumn;
$item->save();
}
}
/* Index */
if ( $item instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->index( $item );
}
/* Send notifications */
if ( !$item->hidden() )
{
$item->sendNotifications();
}
elseif( $item->hidden() !== -1 )
{
$item->sendUnapprovedNotification();
}
/* Output */
return $item;
}
/**
* View Comments or Reviews
*
* @param int $id ID Number
* @param string $commentClass The class
* @param array $where Base where clause
* @return \IPS\Api\PaginatedResponse
*/
protected function _comments( $id, $commentClass, $where = array() )
{
/* Init */
$itemClass = $this->class;
$item = $itemClass::load( $id );
$itemIdColumn = $itemClass::$databaseColumnId;
$where [] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=?', $item->$itemIdColumn );
/* Hidden? */
if ( isset( \IPS\Request::i()->hidden ) AND in_array( 'IPS\Content\Hideable', class_implements( $commentClass ) ) )
{
if ( \IPS\Request::i()->hidden )
{
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'] . '<>0' );
}
else
{
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'] . '<>1' );
}
}
else
{
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
{
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'] . '=0' );
}
else
{
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'] . '=1' );
}
}
}
if ( $commentClass::commentWhere() !== NULL )
{
$where[] = $commentClass::commentWhere();
}
/* Sort */
$sortBy = $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['date'];
$sortDir = ( isset( \IPS\Request::i()->sortDir ) and in_array( mb_strtolower( \IPS\Request::i()->sortDir ), array( 'asc', 'desc' ) ) ) ? \IPS\Request::i()->sortDir : 'asc';
return new \IPS\Api\PaginatedResponse(
200,
\IPS\Db::i()->select( '*', $commentClass::$databaseTable, $where, "{$sortBy} {$sortDir}" ),
isset( \IPS\Request::i()->page ) ? \IPS\Request::i()->page : 1,
$commentClass,
\IPS\Db::i()->select( 'COUNT(*)', $commentClass::$databaseTable, $where )->first()
);
}
}
+299 -175
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Comment Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
@@ -53,9 +53,11 @@ abstract class _Comment extends \IPS\Content
* @param bool|NULL $incrementPostCount Increment post count? If NULL, will use static::incrementPostCount()
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @return static
*/
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL )
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
{
if ( $member === NULL )
{
@@ -93,7 +95,7 @@ abstract class _Comment extends \IPS\Content
break;
case 'ip_address':
$val = \IPS\Request::i()->ipAddress();
$val = $ipAddress ?: \IPS\Request::i()->ipAddress();
break;
case 'first':
@@ -101,43 +103,62 @@ abstract class _Comment extends \IPS\Content
break;
case 'approved':
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
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 = $item->moderateNewReviews( $member ) ? 0 : 1;
$val = TRUE;
}
elseif ( $hiddenStatus === NULL )
{
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
$val = $item->moderateNewReviews( $member ) ? 0 : 1;
}
else
{
$val = $item->moderateNewComments( $member ) ? 0 : 1;
}
}
else
{
$val = $item->moderateNewComments( $member ) ? 0 : 1;
}
/* 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 ( $first )
{
$val = TRUE;
switch ( $hiddenStatus )
{
case 0:
$val = 1;
break;
case 1:
$val = 0;
break;
case -1:
$val = -1;
break;
}
}
break;
case 'hidden':
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
$val = $item->moderateNewReviews( $member ) ? 1 : 0;
}
else
{
$val = $item->moderateNewComments( $member ) ? 1 : 0;
}
/* 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 ( $first )
{
$val = FALSE;
$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
}
if ( ! $first and $item->approvedButHidden() )
elseif ( $item->approvedButHidden() )
{
$val = 2;
}
elseif ( $hiddenStatus === NULL )
{
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
$val = $item->moderateNewReviews( $member ) ? 1 : 0;
}
else
{
$val = $item->moderateNewComments( $member ) ? 1 : 0;
}
}
else
{
$val = $hiddenStatus;
}
break;
}
@@ -149,6 +170,35 @@ abstract class _Comment extends \IPS\Content
}
$obj->save();
/* Pass this through our profanity filters to see if it needs to be mod queued */
try
{
$hiddenByFilter = FALSE;
if ( !$obj->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$obj->isFirst() ) ) AND !$member->group['g_bypass_badwords'] )
{
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $comment );
}
}
catch( \BadMethodCallException $e ) { }
if ( $hiddenByFilter === TRUE )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
/* 'approved' is easy, clear and concise */
$column = static::$databaseColumnMap['approved'];
$obj->$column = 0;
}
else if ( isset( static::$databaseColumnMap['hidden'] ) )
{
/* 'hidden' is backwards */
$column = static::$databaseColumnMap['hidden'];
$obj->$column = 1;
}
$obj->save();
}
/* Increment post count */
try
{
@@ -197,13 +247,16 @@ abstract class _Comment extends \IPS\Content
}
/* Send notifications */
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
if ( !in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
$obj->sendNotifications();
}
else if( $obj->hidden() === 1 )
{
$obj->sendUnapprovedNotification();
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
{
$obj->sendNotifications();
}
else if( $obj->hidden() === 1 )
{
$obj->sendUnapprovedNotification();
}
}
/* Update item */
@@ -327,66 +380,20 @@ abstract class _Comment extends \IPS\Content
}
}
/**
* @brief Cached URLs
*/
protected $_url = array();
/**
* Get URL
*
* @param string|NULL $action Action
* @return \IPS\Http\Url
*/
public function url( $action=NULL )
public function url( $action='find' )
{
$_key = md5( $action );
if( !isset( $this->_url[ $_key ] ) )
{
$itemClass = static::$itemClass;
$itemField = static::$databaseColumnMap['item'];
$idColumn = static::$databaseColumnId;
$this->_url[ $_key ] = $this->item()->url();
if( $action )
{
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( array( 'do' => $action . 'Comment', 'comment' => $this->$idColumn ) );
}
else
{
$where = array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=? AND ' . static::$databasePrefix . static::$databaseColumnId . '<=?', $this->$itemField, $this->$idColumn ) );
if ( static::commentWhere() !== NULL )
{
$where[] = static::commentWhere();
}
if ( static::modPermission( 'view_hidden' ) === FALSE )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 1 );
}
elseif( isset( static::$databaseColumnMap['hidden'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 0 );
}
}
$commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', static::$databaseTable, $where, static::$databasePrefix . static::$databaseColumnMap['date'] . ' asc' )->first();
$page = ceil( $commentPosition / $itemClass::getCommentsPerPage() );
if ( $page != 1 )
{
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( 'page', $page );
}
$this->_url[ $_key ] = $this->_url[ $_key ]->setFragment( 'comment-' . $this->$idColumn );
}
}
return $this->_url[ $_key ];
$idColumn = static::$databaseColumnId;
return $this->item()->url()->setQueryString( array(
'do' => $action . 'Comment',
'comment' => $this->$idColumn
) );
}
/**
@@ -703,13 +710,14 @@ abstract class _Comment extends \IPS\Content
}
/**
* Users to receieve immediate notifications
* Users to receive immediate notifications
*
* @param int|array $limit LIMIT clause
* @param string|NULL $extra Additional data
* @param int|array $limit LIMIT clause
* @param string|NULL $extra Additional data
* @param boolean $countOnly Just return the count
* @return \IPS\Db\Select
*/
public function notificationRecipients( $limit=array( 0, 25 ), $extra=NULL )
public function notificationRecipients( $limit=array( 0, 25 ), $extra=NULL, $countOnly=FALSE )
{
$memberFollowers = $this->author()->followers( 3, array( 'immediate' ), $this->mapped('date'), NULL, NULL, NULL );
@@ -720,11 +728,32 @@ abstract class _Comment extends \IPS\Content
$memberFollowers
);
return \IPS\Db::i()->union( $unions, 'follow_added', $limit, 'follow_member_id, follow_id, follow_app, follow_area, follow_rel_id, follow_added, follow_is_anon, follow_notify_do, follow_notify_meta, follow_notify_freq, follow_notify_sent, follow_visible', FALSE, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
if ( $countOnly )
{
$return = 0;
foreach ( $unions as $query )
{
$return += $query->count();
}
return $return;
}
else
{
return \IPS\Db::i()->union( $unions, 'follow_added', $limit, NULL, FALSE, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
}
}
else
{
return $this->item()->followers( static::FOLLOW_PUBLIC + static::FOLLOW_ANONYMOUS, array( 'immediate' ), $this->mapped('date'), $limit, 'follow_added', \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
$query = $this->item()->followers( static::FOLLOW_PUBLIC + static::FOLLOW_ANONYMOUS, array( 'immediate' ), $this->mapped('date'), $limit, 'follow_added', \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
if ( $countOnly )
{
return $query->count();
}
else
{
return $query;
}
}
}
@@ -742,9 +771,10 @@ abstract class _Comment extends \IPS\Content
/**
* Syncing to run when hiding
*
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onHide()
public function onHide( $member )
{
$item = $this->item();
@@ -761,8 +791,8 @@ abstract class _Comment extends \IPS\Content
{
if ( $item->container()->_comments !== NULL )
{
$item->container()->_comments = $item->container()->_comments - 1;
$item->container()->setLastComment();
$item->container()->resetCommentCounts();
$item->container()->save();
}
} catch ( \BadMethodCallException $e ) {}
@@ -771,36 +801,29 @@ abstract class _Comment extends \IPS\Content
/**
* Syncing to run when unhiding
*
* @param bool $approving If true, is being approved for the first time
* @param bool $approving If true, is being approved for the first time
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onUnhide( $approving )
public function onUnhide( $approving, $member )
{
$item = $this->item();
if ( $approving )
{
try
/* We should only do this if it is an actual account, and not a guest. */
if ( $this->author()->member_id )
{
if ( $item->container()->_unapprovedComments !== NULL )
try
{
if ( $item->container()->_unapprovedComments > 0 )
if ( static::incrementPostCount( $item->container() ) )
{
$item->container()->_unapprovedComments = $item->container()->_unapprovedComments - 1;
$item->container()->save();
$this->author()->member_posts++;
$this->author()->save();
}
}
} catch ( \BadMethodCallException $e ) {}
try
{
if ( static::incrementPostCount( $item->container() ) )
{
$this->author()->member_posts++;
$this->author()->save();
}
catch( \BadMethodCallException $e ) { }
}
catch( \BadMethodCallException $e ) { }
}
@@ -813,8 +836,8 @@ abstract class _Comment extends \IPS\Content
{
if ( $item->container()->_comments !== NULL )
{
$item->container()->_comments = $item->container()->_comments + 1;
$item->container()->setLastComment();
$item->container()->resetCommentCounts();
$item->container()->save();
}
} catch ( \BadMethodCallException $e ) {}
@@ -832,9 +855,22 @@ abstract class _Comment extends \IPS\Content
$idColumn = $item::$databaseColumnId;
$itemColumn = static::$databaseColumnMap['item'];
$commentIdColumn = static::$databaseColumnId;
$this->$itemColumn = $item->$idColumn;
$this->save();
/* The new item needs to re-claim any attachments associated with this comment */
\IPS\Db::i()->update( 'core_attachments_map', array( 'id1' => $item->$idColumn ), array( "location_key=? AND id1=? AND id2=?", $oldItem::$application . '_' . ucfirst( $oldItem::$module ), $oldItem->$idColumn, $this->$commentIdColumn ) );
/* Update notifications */
\IPS\Db::i()->update( 'core_notifications', array( 'item_id' => $item->$idColumn ), array( 'item_class=? and item_id=? and item_sub_class=? and item_sub_id=?', (string) get_class( $item ), $oldItem->$idColumn, (string) get_called_class(), $this->$commentIdColumn ) );
/* Update reputation */
if ( $this instanceof \IPS\Content\Reputation )
{
\IPS\Db::i()->update( 'core_reputation_index', array( 'item_id' => $item->$idColumn ), array( 'class_type_id_hash=?', md5( get_class( $this ) . ':' . $oldItem->$idColumn ) ) );
}
$oldItem->rebuildFirstAndLastCommentData();
$item->rebuildFirstAndLastCommentData();
@@ -873,13 +909,31 @@ abstract class _Comment extends \IPS\Content
*/
public function delete()
{
/* Remove from search index first */
if ( $this instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->removeFromSearchIndex( $this );
}
/* Init */
$idColumn = static::$databaseColumnId;
$itemClass = static::$itemClass;
$itemIdColumn = $itemClass::$databaseColumnId;
/* It is possible to delete a comment that is orphaned, so let's try to protect against that */
try
{
$item = $this->item();
$itemId = $this->item()->$itemIdColumn;
}
catch( \OutOfRangeException $e )
{
$item = NULL;
$itemId = $this->mapped('item');
}
/* Unclaim attachments */
\IPS\File::unclaimAttachments( $itemClass::$application . '_' . ucfirst( $itemClass::$module ), $this->item()->$itemIdColumn, $this->$idColumn );
\IPS\File::unclaimAttachments( $itemClass::$application . '_' . ucfirst( $itemClass::$module ), $itemId, $this->$idColumn );
/* Reduce the number of comment/reviews count on the item but only if the item is unapproved or visible
* - hidden as opposed to unapproved items do not get included in either of the unapproved_comments/num_comments columns */
@@ -890,14 +944,14 @@ abstract class _Comment extends \IPS\Content
{
$columnName = ( $this->hidden() === 1 ) ? 'unapproved_reviews' : 'num_reviews';
}
if ( isset( $itemClass::$databaseColumnMap[$columnName] ) )
if ( isset( $itemClass::$databaseColumnMap[$columnName] ) AND $item !== NULL )
{
$column = $itemClass::$databaseColumnMap[$columnName];
if ( $this->item()->$column > 0 )
if ( $item->$column > 0 )
{
$this->item()->$column--;
$this->item()->save();
$item->$column--;
$item->save();
}
}
}
@@ -905,27 +959,27 @@ abstract class _Comment extends \IPS\Content
{
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
if( isset( $itemClass::$databaseColumnMap['hidden_reviews'] ) )
if( isset( $itemClass::$databaseColumnMap['hidden_reviews'] ) AND $item !== NULL )
{
$column = $itemClass::$databaseColumnMap['hidden_reviews'];
if ( $this->item()->$column > 0 )
if ( $item->$column > 0 )
{
$this->item()->$column--;
$this->item()->save();
$item->$column--;
$item->save();
}
}
}
else
{
if( isset( $itemClass::$databaseColumnMap['hidden_comments'] ) )
if( isset( $itemClass::$databaseColumnMap['hidden_comments'] ) AND $item !== NULL )
{
$column = $itemClass::$databaseColumnMap['hidden_comments'];
if ( $this->item()->$column > 0 )
if ( $item->$column > 0 )
{
$this->item()->$column--;
$this->item()->save();
$item->$column--;
$item->save();
}
}
}
@@ -934,7 +988,7 @@ abstract class _Comment extends \IPS\Content
/* Delete any notifications telling people about this */
$memberIds = array();
foreach( \IPS\DB::i()->select( 'member', 'core_notifications', array( 'item_sub_class=? AND item_sub_id=?', (string) get_called_class(), (int) $this->$idColumn ) ) as $member )
foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'item_sub_class=? AND item_sub_id=?', (string) get_called_class(), (int) $this->$idColumn ) ) as $member )
{
$memberIds[ $member ] = $member;
}
@@ -949,48 +1003,42 @@ abstract class _Comment extends \IPS\Content
/* Actually delete */
parent::delete();
/* Remove from search index */
if ( $this instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->removeFromSearchIndex( $this );
}
/* Update last comment/review data for container and item */
try
{
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
if ( $item !== NULL AND in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
{
if ( $this->item()->container()->_reviews !== NULL )
if ( $item->container()->_reviews !== NULL )
{
$this->item()->container()->_reviews = ( $this->item()->container()->_reviews - 1 );
$this->item()->resyncLastReview();
$this->item()->save();
$this->item()->container()->setLastReview();
$item->container()->_reviews = ( $item->container()->_reviews - 1 );
$item->resyncLastReview();
$item->save();
$item->container()->setLastReview();
}
if ( $this->item()->container()->_unapprovedReviews !== NULL )
if ( $item->container()->_unapprovedReviews !== NULL )
{
$this->item()->container()->_unapprovedReviews = ( $this->item()->container()->_unapprovedReviews > 0 ) ? ( $this->item()->container()->_unapprovedReviews - 1 ) : 0;
$item->container()->_unapprovedReviews = ( $item->container()->_unapprovedReviews > 0 ) ? ( $item->container()->_unapprovedReviews - 1 ) : 0;
}
$this->item()->container()->save();
$item->container()->save();
}
else if ( $this->item()->container() !== NULL )
else if ( $item !== NULL AND $item->container() !== NULL )
{
if ( $this->item()->container()->_comments !== NULL )
if ( $item->container()->_comments !== NULL )
{
if ( !$this->hidden() )
{
$this->item()->container()->_comments = ( $this->item()->container()->_comments > 0 ) ? ( $this->item()->container()->_comments - 1 ) : 0;
$item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
}
$this->item()->resyncLastComment();
$this->item()->save();
$this->item()->container()->setLastComment();
$item->resyncLastComment();
$item->save();
$item->container()->setLastComment();
}
if ( $this->item()->container()->_unapprovedComments !== NULL and $this->hidden() === 1 )
if ( $item->container()->_unapprovedComments !== NULL and $this->hidden() === 1 )
{
$this->item()->container()->_unapprovedComments = ( $this->item()->container()->_unapprovedComments > 0 ) ? ( $this->item()->container()->_unapprovedComments - 1 ) : 0;
$item->container()->_unapprovedComments = ( $item->container()->_unapprovedComments > 0 ) ? ( $item->container()->_unapprovedComments - 1 ) : 0;
}
$this->item()->container()->save();
$item->container()->save();
}
}
catch ( \BadMethodCallException $e ) {}
@@ -1035,6 +1083,12 @@ abstract class _Comment extends \IPS\Content
$container->setLastReview();
$container->save();
}
/* Update search index */
if ( $this instanceof \IPS\Content\Searchable )
{
\IPS\Content\Search\Index::i()->index( $this );
}
}
/**
@@ -1064,17 +1118,17 @@ abstract class _Comment extends \IPS\Content
* @param string $order MySQL ORDER BY clause (NULL to order by date)
* @param int|array $limit Limit clause
* @param string|NULL $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index, or NULL to ignore permissions
* @param bool|NULL $includeHiddenComments Include hidden comments? Boolean or NULL to detect if currently logged member has permission
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @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.
* @param mixed $includeHiddenComments Include hidden comments? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
* @param int $queryFlags Select bitwise flags
* @param \IPS\Member $member The member (NULL to use currently logged in member)
* @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data)
* @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data)
* @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data)
* @param bool $countOnly If true will return the count
* @param array|null $joins Additional arbitrary joins for the query
* @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 static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenComments=NULL, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenComments=\IPS\Content\Hideable::FILTER_AUTOMATIC, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
{
/* Get the item class - we need it later */
$itemClass = static::$itemClass;
@@ -1106,9 +1160,36 @@ abstract class _Comment extends \IPS\Content
$order = $order ?: ( static::$databasePrefix . static::$databaseColumnMap['date'] . ' DESC' );
/* Exclude hidden comments */
$includeHiddenComments = ( $includeHiddenComments === NULL ) ? static::modPermission( 'view_hidden' ) : $includeHiddenComments;
if( $includeHiddenComments === \IPS\Content\Hideable::FILTER_AUTOMATIC )
{
if( static::modPermission( 'view_hidden', $member ) )
{
$includeHiddenComments = \IPS\Content\Hideable::FILTER_SHOW_HIDDEN;
}
else
{
$includeHiddenComments = \IPS\Content\Hideable::FILTER_OWN_HIDDEN;
}
}
if ( in_array( 'IPS\Content\Hideable', class_implements( get_called_class() ) ) and !$includeHiddenComments )
if ( in_array( 'IPS\Content\Hideable', class_implements( get_called_class() ) ) and $includeHiddenComments === \IPS\Content\Hideable::FILTER_ONLY_HIDDEN )
{
/* If we can't view hidden stuff, just return an empty array now */
if( !static::modPermission( 'view_hidden', $member ) )
{
return array();
}
if ( isset( static::$databaseColumnMap['approved'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 0 );
}
elseif ( isset( static::$databaseColumnMap['hidden'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 1 );
}
}
elseif ( in_array( 'IPS\Content\Hideable', class_implements( get_called_class() ) ) and ( $includeHiddenComments === \IPS\Content\Hideable::FILTER_OWN_HIDDEN OR $includeHiddenComments === \IPS\Content\Hideable::FILTER_PUBLIC_ONLY ) )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
@@ -1120,16 +1201,17 @@ abstract class _Comment extends \IPS\Content
}
}
/* Exclude hidden items */
if ( in_array( 'IPS\Content\Hideable', class_implements( $itemClass ) ) and !$includeHiddenComments )
/* Exclude hidden items. We don't check FILTER_ONLY_HIDDEN because we should return hidden comments in both approved and unapproved topics. */
if ( in_array( 'IPS\Content\Hideable', class_implements( $itemClass ) ) and ( $includeHiddenComments === \IPS\Content\Hideable::FILTER_OWN_HIDDEN OR $includeHiddenComments === \IPS\Content\Hideable::FILTER_PUBLIC_ONLY ) )
{
$member = $member ?: \IPS\Member::loggedIn();
$authorCol = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'];
if ( isset( $itemClass::$databaseColumnMap['approved'] ) )
{
$col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['approved'];
if ( \IPS\Member::loggedIn()->member_id )
if ( $member->member_id AND $includeHiddenComments !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
{
$itemWhere[] = array( "( {$col}=1 OR ( {$col}=0 AND {$authorCol}=" . \IPS\Member::loggedIn()->member_id . " ) )" );
$itemWhere[] = array( "( {$col}=1 OR ( {$col}=0 AND {$authorCol}=" . $member->member_id . " ) )" );
}
else
{
@@ -1139,9 +1221,9 @@ abstract class _Comment extends \IPS\Content
elseif ( isset( $itemClass::$databaseColumnMap['hidden'] ) )
{
$col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['hidden'];
if ( \IPS\Member::loggedIn()->member_id )
if ( $member->member_id AND $includeHiddenComments !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
{
$itemWhere[] = array( "( {$col}=0 OR ( {$col}=1 AND {$authorCol}=" . \IPS\Member::loggedIn()->member_id . " ) )" );
$itemWhere[] = array( "( {$col}=0 OR ( {$col}=1 AND {$authorCol}=" . $member->member_id . " ) )" );
}
else
{
@@ -1169,7 +1251,7 @@ abstract class _Comment extends \IPS\Content
}
}
if ( $joinContainer )
if ( $joinContainer AND isset( $itemClass::$containerNodeClass ) )
{
$containerClass = $itemClass::$containerNodeClass;
$joins[] = array(
@@ -1181,14 +1263,27 @@ abstract class _Comment extends \IPS\Content
/* Build the select clause */
if( $countOnly )
{
$select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, NULL, NULL, $queryFlags )
->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' );
if ( in_array( 'IPS\Content\Permissions', class_implements( $itemClass ) ) AND $permissionKey !== NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
$containerClass = $itemClass::$containerNodeClass;
$select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, NULL, NULL, $queryFlags )
->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' )
->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . ' AND (' . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->groups ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . '=? )', $containerClass::$permApp, $containerClass::$permType, '*' ), 'STRAIGHT_JOIN' );
}
else
{
$select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, NULL, NULL, $queryFlags )
->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' );
}
if ( count( $joins ) )
{
foreach( $joins as $join )
{
$select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), 'LEFT' );
$select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), ( isset( $join['type'] ) ) ? $join['type'] : 'LEFT' );
}
}
return $select->first();
@@ -1208,13 +1303,15 @@ abstract class _Comment extends \IPS\Content
if ( in_array( 'IPS\Content\Permissions', class_implements( $itemClass ) ) AND $permissionKey !== NULL )
{
$member = $member ?: \IPS\Member::loggedIn();
$containerClass = $itemClass::$containerNodeClass;
$selectClause .= ', ' . $itemClass::$databaseTable . '.*';
$select = \IPS\Db::i()->select( $selectClause, static::$databaseTable, $where, $order, $limit, NULL, NULL, $queryFlags )
->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' )
->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . ' AND (' . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], \IPS\Member::loggedIn()->groups ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . '=? )', $containerClass::$permApp, $containerClass::$permType, '*' ), 'STRAIGHT_JOIN' );
->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . ' AND (' . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->groups ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . '=? )', $containerClass::$permApp, $containerClass::$permType, '*' ), 'STRAIGHT_JOIN' );
}
else
{
@@ -1226,7 +1323,7 @@ abstract class _Comment extends \IPS\Content
{
foreach( $joins as $join )
{
$select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), 'LEFT' );
$select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), ( isset( $join['type'] ) ) ? $join['type'] : 'LEFT' );
}
}
@@ -1294,7 +1391,7 @@ abstract class _Comment extends \IPS\Content
$idColumn = static::$databaseColumnId;
$shareUrl = $this->url( 'find' )->setQueryString( 'comment', $this->$idColumn );
$this->sharelinks = \IPS\core\ShareLinks\Service::getAllServices( $shareUrl, $this->item()->mapped('title') );
$this->sharelinks = \IPS\core\ShareLinks\Service::getAllServices( $shareUrl, $this->item()->mapped('title'), NULL, $this->item() );
}
}
@@ -1311,6 +1408,33 @@ abstract class _Comment extends \IPS\Content
return NULL;
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse int item_id The ID number of the item this belongs to
* @apiresponse \IPS\Member author Author
* @apiresponse datetime date Date
* @apiresponse string content The content
* @apiresponse bool hidden Is hidden?
* @apiresponse string url URL to content
*/
public function apiOutput()
{
$idColumn = static::$databaseColumnId;
$itemColumn = static::$databaseColumnMap['item'];
return array(
'id' => $this->$idColumn,
'item_id' => $this->$itemColumn,
'author' => $this->author()->apiOutput(),
'date' => \IPS\DateTime::ts( $this->mapped('date') )->rfc3339(),
'content' => $this->content(),
'hidden' => (bool) $this->hidden(),
'url' => (string) $this->url()
);
}
/* !Embeddable */
/**
+249 -128
View File
@@ -2,9 +2,9 @@
/**
* @brief Abstract Content Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 3 Oct 2013
* @version SVN_VERSION_NUMBER
*/
@@ -98,22 +98,28 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
{
if ( isset( $data[ static::$databaseTable ] ) and is_array( $data[ static::$databaseTable ] ) )
{
/* Add author data to multiton storeto prevent ->author() running another query later */
{
/* Add author data to multiton store to prevent ->author() running another query later */
if ( isset( $data['author'] ) and is_array( $data['author'] ) )
{
$author = \IPS\Member::constructFromData( $data['author'], FALSE );
if ( isset( $data['author_pfields'] ) )
{
unset( $data['author_pfields']['member_id'] );
$author->contentProfileFields();
}
}
/* Load content */
$obj = parent::constructFromData( $data[ static::$databaseTable ], $updateMultitonStoreIfExists );
/* Add reputation if it was passed*/
if ( isset( $data['core_reputation_index'] ) and is_array( $data['core_reputation_index'] ) )
{
$obj->_data = array_merge( $obj->_data, $data['core_reputation_index'] );
}
/* Return */
return $obj;
}
@@ -248,8 +254,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
*/
public function truncated( $oneLine=FALSE )
{
/* Specifically remove quotes, and any scripts (which someone with HTML posting allowed may have legitimately enabled */
$text = \IPS\Text\Parser::removeElements( $this->content(), array( 'blockquote', 'script' ) );
/* Specifically remove quotes, any scripts (which someone with HTML posting allowed may have legitimately enabled, and spoilers (to prevent contents from being revealed) */
$text = \IPS\Text\Parser::removeElements( $this->content(), array( 'blockquote', 'script', 'div[class=ipsSpoiler]' ) );
/* Convert headers and paragraphs into line breaks or just spaces */
$text = str_replace( array( '</p>', '</h1>', '</h2>', '</h3>', '</h4>', '</h5>', '</h6>' ), ( $oneLine ? ' ' : '<br>' ), $text );
@@ -261,7 +267,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$text = strip_tags( $text, '<br>' );
/* Remove any <br>s from the start so there isn't just blank space at the top, but maintaining <br>s elsewhere */
$text = preg_replace( '/^[(\s)(<br>)\x{00a0}]*/u', '', $text );
$text = preg_replace( '/^(\s|<br>|' . chr(0xC2) . chr(0xA0) . ')+?/', '', $text );
/* Return */
return $text;
@@ -376,7 +382,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
*/
public static function canSeeMultiModTools( \IPS\Member $member = NULL, \IPS\Node\Model $container = NULL )
{
return static::modPermission( 'edit', $member, $container ) or static::modPermission( 'hide', $member, $container ) or static::modPermission( 'unhide', $member, $container ) or static::modPermission( 'delete', $member, $container );
return static::modPermission( 'pin', $member, $container ) or static::modPermission( 'unpin', $member, $container ) or static::modPermission( 'feature', $member, $container ) or static::modPermission( 'unfeature', $member, $container ) or static::modPermission( 'edit', $member, $container ) or static::modPermission( 'hide', $member, $container ) or static::modPermission( 'unhide', $member, $container ) or static::modPermission( 'delete', $member, $container );
}
/**
@@ -558,11 +564,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/**
* Hide
*
* @param \IPS\Member|NULL $member The member doing the action (NULL for currently logged in member)
* @param string $reason Reason
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @param string $reason Reason
* @return void
*/
public function hide( \IPS\Member $member = NULL, $reason = NULL )
public function hide( $member, $reason = NULL )
{
if ( isset( static::$databaseColumnMap['hidden'] ) )
{
@@ -585,7 +591,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->$column = -1;
$this->save();
$this->onHide();
$this->onHide( $member );
if ( static::$hideLogKey )
{
@@ -594,7 +600,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
\IPS\Db::i()->insert( 'core_soft_delete_log', array(
'sdl_obj_id' => $this->$idColumn,
'sdl_obj_key' => static::$hideLogKey,
'sdl_obj_member_id' => intval( $member ? $member->member_id : \IPS\Member::loggedIn()->member_id ),
'sdl_obj_member_id' => $member === FALSE ? 0 : intval( $member ? $member->member_id : \IPS\Member::loggedIn()->member_id ),
'sdl_obj_date' => time(),
'sdl_obj_reason' => $reason,
@@ -618,17 +624,17 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/**
* Unhide
*
* @param \IPS\Member|NULL $member The member doing the action (NULL for currently logged in member)
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function unhide( $member=NULL )
public function unhide( $member )
{
/* If we're approving, we have to do extra stuff */
$approving = FALSE;
if ( $this->hidden() === 1 )
{
$approving = TRUE;
if ( isset( static::$databaseColumnMap['approved_by'] ) )
if ( isset( static::$databaseColumnMap['approved_by'] ) and $member !== FALSE )
{
$column = static::$databaseColumnMap['approved_by'];
$this->$column = $member ? $member->member_id : \IPS\Member::loggedIn()->member_id;
@@ -639,8 +645,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$this->$column = time();
}
}
/* Now do the actual stuff */
if ( isset( static::$databaseColumnMap['hidden'] ) )
{
@@ -671,7 +676,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
throw new \RuntimeException;
}
$this->save();
$this->onUnhide( $approving );
$this->onUnhide( $approving, $member );
/* And update the tags perm cache */
if ( $this instanceof \IPS\Content\Tags )
@@ -690,10 +695,9 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$this->sendApprovedNotification();
}
}
/**
* Send
$this->expireWidgetCaches();
}
/**
* Blurb for when/why/by whom this content was hidden
@@ -783,6 +787,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
try
{
$index = \IPS\core\Reports\Report::load( $this->$idColumn, 'content_id', array( 'class=?', get_called_class() ) );
$index->num_reports = $index->num_reports + 1;
}
catch ( \OutOfRangeException $e )
{
@@ -792,10 +797,18 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$index->perm_id = $this->permId();
$index->first_report_by = (int) \IPS\Member::loggedIn()->member_id;
$index->first_report_date = time();
$index->last_updated = time();
$index->author = (int) $this->author()->member_id;
$index->num_reports = 1;
$index->num_comments = 0;
}
/* Only set this to a new report if it is not already under review */
if( $index->status != 2 )
{
$index->status = 1;
}
$index->status = 1;
$index->save();
/* Create a report */
@@ -809,10 +822,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
\IPS\Db::i()->insert( 'core_rc_reports', $reportInsert );
/* Rebuild */
$index->rebuild();
/* Send notification to mods */
$moderators = array( 'm' => array(), 'g' => array() );
foreach ( \IPS\Db::i()->select( '*', 'core_moderators' ) as $mod )
@@ -869,14 +878,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
break;
case 'author_name':
$this->$col = $newAuthor->member_id ? $newAuthor->name : '';
/* Real name will contain the custom guest name if available or '' if not */
$this->$col = $newAuthor->member_id ? $newAuthor->name : $newAuthor->real_name;
break;
}
}
}
$this->save();
if ( \IPS\Dispatcher::hasInstance() )
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' )
{
\IPS\Session::i()->modLog( 'modlog__action_changeauthor', array( static::$title => TRUE, $this->url()->__toString() => FALSE, $this->mapped('title') ?: ( method_exists( $this, 'item' ) ? $this->item()->mapped('title') : NULL ) => FALSE ), ( $this instanceof \IPS\Content\Item ) ? $this : $this->item() );
}
@@ -888,15 +898,17 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array $template Optional custom template
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem )
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL )
{
/* Item details */
$itemClass = $indexData['index_class'];
@@ -913,6 +925,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Object URL */
$indefiniteArticle = static::_indefiniteArticle( $containerData );
$definiteArticle = static::_definiteArticle( $containerData );
if ( in_array( 'IPS\Content\Comment', class_parents( get_called_class() ) ) )
{
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
@@ -927,22 +940,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
$definiteArticle = $itemClass::_definiteArticle( $containerData );
}
else
{
$objectUrl = $itemUrl;
$showRepUrl = $itemUrl->setQueryString( 'do', 'showRep' );
}
$articles = array( 'indefinite' => $indefiniteArticle, 'definite' => \IPS\Member::loggedIn()->language()->addToStack( $itemClass::$title, FALSE, array( 'strtolower' => TRUE ) ) );
$articles = array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle );
/* Container details */
$containerUrl = NULL;
$containerTitle = NULL;
if ( isset( $itemClass::$containerNodeClass ) )
{
$containerClass = $itemClass::$containerNodeClass;
$containerTitle = \IPS\Member::loggedIn()->language()->addToStack( $containerClass::$titleLangPrefix . $indexData['index_container_id'] );
$containerUrl = $containerClass::urlFromIndexData( $indexData, $itemData, $containerData );
$containerClass = $itemClass::$containerNodeClass;
$containerTitle = $containerClass::titleFromIndexData( $indexData, $itemData, $containerData );
$containerUrl = $containerClass::urlFromIndexData( $indexData, $itemData, $containerData );
}
/* Reputation */
@@ -950,9 +964,14 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Snippet */
$snippet = static::searchResultSnippet( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $view );
/* Return */
return \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResult( $indexData, $articles, $authorData, $itemData, $unread, $asItem ? $itemUrl : $objectUrl, $itemUrl, $containerUrl, $containerTitle, $repCount, $showRepUrl, $snippet, $iPostedIn, $view );
if ( $template === NULL )
{
$template = array( \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' ), 'searchResult' );
}
/* Return */
return call_user_func_array( $template, array( $indexData, $articles, $authorData, $itemData, $unread, $asItem ? $itemUrl : $objectUrl, $itemUrl, $containerUrl, $containerTitle, $repCount, $showRepUrl, $snippet, $iPostedIn, $view, $canIgnoreComments ) );
}
/**
@@ -961,7 +980,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param string $view 'expanded' or 'condensed'
@@ -1138,7 +1157,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$idColumn = static::$databaseColumnId;
\IPS\Db::i()->delete( 'core_reputation_index', array( 'app=? AND type=? AND member_id=? AND type_id=?', static::$application, static::$reputationType, $member->member_id, $this->$idColumn ) );
if ( $this->repGiven( $member ) )
{
if ( $this->reputation !== NULL )
@@ -1148,7 +1167,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
else
{
$limit = ( $type === 1 ) ? $member->group['g_rep_max_positive'] : $member->group['g_rep_max_negative'];
if ( $limit !== 999 )
@@ -1161,13 +1179,16 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
}
\IPS\Db::i()->insert( 'core_reputation_index', array(
'member_id' => $member->member_id,
'app' => static::$application,
'type' => static::$reputationType,
'type_id' => $this->$idColumn,
'rep_date' => time(),
'rep_rating' => $type,
'member_received' => (int) $this->author()->member_id
'member_id' => $member->member_id,
'app' => static::$application,
'type' => static::$reputationType,
'type_id' => $this->$idColumn,
'rep_date' => time(),
'rep_rating' => $type,
'member_received' => (int) $this->author()->member_id,
'class_type_id_hash' => md5( get_class( $this ) . ':' . $this->$idColumn ),
'rep_class' => get_class( $this ),
'item_id' => (int) $this->mapped('item')
) );
/* Send Notification */
@@ -1275,59 +1296,84 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
*/
public function likeBlurb()
{
if( $this->likeBlurb !== NULL )
if( $this->likeBlurb === NULL )
{
return $this->likeBlurb;
}
$reputation = $this->reputation();
$people = array();
/* Nobody */
if ( !$reputation )
{
return '';
}
/* You */
if ( $this->repGiven() === 1 )
{
$reputation--;
if ( $reputation )
/* Did anyone like it? */
$numberOfLikes = $this->reputation(); # int
if ( $numberOfLikes )
{
$people[] = \IPS\Member::loggedIn()->language()->addToStack('like_blurb_you_and_others');
/* Is it just us? */
$userLiked = ( $this->repGiven() === 1 );
if ( $userLiked and $numberOfLikes < 2 )
{
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack('like_blurb_just_you');
}
/* Nope, we need to display a number... */
else
{
$peopleToDisplayInMainView = array();
$andXOthers = $numberOfLikes;
/* If the user liked, we always show "You" first */
if ( $userLiked )
{
$peopleToDisplayInMainView[] = \IPS\Member::loggedIn()->language()->addToStack('like_blurb_you_and_others');
$andXOthers--;
}
/* If we have permission to see who the others are, we need to do that... */
if ( \IPS\Member::loggedIn()->group['gbw_view_reps'] )
{
$peopleToDisplayInSecondaryView = array();
/* Some random names */
$idColumn = static::$databaseColumnId;
$i = 0;
$peopleToDisplayInSecondaryView = array();
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array( 'app=? AND type=? AND type_id=? AND member_id!=? AND rep_rating=?', static::$application, static::$reputationType, $this->$idColumn, ( \IPS\Member::loggedIn()->member_id ) ?: 0, 1 ), 'RAND()', $userLiked ? 17 : 18 ) as $rep )
{
if ( $i < ( $userLiked ? 2 : 3 ) )
{
$peopleToDisplayInMainView[] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->userLink( \IPS\Member::load( $rep['member_id'] ) );
$andXOthers--;
}
else
{
$peopleToDisplayInSecondaryView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
}
$i++;
}
/* If there's people to display in the secondary view, add that */
if ( $peopleToDisplayInSecondaryView )
{
if ( count( $peopleToDisplayInSecondaryView ) < $andXOthers )
{
$peopleToDisplayInSecondaryView[] = \IPS\Member::loggedIn()->language()->addToStack( 'like_blurb_others_secondary', FALSE, array( 'pluralize' => array( $andXOthers - count( $peopleToDisplayInSecondaryView ) ) ) );
}
$peopleToDisplayInMainView[] = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->reputationOthers( $this->url( 'showRep' ), \IPS\Member::loggedIn()->language()->addToStack( 'like_blurb_others', FALSE, array( 'pluralize' => array( $andXOthers ) ) ), json_encode( $peopleToDisplayInSecondaryView ) );
}
}
/* Otherwise the "and X others" will just be static */
else
{
$peopleToDisplayInMainView[] = \IPS\Member::loggedIn()->language()->addToStack( empty( $peopleToDisplayInMainView ) ? 'like_blurb_generic' : 'like_blurb_others', FALSE, array( 'pluralize' => array( $andXOthers ) ) );
}
/* Put it all together */
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'like_blurb', FALSE, array( 'pluralize' => array( $numberOfLikes ), 'htmlsprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $peopleToDisplayInMainView ) ) ) );
}
}
/* Nobody liked it - show nothing */
else
{
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack('like_blurb_just_you');
return $this->likeBlurb;
$this->likeBlurb = '';
}
}
if( \IPS\Member::loggedIn()->group['gbw_view_reps'] )
{
/* 3 random names */
$idColumn = static::$databaseColumnId;
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array( 'app=? AND type=? AND type_id=? AND member_id!=? AND rep_rating=?', static::$application, static::$reputationType, $this->$idColumn, ( \IPS\Member::loggedIn()->member_id ) ?: 0, 1 ), 'RAND()', 3 ) as $rep )
{
$people[] = \IPS\Theme::i()->getTemplate( 'global', 'core' )->userLink( \IPS\Member::load( $rep['member_id'] ) );
$reputation--;
}
}
/* X others */
$count = count( $people ) + $reputation;
if ( $reputation )
{
$others = \IPS\Member::loggedIn()->language()->addToStack( empty( $people ) ? 'like_blurb_generic' : 'like_blurb_others', FALSE, array( 'pluralize' => array( $reputation ) ) );
if ( \IPS\Member::loggedIn()->group['gbw_view_reps'] )
{
$others = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->reputationOthers( $this, $others );
}
$people[] = $others;
}
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'like_blurb', FALSE, array( 'pluralize' => array( $count ), 'htmlsprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $people ) ) ) );
return $this->likeBlurb;
}
@@ -1405,7 +1451,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$idColumn = $this::$databaseColumnId;
try
{
$count = $this->notificationRecipients()->count( TRUE );
$count = $this->notificationRecipients( NULL, NULL, TRUE );
}
catch ( \BadMethodCallException $e )
{
@@ -1415,7 +1461,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
/* Queue if there's lots, or just send them */
if ( $count > static::NOTIFICATIONS_PER_BATCH )
{
\IPS\Task::queue( 'core', 'Follow', array( 'class' => get_class( $this ), 'item' => $this->$idColumn, 'exclude' => array_keys( $sentTo ) ), 1 );
\IPS\Task::queue( 'core', 'Follow', array( 'class' => get_class( $this ), 'item' => $this->$idColumn, 'sentTo' => $sentTo ), 1 );
}
else
{
@@ -1427,11 +1473,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* Send notifications batch
*
* @param int $offset Current offset
* @param array $exclude IDs to exclude
* @param array $sentTo Members who have already received a notification and how - e.g. array( 1 => array( 'inline', 'email' )
* @param string|NULL $extra Additional data
* @return int|null New offset or NULL if complete
*/
public function sendNotificationsBatch( $offset=0, $exclude=array(), $extra=NULL )
public function sendNotificationsBatch( $offset=0, &$sentTo=array(), $extra=NULL )
{
$followIds = array();
$followers = $this->notificationRecipients( array( $offset, static::NOTIFICATIONS_PER_BATCH ), $extra );
@@ -1442,19 +1488,20 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
foreach ( $followers as $follower )
{
$member = \IPS\Member::load( $follower['follow_member_id'] );
if ( $member != $this->author() and $this->canView( $member ) and !in_array( $follower['follow_member_id'], $exclude ) )
if ( $member != $this->author() and $this->canView( $member ) )
{
$followIds[] = $follower['follow_id'];
$notification->recipients->attach( $member, $follower );
}
}
$notification->send();
/* Log that we sent it */
if( count( $followIds ) )
{
\IPS\Db::i()->update( 'core_follow', array( 'follow_notify_sent' => time() ), \IPS\Db::i()->in( 'follow_id', $followIds ) );
}
$sentTo = $notification->send( $sentTo );
/* Update the queue */
$newOffset = $offset + static::NOTIFICATIONS_PER_BATCH;
@@ -1486,29 +1533,59 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
foreach( \IPS\Db::i()->select( '*', 'core_moderators' ) AS $mod )
{
$canView = FALSE;
$canApprove = FALSE;
if ( $mod['perms'] == '*' )
{
$canView = TRUE;
$canApprove = TRUE;
}
if ( $canView === FALSE )
else
{
$perms = json_decode( $mod['perms'], TRUE );
if ( isset( $perms['can_view_hidden_content'] ) AND $perms['can_view_hidden_content'] )
foreach ( array( 'canView' => 'can_view_hidden_', 'canApprove' => 'can_unhide_' ) as $varKey => $modPermKey )
{
$canView = TRUE;
}
else if ( isset( $perms['can_view_hidden_' . static::$title ] ) AND $perms['can_view_hidden_' . static::$title ] )
{
$canView = TRUE;
if ( isset( $perms[ $modPermKey . 'content' ] ) AND $perms[ $modPermKey . 'content' ] )
{
$$varKey = TRUE;
}
else
{
try
{
$container = ( $this instanceof \IPS\Content\Comment ) ? $this->item()->container() : $this->container();
$containerClass = get_class( $container );
$title = static::$title;
if
(
isset( $containerClass::$modPerm )
and
(
$perms[ $containerClass::$modPerm ] === -1
or
(
is_array( $perms[ $containerClass::$modPerm ] )
and
in_array( $container->_id, $perms[ $containerClass::$modPerm ] )
)
)
and
$perms["{$modPermKey}{$title}"]
)
{
$$varKey = TRUE;
}
}
catch ( \BadMethodCallException $e ) { }
}
}
}
if ( $canView === TRUE )
if ( $canView === TRUE and $canApprove === TRUE )
{
$moderators[ $mod['type'] ][] = $mod['id'];
}
}
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'unapproved_content', $this, array( $this, $this->author() ) );
foreach ( \IPS\Db::i()->select( '*', 'core_members', ( count( $moderators['m'] ) ? \IPS\Db::i()->in( 'member_id', $moderators['m'] ) . ' OR ' : '' ) . \IPS\Db::i()->in( 'member_group_id', $moderators['g'] ) . ' OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', $moderators['g'] ) ) as $member )
{
@@ -1537,7 +1614,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
* Send quote and mention notifications
*
* @param array $exclude An array of member IDs *not* to send notifications to
* @return array Member IDs sent to
* @return array The members that were notified and how they were notified
*/
protected function sendQuoteAndMentionNotifications( $exclude=array() )
{
@@ -1549,7 +1626,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
*
* @param array array( 'quotes' => array( ... member IDs ... ), 'mentions' => array( ... member IDs ... ) )
* @param array $exclude An array of member IDs *not* to send notifications to
* @return array Member IDs sent to
* @return array The members that were notified and how they were notified
*/
protected function _sendQuoteAndMentionNotifications( $data, $exclude=array() )
{
@@ -1563,16 +1640,16 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
} );
if ( !empty( $data['quotes'] ) )
{
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'quote', ( $this instanceof \IPS\Content\Item ) ? $this : $this->item(), array( $this ), array( $this->author()->member_id ) );
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'quote', ( $this instanceof \IPS\Content\Item ) ? $this : $this->item(), array( $this ), array( $this->author()->member_id ), FALSE );
foreach ( $data['quotes'] as $quote )
{
$member = \IPS\Member::load( $quote );
if ( $member != $this->author() and $this->canView( $member ) )
if ( $member->member_id and $member != $this->author() and $this->canView( $member ) )
{
$notification->recipients->attach( $member );
}
}
$sentTo = array_merge( $notification->send(), $sentTo );
$sentTo = $notification->send( $sentTo );
}
/* Mentions */
@@ -1582,16 +1659,16 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
} );
if ( !empty( $data['mentions'] ) )
{
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'mention', ( $this instanceof \IPS\Content\Item ) ? $this : $this->item(), array( $this ), array( $this->author()->member_id ) );
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'mention', ( $this instanceof \IPS\Content\Item ) ? $this : $this->item(), array( $this ), array( $this->author()->member_id ), FALSE );
foreach ( $data['mentions'] as $mention )
{
$member = \IPS\Member::load( $mention );
if ( $member != $this->author() and $this->canView( $member ) )
if ( $member != $this->author() and $this->canView( $member ) and !$member->isIgnoring( $this->author(), 'mentions' ) )
{
$notification->recipients->attach( $member );
}
}
$sentTo = array_merge( $notification->send(), $sentTo );
$sentTo = $notification->send( $sentTo );
}
/* Return */
@@ -1608,9 +1685,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
{
$return = array( 'quotes' => array(), 'mentions' => array() );
$document = new \DOMDocument;
libxml_use_internal_errors(TRUE);
if ( @$document->loadHTML( '<div>' . $content . '</div>' ) !== FALSE )
$document = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
if ( @$document->loadHTML( \IPS\Xml\DOMDocument::wrapHtml( '<div>' . $content . '</div>' ) ) !== FALSE )
{
/* Quotes */
foreach( $document->getElementsByTagName('blockquote') as $quote )
@@ -1625,9 +1701,8 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
foreach( $document->getElementsByTagName('a') as $link )
{
if ( $link->getAttribute('data-mentionid') )
{
$path = explode( '/', $link->getNodePath() );
if ( !in_array( 'blockquote', $path ) )
{
if ( !preg_match( '/\/blockquote(\[\d*\])?\//', $link->getNodePath() ) )
{
$return['mentions'][] = $link->getAttribute('data-mentionid');
}
@@ -1696,6 +1771,18 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
return $classes;
}
/**
* Override the HTML parsing enabled flag for rebuilds?
*
* @note By default this will return FALSE, but classes can override
* @see \IPS\forums\Topic\Post
* @return bool
*/
public function htmlParsingEnforced()
{
return FALSE;
}
/**
* Return any custom multimod actions this content item supports
*
@@ -1753,4 +1840,38 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__indefart_' . static::$title, FALSE );
}
/**
* Definite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public function definiteArticle( \IPS\Lang $lang = NULL )
{
$container = ( $this instanceof \IPS\Content\Comment ) ? $this->item()->containerWrapper() : $this->containerWrapper();
return static::_definiteArticle( $container ? $container->_data : array(), $lang );
}
/**
* Definite Article
*
* @param \IPS\Lang|NULL $language The language to use, or NULL for the language of the currently logged in member
* @return string
*/
public static function _definiteArticle( array $containerData = NULL, \IPS\Lang $lang = NULL )
{
$lang = $lang ?: \IPS\Member::loggedIn()->language();
return $lang->addToStack( '__defart_' . static::$title, FALSE );
}
/**
* Get preview image for share services
*
* @return string
*/
public function shareImage()
{
return (string) \IPS\Theme::i()->logo_sharer;
}
}
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Edit History Interface for Content Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 14 Oct 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Embeddable Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 22 Apr 2014
* @version SVN_VERSION_NUMBER
*/
+19 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Extension Generator
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 26 Dec 2013
* @version SVN_VERSION_NUMBER
*/
@@ -27,6 +27,11 @@ abstract class _ExtensionGenerator
* @brief If TRUE, will prevent comment classes being included
*/
protected static $contentItemsOnly = FALSE;
/**
* @brief If TRUE, will include archive classes
*/
protected static $includeArchive = FALSE;
/**
* Generate Extensions
@@ -37,11 +42,21 @@ abstract class _ExtensionGenerator
{
$return = array();
foreach ( \IPS\Content::routedClasses( FALSE, FALSE, static::$contentItemsOnly ) as $_class )
foreach ( \IPS\Content::routedClasses( FALSE, static::$includeArchive, static::$contentItemsOnly ) as $_class )
{
$obj = new static;
$obj->class = $_class;
\IPS\Member::loggedIn()->language()->words[ 'ipAddresses__core_Content_' . str_replace( '\\', '_', mb_substr( $_class, 4 ) ) ] = \IPS\Member::loggedIn()->language()->addToStack( $_class::$title . '_pl', FALSE );
if ( \IPS\Dispatcher::hasInstance() )
{
$language = \IPS\Member::loggedIn()->language();
}
else
{
$language = \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
}
$language->words[ 'ipAddresses__core_Content_' . str_replace( '\\', '_', mb_substr( $_class, 4 ) ) ] = $language->addToStack( ( ( isset( $_class::$archiveTitle ) ) ? $_class::$archiveTitle : $_class::$title ) . '_pl', FALSE );
$return[ 'Content_' . str_replace( '\\', '_', mb_substr( $_class, 4 ) ) ] = $obj;
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Featurable Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 12 Nov 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Followable Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 02 Dec 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Polls Interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 10 Jan 2013
* @version SVN_VERSION_NUMBER
*/
+26 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Hidebale Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 6 Nov 2013
* @version SVN_VERSION_NUMBER
*/
@@ -25,4 +25,28 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
interface Hideable
{
/**
* @brief Filter: Determine if the member is a moderator with permission to view hidden content (default)
*/
const FILTER_AUTOMATIC = NULL;
/**
* @brief Filter: Show hidden content regardless of permissions (use very cautiously)
*/
const FILTER_SHOW_HIDDEN = TRUE;
/**
* @brief Filter: Only return unapproved content that the current viewing user submitted
*/
const FILTER_OWN_HIDDEN = FALSE;
/**
* @brief Filter: Only return hidden content (used primarily for widgets when you select 'hidden', respects permission to view hidden content)
*/
const FILTER_ONLY_HIDDEN = 0;
/**
* @brief Filter: Only return public content (approved and not hidden, useful when things may get cached)
*/
const FILTER_PUBLIC_ONLY = -1;
}
+1079 -353
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Lockable Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 12 Nov 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Moderator Permissions Interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 1 Oct 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Permissions Interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Pinnable Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 12 Nov 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Polls Interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 10 Jan 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Rating Interface for Content Models/Comments
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 15 Jan 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Read/Unread Tracking Interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Interface for Reportable Comment Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Reputation interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 27 Nov 2013
* @version SVN_VERSION_NUMBER
*/
+121 -17
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Review Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 4 Nov 2013
* @version SVN_VERSION_NUMBER
*/
@@ -31,24 +31,26 @@ abstract class _Review extends \IPS\Content\Comment
/**
* Create first comment (created with content item)
*
* @param \IPS\Content\Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param int $rating The rating (1-5)
* @param string $guestName If author is a guest, the name to use
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @param \IPS\Content\Item $item The content item just created
* @param string $comment The comment
* @param bool $first Is the first comment?
* @param int $rating The rating (1-5)
* @param string $guestName If author is a guest, the name to use
* @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member.
* @param \IPS\DateTime|NULL $time The time
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
* @return static
* @throws \InvalidArgumentException
*/
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL )
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
{
if ( !is_int( $rating ) or $rating < 1 or $rating > intval( \IPS\Settings::i()->reviews_rating_out_of ) )
{
throw new \InvalidArgumentException;
}
$obj = parent::create( $item, $comment, $first, $guestName, $member, $time );
$obj = parent::create( $item, $comment, $first, $guestName, NULL, $member, $time, $ipAddress );
foreach ( array( 'rating', 'votes_data' ) as $k )
{
@@ -81,12 +83,22 @@ abstract class _Review extends \IPS\Content\Comment
$obj->save();
/* Have to do this AFTER rating is set */
/* Have to do these AFTER rating is set */
$itemClass = static::$itemClass;
$ratingField = $itemClass::$databaseColumnMap['rating'];
$obj->item()->$ratingField = $obj->item()->averageReviewRating() ?: 0;
$obj->item()->save();
/* Send notifications */
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
{
$obj->sendNotifications();
}
else if( $obj->hidden() === 1 )
{
$obj->sendUnapprovedNotification();
}
return $obj;
}
@@ -121,6 +133,11 @@ abstract class _Review extends \IPS\Content\Comment
$lastReviewByField = $item::$databaseColumnMap['last_review_by'];
$item->$lastReviewByField = $this->author()->member_id;
}
if ( isset( $item::$databaseColumnMap['last_review_name'] ) )
{
$lastReviewNameField = $item::$databaseColumnMap['last_review_name'];
$item->$lastReviewNameField = $this->author()->name;
}
if ( isset( $item::$databaseColumnMap['num_reviews'] ) )
{
$numReviewsField = $item::$databaseColumnMap['num_reviews'];
@@ -180,7 +197,26 @@ abstract class _Review extends \IPS\Content\Comment
}
else
{
$commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', static::$databaseTable, array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=? AND ' . static::$databasePrefix . static::$databaseColumnId . '<=?', $this->$itemField, $this->$idColumn ), static::$databasePrefix . static::$databaseColumnMap['date'] . ' asc' )->first();
$where = array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=? AND ' . static::$databasePrefix . static::$databaseColumnId . '<=?', $this->$itemField, $this->$idColumn ) );
if ( static::commentWhere() !== NULL )
{
$where[] = static::commentWhere();
}
if ( static::modPermission( 'view_hidden' ) === FALSE )
{
if ( isset( static::$databaseColumnMap['approved'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 1 );
}
elseif( isset( static::$databaseColumnMap['hidden'] ) )
{
$where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 0 );
}
}
$commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', static::$databaseTable, $where )->first();
$page = ceil( $commentPosition / $itemClass::$reviewsPerPage );
if ( $page != 1 )
{
@@ -207,9 +243,10 @@ abstract class _Review extends \IPS\Content\Comment
/**
* Syncing to run when hiding
*
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onHide()
public function onHide( $member )
{
$item = $this->item();
if ( $item->container()->_reviews !== NULL )
@@ -218,6 +255,12 @@ abstract class _Review extends \IPS\Content\Comment
$item->container()->setLastReview();
$item->container()->save();
}
if ( isset( $item::$databaseColumnMap['hidden_reviews'] ) )
{
$column = $item::$databaseColumnMap['hidden_reviews'];
$item->$column = $item->mapped('hidden_reviews') + 1;
}
if ( isset( $item::$databaseColumnMap['num_reviews'] ) )
{
$column = $item::$databaseColumnMap['num_reviews'];
@@ -234,10 +277,11 @@ abstract class _Review extends \IPS\Content\Comment
/**
* Syncing to run when unhiding
*
* @param bool $approving If true, is being approved for the first time
* @param bool $approving If true, is being approved for the first time
* @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
* @return void
*/
public function onUnhide( $approving )
public function onUnhide( $approving, $member )
{
$item = $this->item();
@@ -255,6 +299,11 @@ abstract class _Review extends \IPS\Content\Comment
$item->container()->save();
}
}
else if ( isset( $item::$databaseColumnMap['hidden_reviews'] ) )
{
$column = $item::$databaseColumnMap['hidden_reviews'];
$item->$column = $item->mapped('hidden_reviews') - 1;
}
if ( isset( $item::$databaseColumnMap['num_reviews'] ) )
{
@@ -285,6 +334,27 @@ abstract class _Review extends \IPS\Content\Comment
{
return FALSE;
}
/**
* Can view?
*
* @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member
* @return bool
*/
public function canView( $member=NULL )
{
if( $member === NULL )
{
$member = \IPS\Member::loggedIn();
}
if ( $this instanceof \IPS\Content\Hideable and $this->hidden() and !$this->item()->canViewHiddenReviews( $member ) and ( $this->hidden() !== 1 or $this->author() !== $member ) )
{
return FALSE;
}
return $this->item()->canView( $member );
}
/**
* Warning Reference Key
@@ -338,7 +408,41 @@ abstract class _Review extends \IPS\Content\Comment
parent::delete();
$itemClass = static::$itemClass;
$ratingField = $itemClass::$databaseColumnMap['rating'];
$this->item()->$ratingField = $this->item()->averageReviewRating() ?: 0;
$this->item()->$ratingField = $this->item()->averageReviewRating() ?: 0;
$this->item()->save();
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse int item The ID number of the item this belongs to
* @apiresponse \IPS\Member author Author
* @apiresponse datetime date Date
* @apiresponse int rating The number of stars this review gave
* @apiresponse int votesTotal The number of users that have voted if this review was helpful or unhelpful
* @apiresponse int votesHelpful The number of users that voted helpful
* @apiresponse string content The content
* @apiresponse bool hidden Is hidden?
* @apiresponse string url URL to content
*/
public function apiOutput()
{
$idColumn = static::$databaseColumnId;
$itemColumn = static::$databaseColumnMap['item'];
return array(
'id' => $this->$idColumn,
'item_id' => $this->$itemColumn,
'author' => $this->author()->apiOutput(),
'date' => \IPS\DateTime::ts( $this->mapped('date') )->rfc3339(),
'rating' => $this->mapped('rating'),
'votesTotal' => $this->mapped('votes_total'),
'votesHelpful' => $this->mapped('votes_helpful'),
'content' => $this->content(),
'hidden' => (bool) $this->hidden(),
'url' => (string) $this->url()
);
}
/* !Embeddable */
+4 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Filter for Search Queries
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 6 Jul 2015
* @version SVN_VERSION_NUMBER
*/
@@ -89,10 +89,10 @@ class _ContentFilter
*/
public static function init( $itemClass, $includeItems=TRUE, $includeComments=TRUE, $includeReviews=TRUE )
{
$obj = new self;
$obj = new static;
$obj->itemClass = $itemClass;
if ( $includeItems and !$itemClass::$firstCommentRequired )
if ( $includeItems and ( !isset( $itemClass::$firstCommentRequired ) OR !$itemClass::$firstCommentRequired ) )
{
$obj->classes[] = $itemClass;
}
+114 -24
View File
@@ -2,9 +2,9 @@
/**
* @brief Abstract Search Index
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -81,8 +81,9 @@ abstract class _Index extends \IPS\Patterns\Singleton
/* Init */
$class = get_class( $object );
$idColumn = $class::$databaseColumnId;
$tags = ( $object instanceof \IPS\Content\Tags ) ? implode( ',', array_filter( array_merge( array( $object->prefix() ), $object->tags() ) ) ) : NULL;
$tags = ( $object instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? implode( ',', array_filter( $object->tags() ) ) : NULL;
$prefix = ( $object instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? $object->prefix() : NULL;
/* If this is an item where the first comment is required, don't index because the comment will serve as both */
if ( $object instanceof \IPS\Content\Item and $class::$firstCommentRequired )
{
@@ -104,7 +105,8 @@ abstract class _Index extends \IPS\Patterns\Singleton
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$title = $object->item()->mapped('title');
$tags = ( $object->item() instanceof \IPS\Content\Tags ) ? implode( ',', array_filter( array_merge( array( $object->item()->prefix() ), $object->item()->tags() ) ) ) : NULL;
$tags = ( $object->item() instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? implode( ',', array_filter( $object->item()->tags() ) ) : NULL;
$prefix = ( $object->item() instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? $object->item()->prefix() : NULL;
$isForItem = TRUE;
}
}
@@ -116,14 +118,23 @@ abstract class _Index extends \IPS\Patterns\Singleton
}
else
{
$dateUpdated = ( $object instanceof \IPS\Content\Item ) ? $object->mapped('last_comment') : $object->mapped('edit_time');
$dateUpdated = ( $object instanceof \IPS\Content\Item ) ? $object->mapped('last_comment') : $object->mapped('date');
}
/* Is the the latest content? */
$isLastComment = 0;
if ( $object instanceof \IPS\Content\Comment )
{
$item = $object->item();
try
{
$item = $object->item();
}
catch( \OutOfRangeException $ex )
{
/* Comment has no parent item, return */
return NULL;
}
$latestThing = 0;
foreach ( array( 'updated', 'last_comment', 'last_review' ) as $k )
{
@@ -137,16 +148,55 @@ abstract class _Index extends \IPS\Patterns\Singleton
{
$isLastComment = 1;
}
/* If we are re-indexing the first post of an item, which is triggered by a comment being added to a $itemClass::$firstCommentRequired,
then ensure that the isLastComment flag is not added as we index the comment first, which means the index_is_last_comment is incorrectly reset to 0 for the comment itself */
if ( $isForItem and $isLastComment )
{
if ( isset( $item::$databaseColumnMap['num_comments'] ) and $item->mapped('num_comments') > 1 )
{
$isLastComment = 0;
}
}
}
else if ( $object instanceof \IPS\Content\Item and ! $class::$firstCommentRequired )
{
/* If this is item itself and not a comment, then we will store it as the last comment so the activity stream fetches the data correctly */
$isLastComment = 1;
if ( isset( $class::$databaseColumnMap['num_comments'] ) and $object->mapped('num_comments') )
{
$isLastComment = 0;
}
else if ( isset( $class::$databaseColumnMap['num_reviews'] ) and $object->mapped('num_reviews') )
{
$isLastComment = 0;
}
/* Is the item itself searchable but the comment not? */
$commentClass = $object::$commentClass;
if ( ! ( is_subclass_of( $commentClass, 'IPS\Content\Searchable' ) ) )
{
/* Then make this the last comment so it remains searchable */
$isLastComment = 1;
}
}
/* Take the HTML out of the content */
$content = trim( strip_tags( preg_replace( "/(<br(?: \/)?>|<\/p>)/i", ' ', preg_replace( "#<blockquote(.*?)>(.*)</blockquote>#uis", "", preg_replace( "#<script(.*?)>(.*)</script>#uis", "", ' ' . $object->mapped('content') . ' ' ) ) ) ) );
/* Strip spoilers */
$content = $object->mapped('content');
if ( preg_match( '#<div\s+?class=["\']ipsSpoiler["\']#', $content ) )
{
$content = \IPS\Text\Parser::removeElements( $content, array( 'div[class=ipsSpoiler]' ) );
}
/* Take the HTML out of the content */
$content = trim( str_replace( chr(0xC2) . chr(0xA0), ' ', strip_tags( preg_replace( "/(<br(?: \/)?>|<\/p>)/i", ' ', preg_replace( "#<blockquote(?:[^>]+?)>.+?(?<!<blockquote)</blockquote>#s", " ", preg_replace( "#<script(.*?)>(.*)</script>#uis", "", ' ' . $content . ' ' ) ) ) ) ) );
/* Work out the hidden status */
$hiddenStatus = $object->hidden();
if ( $hiddenStatus === 0 and method_exists( $object, 'item' ) and $object->item()->hidden() )
{
$hiddenStatus = $isForItem ? 1 : 2;
$hiddenStatus = $isForItem ? $object->item()->hidden() : 2;
}
if ( $hiddenStatus !== 0 and method_exists( $object, 'item' ) and $object->item()->isFutureDate() )
{
@@ -162,16 +212,45 @@ abstract class _Index extends \IPS\Patterns\Singleton
{
try
{
$itemIndexId = $this->getIndexId( $object::load( $object->item()->mapped('first_comment_id') ) );
$itemIndexId = $this->getIndexId( $object->item()->firstComment() );
}
catch ( \Exception $e ) { }
}
else
{
$itemIndexId = $this->getIndexId( $object->item() );
try
{
$itemIndexId = $this->getIndexId( $object->item() );
}
catch ( \UnderflowException $e )
{
try
{
/* Try and index parent */
\IPS\Content\Search\Index::i()->index( $object->item() );
$itemIndexId = $this->getIndexId( $object->item() );
}
catch( \Exception $ex )
{
return NULL;
}
}
}
}
else if ( $object instanceof \IPS\Content\Item )
{
if ( ! $object::$firstCommentRequired )
{
/* See if this has already been indexed */
try
{
/* Good, we need the index_item_index_id so this is not wiped on re-index */
$itemIndexId = $this->getIndexId( $object );
}
catch ( \Exception $e ) { }
}
}
/* Return */
return array(
'index_class' => $class,
@@ -185,6 +264,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
'index_date_updated' => intval( $dateUpdated ?: $object->mapped('date') ),
'index_author' => (int) $object->mapped('author'),
'index_tags' => $tags,
'index_prefix' => $prefix,
'index_hidden' => $hiddenStatus,
'index_item_index_id' => $itemIndexId,
'index_item_author' => intval( ( $object instanceof \IPS\Content\Item ) ? $object->mapped('author') : $object->item()->mapped('author') ),
@@ -266,18 +346,19 @@ abstract class _Index extends \IPS\Patterns\Singleton
/**
* Mass Update (when permissions change, for example)
*
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $$newItemAuthorId The new item author ID (if applicable)
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $newItemAuthorId The new item author ID (if applicable)
* @param bool $addAuthorToPermissions If true, the index_author_id will be added to $newPermissions - used when changing the permissions for a node which allows access only to author's items
* @return void
*/
abstract public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL );
abstract public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL, $addAuthorToPermissions = FALSE );
/**
* Update data for the first and last comment after a merge
@@ -295,4 +376,13 @@ abstract class _Index extends \IPS\Patterns\Singleton
* @return void
*/
abstract public function prune( \IPS\DateTime $cutoff = NULL );
/**
* Reset the last comment flag in any given class/index_item_id
*
* @param string $class The class
* @param int|NULL $indexItemId The index item ID
* @return void
*/
abstract public function resetLastComment( $class, $indexItemId );
}
+264 -48
View File
@@ -2,9 +2,9 @@
/**
* @brief MySQL Search Index
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -33,10 +33,14 @@ class _Index extends \IPS\Content\Search\Index
{
/* Get the index data */
$indexData = $this->indexData( $object );
$class = get_class( $object );
/* If we got the data... */
if( $indexData )
{
/* MySQL specific look up hash for easy referencing across tables */
$indexData['index_class_type_id_hash'] = md5( $indexData['index_class'] . ':' . $indexData['index_object_id'] );
/* If nobody has permission to access it, just remove it */
if ( !$indexData['index_permissions'] )
{
@@ -45,52 +49,159 @@ class _Index extends \IPS\Content\Search\Index
/* Otherwise, go ahead... */
else
{
/* Index it */
$newIndexId = \IPS\Db::i()->replace( 'core_search_index', $indexData, TRUE );
/* If that was successful... */
if ( $newIndexId )
$existingData = NULL;
$existingIndexId = NULL;
$resetLastComment = FALSE;
$newIndexId = NULL;
try
{
$class = get_class( $object );
$databaseColumnId = $object::$databaseColumnId;
$existingData = \IPS\Db::i()->select( 'index_id, index_class, index_object_id, index_item_id, index_hidden, index_is_last_comment, index_author', 'core_search_index', array( 'index_class=? AND index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) )->first();
$existingIndexId = $existingData['index_id'];
}
catch( \Exception $e ) { }
/* Adjust tags */
$tags = NULL;
if ( array_key_exists( 'index_tags', $indexData ) )
{
$tags = array_filter( array_merge ( array( $indexData['index_prefix'] ), explode( ',', $indexData['index_tags'] ) ) );
$prefix = $indexData['index_prefix'];
unset( $indexData['index_tags'] );
unset( $indexData['index_prefix'] );
}
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
{
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
$indexData['index_is_last_comment'] = 0;
/* Set index_item_index_id on other index items */
if ( $object instanceof \IPS\Content\Item )
$resetLastComment = TRUE;
}
else if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
{
$classes = array( $class );
/* If this is the latest comment, unflag what was set before on both item and comment */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
}
else if ( $object instanceof \IPS\Content\Item )
{
$subClasses = array( $class );
if ( isset( $class::$commentClass ) )
{
$subClasses[] = $class::$commentClass;
$classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$subClasses[] = $class::$reviewClass;
}
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( array( \IPS\Db::i()->in( 'index_class', $subClasses ) ), array( 'index_item_id=?', $object->$databaseColumnId ) ) );
}
elseif ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$itemColumnId = $class::$databaseColumnMap['item'];
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( \IPS\Db::i()->in( 'index_class', array( $class, $class::$itemClass ) ) . ' AND index_item_id=?', $object->$itemColumnId ) );
$classes[] = $class::$reviewClass;
}
}
/* And also set core_follow.follow_index_id */
if ( $object instanceof \IPS\Content\Comment )
{
$class = $class::$itemClass;
}
\IPS\Db::i()->update( 'core_follow', array( 'follow_index_id' => $newIndexId ), array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', $object::$application, mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ), $object->$databaseColumnId ) );
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=?', $indexData['index_item_id'] ) );
}
/* If this is the latest comment, unflag what was set before */
if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
if ( $existingData !== NULL and ( $indexData['index_class'] == $existingData['index_class'] and $indexData['index_object_id'] == $existingData['index_object_id'] ) )
{
\IPS\Db::i()->update( 'core_search_index', $indexData, array( 'index_class=? and index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) );
$newIndexId = $existingIndexId;
}
else
{
if ( $existingData !== NULL )
{
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( 'index_class=? AND index_item_id=? AND index_id<>?', $indexData['index_class'], $indexData['index_item_id'], $newIndexId ) );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? and index_object_id=?', $indexData['index_class'], $indexData['index_object_id'] ) );
}
try
{
$newIndexId = \IPS\Db::i()->insert( 'core_search_index', $indexData );
}
catch( \IPS\Db\Exception $e )
{
if ( $e->getCode() == 1062 )
{
/* Duplicate key which could be caused by a race condition on rebuild. Use replace in this case, as it is more expensive than an insert, so we only use it when we have to */
$newIndexId = \IPS\Db::i()->replace( 'core_search_index', $indexData );
}
}
}
/* If that was successful... */
if ( $newIndexId )
{
/* Remove existing tags */
if ( $existingIndexId )
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id=?', $existingIndexId ) );
}
/* Add add them back if we have any */
if ( count( $tags ) )
{
foreach( $tags as $tag )
{
\IPS\Db::i()->replace( 'core_search_index_tags', array( 'index_id' => $newIndexId, 'index_tag' => $tag, 'index_is_prefix' => ( $tag == $prefix ) ) );
}
}
/* Populate the map table, we always populate it under the item class regardless */
if ( $existingData == NULL or ( $existingData !== NULL and ( $existingData['index_author'] != $indexData['index_author'] OR $existingData['index_item_id'] != $indexData['index_item_id'] ) ) )
{
\IPS\Db::i()->replace( 'core_search_index_item_map', array( 'index_author_id' => $indexData['index_author'], 'index_item_id' => $indexData['index_item_id'], 'index_class' => ( $object instanceof \IPS\Content\Comment ? $object::$itemClass : $class ) ) );
}
$databaseColumnId = $object::$databaseColumnId;
/* Set index_item_index_id on other index items */
if ( $existingIndexId != $newIndexId )
{
if ( $object instanceof \IPS\Content\Item )
{
$subClasses = array( $class );
if ( isset( $class::$commentClass ) )
{
$subClasses[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$subClasses[] = $class::$reviewClass;
}
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( array( \IPS\Db::i()->in( 'index_class', $subClasses ) ), array( 'index_item_id=?', $object->$databaseColumnId ) ) );
}
elseif ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( $itemClass::$firstCommentRequired and $object->isFirst() )
{
$itemColumnId = $class::$databaseColumnMap['item'];
\IPS\Db::i()->update( 'core_search_index', array( 'index_item_index_id' => $newIndexId ), array( \IPS\Db::i()->in( 'index_class', array( $class, $class::$itemClass ) ) . ' AND index_item_id=?', $object->$itemColumnId ) );
}
}
/* And also set core_follow.follow_index_id */
if ( $object instanceof \IPS\Content\Comment )
{
$class = $class::$itemClass;
}
\IPS\Db::i()->update( 'core_follow', array( 'follow_index_id' => $newIndexId ), array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', $object::$application, mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ), $object->$databaseColumnId ) );
}
}
if ( $resetLastComment )
{
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
}
}
}
@@ -118,22 +229,93 @@ class _Index extends \IPS\Content\Search\Index
{
$class = get_class( $object );
$idColumn = $class::$databaseColumnId;
/* Tags */
$this->_deleteTagsFromIndex( $class, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_object_id=?', $class, $object->$idColumn ) );
/* If this was a comment, we really need to reset the index_is_last_comment flag if it was set */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
$classes = array( $class );
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
try
{
$this->resetLastComment( $classes, $object->mapped('item') );
}
catch( \Exception $ex ) { }
/* We need to see if this is the only comment the author has in this item and if so, remove their map */
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index', array( \IPS\Db::i()->in('index_class', $classes ) . ' and index_item_id=? and index_author=?', $object->mapped('item'), (int) $object->mapped('author') ) )->first() )
{
try
{
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_class=? AND index_item_id=? and index_author_id=?', $itemClass, $object->mapped('item'), (int) $object->mapped('author') ) );
}
catch( \Exception $ex ) { }
}
}
else if ( $object instanceof \IPS\Content\Item )
{
/* Just remove all rows matching the item and class. */
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_class=? AND index_item_id=?', $class, $object->$idColumn ) );
}
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
$this->_deleteTagsFromIndex( $commentClass, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $commentClass, $object->$idColumn ) );
}
if ( isset( $class::$reviewClass ) )
{
$reviewClass = $class::$reviewClass;
$this->_deleteTagsFromIndex( $reviewClass, $object->$idColumn );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $reviewClass, $object->$idColumn ) );
}
}
/**
* Return the index IDs associated with this class and $id
*
* @param \IPS\Content object $class The class
* @param int $id The index_item_id
* @return array
*/
protected function _deleteTagsFromIndex( $class, $id )
{
try
{
$ids = iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_item_id=?', $class, $id ) ) );
}
catch( \Exception $ex )
{
$ids = FALSE;
}
if ( is_array( $ids ) and count( $ids ) < 1000 )
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_id', $ids ) ) );
}
else
{
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_item_id=?', $class, $id ) ) ) );
}
}
/**
* Removes all content for a classs
*
@@ -149,24 +331,27 @@ class _Index extends \IPS\Content\Search\Index
$where[] = array( 'index_container_id=?', $containerId );
}
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_item_id IN( ? )', \IPS\Db::i()->select( 'index_item_id', 'core_search_index', $where ) ) );
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', $where ) ) );
\IPS\Db::i()->delete( 'core_search_index', $where );
}
/**
* Mass Update (when permissions change, for example)
*
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $newItemAuthorId The new item author ID (if applicable)
* @param string $class The class
* @param int|NULL $containerId The container ID to update, or NULL
* @param int|NULL $itemId The item ID to update, or NULL
* @param string|NULL $newPermissions New permissions (if applicable)
* @param int|NULL $newHiddenStatus New hidden status (if applicable) special value 2 can be used to indicate hidden only by parent
* @param int|NULL $newContainer New container ID (if applicable)
* @param int|NULL $authorId The author ID to update, or NULL
* @param int|NULL $newItemId The new item ID (if applicable)
* @param int|NULL $newItemAuthorId The new item author ID (if applicable)
* @param bool $addAuthorToPermissions If true, the index_author_id will be added to $newPermissions - used when changing the permissions for a node which allows access only to author's items
* @return void
*/
public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL )
public function massUpdate( $class, $containerId = NULL, $itemId = NULL, $newPermissions = NULL, $newHiddenStatus = NULL, $newContainer = NULL, $authorId = NULL, $newItemId = NULL, $newItemAuthorId = NULL, $addAuthorToPermissions = FALSE )
{
$where = array( array( 'index_class=?', $class ) );
if ( $containerId !== NULL )
@@ -204,6 +389,12 @@ class _Index extends \IPS\Content\Search\Index
{
\IPS\Db::i()->update( 'core_search_index', $update, $where );
}
if ( $addAuthorToPermissions )
{
$addAuthorToPermissionsWhere = $where;
$addAuthorToPermissionsWhere[] = array( 'index_author<>0' );
\IPS\Db::i()->update( 'core_search_index', "index_permissions = CONCAT( index_permissions, ',m.', index_author )", $addAuthorToPermissionsWhere );
}
if ( $newHiddenStatus !== NULL )
{
@@ -239,7 +430,6 @@ class _Index extends \IPS\Content\Search\Index
if ( $item::$firstCommentRequired )
{
$update['index_title'] = NULL;
$update['index_tags'] = NULL;
}
\IPS\Db::i()->update( 'core_search_index', $update, array( 'index_class=? AND index_item_id=?', $item::$commentClass, $item->$idColumn ) );
@@ -264,11 +454,37 @@ class _Index extends \IPS\Content\Search\Index
{
if ( $cutoff )
{
\IPS\DB::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_item_id IN( ? )', \IPS\Db::i()->select( 'index_item_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) ) ) );
\IPS\Db::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
}
else
{
\IPS\DB::i()->delete( 'core_search_index' );
\IPS\Db::i()->delete( 'core_search_index_item_map' );
\IPS\Db::i()->delete( 'core_search_index_tags' );
\IPS\Db::i()->delete( 'core_search_index' );
}
}
/**
* Reset the last comment flag in any given class/index_item_id
*
* @param array $classes The classes (when first post is required, this is typically just \IPS\forums\Topic\Post but for others, it will be both item and comment classes)
* @param int|NULL $indexItemId The index item ID
* @return void
*/
public function resetLastComment( $classes, $indexItemId )
{
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=?', $indexItemId ) );
try
{
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 1 ), array( 'index_class=? AND index_object_id=?', $latest['index_class'], $latest['index_object_id'] ) );
/* Now reset the item index with the latest comment time */
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, $indexItemId ) );
}
catch( \Exception $ex ) { }
}
}
+575 -129
View File
@@ -2,9 +2,9 @@
/**
* @brief MySQL Search Query
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -38,6 +38,11 @@ class _Query extends \IPS\Content\Search\Query
*/
protected $hiddenClause = NULL;
/**
* @brief The WHERE clause for last updated date
*/
protected $lastUpdatedClause = NULL;
/**
* @brief The offset
*/
@@ -57,6 +62,17 @@ class _Query extends \IPS\Content\Search\Query
* @brief Item classes included
*/
protected $itemClasses = NULL;
/**
* @brief Force specific table index
*/
protected $forceIndex = NULL;
/**
* @brief Filter by items I posted in?
* @see filterByItemsIPostedIn()
*/
protected $filterByItemsIPostedIn = FALSE;
/**
* Filter by multiple content types
@@ -74,7 +90,7 @@ class _Query extends \IPS\Content\Search\Query
{
$this->itemClasses = array();
}
/* Loop the filters */
foreach ( $contentFilters as $filter )
{
@@ -101,6 +117,18 @@ class _Query extends \IPS\Content\Search\Query
$clause[] = \IPS\Db::i()->in( 'index_container_id', $filter->containerIds, $filter->containerIdFilter === FALSE );
}
$itemClass = $filter->itemClass;
if ( isset( $itemClass::$containerNodeClass ) )
{
$containerClass = $itemClass::$containerNodeClass;
$unsearchableIds = $containerClass::unsearchableNodeIds();
if ( $unsearchableIds != NULL )
{
$clause[] = \IPS\Db::i()->in( 'index_container_id', $unsearchableIds, TRUE );
}
}
/* Set the item IDs */
if ( $filter->itemIdFilter !== NULL )
{
@@ -181,7 +209,7 @@ class _Query extends \IPS\Content\Search\Query
}
return $this;
}
}
/**
* Filter for profile
@@ -191,9 +219,26 @@ class _Query extends \IPS\Content\Search\Query
*/
public function filterForProfile( \IPS\Member $member )
{
$this->where[] = array( '( index_author=? OR ( index_class=? AND index_container_id=? ) )', $member->member_id, 'IPS\core\Statuses\Status', $member->member_id );
return $this;
/* Check to see if this member has even had anyone write on their status */
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index', array( 'index_class=? AND index_container_id=?', 'IPS\core\Statuses\Status', $member->member_id ) )->first();
if ( $count )
{
if ( $count > 500 )
{
$this->where[] = array( '( index_author=? OR ( index_class=? AND index_container_id=? ) )', $member->member_id, 'IPS\core\Statuses\Status', $member->member_id );
}
else
{
$this->where[] = array( '( index_author=? OR (' . \IPS\Db::i()->in( 'index_id', iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_container_id=?', 'IPS\core\Statuses\Status', $member->member_id ) ) ) ) . ') )', $member->member_id );
}
return $this;
}
else
{
return $this->filterByAuthor( $member );
}
}
/**
@@ -221,6 +266,9 @@ class _Query extends \IPS\Content\Search\Query
{
$where = array();
$params = array();
$followApps = $followAreas = $case = $containerCase = array();
$followedItems = array();
$followedContainers = array();
/* Are we including items or containers? */
if ( $includeContainers or $includeItems )
@@ -240,60 +288,114 @@ class _Query extends \IPS\Content\Search\Query
}
/* Loop them */
foreach ( $classes as $class )
{
if( is_subclass_of( $class, 'IPS\Content\Followable' ) )
{
/* Construct a subquery which will get the IDs of the items of this class that we follow */
$subQueryWhere = array( array( 'follow_app=?', $class::$application ) );
$followApps[ $class::$application ] = $class::$application;
$followArea = mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) );
if ( $includeContainers and $includeItems )
{
$subQueryWhere[] = array( '( follow_area=? OR follow_area=? )', mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) ), mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ) );
$followAreas[] = mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) );
$followAreas[] = $followArea;
}
elseif ( $includeItems )
{
$subQueryWhere[] = array( 'follow_area=?', mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ) );
$followAreas[] = $followArea;
}
elseif ( $includeContainers )
{
$subQueryWhere[] = array( 'follow_area=?', mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) ) );
$followAreas[] = mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) );
}
$subQueryWhere[] = array( 'follow_member_id=?', $this->member->member_id );
$subQuery = \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', $subQueryWhere );
/* If we're only doing one class, there's no point in doing "item_class=? AND ( item_class=? AND item_id IN(?) )" - we can just do "item_class=? AND item_id IN(?)" */
if ( count( $classes ) == 1 )
/* Work out what classes this applies to - need to specify comment and review classes */
if ( ! $class::$firstCommentRequired )
{
$where[] = 'index_item_id IN(?)';
$params[] = $subQuery;
$case[ $followArea ][] = $class;
}
/* But otherwise we need to group by class, because item IDs are not unique */
else
if( $includeContainers )
{
/* Work out what classes this applies to - need to specify comment and review classes */
$_classes = array( $class );
if ( isset( $class::$commentClass ) )
{
$_classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$_classes[] = $class::$reviewClass;
}
/* Constuct the where */
$where[] = '( ' . \IPS\Db::i()->in( 'index_class', $_classes ) . ' AND index_item_id IN( ? ) )';
$params[] = $subQuery;
$containerCase[ $followArea ] = mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) ) ;
}
if ( isset( $class::$commentClass ) )
{
$case[ $followArea ][] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$case[ $followArea ][] = $class::$reviewClass;
}
}
}
/* Get the stuff we follow */
foreach( \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_member_id=? AND ' . \IPS\Db::i()->in( 'follow_app', $followApps ) . ' AND ' . \IPS\Db::i()->in( 'follow_area', $followAreas ), $this->member->member_id ) ) as $follow )
{
if( array_key_exists( $follow['follow_area'], $case ) )
{
$followedItems[ $follow['follow_area'] ][] = $follow['follow_rel_id'];
}
else if( in_array( $follow['follow_area'], $containerCase ) )
{
$followedContainers[ $follow['follow_area'] ][] = $follow['follow_rel_id'];
}
}
}
foreach( $followedItems as $area => $item )
{
$where[] = '( ' . \IPS\Db::i()->in( 'index_class', $case[ $area ] ) . " AND index_item_id IN(" . implode( ',', $item ) . ") )";
}
foreach( $followedContainers as $area => $container )
{
$indexClasses = array();
foreach( $containerCase as $followArea => $containerArea )
{
if( $containerArea == $area )
{
$indexClasses = $case[ $followArea ];
}
}
$where[] = '( ' . \IPS\Db::i()->in( 'index_class', $indexClasses ) . " AND index_container_id IN(" . implode( ',', $container ) . ") )";
}
/* Are we including content posted by followed members? */
if ( $includeMembers )
{
$where[] = 'index_author IN(?)';
$params[] = \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_member_id=?', 'core', 'member', $this->member->member_id ) );
/* Another area where a small result set can drastically slow down the entire query */
try
{
$followed = iterator_to_array( \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_member_id=?', 'core', 'member', $this->member->member_id ), 'follow_rel_id asc', array( 0, 501 ) ) );
if ( count( $followed ) == 501 )
{
/* Assume we have loads of matches, so do a full query */
$where[] = 'index_author IN(?)';
$params[] = \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_member_id=?', 'core', 'member', $this->member->member_id ) );
}
else if ( count( $followed ) )
{
/* IN is not a SIN. It's been a long day */
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
}
else
{
/* There are no results */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
}
}
catch( \UnderflowException $ex )
{
/* There are no results */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
}
}
/* Put it all together */
@@ -301,33 +403,30 @@ class _Query extends \IPS\Content\Search\Query
{
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
}
else
{
/* If we want to filter by followed content, and we don't actually follow any content then we shouldn't return anything */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
}
/* And return */
return $this;
}
/**
* Filter by content the user has posted in
* Filter by content the user has posted in. This must be at the end of the chain.
*
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
public function filterByItemsIPostedIn()
{
$this->joins[] = array(
'from' => \IPS\Db::i()->select(
'index_item_index_id',
array('core_search_index', 'sub' ),
array( array( 'index_author=' . intval( $this->member->member_id ) ) ),
NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT
),
'where' => 'sub.index_item_index_id=main.index_item_index_id',
'type' => 'STRAIGHT_JOIN'
);
{
/* We have to set a property because we need the other data like other filters and ordering to figure this out */
$this->filterByItemsIPostedIn = TRUE;
/* Return for daisy chaining */
return $this;
}
/**
* Filter by content the user has not read
*
@@ -382,8 +481,11 @@ class _Query extends \IPS\Content\Search\Query
}
}
/* Put it all together */
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
if ( count( $where ) )
{
/* Put it all together */
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
}
}
/**
@@ -442,6 +544,11 @@ class _Query extends \IPS\Content\Search\Query
{
unset( $markers[ $_key ] );
}
/* If the existing timestamp is higher, retain that since we reset the $markers array below */
else
{
$mtime = $_mtime;
}
}
$markers[ $mtime . '.' . $mid ] = $mid;
@@ -546,11 +653,11 @@ class _Query extends \IPS\Content\Search\Query
{
if ( $start )
{
$this->where[] = array( 'index_date_updated>?', $start->getTimestamp() );
$this->lastUpdatedClause[] = array( 'index_date_updated>?', $start->getTimestamp() );
}
if ( $end )
{
$this->where[] = array( 'index_date_updated<?', $end->getTimestamp() );
$this->lastUpdatedClause[] = array( 'index_date_updated<?', $end->getTimestamp() );
}
return $this;
}
@@ -615,7 +722,7 @@ class _Query extends \IPS\Content\Search\Query
break;
case static::ORDER_OLDEST_CREATED:
$this->order = 'index_date_updated DESC';
$this->order = 'index_date_created ASC';
break;
case static::ORDER_RELEVANCY:
@@ -636,86 +743,77 @@ class _Query extends \IPS\Content\Search\Query
*/
protected function _searchWhereClause( $term = NULL, $tags = NULL, $method = 1 )
{
/* Boolean tags */
$tagWhere = NULL;
$termWhere = NULL;
/* Do we have tags? */
if ( $tags !== NULL )
{
$theTags = array();
foreach( $tags as $tagEntry )
$itemsOnlyTagSearch = '';
if ( $method & static::TAGS_MATCH_ITEMS_ONLY )
{
foreach( explode( ' ', $tagEntry ) as $_term )
$itemsOnlyTagSearch = 'index_item_index_id=index_id AND ';
}
/* Large index tables and small tag tables can cause a significant slow down in this query execution, so we attempt to pre-fetch some results */
try
{
$tagIds = iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_tag', $tags ) ), 'index_id asc', array( 0, 501 ) ) );
/* Now, if we have 501 results, then we have to assume there are more, so the join is required */
if ( count( $tagIds ) == 501 )
{
if( mb_strlen( $_term ) > 2 )
{
$theTags[] = "+" . $_term;
}
else
{
$theTags[] = $_term;
}
$tagWhere = array( $itemsOnlyTagSearch . 'index_item_index_id IN ( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_tag', $tags ) ) ) );
}
else
{
$tagWhere = array( $itemsOnlyTagSearch . \IPS\Db::i()->in( 'index_item_index_id', $tagIds ) );
}
}
catch( \UnderflowException $ex )
{
/* No matches at all */
if ( $method & static::TERM_AND_TAGS )
{
/* We want to match term and tags, so return an impossible result set */
$tagWhere = array( "1=2" );
}
else
{
$tagWhere = array( "1=1" );
}
$searchTags[] = implode( ' ', $theTags );
}
}
/* Do we have a term? */
$where = array();
if ( $term !== NULL )
{
$indexes = 'index_content,index_title';
if ( $method & static::TERM_TITLES_ONLY )
$termWhere = static::matchClause( $method & static::TERM_TITLES_ONLY ? 'index_title' : 'index_content,index_title', $term, $method & static::TERM_OR_MODE ? '' : '+' );
}
/* Put those two together */
if ( $termWhere !== NULL and $tagWhere !== NULL )
{
if ( $method & static::TERM_OR_TAGS )
{
$indexes = 'index_title';
$where[] = array_merge( array( '( ( ' . array_shift( $termWhere ) . ' ) OR ( ' . array_shift( $tagWhere ) . ' ) )' ), array_merge( $termWhere, $tagWhere ) );
}
/* Default to 'AND' boolean mode search unless asked otherwise*/
if ( ! ( $method & static::TERM_OR_MODE ) and ! static::termIsPhrase( $term ) )
{
$theWords = array();
foreach( static::termAsWordsArray( $term ) as $_term )
{
$potentialOperator = mb_substr( $_term, 0, 1 );
if ( ! in_array( $potentialOperator, array( '-', '+', '~' ) ) )
{
if( mb_strlen( $_term ) > 2 )
{
$theWords[] = "+" . $_term;
}
}
}
$term = implode( ' ', $theWords );
}
/* If we also have tags, create a combined where */
if ( $tags !== NULL )
{
if ( $method & static::TERM_OR_TAGS )
{
$where[] = array( "( MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE) ) OR " . \IPS\Db::i()->findInSet( 'index_tags', $tags ), $term );
}
else
{
$where[] = array( "MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE)", $term );
$where[] = \IPS\Db::i()->findInSet( 'index_tags', $tags );
}
}
/* Or just use the term */
else
{
$where[] = array( "MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE)", $term );
$where[] = $termWhere;
$where[] = $tagWhere;
}
}
/* Or do we have tags? */
elseif ( $tags !== NULL )
{
$where[] = \IPS\Db::i()->findInSet( 'index_tags', $tags );
if ( $this->order = 'calcscore DESC' )
{
$this->setOrder( static::ORDER_NEWEST_UPDATED );
}
/* Or just use the term if that's all we have */
elseif ( $termWhere !== NULL )
{
$where[] = $termWhere;
}
/* Or just use tags if that's all we have */
elseif ( $tagWhere !== NULL )
{
$where[] = $tagWhere;
}
/* Only get stuff we have permission for */
@@ -725,16 +823,271 @@ class _Query extends \IPS\Content\Search\Query
$where[] = $this->hiddenClause;
}
/* MOST PERTINENT search? */
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
/* Filer by items I posted in? */
if ( $this->filterByItemsIPostedIn and $this->member->member_id )
{
$this->select['main'] .= ',' . "( ( MATCH (index_title) AGAINST ('" . \IPS\Db::i()->escape_string( $term ) ."' IN BOOLEAN MODE) * 5 ) + (MATCH ({$indexes}) AGAINST ('" . \IPS\Db::i()->escape_string( $term ) ."' IN BOOLEAN MODE) ) ) / POWER( ( ( UNIX_TIMESTAMP( NOW() ) - index_date_updated ) / 3600 ) + 2, 1.5 ) AS calcscore";
/* Another area where a small result set can drastically slow down the entire query */
try
{
/* Work out what classes we need to examine */
if ( $this->itemClasses !== NULL )
{
$classes = $this->itemClasses;
}
else
{
$classes = array();
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
{
$classes = array_merge( $object->classes, $classes );
}
}
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
if ( count( $results ) == 501 )
{
/* Assume we have loads of matches, so do a full query */
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
$query = \IPS\Db::i()->select( 'index_item_id', array( 'core_search_index_item_map', 'sub' ), $subClause );
$where[] = array( 'EXISTS(?)', $query );
}
else if ( count( $results ) )
{
$classIds = array();
foreach( $results as $result )
{
$classIds[ $result['index_class'] ][] = $result['index_item_id'];
}
$subWhere = array();
foreach( $classIds as $class => $ids )
{
$stack = array( $class );
if ( isset( $class::$commentClass ) )
{
$stack[] = $class::$commentClass;
}
$subWhere[] = '(' . \IPS\Db::i()->in( 'index_class', $stack ) . ' and ' . \IPS\Db::i()->in( 'index_item_id', $ids ) . ')';
}
/* IN is not a SIN. It's been a long day */
$where[] = '(' . implode( ' OR ', $subWhere ) . ')';
}
else
{
/* There are no results */
$where[] = "1=2 /*Filter by items I posted in returned nothing*/";
}
}
catch( \UnderflowException $ex )
{
/* There are no results */
$where[] = "1=2 /*Filter by items I posted in returned nothing*/";
}
}
/* Filter by last updated? */
if ( $this->lastUpdatedClause !== NULL )
{
foreach( $this->lastUpdatedClause as $clause )
{
$where[] = $clause;
}
}
/* Return */
return $where;
}
/**
* Makes an embedded search clause to convert comment classes to item classes
*
* @param string $column Column to use in the clause
* @return string
*/
protected function makeEmbeddedIfClauseForSearchMapTable( $column )
{
/* Work out what classes we need to examine */
if ( $this->itemClasses !== NULL )
{
$classes = $this->itemClasses;
}
else
{
$classes = array();
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
{
$classes = array_merge( $object->classes, $classes );
}
}
$stack = array();
foreach( $classes as $class )
{
if ( isset( $class::$commentClass ) )
{
$stack[ $class::$commentClass ] = $class;
}
}
if ( count( $stack ) )
{
$if = '';
foreach( $stack as $commentClass => $class )
{
$if .= " IF( main.index_class='" . \IPS\Db::i()->escape_string( $commentClass ) . "', '" . \IPS\Db::i()->escape_string( $class ) . "', ";
}
$if .= 'main.index_class' . str_repeat( ' )', count( $stack ) );
}
return $column . '=' . ( $if == '' ? $column : $if );
}
/**
* Match clause
*
* @param string $columns Columns to match against (e.g. "index_tags" or "index_title,index_content")
* @param string $term The term
* @param string $defaultOperator The default operator to add to each word if there isn't one - "+" is "and mode"; "" is "or mode"
* @param bool $prepared If FALSE, does not use prepared statements (used for the sorting algorithm because you can't use ?s in the select clause)
* @return array|string
*/
public static function matchClause( $columns, $term, $defaultOperator='+', $prepared=TRUE )
{
/* Loop the words */
$words = array();
/* If we have a phrase, we'd normally use the boolean engine to search it, but this is incredibly slow on larger tables (5+ million rows) because MySQL searches all the words in the word index
but if some words are below the minimum length, it performs a full table scan which is very expensive.
The solution here is to break down the phrase (i love the chocolate cake) into usable keywords ('love +chocolate +cake') and back up the full text results with a LIKE '%i love the chocolate cake%'
search which is actually very efficient as MySQL performs a search on the FT index first to narrow down the results */
if ( static::termIsPhrase( $term ) )
{
$term = str_replace( array( "'", '"' ), '', $term );
foreach ( static::termAsWordsArray( $term ) as $word )
{
/* Operators are not allowed */
$word = preg_replace( '/^([\+\-~<>]){1,}/', '', $word );
$word = preg_replace( '/([\+\-~\*<>]){2,}$/', '$1', $word );
/* +* and +- are not allowed anywhere in the word */
$word = str_replace( array( '+*', '+-', '+' ), '', $word );
/* Trailing + or -s are not allowed */
$word = rtrim( $word, '+-' );
/* Nor is @ or parenthesis (while paranthesis can be used to group words and apply operators to the group,
(e.g. "+apple +(>turnover <strudel)") - it's unlikely a user intends this behaviour) */
$word = str_replace( array( '(', ')' ), '', str_replace( '@', ' ', $word ) );
if ( $word )
{
$words[] = $word;
}
}
$booleanTerm = implode( ' +', $words );
/* Return */
if ( $prepared )
{
if ( mb_strstr( $columns, ',' ) )
{
$like = array();
$extra = '';
foreach( explode( ',', $columns ) as $col )
{
$like[] = $col . ' LIKE \'%' . \IPS\Db::i()->escape_string( $term ) . '%\'';
}
$extra = implode( ' OR ', $like );
}
else
{
$extra = $columns . ' LIKE \'%' . \IPS\Db::i()->escape_string( $term ) . '%\'';
}
return array( "MATCH({$columns}) AGAINST (? IN BOOLEAN MODE) AND (" . $extra . ")", $booleanTerm );
}
else
{
return "MATCH({$columns}) AGAINST ('" . \IPS\Db::i()->escape_string( $booleanTerm ) . "' IN BOOLEAN MODE)";
}
}
else
{
foreach ( static::termAsWordsArray( $term ) as $word )
{
/* Add the default operator */
if ( $defaultOperator and !in_array( mb_substr( $word, 0, 1 ), array( '+', '-', '~', '<', '>' ) ) )
{
/* Clear out leading * symbols so we don't end up with +*a** */
$word = $defaultOperator . ltrim( $word, '*' );
}
/* Double operators are not allowed */
$word = preg_replace( '/^([\+\-~\*<>]){2,}/', '$1', $word );
$word = preg_replace( '/([\+\-~\*<>]){2,}$/', '$1', $word );
/* Trailing + or -s are not allowed */
$word = rtrim( $word, '+-' );
/* These rules only apply if we're not in a quoted phrase... */
if ( !static::termIsPhrase( $word ) )
{
/* We can't have any other operators as MySQL will interpret them as a separate word. If they exist, wrap the word in quotes */
if ( preg_match( '/^.+[\+\-~<>]/', $word ) )
{
$word = "\"" . str_replace( '"', '', ltrim( $word, $defaultOperator ) ) . "\"";
}
/* Otherwise carry on... */
else
{
/* +* and +- are not allowed anywhere in the word */
$word = str_replace( array( '+*', '+-' ), '+', $word );
/* Nor is @ or parenthesis (while paranthesis can be used to group words and apply operators to the group,
(e.g. "+apple +(>turnover <strudel)") - it's unlikely a user intends this behaviour) */
$word = str_replace( array( '(', ')' ), '', str_replace( '@', ' ', $word ) );
}
}
/* Add it */
$words[] = $word;
}
$term = implode( ' ', $words );
/* Return */
if ( $prepared )
{
if ( count( $words ) > 1 )
{
return array( "MATCH({$columns}) AGAINST (? IN BOOLEAN MODE)", $term );
}
else
{
return array( "MATCH({$columns}) AGAINST (?)", ltrim( $term, $defaultOperator ) );
}
}
else
{
if ( count( $words ) > 1 )
{
return "MATCH({$columns}) AGAINST ('" . \IPS\Db::i()->escape_string( $term ) . "' IN BOOLEAN MODE)";
}
else
{
return "MATCH({$columns}) AGAINST ('" . \IPS\Db::i()->escape_string( ltrim( $term, $defaultOperator ) ) . "')";
}
}
}
}
/**
* Search
*
@@ -745,14 +1098,55 @@ class _Query extends \IPS\Content\Search\Query
*/
public function search( $term = NULL, $tags = NULL, $method = 1 )
{
/* Set the select clause */
$select = implode( ', ', $this->select );
/* Get the where clause */
$where = array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) );
$query = \IPS\Db::i()->select( implode( ', ', $this->select ), array( 'core_search_index', 'main' ), $where, $this->order, array( $this->offset, $this->resultsToGet ), NULL, NULL );
/* Set order clause */
$order = $this->order;
/* But we're sorting by relevancy, we need to actually select that value with our fancy algorithm */
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
{
/* But we can only do that if there's a term (rather than tag-only) */
if ( $term !== NULL )
{
$select .= ', '
. '('
. '(' . static::matchClause( 'index_title', $term, $method & static::TERM_OR_MODE ? '' : '+', FALSE ) . '*5)' // The title score times 5
. '+'
. '(' . static::matchClause( 'index_content,index_title', $term, $method & static::TERM_OR_MODE ? '' : '+', FALSE ) . ')' // Plus the content score times 1
. ')'
. '/' // Divided by
. 'POWER('
. '( ( UNIX_TIMESTAMP( NOW() ) - ( CASE WHEN index_date_updated <= UNIX_TIMESTAMP( NOW() ) THEN index_date_updated ELSE 0 END )) / 3600 ) + 2' // The number of days between now and the updated date, plus 2
. ',1.5)' // To the power of 1.5
. ' AS calcscore';
}
/* So if we don't have a term, fallback to last updated */
else
{
$order = 'index_date_updated DESC';
}
}
/* Construct the query */
$query = \IPS\Db::i()->select( $select, array( 'core_search_index', 'main' ), $where, $order, array( $this->offset, $this->resultsToGet ), NULL, NULL );
foreach ( $this->joins as $data )
{
$query->join( $data['from'], $data['where'], isset( $data['type'] ) ? $data['type'] : 'LEFT' );
}
$count = $this->count( $term, $tags, $method );
/* Force index? */
if ( $this->forceIndex )
{
$query->forceIndex( $this->forceIndex );
}
/* Return */
$count = $this->count( $term, $tags, $method, FALSE );
return new \IPS\Content\Search\Results( iterator_to_array( $query ), $count );
}
@@ -762,17 +1156,69 @@ class _Query extends \IPS\Content\Search\Query
* @param string|null $term The term to search for
* @param array|null $tags The tags to search for
* @param int $method \IPS\Content\Search\Index::i()->TERM_OR_TAGS or \IPS\Content\Search\Index::i()->TERM_AND_TAGS
* @return int
* @param boolean $returnCountAsInt If TRUE, it will return the count as an integer, when FALSE it will return the \IPS\Db\Select object
* @return \IPS\Db\Query|int
*/
public function count( $term = NULL, $tags = NULL, $method = 1 )
public function count( $term = NULL, $tags = NULL, $method = 1, $returnCountAsInt=TRUE )
{
$query = \IPS\Db::i()->select( 'COUNT(*)', array( 'core_search_index', 'main' ), array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) ) );
/* Get the where clause */
$where = array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) );
/* Construct the query */
$query = \IPS\Db::i()->select( 'COUNT(*)', array( 'core_search_index', 'main' ), $where );
foreach ( $this->joins as $data )
{
$query->join( $data['from'], $data['where'], isset( $data['type'] ) ? $data['type'] : 'LEFT' );
}
return $query->first();
/* Return */
return ( ! $returnCountAsInt ) ? $query : $query->first();
}
/**
* Get the default date cut off
*
* @return string
*/
public function getDefaultDateCutOff()
{
if ( static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
{
return 'year';
}
return 'any';
}
/**
* Get the default sort method
*
* @return string
*/
public function getDefaultSortMethod()
{
if ( static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
{
return 'newest';
}
return 'relevancy';
}
protected static $tableCount = NULL;
/**
* Get the row count from the core_search_index table
*
* @return string
*/
protected static function getTableSize()
{
if ( static::$tableCount === NULL )
{
static::$tableCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index' )->first();
}
return static::$tableCount;
}
}
+155 -33
View File
@@ -2,9 +2,9 @@
/**
* @brief Abstract Search Query
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -27,6 +27,7 @@ abstract class _Query
const TERM_AND_TAGS = 2;
const TERM_OR_MODE = 4;
const TERM_TITLES_ONLY = 8;
const TAGS_MATCH_ITEMS_ONLY = 16;
const HIDDEN_VISIBLE = 0;
const HIDDEN_UNAPPROVED = 1;
@@ -80,22 +81,13 @@ abstract class _Query
/* Exclude disabled applications */
$filters = array();
foreach ( \IPS\Application::applications() as $application )
foreach ( \IPS\Content::routedClasses( $member, FALSE, TRUE ) as $class )
{
if ( !$application->enabled )
{
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $extension )
{
foreach ( $extension->classes as $class )
{
$filters[] = \IPS\Content\Search\ContentFilter::init( $class );
}
}
}
$filters[] = \IPS\Content\Search\ContentFilter::init( $class );
}
if ( !empty( $filters ) )
{
$this->filterByContent( $filters, FALSE );
$this->filterByContent( $filters );
}
}
@@ -231,6 +223,26 @@ abstract class _Query
*/
abstract public function search( $term = NULL, $tags = NULL, $method = 1 );
/**
* Get the default date cut off
*
* @return string
*/
public function getDefaultDateCutOff()
{
return 'any';
}
/**
* Get the default sort method
*
* @return string
*/
public function getDefaultSortMethod()
{
return 'relevancy';
}
/**
* Get count
*
@@ -256,38 +268,148 @@ abstract class _Query
}
/**
* Term as words array
* Returns an array of words. A word is considered as either an entire phrase (eg "sticks stones") when $ignorePhrase is FALSE, or a sequence of characters delimited by a space.
* Words smaller than 2 characters are ignored.
* Convert the term into an array of words
*
* @param string $term The term to search for
* @param boolean $ignorePhrase When true, phrases are stripped of quotes and treated as normal words
* @param int $minLength The minimum length a sequence of characters has to be before it is considered a word
* @param string $term The term to search for
* @param boolean $ignorePhrase When true, phrases are stripped of quotes and treated as normal words
* @param int|NULL $minLength The minimum length a sequence of characters has to be before it is considered a word. If null, ft_min_word_len/innodb_ft_min_token_size is used.
* @param int|NULL $maxLength The maximum length a sequence of characters can be for it to be considered a word. If null, ft_max_word_len/innodb_ft_max_token_size is used.
* @return array
*/
public static function termAsWordsArray( $term, $ignorePhrase=FALSE, $minLength=3 )
{
if ( static::termIsPhrase( $term ) )
public static function termAsWordsArray( $term, $ignorePhrase=FALSE, $minLength=NULL, $maxLength=NULL )
{
/* If we haven't set a preferred min/max length, use the MySQL configuration */
if ( $minLength === NULL or $maxLength === NULL )
{
if ( ! $ignorePhrase )
/* If we don't already know what they are, get the values from the MySQL configuration */
if ( ( $minLength === NULL and !isset( \IPS\Data\Store::i()->mysqlMinWord ) ) or ( $maxLength === NULL and !isset( \IPS\Data\Store::i()->mysqlMaxWord ) ) )
{
return array( $term );
/* The variable we need depends on whether the table is MyISAM or InnoDB */
$tableDefinition = \IPS\Db::i()->getTableDefinition('core_search_index');
if ( $tableDefinition['engine'] == 'InnoDB' )
{
$minVariable = 'innodb_ft_min_token_size';
$maxVariable = 'innodb_ft_max_token_size';
}
else
{
$minVariable = 'ft_min_word_len';
$maxVariable = 'ft_max_word_len';
}
/* Now fetch those */
try
{
foreach ( new \IPS\Db\Select( 'SHOW VARIABLES WHERE Variable_Name=? OR Variable_Name=?', array( $minVariable, $maxVariable ), \IPS\Db::i() ) as $row )
{
if ( $row['Variable_name'] === $minVariable )
{
\IPS\Data\Store::i()->mysqlMinWord = intval( $row['Value'] );
}
elseif ( $row['Variable_name'] === $maxVariable )
{
\IPS\Data\Store::i()->mysqlMaxWord = intval( $row['Value'] );
}
}
}
catch( \IPS\Db\Exception $e ) { }
/* If we weren't able to get them, set sensible defaults */
if ( !isset( \IPS\Data\Store::i()->mysqlMinWord ) )
{
\IPS\Data\Store::i()->mysqlMinWord = 3;
}
if ( !isset( \IPS\Data\Store::i()->mysqlMaxWord ) )
{
\IPS\Data\Store::i()->mysqlMaxWord = 84;
}
}
else
/* Set */
if ( $minLength === NULL )
{
$term = str_replace( '"', '', $term );
$minLength = \IPS\Data\Store::i()->mysqlMinWord;
}
if ( $maxLength === NULL )
{
$maxLength = \IPS\Data\Store::i()->mysqlMaxWord;
}
}
$theWords = array();
foreach( explode( ' ', $term ) as $_term )
/* Sub quotes with smaller than min word strings can cause issues for larger tables, so we convert the sub quote into normal words for a boolean search */
if ( ! static::termIsPhrase( $term ) )
{
if ( mb_strlen( $_term ) > $minLength )
preg_match_all( '#"([^"]+?)"#', $term, $matches, PREG_SET_ORDER );
foreach( $matches as $match )
{
$theWords[] = $_term;
$allWordsInPhraseAreGood = true;
$subWords = array();
foreach( explode( ' ', $match[1] ) as $subWord )
{
if ( mb_strlen( $subWord ) <= $minLength or mb_strlen( $subWord ) >= $maxLength )
{
$allWordsInPhraseAreGood = false;
}
else
{
$subWords[] = $subWord;
}
}
if ( ! $allWordsInPhraseAreGood )
{
$term = str_replace( $match[0], implode( ' ', $subWords ), $term );
}
}
}
return $theWords;
/* Parse */
$words = array();
$currentWord = '';
$inQuote = false;
$termLength = mb_strlen( $term );
for ( $i = 0; $i < $termLength; $i++ )
{
$c = mb_substr( $term, $i, 1 );
if ( $c == '"' )
{
if ( $ignorePhrase )
{
continue;
}
$inQuote = !$inQuote;
}
elseif ( $c == ' ' and !$inQuote )
{
$words[] = trim( $currentWord );
$currentWord = '';
}
$currentWord .= $c;
}
$words[] = trim( $currentWord );
/* Now check each of the words is acceptable */
$finalWords = array();
foreach( $words as $word )
{
if ( mb_strlen( $word ) >= $minLength and mb_strlen( $word ) <= $maxLength )
{
$finalWords[] = $word;
}
}
/* And return */
return $finalWords;
}
/**
* Is a rebuild task running?
*
* @return boolean
*/
public static function isRebuildRunning()
{
return \IPS\Db::i()->select( 'COUNT(*)', 'core_queue', array( '`key`=?', 'RebuildSearchIndex' ) )->first() ? TRUE : FALSE;
}
}
+26 -6
View File
@@ -2,9 +2,9 @@
/**
* @brief Search Result from Index
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 15 Sep 2015
* @version SVN_VERSION_NUMBER
*/
@@ -59,7 +59,7 @@ class _Content extends \IPS\Content\Search\Result
* @param array $indexData Data from index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the author. Only includes columns returned by container::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
@@ -83,11 +83,31 @@ class _Content extends \IPS\Content\Search\Result
*
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array|NULL $template Optional custom template
* @return string
*/
public function html( $view = 'expanded', $asItem = FALSE )
public function html( $view = 'expanded', $asItem = FALSE, $canIgnoreComments=FALSE, $template=NULL )
{
return call_user_func( array( $this->indexData['index_class'], 'searchResult' ), $this->indexData, $this->authorData, $this->itemData, $this->containerData, $this->reputationData, $this->reviewRating, $this->iPostedIn, $view, $asItem );
return call_user_func( array( $this->indexData['index_class'], 'searchResult' ), $this->indexData, $this->authorData, $this->itemData, $this->containerData, $this->reputationData, $this->reviewRating, $this->iPostedIn, $view, $asItem, $canIgnoreComments, $template );
}
/**
* Return search index data as an array
*
* @return array( 'indexData' => array() ... )
*/
public function asArray()
{
return array(
'indexData' => $this->indexData,
'authorData' => $this->authorData,
'itemData' => $this->itemData,
'containerData' => $this->containerData,
'reputationData' => $this->reputationData,
'reviewRating' => $this->reviewRating,
'iPostedIn' => $this->iPostedIn
);
}
/**
@@ -100,6 +120,6 @@ class _Content extends \IPS\Content\Search\Result
{
$class = $this->indexData['index_class'];
$object = $class::load( $this->indexData['index_object_id'] );
$document->addItem( $object->mapped('title'), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ) );
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->mapped('title') : $object->mapped('title'), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ) );
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Search Result not from Index
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 15 Sep 2015
* @version SVN_VERSION_NUMBER
*/
+8 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Search Result
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 15 Sep 2015
* @version SVN_VERSION_NUMBER
*/
@@ -47,7 +47,12 @@ abstract class _Result
$yesterday = clone $now;
$yesterday = $yesterday->sub( new \DateInterval('P1D') );
$diff = $date->diff( $now );
if ( $diff->h < 1 && !$diff->d && !$diff->m )
if ( $date->format('Y-m-d') == $yesterday->format('Y-m-d') )
{
return 'yesterday';
}
elseif ( $diff->h < 1 && !$diff->d && !$diff->m )
{
return 'past_hour';
}
@@ -55,10 +60,6 @@ abstract class _Result
{
return 'today';
}
elseif ( $date->format('Y-m-d') == $yesterday->format('Y-m-d') )
{
return 'yesterday';
}
elseif ( !$diff->y and !$diff->m and $diff->d < 7 )
{
return 'last_week';
+159 -78
View File
@@ -2,9 +2,9 @@
/**
* @brief Search Results
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
@@ -62,12 +62,17 @@ class _Results extends \ArrayIterator
* @brief Index IDs of items I have posted in
*/
protected $iPostedIn = array();
/**
* @brief Tag values for index_ids returned from search
*/
protected $tags = array();
/**
* Constructor
*
* @param array $results The results
* @param int $countAllRows Count for all rows
* @param array $results The results
* @param \IPS\Db\Select|int $countAllRows Count for all rows query or int
* @return void
*/
public function __construct( $results, $countAllRows )
@@ -90,6 +95,7 @@ class _Results extends \ArrayIterator
$reputationIds = array();
$reviewRatings = array();
$itemIndexIds = array();
$indexIds = array();
/* Loop */
foreach ( $this->getArrayCopy() as $result )
@@ -173,7 +179,10 @@ class _Results extends \ArrayIterator
}
/* Load author data */
$this->authorData = iterator_to_array( \IPS\Db::i()->select( \IPS\Member::columnsForPhoto(), 'core_members', \IPS\Db::i()->in( 'member_id', $membersToLoad ) )->setKeyField( 'member_id' ) );
if( count( $membersToLoad ) )
{
$this->authorData = iterator_to_array( \IPS\Db::i()->select( \IPS\Member::columnsForPhoto(), 'core_members', \IPS\Db::i()->in( 'member_id', $membersToLoad ) )->setKeyField( 'member_id' ) );
}
/* Load container data */
foreach ( $containersToLoad as $containerClass => $containerIds )
@@ -221,9 +230,16 @@ class _Results extends \ArrayIterator
/* Load data for the "I posted in this" stars */
if ( count( $itemIndexIds ) and \IPS\Member::loggedIn()->member_id )
{
$this->iPostedIn = iterator_to_array( \IPS\Db::i()->select( 'index_item_index_id', 'core_search_index', array( array( \IPS\Db::i()->in( 'index_item_index_id', $itemIndexIds ) ), array( 'index_author=?', \IPS\Member::loggedIn()->member_id ) ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT ) );
$this->iPostedIn = iterator_to_array( \IPS\Db::i()->select( 'index_item_index_id', 'core_search_index', array( array( \IPS\Db::i()->in( 'index_item_index_id', $itemIndexIds ) ), array( 'index_author=?', \IPS\Member::loggedIn()->member_id ) ) )->setKeyField('index_item_index_id') );
}
/* Load tags and prefixes */
if ( count( $itemIndexIds ) )
{
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag) as index_tags', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 0' ) ), NULL, NULL, 'index_id' )->setKeyField('index_id') );
$this->prefixes = iterator_to_array( \IPS\Db::i()->select( 'index_id, index_tag as index_prefix', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 1' ) ) )->setKeyField('index_id') );
}
/* Set that we're initiated */
$this->initated = TRUE;
}
@@ -236,7 +252,7 @@ class _Results extends \ArrayIterator
public function current()
{
$data = parent::current();
$class = $data['index_class'];
$itemClass = in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
$containerClass = isset( $itemClass::$containerNodeClass ) ? $itemClass::$containerNodeClass : NULL;
@@ -247,6 +263,14 @@ class _Results extends \ArrayIterator
$reputationData = $this->reputationData[ $class::$application ][ $class::$reputationType ][ $data['index_object_id'] ];
}
/* Ensure we have an item if results are gathered from the map table */
if ( ! isset( $this->itemData[ $itemClass ][ $data['index_item_id'] ] ) )
{
/* This should not happen in production, so debug log and move on to the next item */
\IPS\Log::debug( "index_item_id ID " . $data['index_item_id'] . " for " . $itemClass . " has no itemData" , 'searchResults' );
return static::next();
}
$itemData = $this->itemData[ $itemClass ][ $data['index_item_id'] ];
if ( $class == 'IPS\core\Statuses\Status' and $data['index_container_id'] != $data['index_author'] )
{
@@ -260,6 +284,16 @@ class _Results extends \ArrayIterator
$itemData['author'] = $this->authorData[ $memberId ];
}
}
if ( isset( $this->tags[ $data['index_item_index_id'] ] ) )
{
$data['index_tags'] = $this->tags[ $data['index_item_index_id'] ]['index_tags'];
}
if ( isset( $this->prefixes[ $data['index_item_index_id'] ]) )
{
$data['index_prefix'] = $this->prefixes[ $data['index_item_index_id'] ]['index_prefix'];
}
return new Result\Content(
$data,
@@ -296,7 +330,20 @@ class _Results extends \ArrayIterator
{
if ( $allRows )
{
return $this->countAllRows;
if ( is_integer( $this->countAllRows ) )
{
return $this->countAllRows;
}
else if ( gettype( $this->countAllRows ) === 'object' and get_class( $this->countAllRows ) === 'IPS\Db\Select' )
{
$this->countAllRows = $this->countAllRows->first();
return $this->countAllRows;
}
else
{
throw new \LogicException;
}
}
else
{
@@ -431,6 +478,9 @@ class _Results extends \ArrayIterator
}
}
/* If an application was not updated or is not installed, do not try to fetch it or we can get a class does not exist fatal error */
$where[] = array( "follow_app IN('" . implode( "','", array_keys( \IPS\Application::enabledApplications() ) ) . "')" );
foreach ( \IPS\Db::i()->select( array( 'follow_app', 'follow_area', 'follow_rel_id', 'follow_member_id', 'follow_added' ), 'core_follow', $where, 'follow_added DESC', 10 ) as $follow )
{
if ( $follow['follow_app'] == 'core' and $follow['follow_area'] == 'member' )
@@ -445,12 +495,16 @@ class _Results extends \ArrayIterator
$class = 'IPS\\' . $follow['follow_app'] . '\\' . ucfirst( $follow['follow_area'] );
try
{
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
$extraItems[] = new Result\Custom(
\IPS\DateTime::ts( $follow['follow_added'] ),
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_followed', FALSE, array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $follow['follow_member_id'] ) ), \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingFollowed->url(), FALSE, $thingBeingFollowed instanceof \IPS\Node\Model ? $thingBeingFollowed->_title : $thingBeingFollowed->mapped('title'), FALSE ) ) ) )
);
if ( class_exists( $class ) )
{
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
$extraItems[] = new Result\Custom(
\IPS\DateTime::ts( $follow['follow_added'] ),
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_followed', FALSE, array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $follow['follow_member_id'] ) ), \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingFollowed->url(), FALSE, $thingBeingFollowed instanceof \IPS\Node\Model ? $thingBeingFollowed->_title : $thingBeingFollowed->mapped('title'), FALSE ) ) ) )
);
}
}
catch ( \OutOfRangeException $e ) { }
}
@@ -458,7 +512,7 @@ class _Results extends \ArrayIterator
}
/* Likes */
if ( in_array( 'like', $extra ) or in_array( 'rep_neg', $extra ) )
if ( \IPS\Settings::i()->reputation_enabled and \IPS\Member::loggedIn()->group['gbw_view_reps'] and ( in_array( 'like', $extra ) or in_array( 'rep_neg', $extra ) ) )
{
$where = array( array( 'rep_date>?', $lastTime ) );
if ( $firstTime )
@@ -473,72 +527,28 @@ class _Results extends \ArrayIterator
{
$where[] = array( 'rep_rating=-1' );
}
/* get only the reputation data for installed applications */
$where[] = \IPS\Db::i()->in( 'app', array_keys( \IPS\Application::applications() ) ) ;
if ( $author )
{
$where[] = array( '( member_id=? OR member_received=? )', $author->member_id, $author->member_id );
}
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'rep_date DESC', 10 ) as $rep )
{
try
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array_merge( $where, array( array( 'member_id=?', $author->member_id ) ) ), 'rep_date DESC', 10 ) as $rep )
{
$thingBeingRepped = NULL;
foreach ( \IPS\Application::load( $rep['app'] )->extensions( 'core', 'ContentRouter', TRUE, TRUE ) as $ext )
{
foreach ( $ext->classes as $class )
{
if ( in_array( 'IPS\Content\Reputation', class_implements( $class ) ) and $class::$reputationType == $rep['type'] )
{
$thingBeingRepped = $class::loadAndCheckPerms( $rep['type_id'] );
break;
}
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
if ( in_array( 'IPS\Content\Reputation', class_implements( $commentClass ) ) and $commentClass::$reputationType == $rep['type'] )
{
$thingBeingRepped = $commentClass::loadAndCheckPerms( $rep['type_id'] );
break;
}
}
if ( isset( $class::$reviewClass ) )
{
$reviewClass = $class::$reviewClass;
if ( in_array( 'IPS\Content\Reputation', class_implements( $reviewClass ) ) and $reviewClass::$reputationType == $rep['type'] )
{
$thingBeingRepped = $reviewClass::loadAndCheckPerms( $rep['type_id'] );
break;
}
}
}
}
if ( !$thingBeingRepped )
{
throw new \OutOfRangeException;
}
if ( \IPS\Settings::i()->reputation_point_types == 'like' )
{
$lang = 'activity_member_liked';
}
elseif ( $rep['rep_rating'] > 0 )
{
$lang = 'activity_rep_pos';
}
else
{
$lang = 'activity_rep_neg';
}
$extraItems[] = new Result\Custom(
\IPS\DateTime::ts( $rep['rep_date'] ),
\IPS\Member::loggedIn()->language()->addToStack( $lang, FALSE, array( 'htmlsprintf' => array(
\IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $rep['member_id'] ) ),
$thingBeingRepped->indefiniteArticle(),
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingRepped->url(), FALSE, $thingBeingRepped instanceof \IPS\Content\Item ? $thingBeingRepped->mapped('title') : $thingBeingRepped->item()->mapped('title'), FALSE )
) ) )
);
$this->addReputationExtraItem( $rep, $extraItems );
}
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array_merge( $where, array( array( 'member_received=?', $author->member_id ) ) ), 'rep_date DESC', 10 ) as $rep )
{
$this->addReputationExtraItem( $rep, $extraItems );
}
}
else
{
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'rep_date DESC', 10 ) as $rep )
{
$this->addReputationExtraItem( $rep, $extraItems );
}
catch ( \OutOfRangeException $e ) { }
}
}
}
@@ -567,4 +577,75 @@ class _Results extends \ArrayIterator
/* And return */
return $results;
}
/**
* Add reputation given/received to extra things array
*
* @param array $rep Reputation row from database
* @param array $extraItems Array of extra items (passed by reference)
* @return void
*/
protected function addReputationExtraItem( $rep, &$extraItems )
{
try
{
$thingBeingRepped = NULL;
foreach ( \IPS\Application::load( $rep['app'] )->extensions( 'core', 'ContentRouter', TRUE, TRUE ) as $ext )
{
foreach ( $ext->classes as $class )
{
if ( in_array( 'IPS\Content\Reputation', class_implements( $class ) ) and $class::$reputationType == $rep['type'] )
{
$thingBeingRepped = $class::loadAndCheckPerms( $rep['type_id'] );
break;
}
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
if ( in_array( 'IPS\Content\Reputation', class_implements( $commentClass ) ) and $commentClass::$reputationType == $rep['type'] )
{
$thingBeingRepped = $commentClass::loadAndCheckPerms( $rep['type_id'] );
break;
}
}
if ( isset( $class::$reviewClass ) )
{
$reviewClass = $class::$reviewClass;
if ( in_array( 'IPS\Content\Reputation', class_implements( $reviewClass ) ) and $reviewClass::$reputationType == $rep['type'] )
{
$thingBeingRepped = $reviewClass::loadAndCheckPerms( $rep['type_id'] );
break;
}
}
}
}
if ( !$thingBeingRepped )
{
throw new \OutOfRangeException;
}
if ( \IPS\Settings::i()->reputation_point_types == 'like' )
{
$lang = 'activity_member_liked';
}
elseif ( $rep['rep_rating'] > 0 )
{
$lang = 'activity_rep_pos';
}
else
{
$lang = 'activity_rep_neg';
}
$extraItems[] = new Result\Custom(
\IPS\DateTime::ts( $rep['rep_date'] ),
\IPS\Member::loggedIn()->language()->addToStack( $lang, FALSE, array( 'htmlsprintf' => array(
\IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $rep['member_id'] ) ),
$thingBeingRepped->indefiniteArticle(),
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingRepped->url(), FALSE, $thingBeingRepped instanceof \IPS\Content\Item ? $thingBeingRepped->mapped('title') : $thingBeingRepped->item()->mapped('title'), FALSE )
) ) )
);
}
catch ( \OutOfRangeException $e ) { }
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Interface for Searchable Content/Comment Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
+4 -4
View File
@@ -2,9 +2,9 @@
/**
* @brief Delicious share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='https://delicious.com/tools'>Delicious button documentation</a>
@@ -47,7 +47,7 @@ class _Delicious
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
@@ -75,7 +75,7 @@ class _Delicious
*/
public function __toString()
{
$url = \IPS\Http\Url::external( "http://delicious.com/save?jump=close&noui=1&v=5" )->setQueryString( 'provider', urlencode( \IPS\Settings::i()->board_name ) );
$url = \IPS\Http\Url::external( "https://del.icio.us/save?jump=close&noui=1&v=5" )->setQueryString( 'provider', urlencode( \IPS\Settings::i()->board_name ) );
if( $this->url !== NULL )
{
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Digg share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='http://ftllc.com/articles/social-media-buttons.php'>Digg button</a> (This was the best I could find...Digg has removed most of their button documentation it seems)
@@ -48,7 +48,7 @@ class _Digg
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Email share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
*/
@@ -45,7 +45,7 @@ class _Email
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
+18 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Facebook share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='https://developers.facebook.com/docs/reference/plugins/like/'>Facebook like button documentation</a>
@@ -46,7 +46,7 @@ class _Facebook
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
@@ -73,7 +73,7 @@ class _Facebook
/* If we include a URL, we'll post it as a link to the wall, otherwise we'll just update the user's status */
if ( $url !== NULL )
{
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . \IPS\Member::loggedIn()->fb_uid . "/links" )->request()->post( array(
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . \IPS\Member::loggedIn()->fb_uid . "/feed" )->request()->post( array(
'message' => $content,
'link' => (string) $url,
'access_token' => \IPS\Member::loggedIn()->fb_token,
@@ -99,7 +99,7 @@ class _Facebook
}
catch( \IPS\Http\Request\Exception $e )
{
IPS\Log::i( LOG_WARNING )->write( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'facebook' );
\IPS\Log::log( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'facebook' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
}
@@ -113,12 +113,23 @@ class _Facebook
/**
* Add any additional form elements to the configuration form. These must be setting keys that the service configuration form can save as a setting.
*
* @param \IPS\Helpers\Form $form Configuration form for this service
* @param \IPS\Helpers\Form $form Configuration form for this service
* @param \IPS\core\ShareLinks\Service $service The service
* @return void
*/
public static function modifyForm( \IPS\Helpers\Form &$form )
public static function modifyForm( \IPS\Helpers\Form &$form, $service )
{
$form->add( new \IPS\Helpers\Form\Select( 'fbc_bot_group', \IPS\Settings::i()->fbc_bot_group, TRUE, array( 'options' => array_combine( array_keys( \IPS\Member\Group::groups() ), array_map( function( $_group ) { return (string) $_group; }, \IPS\Member\Group::groups() ) ) ) ) );
if ( array_key_exists( 'Facebook', \IPS\Login::handlers( TRUE ) ) )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Facebook', $service->autoshare, false ) );
}
else
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Facebook', FALSE, false, array( 'disabled' => TRUE ) ) );
\IPS\Member::loggedIn()->language()->words['share_autoshare_Facebook_desc'] = \IPS\Member::loggedIn()->language()->addToStack('share_autoshare_Facebook_disabled');
}
}
/**
@@ -2,9 +2,9 @@
/**
* @brief Google +1 share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='https://developers.google.com/+/web/+1button/'>Google +1 button documentation</a>
@@ -46,7 +46,7 @@ class _Googleplusone
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief Linked In share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='http://developer.linkedin.com/plugins/share-plugin-generator'>Linked In button documentation</a>
@@ -46,7 +46,7 @@ class _Linkedin
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
@@ -0,0 +1,54 @@
<?php
namespace IPS\Content\ShareServices;
class _Odnoklassniki
{
/**
* @brief URL to the content item
*/
protected $url = NULL;
/**
* @brief Title of the content item
*/
protected $title = NULL;
/**
* Constructor
*
* @param \IPS\Http\Url $url URL to the content [optional - if omitted, some services will figure out on their own]
* @param string $title Default text for the content, usually the title [optional - if omitted, some services will figure out on their own]
* @return void
*/
public function __construct( \IPS\Http\Url $url=NULL, $title=NULL )
{
$this->url = $url;
$this->title = $title;
}
/**
* Add any additional form elements to the configuration form. These must be setting keys that the service configuration form can save as a setting.
*
* @param \IPS\Helpers\Form $form Configuration form for this service
* @return void
*/
public static function modifyForm( \IPS\Helpers\Form &$form )
{
}
static function canAutoshare() {}
/**
* Return the HTML code to show the share link
*
* @return string
*/
public function __toString()
{
return '<a href="http://www.odnoklassniki.ru/dk?st.cmd=addShare&amp;st.s=1&amp;st._surl='.$this->url.'" class="cShareLink cShareLink_ok" style="background-color:#F4811F;" target="_blank" data-controller="core.front.core.sharelink" data-role="shareLink" _title="Поделиться в Одноклассниках" data-ipstooltip="">
<i class="fa fa-odnoklassniki"></i>
</a>';
}
}
@@ -0,0 +1,92 @@
<?php
/**
* @brief Pinterest share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @since 01 Dec 2016
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Content\ShareServices;
/* 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;
}
/**
* Pinterest share link
*/
class _Pinterest
{
/**
* @brief URL to the content item
*/
protected $url = NULL;
/**
* @brief Title of the content item
*/
protected $title = NULL;
/**
* @brief Ccontent item
*/
protected $item = NULL;
/**
* Constructor
*
* @param \IPS\Http\Url $url URL to the content [optional - if omitted, some services will figure out on their own]
* @param string $title Default text for the content, usually the title [optional - if omitted, some services will figure out on their own]
* @return void
*/
public function __construct( \IPS\Http\Url $url=NULL, $title=NULL, $item=NULL )
{
$this->url = $url;
$this->title = $title;
$this->item = $item;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
* @return boolean
*/
public static function canAutoshare()
{
return FALSE;
}
/**
* Add any additional form elements to the configuration form. These must be setting keys that the service configuration form can save as a setting.
*
* @param \IPS\Helpers\Form $form Configuration form for this service
* @param \IPS\core\ShareLinks\Service $service The service
* @return void
*/
public static function modifyForm( \IPS\Helpers\Form &$form, $service )
{
}
/**
* Return the HTML code to show the share link
*
* @return string
*/
public function __toString()
{
if ( $this->item )
{
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->pinterest( \IPS\Http\Url::external( 'http://pinterest.com/pin/create/button/' )->setQueryString( 'url', (string) $this->url )->setQueryString( 'media', (string) $this->item->shareImage() ) );
}
else
{
return '';
}
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Reddit share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='http://www.reddit.com/buttons/‎'>Reddit button documentation</a>
+3 -3
View File
@@ -2,9 +2,9 @@
/**
* @brief StumbleUpon share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='http://www.stumbleupon.com/dt/badges/create'>StumbleUpon button documentation</a>
@@ -47,7 +47,7 @@ class _Stumble
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
+30 -8
View File
@@ -2,9 +2,9 @@
/**
* @brief Twitter share link
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 11 Sept 2013
* @version SVN_VERSION_NUMBER
* @see <a href='https://dev.twitter.com/docs/tweet-button'>Tweet button documentation</a>
@@ -46,7 +46,7 @@ class _Twitter
$this->url = $url;
$this->title = $title;
}
/**
* Determine whether the logged in user has the ability to autoshare
*
@@ -93,7 +93,7 @@ class _Twitter
}
catch ( \IPS\Http\Request\Exception $e )
{
\IPS\Log::i( LOG_WARNING )->write( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'twitter' );
\IPS\Log::log( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'twitter' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
}
@@ -119,7 +119,7 @@ class _Twitter
}
catch ( \IPS\Http\Request\Exception $e )
{
\IPS\Log::i( LOG_WARNING )->write( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'twitter' );
\IPS\Log::log( \IPS\Member::loggedIn()->id . ': '. $e->getMessage(), 'twitter' );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
}
@@ -133,12 +133,23 @@ class _Twitter
/**
* Add any additional form elements to the configuration form. These must be setting keys that the service configuration form can save as a setting.
*
* @param \IPS\Helpers\Form $form Configuration form for this service
* @param \IPS\Helpers\Form $form Configuration form for this service
* @param \IPS\core\ShareLinks\Service $service The service
* @return void
*/
public static function modifyForm( \IPS\Helpers\Form &$form )
public static function modifyForm( \IPS\Helpers\Form &$form, $service )
{
$form->add( new \IPS\Helpers\Form\Text( 'twitter_hashtag', \IPS\Settings::i()->twitter_hashtag, FALSE ) );
if ( array_key_exists( 'Twitter', \IPS\Login::handlers( TRUE ) ) )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Twitter', $service->autoshare, false ) );
}
else
{
$form->add( new \IPS\Helpers\Form\YesNo( 'share_autoshare_Twitter', FALSE, false, array( 'disabled' => TRUE ) ) );
\IPS\Member::loggedIn()->language()->words['share_autoshare_Twitter_desc'] = \IPS\Member::loggedIn()->language()->addToStack('share_autoshare_Twitter_disabled');
}
}
/**
@@ -150,16 +161,27 @@ class _Twitter
{
try
{
$url = preg_replace_callback( "{[^0-9a-z_.!~*'();,/?:@&=+$#-]}i",
function ( $m )
{
return sprintf( '%%%02X', ord( $m[0] ) );
},
$this->url) ;
$title = $this->title ?: NULL;
if ( \IPS\Settings::i()->twitter_hashtag !== '')
{
$title .= ' ' . \IPS\Settings::i()->twitter_hashtag;
}
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->twitter( urlencode( $this->url ), rawurlencode( $title ) );
return \IPS\Theme::i()->getTemplate( 'sharelinks', 'core' )->twitter( urlencode( $url ), rawurlencode( $title ) );
}
catch ( \Exception $e )
{
\IPS\IPS::exceptionHandler( $e );
}
catch ( \Throwable $e )
{
\IPS\IPS::exceptionHandler( $e );
}
}
}
@@ -0,0 +1,54 @@
<?php
namespace IPS\Content\ShareServices;
class _Vkontakte
{
/**
* @brief URL to the content item
*/
protected $url = NULL;
/**
* @brief Title of the content item
*/
protected $title = NULL;
/**
* Constructor
*
* @param \IPS\Http\Url $url URL to the content [optional - if omitted, some services will figure out on their own]
* @param string $title Default text for the content, usually the title [optional - if omitted, some services will figure out on their own]
* @return void
*/
public function __construct( \IPS\Http\Url $url=NULL, $title=NULL )
{
$this->url = $url;
$this->title = $title;
}
/**
* Add any additional form elements to the configuration form. These must be setting keys that the service configuration form can save as a setting.
*
* @param \IPS\Helpers\Form $form Configuration form for this service
* @return void
*/
public static function modifyForm( \IPS\Helpers\Form &$form )
{
}
static function canAutoshare() {}
/**
* Return the HTML code to show the share link
*
* @return string
*/
public function __toString()
{
return '<a href="https://vk.com/share.php?url='.$this->url.'&title='.$this->title.'" class="cShareLink cShareLink_vk" style="background-color:#56789C;" target="_blank" data-controller="core.front.core.sharelink" data-role="shareLink" _title="Поделиться в Вконтакте" data-ipstooltip="">
<i class="fa fa-vk"></i>
</a>';
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Interface for Shareable Comment Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jul 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Tags interface for Content Models
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 14 Nov 2013
* @version SVN_VERSION_NUMBER
*/
+2 -2
View File
@@ -2,9 +2,9 @@
/**
* @brief Interface for Tracking Views
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 8 Jan 2014
* @version SVN_VERSION_NUMBER
*/
+97 -29
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Item Feed Widget
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage forums
* @since 16 Oct 2014
* @version SVN_VERSION_NUMBER
@@ -45,7 +45,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
}
/* Block title */
$form->add( new \IPS\Helpers\Form\Text( 'widget_feed_title', isset( $this->configuration['widget_feed_title'] ) ? $this->configuration['widget_feed_title'] : \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'widget_feed_title', isset( $this->configuration['language_key'] ) ? NULL : \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' ), FALSE, array( 'app' => 'cms', 'key' => ( isset( $this->configuration['language_key'] ) ? $this->configuration['language_key'] : NULL ) ) ) );
/* Container */
if ( isset( $class::$containerNodeClass ) )
@@ -54,7 +54,8 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
'class' => $class::$containerNodeClass,
'zeroVal' => 'all',
'permissionCheck' => 'view',
'multiple' => true
'multiple' => true,
'forceOwner' => false,
) ) );
}
@@ -95,6 +96,16 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
$form->add( new \IPS\Helpers\Form\Radio( 'widget_feed_status_featured', isset( $this->configuration['widget_feed_status_featured'] ) ? $this->configuration['widget_feed_status_featured'] : 'any', FALSE, array( 'options' => $types ) ) );
}
if ( in_array( 'IPS\Content\Hideable', class_implements( $class ) ) )
{
$types = array(
'any' => 'mod_confirm_either',
'visible' => 'mod_confirm_visible',
'hidden' => 'mod_confirm_hidden'
);
$form->add( new \IPS\Helpers\Form\Radio( 'widget_feed_comment_status_visible', isset( $this->configuration['widget_feed_comment_status_visible'] ) ? $this->configuration['widget_feed_comment_status_visible'] : 'any', FALSE, array( 'options' => $types ) ) );
}
/* Author */
$author = NULL;
@@ -188,6 +199,15 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
$values['widget_feed_author'] = $members;
}
if ( !isset( $this->configuration['language_key'] ) )
{
$this->configuration['language_key'] = 'widget_title_' . md5( uniqid() );
}
$values['language_key'] = $this->configuration['language_key'];
\IPS\Lang::saveCustom( 'core', $this->configuration['language_key'], $values['widget_feed_title'] );
unset( $values['widget_feed_title'] );
return $values;
}
@@ -245,7 +265,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
}
/* Author */
if ( isset( $this->configuration['widget_feed_author'] ) and is_array( $this->configuration['widget_feed_author'] ) )
if ( isset( $this->configuration['widget_feed_author'] ) and is_array( $this->configuration['widget_feed_author'] ) and count( $this->configuration['widget_feed_author'] ) )
{
$where[] = array( \IPS\Db::i()->in( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['author'], $this->configuration['widget_feed_author'] ) );
}
@@ -287,7 +307,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
public function render()
{
$class = static::$class;
$joins = NULL;
$where = $this->buildWhere();
if ( isset( $this->configuration['widget_feed_tags'] ) and is_array( $this->configuration['widget_feed_tags'] ) and count( $this->configuration['widget_feed_tags'] ) )
{
@@ -295,36 +315,84 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
$binds = array( $class::$application, $class::$module );
foreach( $this->configuration['widget_feed_tags'] as $tag )
{
$tagWhere[] = "( ? LIKE CONCAT( tag_text, '%') )";
$tagWhere[] = "( tag_text LIKE CONCAT( ?, '%') )";
$binds[] = $tag;
}
$joins = array( array(
'select' => 'tag_id',
'from' => 'core_tags',
'where' => array( array_merge( array( 'tag_meta_app=? AND tag_meta_area=? AND (' . implode( ' OR ', $tagWhere ) . ') and core_tags.tag_meta_id=' . $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId ), $binds ) ),
'type' => 'STRAIGHT_JOIN'
) );
$where[] = array( $class::$databaseColumnId . ' IN (?)', \IPS\Db::i()->select( 'tag_meta_id', 'core_tags', array( array_merge( array( 'tag_meta_app=? AND tag_meta_area=? AND (' . implode( ' OR ', $tagWhere ) . ')' ), $binds ) ) ) );
}
$items = $class::getItemsWithPermission(
$this->buildWhere(),
( isset( $this->configuration['widget_feed_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( $class::$databasePrefix . $this->configuration['widget_feed_sort_on'] . ' ' . $this->configuration['widget_feed_sort_dir'] ) : NULL,
isset( $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5,
( !isset( $this->configuration['widget_feed_use_perms'] ) or $this->configuration['widget_feed_use_perms'] ) ? 'read' : NULL,
FALSE,
0,
NULL,
FALSE,
FALSE,
FALSE,
FALSE,
$joins
);
/* What visible status are we checking? */
$hidden = \IPS\Content\Hideable::FILTER_AUTOMATIC;
if( isset( $this->configuration['widget_feed_comment_status_visible'] ) )
{
switch( $this->configuration['widget_feed_comment_status_visible'] )
{
case 'visible':
$hidden = \IPS\Content\Hideable::FILTER_PUBLIC_ONLY;
break;
case 'hidden':
$hidden = \IPS\Content\Hideable::FILTER_ONLY_HIDDEN;
break;
}
}
/* As the block config can try and search all rows in topics/records, etc, we can end up with a tmp table and block nested buffer on members table, so we just query members after separately */
$items = iterator_to_array( $class::getItemsWithPermission(
$where, /* Where clause */
( isset( $this->configuration['widget_feed_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( $class::$databasePrefix . $this->configuration['widget_feed_sort_on'] . ' ' . $this->configuration['widget_feed_sort_dir'] ) : ( $class::$databasePrefix . $class::$databaseColumnMap['updated'] . ' DESC' ), /* Order */
isset( $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5, /* Limit */
( !isset( $this->configuration['widget_feed_use_perms'] ) or $this->configuration['widget_feed_use_perms'] ) ? 'read' : NULL, /* Permission key to check against */
$hidden, /* Whether or not to include hidden items */
0, NULL, FALSE, FALSE, FALSE, FALSE, NULL, FALSE, TRUE, FALSE, FALSE
) );
if ( count( $items ) )
{
return $this->output( $items, isset( $this->configuration['widget_feed_title'] ) ? $this->configuration['widget_feed_title'] : \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' ) );
$memberIds = array();
$members = array();
foreach( $items as $item )
{
foreach( array( 'author', 'last_comment_by' ) as $field )
{
if ( $item->mapped( $field ) )
{
$memberIds[] = $item->mapped( $field );
}
}
$memberIds = array_unique( $memberIds );
}
if ( count( $memberIds ) )
{
$members = \IPS\Db::i()->select( '*', 'core_members', array( \IPS\Db::i()->in( 'member_id', $memberIds ) ) )->setKeyField('member_id');
if ( count( $members ) )
{
foreach( $members as $member )
{
\IPS\Member::constructFromData( $member, FALSE );
}
}
}
if ( isset( $this->configuration['language_key'] ) )
{
$title = \IPS\Member::loggedIn()->language()->addToStack( $this->configuration['language_key'], FALSE, array( 'escape' => TRUE ) );
}
elseif ( isset( $this->configuration['widget_feed_title'] ) )
{
$title = $this->configuration['widget_feed_title'];
}
else
{
$title = \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl' );
}
return $this->output( $items, $title );
}
else
{
+68 -7
View File
@@ -2,9 +2,9 @@
/**
* @brief Content Item Feed Widget
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage forums
* @since 16 Oct 2014
* @version SVN_VERSION_NUMBER
@@ -28,7 +28,40 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
* Class
*/
protected static $class;
/**
* @brief Moderator permission to generate caches on [optional]
*/
protected $moderatorPermissions = array();
/**
* Constructor
*
* @param String $uniqueKey Unique key for this specific instance
* @param array $configuration Widget custom configuration
* @param null|string|array $access Array/JSON string of executable apps (core=sidebar only, content=IP.Content only, etc)
* @param null|string $orientation Orientation (top, bottom, right, left)
* @param boolean $allowReuse If true, when the block is used, it will remain in the sidebar so it can be used again.
* @param string $menuStyle Menu is a drop down menu, modal is a bigger modal panel.
* @return void
*/
public function __construct( $uniqueKey, array $configuration, $access=null, $orientation=null )
{
parent::__construct( $uniqueKey, $configuration, $access, $orientation );
if( count( $this->moderatorPermissions ) )
{
$cacheKeyChecks = array();
foreach( $this->moderatorPermissions as $permission )
{
$cacheKeyChecks[] = \IPS\Member::loggedIn()->modPermission( $permission );
}
$this->cacheKey .= '_' . implode( '_', $cacheKeyChecks );
}
}
/**
* Specify widget configuration
*
@@ -205,6 +238,17 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
unset( $values[ 'widget_feed_container_' . $class::$title ] );
}
if ( is_array( $values['widget_feed_item_author'] ) )
{
$members = array();
foreach( $values['widget_feed_item_author'] as $member )
{
$members[] = $member->member_id;
}
$values['widget_feed_item_author'] = $members;
}
return $values;
}
@@ -263,7 +307,7 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
}
/* Author */
if ( isset( $this->configuration['widget_feed_item_author'] ) and is_array( $this->configuration['widget_feed_item_author'] ) )
if ( isset( $this->configuration['widget_feed_item_author'] ) and is_array( $this->configuration['widget_feed_item_author'] ) and count( $this->configuration['widget_feed_item_author'] ) )
{
$where['item'][] = array( \IPS\Db::i()->in( $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'], $this->configuration['widget_feed_item_author'] ) );
}
@@ -296,7 +340,7 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
return $where;
}
/**
* Render a widget
*
@@ -305,13 +349,30 @@ abstract class _WidgetComment extends \IPS\Widget\PermissionCache
public function render()
{
$class = static::$class;
/* What visible status are we checking? */
$hidden = \IPS\Content\Hideable::FILTER_AUTOMATIC;
if( isset( $this->configuration['widget_feed_comment_status_visible'] ) )
{
switch( $this->configuration['widget_feed_comment_status_visible'] )
{
case 'visible':
$hidden = \IPS\Content\Hideable::FILTER_PUBLIC_ONLY;
break;
case 'hidden':
$hidden = \IPS\Content\Hideable::FILTER_ONLY_HIDDEN;
break;
}
}
$items = $class::getItemsWithPermission(
$this->buildWhere(),
( isset( $this->configuration['widget_feed_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( $class::$databasePrefix . $class::$databaseColumnMap['date'] . ' ' . $this->configuration['widget_feed_sort_dir'] ) : NULL,
isset( $this->configuration['widget_feed_show'] ) ? $this->configuration['widget_feed_show'] : 5,
( !isset( $this->configuration['widget_feed_use_perms'] ) or $this->configuration['widget_feed_use_perms'] ) ? 'read' : NULL,
( isset( $this->configuration['widget_feed_comment_status_visible'] ) and ( $this->configuration['widget_feed_comment_status_visible'] == 'either' or $this->configuration['widget_feed_comment_status_visible'] == 'hidden' ) ? TRUE : FALSE )
$hidden
);
if ( count( $items ) )