Version 4.2.0
This commit is contained in:
1 parent
0dc2aee5ed
commit
96997ddd8e
2074 files changed
+125454
-55780
No files matched your search
@@ -1,12 +1,11 @@
|
||||
<?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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Dec 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Api;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Dec 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Api;
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Trait for Content Containers which can be used in Clubs
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 17 Feb 2017
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trait for Content Containers which can be used in Clubs
|
||||
*/
|
||||
trait ClubContainer
|
||||
{
|
||||
/**
|
||||
* Get the database column which stores the club ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function clubIdColumn()
|
||||
{
|
||||
return 'club_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get front-end language string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function clubFrontTitle()
|
||||
{
|
||||
$itemClass = static::$contentItemClass;
|
||||
return $itemClass::$title . '_pl';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get acp language string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function clubAcpTitle()
|
||||
{
|
||||
return static::$nodeTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated club
|
||||
*
|
||||
* @return \IPS\Member\Club|NULL
|
||||
*/
|
||||
public function club()
|
||||
{
|
||||
if ( \IPS\Settings::i()->clubs )
|
||||
{
|
||||
$clubIdColumn = $this->clubIdColumn();
|
||||
if ( $this->$clubIdColumn )
|
||||
{
|
||||
try
|
||||
{
|
||||
return \IPS\Member\Club::load( $this->$clubIdColumn );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set form for creating a node of this type in a club
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form Form object
|
||||
* @return void
|
||||
*/
|
||||
public function clubForm( \IPS\Helpers\Form $form )
|
||||
{
|
||||
$itemClass = static::$contentItemClass;
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'club_node_name', $this->_id ? $this->_title : \IPS\Member::loggedIn()->language()->addToStack( static::clubFrontTitle() ), TRUE, array( 'maxLength' => 255 ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save club form
|
||||
*
|
||||
* @param \IPS\Member\Club $club The club
|
||||
* @param array $values Values
|
||||
* @return void
|
||||
*/
|
||||
public function saveClubForm( \IPS\Member\Club $club, $values )
|
||||
{
|
||||
$nodeClass = get_called_class();
|
||||
$itemClass = $nodeClass::$contentItemClass;
|
||||
|
||||
$this->_saveClubForm( $club, $values );
|
||||
|
||||
$needToUpdatePermissions = FALSE;
|
||||
if ( !$this->_id )
|
||||
{
|
||||
$clubIdColumn = $this->clubIdColumn();
|
||||
$this->$clubIdColumn = $club->id;
|
||||
$this->save();
|
||||
\IPS\Db::i()->insert( 'core_clubs_node_map', array(
|
||||
'club_id' => $club->id,
|
||||
'node_class' => $nodeClass,
|
||||
'node_id' => $this->_id,
|
||||
'name' => $values['club_node_name']
|
||||
) );
|
||||
|
||||
$needToUpdatePermissions = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->save();
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'] ), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( $itemClass::$application, static::$titleLangPrefix . $this->_id, $values['club_node_name'] );
|
||||
\IPS\Lang::saveCustom( $itemClass::$application, static::$titleLangPrefix . $this->_id . '_desc', isset( $values['club_node_description'] ) ? $values['club_node_description'] : '' );
|
||||
|
||||
if ( $needToUpdatePermissions )
|
||||
{
|
||||
$this->setPermissionsToClub( $club );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class-specific routine when saving club form
|
||||
*
|
||||
* @param \IPS\Member\Club $club The club
|
||||
* @param array $values Values
|
||||
* @return void
|
||||
*/
|
||||
public function _saveClubForm( \IPS\Member\Club $club, $values )
|
||||
{
|
||||
// Deliberately does nothing so classes can override
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the permission index permissions to a specific club
|
||||
*
|
||||
* @param \IPS\Member\Club $club The club
|
||||
* @return void
|
||||
*/
|
||||
public function setPermissionsToClub( \IPS\Member\Club $club )
|
||||
{
|
||||
/* Delete current rows */
|
||||
\IPS\Db::i()->delete( 'core_permission_index', array( 'app=? AND perm_type=? AND perm_type_id=?', static::$permApp, static::$permType, $this->_id ) );
|
||||
|
||||
/* Build new rows */
|
||||
$insert = array( 'app' => static::$permApp, 'perm_type' => static::$permType, 'perm_type_id' => $this->_id );
|
||||
foreach ( static::$permissionMap as $k => $v )
|
||||
{
|
||||
if ( in_array( $k, array( 'view', 'read' ) ) )
|
||||
{
|
||||
switch ( $club->type )
|
||||
{
|
||||
case $club::TYPE_PUBLIC:
|
||||
case $club::TYPE_OPEN:
|
||||
$insert[ 'perm_' . $v ] = '*';
|
||||
break;
|
||||
|
||||
case $club::TYPE_CLOSED:
|
||||
case $club::TYPE_PRIVATE:
|
||||
$insert[ 'perm_' . $v ] = "cm,c{$club->id}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ( $club->type )
|
||||
{
|
||||
case $club::TYPE_PUBLIC:
|
||||
$insert[ 'perm_' . $v ] = 'ca';
|
||||
break;
|
||||
|
||||
case $club::TYPE_OPEN:
|
||||
case $club::TYPE_CLOSED:
|
||||
case $club::TYPE_PRIVATE:
|
||||
$insert[ 'perm_' . $v ] = "cm,c{$club->id}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert */
|
||||
$permId = \IPS\Db::i()->insert( 'core_permission_index', $insert );
|
||||
|
||||
/* Update tags permission cache */
|
||||
if ( isset( static::$permissionMap['read'] ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_tags_perms', array( 'tag_perm_text' => $insert[ 'perm_' . static::$permissionMap['read'] ] ), array( 'tag_perm_aap_lookup=?', md5( static::$permApp . ';' . static::$permType . ';' . $this->_id ) ) );
|
||||
}
|
||||
|
||||
/* Make sure this object resets the permissions internally */
|
||||
$this->_permissions = array_merge( array( 'perm_id' => $permId ), $insert );
|
||||
|
||||
/* Update search index */
|
||||
$this->updateSearchIndexPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch All Root Nodes
|
||||
*
|
||||
* @param string|NULL $permissionCheck The permission key to check for or NULL to not check permissions
|
||||
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
|
||||
* @param mixed $where Additional WHERE clause
|
||||
* @return array
|
||||
*/
|
||||
public static function rootsWithClubs( $permissionCheck='view', $member=NULL, $where=array() )
|
||||
{
|
||||
/* Will we need to check permissions? */
|
||||
if ( $permissionCheck !== NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
}
|
||||
|
||||
if( static::$databaseColumnParent !== NULL )
|
||||
{
|
||||
$where[] = array( static::$databasePrefix . static::$databaseColumnParent . '=?', static::$databaseColumnParentRootValue );
|
||||
}
|
||||
|
||||
return static::nodesWithPermission( $permissionCheck, $member, $where );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Moderator Permission
|
||||
*
|
||||
* @param string $type 'edit', 'hide', 'unhide', 'delete', etc.
|
||||
* @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member
|
||||
* @return bool
|
||||
*/
|
||||
public function modPermission( $type, \IPS\Member $member )
|
||||
{
|
||||
if ( \IPS\Settings::i()->clubs )
|
||||
{
|
||||
$clubIdColumn = $this->clubIdColumn();
|
||||
if ( $this->$clubIdColumn and $club = $this->club() )
|
||||
{
|
||||
if ( in_array( $club->memberStatus( $member ), array( \IPS\Member\Club::STATUS_LEADER, \IPS\Member\Club::STATUS_MODERATOR ) ) )
|
||||
{
|
||||
if ( in_array( $type, explode( ',', \IPS\Settings::i()->clubs_modperms ) ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::modPermission( $type, $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get parent list
|
||||
*
|
||||
* @return \SplStack
|
||||
*/
|
||||
public function parents()
|
||||
{
|
||||
$clubIdColumn = static::clubIdColumn();
|
||||
|
||||
/* Only do this if this node is actually associated with a club */
|
||||
if ( $this->$clubIdColumn )
|
||||
{
|
||||
return new \SplStack;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::parents();
|
||||
}
|
||||
}
|
||||
}
|
||||
+212
-45
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Comment Model
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
@@ -168,37 +167,13 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if profanity filters should mod-queue this comment */
|
||||
$obj->checkProfanityFilters( $first );
|
||||
|
||||
/* Save the comment */
|
||||
$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
|
||||
{
|
||||
@@ -271,6 +246,81 @@ abstract class _Comment extends \IPS\Content
|
||||
/* Return */
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check comment against profanity filters
|
||||
*
|
||||
* @note We do not save here, as saving is done in user-land code after
|
||||
* @param bool $first Is this the first comment?
|
||||
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
|
||||
* @return bool Whether to send unapproved notifications
|
||||
*/
|
||||
protected function checkProfanityFilters( $first=FALSE, $edit=TRUE )
|
||||
{
|
||||
/* We need our item */
|
||||
$item = $this->item();
|
||||
|
||||
/* And the author */
|
||||
$member = $this->author();
|
||||
|
||||
/* Pass this through our profanity filters to see if it needs to be mod queued */
|
||||
$sendNotifications = FALSE;
|
||||
|
||||
try
|
||||
{
|
||||
$hiddenByFilter = FALSE;
|
||||
if ( !$this->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$first ) ) AND !$member->group['g_bypass_badwords'] )
|
||||
{
|
||||
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
|
||||
}
|
||||
/* If the comment is hidden (probably requires moderator approval) and this is the first post in the item that requires a comment (i.e. a post in a topic), then we need to set the item as requiring approval instead. */
|
||||
elseif ( $item instanceof \IPS\Content\Hideable AND !$member->group['g_bypass_badwords'] AND ( $item::$firstCommentRequired and $first ) )
|
||||
{
|
||||
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $this->content() );
|
||||
|
||||
if ( isset( $item::$databaseColumnMap['approved'] ) and $itemHiddenByFilter )
|
||||
{
|
||||
/* 'approved' is easy, clear and concise */
|
||||
$column = $item::$databaseColumnMap['approved'];
|
||||
$item->$column = 0;
|
||||
$item->save();
|
||||
}
|
||||
elseif ( isset( $item::$databaseColumnMap['hidden'] ) and $itemHiddenByFilter )
|
||||
{
|
||||
/* 'hidden' is backwards */
|
||||
$column = $item::$databaseColumnMap['hidden'];
|
||||
$item->$column = 1;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
if( $edit === TRUE )
|
||||
{
|
||||
$sendNotifications = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) { }
|
||||
|
||||
if ( $hiddenByFilter === TRUE )
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
/* 'approved' is easy, clear and concise */
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
$this->$column = 0;
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
/* 'hidden' is backwards */
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
$this->$column = 1;
|
||||
}
|
||||
|
||||
$sendNotifications = TRUE;
|
||||
}
|
||||
|
||||
return $sendNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join profile fields when loading comments?
|
||||
@@ -378,6 +428,9 @@ abstract class _Comment extends \IPS\Content
|
||||
$item->container()->save();
|
||||
}
|
||||
}
|
||||
|
||||
/* Are we tracking keywords? */
|
||||
$this->checkKeywords( $this->content() );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,7 +535,7 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Can the member edit their own content? */
|
||||
if ( $member->member_id == $this->author()->member_id and $member->group['g_edit_posts'] and ( !( $item instanceof \IPS\Content\Lockable ) or !$item->locked() ) )
|
||||
if ( $member->member_id == $this->author()->member_id and ( $member->group['g_edit_posts'] == '1' or in_array( get_class( $item ), explode( ',', $member->group['g_edit_posts'] ) ) ) and ( !( $item instanceof \IPS\Content\Lockable ) or !$item->locked() ) )
|
||||
{
|
||||
if ( !$member->group['g_edit_cutoff'] )
|
||||
{
|
||||
@@ -518,7 +571,7 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
catch ( \BadMethodCallException $e ) { }
|
||||
|
||||
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and $member->group['gbw_soft_delete_own'] ) ) );
|
||||
return ( !$this->isFirst() and ( static::modPermission( 'hide', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_hide_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_hide_own_posts'] ) ) ) ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,7 +618,7 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
catch ( \BadMethodCallException $e ) { }
|
||||
|
||||
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and $member->group['g_delete_own_posts'] ) ) );
|
||||
return ( !$this->isFirst() and ( static::modPermission( 'delete', $member, $container ) or ( $member->member_id and $member->member_id == $this->author()->member_id and ( $member->group['g_delete_own_posts'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_delete_own_posts'] ) ) ) ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,6 +649,28 @@ abstract class _Comment extends \IPS\Content
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can report?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return bool
|
||||
*/
|
||||
public function canReport( $member=NULL )
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\ReportCenter ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( $this->hidden() === 1 )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
return ( $member->group['g_can_report'] == '1' or in_array( get_class( $this->item() ), explode( ',', $member->group['g_can_report'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Index Permissions
|
||||
*
|
||||
@@ -624,6 +699,11 @@ abstract class _Comment extends \IPS\Content
|
||||
*/
|
||||
public function isIgnored( $member=NULL )
|
||||
{
|
||||
if ( !\IPS\Settings::i()->ignore_system_on )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( $member === NULL )
|
||||
{
|
||||
$member = \IPS\Member::loggedIn();
|
||||
@@ -661,16 +741,46 @@ abstract class _Comment extends \IPS\Content
|
||||
$valueField = static::$databaseColumnMap['content'];
|
||||
$oldValue = $this->$valueField;
|
||||
$this->$valueField = $newContent;
|
||||
|
||||
$isHiddenNow = $this->hidden();
|
||||
|
||||
/* Check if profanity filters should mod-queue this comment */
|
||||
$sendNotifications = $this->checkProfanityFilters( $this->isFirst(), TRUE );
|
||||
|
||||
$this->save();
|
||||
|
||||
/* Send any new mention/quote notifications */
|
||||
$this->sendAfterEditNotifications( $oldValue );
|
||||
|
||||
/* Resync the item - necessary as profanity filters may have just hidden an otherwise approved post */
|
||||
$item = $this->item();
|
||||
if( $isHiddenNow != $this->hidden() )
|
||||
{
|
||||
$item->resyncCommentCounts();
|
||||
$item->resyncLastComment();
|
||||
$item->save();
|
||||
}
|
||||
|
||||
if ( $container = $item->containerWrapper() )
|
||||
{
|
||||
$container->resetCommentCounts();
|
||||
$container->save();
|
||||
}
|
||||
|
||||
/* Reindex */
|
||||
if ( $this instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
|
||||
/* Send notifications */
|
||||
if ( $sendNotifications AND !in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
|
||||
{
|
||||
if( $this->hidden() === 1 )
|
||||
{
|
||||
$this->sendUnapprovedNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -852,10 +962,11 @@ abstract class _Comment extends \IPS\Content
|
||||
/**
|
||||
* Move Comment to another item
|
||||
*
|
||||
* @param \IPS\Content\Item $item The item to move this comment too.
|
||||
* @param \IPS\Content\Item $item The item to move this comment to
|
||||
* @param bool $skip Skip rebuilding new/old content item data (used for multimod where we can do it in one go after)
|
||||
* @return void
|
||||
*/
|
||||
public function move( \IPS\Content\Item $item )
|
||||
public function move( \IPS\Content\Item $item, $skip=FALSE )
|
||||
{
|
||||
$oldItem = $this->item();
|
||||
|
||||
@@ -872,13 +983,16 @@ abstract class _Comment extends \IPS\Content
|
||||
\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 )
|
||||
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
\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();
|
||||
if( $skip === FALSE )
|
||||
{
|
||||
$oldItem->rebuildFirstAndLastCommentData();
|
||||
$item->rebuildFirstAndLastCommentData();
|
||||
}
|
||||
|
||||
/* Add to search index */
|
||||
if ( $this instanceof \IPS\Content\Searchable )
|
||||
@@ -937,13 +1051,19 @@ abstract class _Comment extends \IPS\Content
|
||||
$item = NULL;
|
||||
$itemId = $this->mapped('item');
|
||||
}
|
||||
|
||||
/* Remove featured comment associations */
|
||||
if( $this->isFeatured() AND $item )
|
||||
{
|
||||
\IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->unfeatureComment( $item, $this );
|
||||
}
|
||||
|
||||
/* Unclaim attachments */
|
||||
\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 */
|
||||
if( $this->hidden() !== -1 )
|
||||
if( $this->hidden() !== -1 AND $this->hidden() !== -2 )
|
||||
{
|
||||
$columnName = ( $this->hidden() === 1 ) ? 'unapproved_comments' : 'num_comments';
|
||||
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
|
||||
@@ -1005,7 +1125,7 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
\IPS\Member::load( $member )->recountNotifications();
|
||||
}
|
||||
|
||||
|
||||
/* Actually delete */
|
||||
parent::delete();
|
||||
|
||||
@@ -1031,7 +1151,7 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
if ( $item->container()->_comments !== NULL )
|
||||
{
|
||||
if ( !$this->hidden() )
|
||||
if ( !$this->hidden() AND $this->hidden() !== -2 )
|
||||
{
|
||||
$item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
|
||||
}
|
||||
@@ -1257,6 +1377,18 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
}
|
||||
|
||||
/* No matter if we can or cannot view hidden items, we do not want these to show as they are queued for deletion */
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['hidden'];
|
||||
$where[] = array( "{$col}!=-2" );
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$col = static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['approved'];
|
||||
$where[] = array( "{$col}!=-2" );
|
||||
}
|
||||
|
||||
if ( $joinContainer AND isset( $itemClass::$containerNodeClass ) )
|
||||
{
|
||||
$containerClass = $itemClass::$containerNodeClass;
|
||||
@@ -1368,6 +1500,30 @@ abstract class _Comment extends \IPS\Content
|
||||
return array( $this->item()->$idColumn, $this->$commentIdColumn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content images
|
||||
*
|
||||
* @param int|null $limit Number of attachments to fetch, or NULL for all
|
||||
*
|
||||
* @return array|NULL
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function contentImages( $limit = NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$item = $this->item();
|
||||
$attachments = array();
|
||||
$itemIdColumn = $item::$databaseColumnId;
|
||||
$internal = \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( 'location_key=? and id1=? and id2=?', $item::$application . '_' . ucfirst( $item::$module ), $item->$itemIdColumn, $this->$idColumn ) );
|
||||
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id IN(?)', $internal ), array( 'attach_is_image=1' ) ), 'attach_id ASC', $limit ) as $row )
|
||||
{
|
||||
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
|
||||
}
|
||||
|
||||
return count( $attachments ) ? $attachments : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Existing warning
|
||||
*/
|
||||
@@ -1414,6 +1570,17 @@ abstract class _Comment extends \IPS\Content
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a featured comment?
|
||||
*
|
||||
* @return bool
|
||||
* @note This is a wrapper for the extension so content items can extend and apply their own logic
|
||||
*/
|
||||
public function isFeatured()
|
||||
{
|
||||
return \IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->isFeatured( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output for API
|
||||
*
|
||||
|
||||
+406
-432
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Abstract Content Model
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 3 Oct 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS;
|
||||
@@ -48,6 +47,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public static $includeInSitemap = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Reputation Store
|
||||
*/
|
||||
protected $reputation;
|
||||
|
||||
/**
|
||||
* Should posting this increment the poster's post count?
|
||||
*
|
||||
@@ -244,6 +248,35 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
return $this->mapped('content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content images
|
||||
*
|
||||
* @param int|null $limit Number of attachments to fetch, or NULL for all
|
||||
*
|
||||
* @return array|NULL
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function contentImages( $limit = NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$internal = NULL;
|
||||
$attachments = array();
|
||||
|
||||
if ( isset( static::$databaseColumnMap['content'] ) )
|
||||
{
|
||||
$internal = \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( 'location_key=? and id1=?', static::$application . '_' . ucfirst( static::$module ), $this->$idColumn ) );
|
||||
}
|
||||
|
||||
if ( $internal )
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id IN(?)', $internal ), array( 'attach_is_image=1' ) ), 'attach_id ASC', $limit ) as $row )
|
||||
{
|
||||
$attachments[] = array( 'core_Attachment' => $row['attach_location'] );
|
||||
}
|
||||
}
|
||||
|
||||
return count( $attachments ) ? $attachments : NULL; }
|
||||
|
||||
/**
|
||||
* Text for use with data-ipsTruncate
|
||||
* Returns the post with paragraphs turned into line breaks
|
||||
@@ -280,12 +313,17 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Reputation )
|
||||
$idColumn = static::$databaseColumnId;
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
\IPS\Db::i()->delete( 'core_reputation_index', array( 'app=? AND type=? AND type_id=?', static::$application, static::$reputationType, $this->$idColumn ) );
|
||||
\IPS\Db::i()->delete( 'core_reputation_index', array( 'app=? AND type=? AND type_id=?', static::$application, $this->reactionType(), $this->$idColumn ) );
|
||||
}
|
||||
|
||||
/* Remove any entries in the promotions table */
|
||||
\IPS\Db::i()->delete( 'core_social_promote', array( 'promote_class=? AND promote_class_id=?', get_class( $this ), $this->$idColumn ) );
|
||||
|
||||
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", get_class( $this ), $this->$idColumn ) );
|
||||
parent::delete();
|
||||
|
||||
$this->expireWidgetCaches();
|
||||
@@ -329,14 +367,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
* Content is hidden?
|
||||
*
|
||||
* @return int
|
||||
* @li -2 is pending deletion
|
||||
* @li -1 is hidden having been hidden by a moderator
|
||||
* @li 0 is unhidden
|
||||
* @li 1 is hidden needing approval
|
||||
* @note The actual column may also contain 2 which means the item is hidden because the parent is hidden, but it is not hidden in itself. This method will return -1 in that case.
|
||||
*
|
||||
* @note A piece of content (item and comment) can have an alias for hidden OR approved.
|
||||
* With hidden: 0=not hidden, 1=hidden (needs moderator approval), -1=hidden by moderator, 2=parent item is hidden
|
||||
* With approved: 1=not hidden, 0=hidden (needs moderator approval), -1=hidden by moderator
|
||||
* With hidden: 0=not hidden, 1=hidden (needs moderator approval), -1=hidden by moderator, 2=parent item is hidden, -2=pending deletion
|
||||
* With approved: 1=not hidden, 0=hidden (needs moderator approval), -1=hidden by moderator, -2=pending deletion
|
||||
*
|
||||
* User posting has moderator approval set: When adding an unapproved ITEM (approved=0, hidden=1) you should *not* increment container()->_comments but you should update container()->_unapprovedItems
|
||||
* User posting has moderator approval set: When adding an unapproved COMMENT (approved=0, hidden=1) you should *not* increment item()->num_comments in item or container()->_comments but you should update item()->unapproved_comments and container()->_unapprovedComments
|
||||
@@ -361,6 +400,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
elseif ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
if ( $this->$column == -2 )
|
||||
{
|
||||
return intval( $this->$column );
|
||||
}
|
||||
return $this->$column == -1 ? intval( $this->$column ) : intval( !$this->$column );
|
||||
}
|
||||
else
|
||||
@@ -430,27 +473,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/* Per-container permission */
|
||||
elseif ( $container )
|
||||
{
|
||||
$containerClass = get_class( $container );
|
||||
$title = static::$title;
|
||||
if
|
||||
(
|
||||
isset( $containerClass::$modPerm )
|
||||
and
|
||||
(
|
||||
$member->modPermission( $containerClass::$modPerm ) === -1
|
||||
or
|
||||
(
|
||||
is_array( $member->modPermission( $containerClass::$modPerm ) )
|
||||
and
|
||||
in_array( $container->_id, $member->modPermission( $containerClass::$modPerm ) )
|
||||
)
|
||||
)
|
||||
and
|
||||
$member->modPermission( "can_{$type}_{$title}" )
|
||||
)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return $container->modPermission( $type, $member );
|
||||
}
|
||||
|
||||
/* Still here? return false */
|
||||
@@ -463,10 +486,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
* @param string $action The action
|
||||
* @param \IPS\Member|NULL $member The member doing the action (NULL for currently logged in member)
|
||||
* @param string|NULL $reason Reason (for hides)
|
||||
* @param bool $immediately Delete Immediately
|
||||
* @return void
|
||||
* @throws \OutOfRangeException|\InvalidArgumentException|\RuntimeException
|
||||
*/
|
||||
public function modAction( $action, \IPS\Member $member = NULL, $reason = NULL )
|
||||
public function modAction( $action, \IPS\Member $member = NULL, $reason = NULL, $immediately = FALSE )
|
||||
{
|
||||
if( $action === 'approve' )
|
||||
{
|
||||
@@ -474,13 +498,19 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Check it's a valid action */
|
||||
if ( !in_array( $action, array( 'pin', 'unpin', 'feature', 'unfeature', 'hide', 'unhide', 'move', 'lock', 'unlock', 'delete', 'publish' ) ) )
|
||||
if ( !in_array( $action, array( 'pin', 'unpin', 'feature', 'unfeature', 'hide', 'unhide', 'move', 'lock', 'unlock', 'delete', 'publish', 'restore', 'restoreAsHidden' ) ) )
|
||||
{
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
/* And that we can do it */
|
||||
if ( !call_user_func( array( $this, 'can' . ucfirst( $action ) ), $member ) )
|
||||
$toCheck = $action;
|
||||
if ( $action == 'restoreAsHidden' )
|
||||
{
|
||||
$toCheck = 'restore';
|
||||
}
|
||||
|
||||
if ( !call_user_func( array( $this, 'can' . ucfirst( $toCheck ) ), $member ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
@@ -541,9 +571,32 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/* Delete is just a method */
|
||||
elseif ( $action === 'delete' )
|
||||
{
|
||||
/* If we are retaining content for a period of time, we need to just hide it instead for deleting later - this only works, though, with items that implement \IPS\Content\Hideable */
|
||||
if ( \IPS\Settings::i()->dellog_retention_period AND ( $this instanceof \IPS\Content\Hideable ) AND $immediately === FALSE )
|
||||
{
|
||||
$this->logDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", get_class( $this ), $this->$idColumn ) );
|
||||
$this->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Restore is just a method */
|
||||
elseif ( $action === 'restore' )
|
||||
{
|
||||
$this->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Restore As Hidden is just a method */
|
||||
elseif ( $action === 'restoreAsHidden' )
|
||||
{
|
||||
$this->restore( TRUE );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Publish is just a method */
|
||||
elseif ( $action === 'publish' )
|
||||
@@ -561,6 +614,200 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log for deletion later
|
||||
*
|
||||
* \IPS\Member|NULL $member The member or NULL for currently logged in
|
||||
* @return void
|
||||
*/
|
||||
public function logDelete( $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
/* Log it! */
|
||||
$log = new \IPS\core\DeletionLog;
|
||||
$log->setContentAndMember( $this, $member );
|
||||
$log->save();
|
||||
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ( $this->container() )
|
||||
{
|
||||
if ( $this->container()->_comments !== NULL )
|
||||
{
|
||||
/* Hidden content already decrements */
|
||||
if ( !$this->hidden() )
|
||||
{
|
||||
$this->container()->_comments = ( $this->container()->_comments - $this->mapped('num_comments') );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->container()->_unapprovedComments !== NULL )
|
||||
{
|
||||
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments - $this->mapped( 'unapproved_comments' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) {}
|
||||
|
||||
$this->$column = -2;
|
||||
$this->save();
|
||||
|
||||
if ( $this instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$item = $this->item();
|
||||
|
||||
/* Update last comment stuff */
|
||||
$item->resyncLastComment();
|
||||
|
||||
/* Update last review stuff */
|
||||
$item->resyncLastReview();
|
||||
|
||||
/* Update number of comments */
|
||||
$item->resyncCommentCounts();
|
||||
|
||||
/* Update number of reviews */
|
||||
$item->resyncReviewCounts();
|
||||
|
||||
/* Save*/
|
||||
$item->save();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ( $this->container() )
|
||||
{
|
||||
$this->container()->setLastComment();
|
||||
$this->container()->save();
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore Content
|
||||
*
|
||||
* @param bool Restore as hidden?
|
||||
* @return void
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function restore( $hidden = FALSE )
|
||||
{
|
||||
try
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$log = \IPS\core\DeletionLog::constructFromData( \IPS\Db::i()->select( '*', 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id", get_class( $this ), $this->$idColumn ) )->first() );
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
/* Restoring as hidden? */
|
||||
if ( $hidden )
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
}
|
||||
|
||||
$this->$column = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
$this->$column = 0;
|
||||
}
|
||||
else if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
$this->$column = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the changes */
|
||||
$this->save();
|
||||
|
||||
/* Reindex the now hidden content */
|
||||
if ( $this instanceof \IPS\Content\Item and static::$firstCommentRequired )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this->firstComment() );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $this );
|
||||
}
|
||||
|
||||
/* Delete the log */
|
||||
$log->delete();
|
||||
|
||||
/* Log to moderate logs */
|
||||
if ( !$hidden )
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $this->container() )
|
||||
{
|
||||
if ( $this->container()->_comments !== NULL )
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item )
|
||||
{
|
||||
$this->container()->_comments = ( $this->container()->_comments + $this->mapped('num_comments') );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->container()->_comments = $this->container()->_comments + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->container()->_unapprovedComments !== NULL )
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item )
|
||||
{
|
||||
$this->container()->_unapprovedComments = ( $this->container()->_unapprovedComments + $this->mapped('unapproved_comments') );
|
||||
}
|
||||
}
|
||||
|
||||
$this->container()->setLastComment();
|
||||
$this->container()->save();
|
||||
|
||||
if ( $this instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$this->item()->rebuildFirstAndLastCommentData();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can restore?*
|
||||
*
|
||||
* @param \IPS\Member|NULL The member, or currently logged in member
|
||||
* @return bool
|
||||
*/
|
||||
public function canRestore( $member=NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
return \IPS\Member::loggedIn()->modPermission('can_restore_deleted_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide
|
||||
*
|
||||
@@ -722,7 +969,43 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blurb for when/why/by whom this content was deleted
|
||||
*
|
||||
* @return string
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function deletedBlurb()
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Hideable ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$log = \IPS\core\DeletionLog::constructFromData( \IPS\Db::i()->select( '*', 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", get_class( $this ), $this->$idColumn ) )->first() );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'deletion_blurb', FALSE, array( 'sprintf' => array( $log->_deleted_by->name, $log->deleted_date->fullYearLocaleDate(), $log->deletion_date->fullYearLocaleDate() ) ) );
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('deleted');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can promote this comment/item?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return boolean
|
||||
*/
|
||||
public function canPromoteToSocialMedia( $member=NULL )
|
||||
{
|
||||
return \IPS\core\Promote::canPromote( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can report?
|
||||
*
|
||||
@@ -741,7 +1024,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Can the member report content? */
|
||||
if ( $member->group['gbw_no_report'] )
|
||||
if ( !$member->group['g_can_report'] )
|
||||
{
|
||||
return 'no_module_permission';
|
||||
}
|
||||
@@ -866,7 +1149,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$oldAuthor = $this->author();
|
||||
|
||||
foreach ( array( 'author', 'author_name' ) as $k )
|
||||
foreach ( array( 'author', 'author_name', 'edit_member_name' ) as $k )
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap[ $k ] ) )
|
||||
{
|
||||
@@ -878,6 +1161,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
break;
|
||||
|
||||
case 'author_name':
|
||||
case 'edit_member_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;
|
||||
@@ -906,9 +1190,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
* @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
|
||||
* @param array $reactions Reaction Data
|
||||
* @return string
|
||||
*/
|
||||
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL )
|
||||
public static function searchResult( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments=FALSE, $template=NULL, $reactions=array() )
|
||||
{
|
||||
/* Item details */
|
||||
$itemClass = $indexData['index_class'];
|
||||
@@ -931,12 +1216,12 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
|
||||
{
|
||||
$objectUrl = $itemUrl->setQueryString( array( 'do' => 'findReview', 'review' => $indexData['index_object_id'] ) );
|
||||
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showRepReview', 'review' => $indexData['index_object_id'] ) );
|
||||
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showReactionsReview', 'review' => $indexData['index_object_id'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$objectUrl = $itemUrl->setQueryString( array( 'do' => 'findComment', 'comment' => $indexData['index_object_id'] ) );
|
||||
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showRepComment', 'comment' => $indexData['index_object_id'] ) );
|
||||
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showReactionsComment', 'comment' => $indexData['index_object_id'] ) );
|
||||
}
|
||||
|
||||
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
|
||||
@@ -945,7 +1230,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
else
|
||||
{
|
||||
$objectUrl = $itemUrl;
|
||||
$showRepUrl = $itemUrl->setQueryString( 'do', 'showRep' );
|
||||
$showRepUrl = $itemUrl->setQueryString( 'do', 'showReactions' );
|
||||
}
|
||||
$articles = array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle );
|
||||
|
||||
@@ -971,7 +1256,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* 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 ) );
|
||||
return call_user_func_array( $template, array( $indexData, $articles, $authorData, $itemData, $unread, $asItem ? $itemUrl : $objectUrl, $itemUrl, $containerUrl, $containerTitle, $repCount, $showRepUrl, $snippet, $iPostedIn, $view, $canIgnoreComments, $reactions ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -991,6 +1276,11 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
return $view == 'expanded' ? \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResultSnippet( $indexData ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a classname applied to the search result block
|
||||
*/
|
||||
public static $searchResultClassName = '';
|
||||
|
||||
/**
|
||||
* Return the filters that are available for selecting table rows
|
||||
*
|
||||
@@ -1039,397 +1329,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
}
|
||||
|
||||
/* !Reputation */
|
||||
|
||||
/**
|
||||
* Can give reputation?
|
||||
*
|
||||
* @note This method is also ran to check if a member can "unrep"
|
||||
* @param int $type 1 for positive, -1 for negative
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return bool
|
||||
*/
|
||||
public function canGiveReputation( $type, \IPS\Member $member = NULL )
|
||||
{
|
||||
return static::_canGiveReputation( $type, $this->author()->member_id, $this->author()->member_group_id, $this->repGiven( $member ), $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can give reputation?
|
||||
*
|
||||
* @note This method is also ran to check if a member can "unrep"
|
||||
* @param int $type 1 for positive, -1 for negative
|
||||
* @param int $authorId The author ID
|
||||
* @param int $authorGroup The author group
|
||||
* @param int $repGiven The reputation we have already given (1 = positive, -1 = negative, 0 = no rep given)
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return bool
|
||||
*/
|
||||
public static function _canGiveReputation( $type, $authorId, $authorGroup, $repGiven, \IPS\Member $member = NULL )
|
||||
{
|
||||
/* Reputation needs to be enabled */
|
||||
if ( !\IPS\Settings::i()->reputation_enabled or !in_array( 'IPS\Content\Reputation', class_implements( get_called_class() ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Guests cannot rep */
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
if ( !$member->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Author needs to not be protected */
|
||||
if ( in_array( $authorGroup, explode( ',', \IPS\Settings::i()->reputation_protected_groups ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Users may not be able to rep themselves */
|
||||
if ( !\IPS\Settings::i()->reputation_can_self_vote and $authorId == $member->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Rep per day */
|
||||
$limit = ( $type === 1 ) ? $member->group['g_rep_max_positive'] : $member->group['g_rep_max_negative'];
|
||||
if ( $limit == 0 and !$repGiven )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Have we already given rep? */
|
||||
switch ( \IPS\Settings::i()->reputation_point_types )
|
||||
{
|
||||
/* If we're using a positive system, we can rep positive only if we haven't repped yet, or negative only if we have */
|
||||
case 'like':
|
||||
case 'positive':
|
||||
return ( $repGiven === 1 ) ? ( $type === -1 ) : ( $type === 1 );
|
||||
|
||||
/* If we're using a negative system, we can rep negative only if we haven't repped yet, or positive only if we have */
|
||||
case 'negative':
|
||||
return $repGiven ? ( $type === 1 ) : ( $type === -1 );
|
||||
|
||||
/* If we're using a positive and negative system - it's more complicated... */
|
||||
case 'both':
|
||||
switch ( $repGiven )
|
||||
{
|
||||
/* If we haven't repped yet, we can do either */
|
||||
case 0:
|
||||
return TRUE;
|
||||
|
||||
/* If we've given positive rep already, we can only do negative now */
|
||||
case 1:
|
||||
return ( $type === -1 );
|
||||
|
||||
/* If we've given negative rep already, we can only do positive now */
|
||||
case -1:
|
||||
return ( $type === 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Catch all */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give reputation
|
||||
*
|
||||
* @param int $type 1 for positive, -1 for negative
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return void
|
||||
* @throws \DomainException|\BadMethodCallException
|
||||
*/
|
||||
public function giveReputation( $type, \IPS\Member $member=NULL )
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Reputation ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
if ( !$this->canGiveReputation( $type, $member ) )
|
||||
{
|
||||
throw new \DomainException( 'cannot_rep_user' );
|
||||
}
|
||||
|
||||
$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 )
|
||||
{
|
||||
unset( $this->reputation[ $member->member_id ] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$limit = ( $type === 1 ) ? $member->group['g_rep_max_positive'] : $member->group['g_rep_max_negative'];
|
||||
|
||||
if ( $limit !== 999 )
|
||||
{
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_reputation_index', array( 'member_id=? AND rep_date>? AND rep_rating=?', $member->member_id, \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) )->getTimestamp(), $type ) )->first();
|
||||
if ( $count >= $limit )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( \IPS\Settings::i()->reputation_point_types == 'like' ? 'rep_daily_exceeded_likes' :'rep_daily_exceeded', FALSE, array( 'sprintf' => array( $limit ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
\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,
|
||||
'class_type_id_hash' => md5( get_class( $this ) . ':' . $this->$idColumn ),
|
||||
'rep_class' => get_class( $this ),
|
||||
'item_id' => (int) $this->mapped('item')
|
||||
) );
|
||||
|
||||
/* Send Notification */
|
||||
if ( $type === 1 AND $this->author()->member_id AND $this->author() != \IPS\Member::loggedIn() AND $this->canView( $this->author() ) )
|
||||
{
|
||||
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'new_likes', $this, array( $this, \IPS\Member::loggedIn() ) );
|
||||
$notification->recipients->attach( $this->author() );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
if ( $this->reputation !== NULL )
|
||||
{
|
||||
$this->reputation[ $member->member_id ] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
if( $this->author()->member_id )
|
||||
{
|
||||
$this->author()->pp_reputation_points += $type;
|
||||
$this->author()->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reputation Cache
|
||||
*/
|
||||
public $reputation = NULL;
|
||||
|
||||
/**
|
||||
* Get reputation count
|
||||
*
|
||||
* @return int
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function reputation()
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Reputation ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
if ( $this->reputation === NULL )
|
||||
{
|
||||
$this->reputation = iterator_to_array( \IPS\Db::i()->select( 'member_id, rep_rating', 'core_reputation_index', $this->getReputationWhereClause() )->setKeyField( 'member_id' )->setValueField( 'rep_rating' ) );
|
||||
return array_sum( $this->reputation );
|
||||
}
|
||||
else
|
||||
{
|
||||
$repCount = 0;
|
||||
foreach( $this->reputation as $member => $value )
|
||||
{
|
||||
if( is_int( $value ) )
|
||||
{
|
||||
$repCount += $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$repCount += $value['rep_rating'];
|
||||
}
|
||||
|
||||
}
|
||||
return $repCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reputation where clause
|
||||
*
|
||||
* @return array
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function getReputationWhereClause()
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Reputation ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$where = array( array( 'app=? AND type=? AND type_id=?', static::$application, static::$reputationType, $this->$idColumn ) );
|
||||
|
||||
switch( \IPS\Settings::i()->reputation_point_types )
|
||||
{
|
||||
case 'positive':
|
||||
case 'like':
|
||||
$where[] = array( 'rep_rating=?', "1" );
|
||||
break;
|
||||
case 'negative':
|
||||
$where[] = array( 'rep_rating=?', "-1" );
|
||||
break;
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached like blurb
|
||||
*/
|
||||
public $likeBlurb = NULL;
|
||||
|
||||
/**
|
||||
* Get "like" blurb (You and X others like this)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function likeBlurb()
|
||||
{
|
||||
if( $this->likeBlurb === NULL )
|
||||
{
|
||||
/* Did anyone like it? */
|
||||
$numberOfLikes = $this->reputation(); # int
|
||||
if ( $numberOfLikes )
|
||||
{
|
||||
/* 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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->likeBlurb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has reputation been given by a particular member?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member)
|
||||
* @return int 1 = Positive rep given. -1 = Negative rep given. 0 = No rep given
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function repGiven( \IPS\Member $member = NULL )
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Reputation ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
if ( $this->reputation === NULL )
|
||||
{
|
||||
$this->reputation();
|
||||
}
|
||||
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
return array_key_exists( $member->member_id, $this->reputation ) ? intval( $this->reputation[ $member->member_id ] ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table showing who has given reputation
|
||||
*
|
||||
* @return \IPS\Helpers\Table\Db
|
||||
* @throws \DomainException
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function reputationTable()
|
||||
{
|
||||
if ( !( $this instanceof \IPS\Content\Reputation ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
if ( !\IPS\Member::loggedIn()->group['gbw_view_reps'] )
|
||||
{
|
||||
throw new \DomainException;
|
||||
}
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( 'core_reputation_index', $this->url('showRep'), $this->getReputationWhereClause() );
|
||||
$table->sortBy = 'rep_date';
|
||||
$table->sortDirection = 'desc';
|
||||
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'reputationLogTable' );
|
||||
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'reputationLog' );
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/* !Follow */
|
||||
|
||||
const FOLLOW_PUBLIC = 1;
|
||||
@@ -1874,4 +1773,79 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return (string) \IPS\Theme::i()->logo_sharer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log keyword usage, if any
|
||||
*
|
||||
* @param string $content Content/text of submission
|
||||
* @param string|NULL $title Title of submission
|
||||
* @return void
|
||||
*/
|
||||
public function checkKeywords( $content, $title=NULL )
|
||||
{
|
||||
/* Do we have any keywords to track? */
|
||||
if( !\IPS\Settings::i()->stats_keywords )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to know the ID */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
|
||||
/* If this is a content item and first comment is required, skip checking the comment */
|
||||
if ( $this instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$itemClass = static::$itemClass;
|
||||
|
||||
if( $itemClass::$firstCommentRequired === TRUE )
|
||||
{
|
||||
/* During initial post, at this point the firstCommentIdColumn value won't be set, so we check for that or explicitly if this is the first post */
|
||||
if( !$this->item()->mapped('first_comment_id') OR $this->$idColumn == $this->item()->mapped('first_comment_id') )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$words = preg_split("/[\s]+/", trim( strip_tags( preg_replace( "/<br( \/)?>/", "\n", $content ) ) ), NULL, PREG_SPLIT_NO_EMPTY );
|
||||
|
||||
if( $title !== NULL )
|
||||
{
|
||||
$titleWords = explode( ' ', $title );
|
||||
$words = array_merge( $words, $titleWords );
|
||||
}
|
||||
|
||||
$words = array_unique( $words );
|
||||
|
||||
$keywords = json_decode( \IPS\Settings::i()->stats_keywords, true );
|
||||
|
||||
$extraData = json_encode( array( 'class' => get_class( $this ), 'id' => $this->$idColumn ) );
|
||||
|
||||
foreach( $keywords as $keyword )
|
||||
{
|
||||
if( in_array( $keyword, $words ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_statistics', array( 'time' => time(), 'type' => 'keyword', 'value_4' => $keyword, 'extra_data' => $extraData ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* !Deprecated */
|
||||
/* The methods below are deprecated and will be removed in Invision Community 4.3 */
|
||||
|
||||
/**
|
||||
* Get reputation count
|
||||
*
|
||||
* @return int
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function reputation()
|
||||
{
|
||||
if ( !\IPS\IPS::classUsesTrait( $this, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
return $this->reactionCount();
|
||||
}
|
||||
}
|
||||
+725
-105
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Edit History Interface 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 14 Oct 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Embeddable Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 22 Apr 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Extension Generator
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 26 Dec 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Featurable Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Followable Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 02 Dec 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Polls Interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 Jan 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Hidebale Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 6 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
+670
-167
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Lockable Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief MetaData Interface for Content Models/Comments
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 Nov 2013
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* MetaData Interface for Content Models/Comments
|
||||
*
|
||||
* @note Content classes will gain special functionality by implementing this interface
|
||||
*/
|
||||
interface MetaData
|
||||
{
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Moderator Permissions Interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 1 Oct 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
@@ -74,6 +73,21 @@ class _ModeratorPermissions
|
||||
$this->actions[] = 'reply_to_locked';
|
||||
}
|
||||
$this->actions[] = 'delete';
|
||||
if ( in_array( 'IPS\Content\MetaData', class_implements( $class ) ) AND $class::supportedMetaDataTypes() )
|
||||
{
|
||||
if ( in_array( 'core_FeaturedComments', $class::supportedMetaDataTypes() ) )
|
||||
{
|
||||
$this->actions[] = 'feature_comments';
|
||||
$this->actions[] = 'unfeature_comments';
|
||||
}
|
||||
|
||||
if ( in_array( 'core_ContentMessages', $class::supportedMetaDataTypes() ) )
|
||||
{
|
||||
$this->actions[] = 'add_item_message';
|
||||
$this->actions[] = 'edit_item_message';
|
||||
$this->actions[] = 'delete_item_message';
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Permissions Interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Pinnable Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Polls Interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 Jan 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Facebook Promotion
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 Feb 2017
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Promote;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Facebook Promotion
|
||||
*/
|
||||
class _Facebook extends PromoteAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'facebook';
|
||||
|
||||
/**
|
||||
* @brief Default settings
|
||||
*/
|
||||
public $defaultSettings = array(
|
||||
'token' => NULL,
|
||||
'owner' => NULL,
|
||||
'page_name' => NULL,
|
||||
'permissions' => NULL,
|
||||
'members' => NULL,
|
||||
'page' => NULL,
|
||||
'tags' => NULL,
|
||||
'image' => NULL,
|
||||
'last_sync' => 0
|
||||
);
|
||||
|
||||
/**
|
||||
* Is connected?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connected()
|
||||
{
|
||||
return ( $this->member->fb_uid and $this->member->fb_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhoto()
|
||||
{
|
||||
if ( ! $this->settings['image'] or $this->settings['last_sync'] < time() - 86400 )
|
||||
{
|
||||
/* Fetch again */
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/picture?type=large" )->request()->get();
|
||||
|
||||
$extension = str_replace( 'image/', '', $response->httpHeaders['Content-Type'] );
|
||||
$newFile = \IPS\File::create( 'core_Promote', 'facebook_' . $this->settings['page'] . '.' . $extension, (string) $response, NULL, FALSE, NULL, FALSE );
|
||||
|
||||
$this->saveSettings( array( 'image' => (string) $newFile->url, 'last_sync' => time() ) );
|
||||
}
|
||||
|
||||
return $this->settings['image'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->settings['page_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check publish permissions
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canPostToPage()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/permissions" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
|
||||
|
||||
if ( isset( $response['error'] ) )
|
||||
{
|
||||
return $response['error']['message'];
|
||||
}
|
||||
|
||||
if ( !empty( $response['data'] ) )
|
||||
{
|
||||
$got = 0;
|
||||
foreach( $response['data'] as $permission )
|
||||
{
|
||||
if ( in_array( $permission['permission'], array( 'manage_pages', 'publish_pages' ) ) and $permission['status'] == 'granted' )
|
||||
{
|
||||
$got++;
|
||||
}
|
||||
}
|
||||
|
||||
return $got === 2;
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages this user manages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPages()
|
||||
{
|
||||
$pages = array();
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/{$this->member->fb_uid}/accounts" )->setQueryString( 'access_token', $this->member->fb_token )->request()->get()->decodeJson();
|
||||
|
||||
if ( !empty( $response['data'] ) )
|
||||
{
|
||||
foreach( $response['data'] as $page )
|
||||
{
|
||||
$pages[ $page['id'] ] = array( $page['name'], $page['access_token'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form elements for this share service
|
||||
*
|
||||
* @param string $text Text for the text entry
|
||||
* @param string $link Short or full link (short when available)
|
||||
* @param string $content Additional text content (usually a comment, or the item content)
|
||||
*
|
||||
* @return array of form elements
|
||||
*/
|
||||
public function form( $text, $link=null, $content=null )
|
||||
{
|
||||
|
||||
|
||||
if ( $this->promote and $this->promote->id )
|
||||
{
|
||||
$_text = $text;
|
||||
}
|
||||
else
|
||||
{
|
||||
$_text = $text;
|
||||
|
||||
if ( $content )
|
||||
{
|
||||
$_text .= "\n" . $content;
|
||||
}
|
||||
|
||||
if ( $link )
|
||||
{
|
||||
$_text .= "\n" . $link;
|
||||
}
|
||||
|
||||
if ( count( $this->settings['tags'] ) )
|
||||
{
|
||||
$_text .= "\n" . '#' . implode( ' #', $this->settings['tags'] );
|
||||
}
|
||||
}
|
||||
|
||||
return array( new \IPS\Helpers\Form\TextArea( 'promote_social_content_facebook', $_text, FALSE, array( 'maxLength' => 2000, 'rows' => 10 ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Post to Facebook
|
||||
*
|
||||
* @param \IPS\core\Promote $promote Promote Object
|
||||
* @return void
|
||||
*/
|
||||
public function post( $promote )
|
||||
{
|
||||
$photos = $promote->imageObjects();
|
||||
|
||||
/* Get the last 20 items to see if we have a duplicate */
|
||||
$items = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/feed" )->setQueryString( array( 'limit' => 20, 'access_token' => $this->settings['token'] ) )->request()->get()->decodeJson();
|
||||
|
||||
if ( isset( $items['data'] ) )
|
||||
{
|
||||
foreach( $items['data'] as $item )
|
||||
{
|
||||
$time = new \IPS\DateTime( $item['created_time'] );
|
||||
$now = new \IPS\DateTime();
|
||||
|
||||
/* Only look back past the last 30 mins */
|
||||
if ( intval( $now->diff( $time )->format('%i') ) > 30 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( preg_replace( '#\s{1,}#', " ", $promote->text['facebook'] ) == preg_replace( '#\s{1,}#', " ", $item['message'] ) )
|
||||
{
|
||||
/* Duplicate */
|
||||
return $item['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $photos or count( $photos ) === 1 )
|
||||
{
|
||||
/* Simple message */
|
||||
try
|
||||
{
|
||||
if ( $photos )
|
||||
{
|
||||
$thePhoto = array_pop( $photos );
|
||||
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/photos" )->request()->post( array(
|
||||
'message' => $promote->text['facebook'],
|
||||
'url' => (string) $this->returnUrlWithProtocol( $thePhoto->url ),
|
||||
'access_token' => $this->settings['token'],
|
||||
'link' => $promote->short_link
|
||||
) )->decodeJson();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/feed" )->request()->post( array(
|
||||
'message' => $promote->text['facebook'],
|
||||
'access_token' => $this->settings['token'],
|
||||
'link' => $promote->short_link
|
||||
) )->decodeJson();
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
|
||||
}
|
||||
|
||||
if ( isset( $this->response['id'] ) )
|
||||
{
|
||||
return $this->response['id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We have multiple photos so we first need to create an album, and then upload into that */
|
||||
try
|
||||
{
|
||||
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $this->settings['page'] . "/albums" )->request()->post( array(
|
||||
'name' => $promote->objectTitle,
|
||||
'access_token' => $this->settings['token'],
|
||||
) )->decodeJson();
|
||||
|
||||
|
||||
if ( ! isset( $this->response['id'] ) )
|
||||
{
|
||||
throw new \InvalidArgumentException('Could not create album');
|
||||
}
|
||||
|
||||
$newAlbumId = $this->response['id'];
|
||||
|
||||
foreach( $photos as $photo )
|
||||
{
|
||||
$this->response = \IPS\Http\Url::external( "https://graph.facebook.com/" . $newAlbumId . "/photos" )->request()->post( array(
|
||||
'message' => $promote->text['facebook'],
|
||||
'url' => (string) $this->returnUrlWithProtocol( $photo->url ),
|
||||
'access_token' => $this->settings['token'],
|
||||
) )->decodeJson();
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('facebook_publish_exception') );
|
||||
}
|
||||
|
||||
return $this->response['id'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the published URL
|
||||
*
|
||||
* @param array $data Data returned from a successful POST
|
||||
* @return \IPS\Http\Url
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getUrl( $data )
|
||||
{
|
||||
if ( $data and preg_match( '#^[0-9_]*$#', $data ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( 'https://facebook.com/' . $data );
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the URL is not protocol relative
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
protected function returnUrlWithProtocol( \IPS\Http\Url $url )
|
||||
{
|
||||
if ( ! $url->data['scheme'] )
|
||||
{
|
||||
$url = $url->setScheme( ( \substr( \IPS\Settings::i()->base_url, 0, 5 ) == 'https' ) ? 'https' : 'http' );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Internal Promotion
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 FEB 2017
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Promote;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal Promotion
|
||||
*/
|
||||
class _Internal extends PromoteAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'plus';
|
||||
|
||||
/**
|
||||
* @brief Default settings
|
||||
*/
|
||||
public $defaultSettings = array();
|
||||
|
||||
/**
|
||||
* Get image
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhoto()
|
||||
{
|
||||
return \IPS\Theme::i()->settings['logo_sharer'] ? \IPS\Theme::i()->settings['logo_sharer'] : \IPS\Theme::i()->settings['logo_front'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return \IPS\Settings::i()->board_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form elements for this share service
|
||||
*
|
||||
* @param string $text Text for the text entry
|
||||
* @param string $link Short or full link (short when available)
|
||||
* @param string $content Additional text content (usually a comment, or the item content)
|
||||
*
|
||||
* @return array of form elements
|
||||
*/
|
||||
public function form( $text, $link=null, $content=null )
|
||||
{
|
||||
return array( new \IPS\Helpers\Form\TextArea( 'promote_social_content_internal', $content ?: $text, FALSE, array( 'maxLength' => 3000, 'rows' => 6 ) ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Post to internal
|
||||
*
|
||||
* @param \IPS\Content\Promote $promote Promote Object
|
||||
* @return void
|
||||
*/
|
||||
public function post( $promote )
|
||||
{
|
||||
return time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the published URL
|
||||
*
|
||||
* @param array $data Data returned from a successful POST
|
||||
* @return \IPS\Http\Url
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getUrl( $data )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Promote Abstract Class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 13 Jun 2013
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Promote;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote Handler
|
||||
*/
|
||||
abstract class _PromoteAbstract extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_social_promote_sharers';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'sharer_';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'key';
|
||||
|
||||
/**
|
||||
* @brief [Node] Order Database Column
|
||||
*/
|
||||
public static $databaseColumnOrder = 'key';
|
||||
|
||||
/**
|
||||
* @brief [Node] Automatically set position for new nodes
|
||||
*/
|
||||
public static $automaticPositionDetermination = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Default settings
|
||||
*/
|
||||
public $defaultSettings = array();
|
||||
|
||||
/**
|
||||
* @brief The last response sent back
|
||||
*/
|
||||
public $response = NULL;
|
||||
|
||||
/**
|
||||
* @brief \IPS\core\Promote object
|
||||
*/
|
||||
public $promote = NULL;
|
||||
|
||||
/**
|
||||
* Construct ActiveRecord from database row
|
||||
*
|
||||
* @param array $data Row from database table
|
||||
* @param bool $updateMultitonStoreIfExists Replace current object in multiton store if it already exists there?
|
||||
* @return static
|
||||
*/
|
||||
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
|
||||
{
|
||||
/* Initiate an object */
|
||||
$classname = '\\IPS\Content\\Promote\\' . \ucfirst( $data['sharer_key'] );
|
||||
|
||||
if ( !class_exists( $classname ) )
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
|
||||
$obj = new $classname;
|
||||
$obj->_new = FALSE;
|
||||
|
||||
/* Import data */
|
||||
$databasePrefixLength = \strlen( static::$databasePrefix );
|
||||
foreach ( $data as $k => $v )
|
||||
{
|
||||
if( static::$databasePrefix )
|
||||
{
|
||||
$k = \substr( $k, $databasePrefixLength );
|
||||
}
|
||||
|
||||
$obj->$k = $v;
|
||||
}
|
||||
$obj->changed = array();
|
||||
|
||||
/* Return */
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
protected $member;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* \IPS\Member $member The member
|
||||
* @returns $this for daisy chaining
|
||||
*/
|
||||
public function setMember( \IPS\Member $member )
|
||||
{
|
||||
$this->member = $member;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lowercase key
|
||||
*
|
||||
* @returns string
|
||||
*/
|
||||
public function get__key()
|
||||
{
|
||||
return mb_strtolower( $this->key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings()
|
||||
{
|
||||
return array_merge( $this->defaultSettings, json_decode( $this->_data['settings'], TRUE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member promote using this service?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canPromote()
|
||||
{
|
||||
return $this->enabled and \IPS\core\Promote::canPromote( $this->member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getPhoto();
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getName();
|
||||
|
||||
/**
|
||||
* Return the published URL
|
||||
*
|
||||
* @param array $data Data returned from a successful POST
|
||||
* @return \IPS\Http\Url
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
abstract function getUrl( $data );
|
||||
|
||||
/**
|
||||
* Bulk save settings
|
||||
*
|
||||
* @param array $settings Array of (k => v) settings
|
||||
* @return void
|
||||
*/
|
||||
public function saveSettings( $settings )
|
||||
{
|
||||
$this->settings = json_encode( array_merge( $this->settings, $settings ) );
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function save()
|
||||
{
|
||||
parent::save();
|
||||
unset( \IPS\Data\Store::i()->promoters );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Twitter Promotion
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 FEB 2017
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Promote;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Promotion
|
||||
*/
|
||||
class _Twitter extends PromoteAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'twitter';
|
||||
|
||||
/**
|
||||
* @brief Default settings
|
||||
*/
|
||||
public $defaultSettings = array(
|
||||
'id' => NULL,
|
||||
'owner' => NULL,
|
||||
'secret' => NULL,
|
||||
'token' => NULL,
|
||||
'name' => NULL,
|
||||
'permissions' => NULL,
|
||||
'members' => NULL,
|
||||
'tags' => NULL,
|
||||
'tags_method' => 'fill',
|
||||
'image' => NULL,
|
||||
'last_sync' => 0
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Twitter log in object
|
||||
*/
|
||||
protected static $twitter = NULL;
|
||||
|
||||
/**
|
||||
* Twitter object
|
||||
*
|
||||
* @return Object
|
||||
*/
|
||||
protected function twitter()
|
||||
{
|
||||
if ( static::$twitter === NULL )
|
||||
{
|
||||
static::$twitter = \IPS\Login::getHandler('Twitter');
|
||||
}
|
||||
|
||||
return static::$twitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhoto()
|
||||
{
|
||||
if ( ! $this->settings['image'] or $this->settings['last_sync'] < time() - 86400 )
|
||||
{
|
||||
$user = $this->twitter()->sendRequest( 'get', 'https://api.twitter.com/1.1/account/verify_credentials.json', array(), $this->settings['token'], $this->settings['secret'] )->decodeJson();
|
||||
|
||||
/* Fetch again */
|
||||
$response = \IPS\Http\Url::external( $user['profile_image_url'] )->request()->get();
|
||||
|
||||
$extension = str_replace( 'image/', '', $response->httpHeaders['Content-Type'] );
|
||||
$newFile = \IPS\File::create( 'core_Promote', 'twitter_' . $this->settings['id'] . '.' . $extension, (string) $response, NULL, FALSE, NULL, FALSE );
|
||||
|
||||
$this->saveSettings( array( 'image' => (string) $newFile->url, 'last_sync' => time() ) );
|
||||
}
|
||||
|
||||
return $this->settings['image'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->settings['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check publish permissions
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canPostToPage()
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->twitter()->sendRequest( 'get', 'https://api.twitter.com/1.1/account/verify_credentials.json', array(), $this->settings['token'], $this->settings['secret'] )->decodeJson();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e ) { }
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get form elements for this share service
|
||||
*
|
||||
* @param string $text Text for the text entry
|
||||
* @param string $link Short or full link (short when available)
|
||||
* @param string $content Additional text content (usually a comment, or the item content)
|
||||
*
|
||||
* @return array of form elements
|
||||
*/
|
||||
public function form( $text, $link=null, $content=null )
|
||||
{
|
||||
$textToActuallyUse = $text;
|
||||
|
||||
if ( $link )
|
||||
{
|
||||
$textToActuallyUse .= ' ' . $link;
|
||||
}
|
||||
|
||||
if ( mb_strlen( $textToActuallyUse ) < 140 )
|
||||
{
|
||||
/* Got any tags to add? */
|
||||
if ( count( $this->settings['tags'] ) )
|
||||
{
|
||||
if ( $this->settings['tags_method'] === 'trim' )
|
||||
{
|
||||
$urlLength = mb_strlen( $link ) + 2; // spaces either side
|
||||
$left = 140 - $urlLength;
|
||||
$tagLength = 0;
|
||||
$tagString = '';
|
||||
$useText = $text;
|
||||
foreach( $this->settings['tags'] as $tag )
|
||||
{
|
||||
if ( $left > 0 )
|
||||
{
|
||||
$tagString .= ' #' . $tag;
|
||||
$left -= mb_strlen( $tagString );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $left > 20 )
|
||||
{
|
||||
$useText = mb_substr( $text, 0, $left );
|
||||
}
|
||||
|
||||
$textToActuallyUse = $useText . ' ' . $link . ' ' . $tagString;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( $this->settings['tags'] as $tag )
|
||||
{
|
||||
$considerAddingThis = ' #' . $tag;
|
||||
|
||||
if ( mb_strlen( $textToActuallyUse . $considerAddingThis ) < 140 )
|
||||
{
|
||||
$textToActuallyUse .= $considerAddingThis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$return = array();
|
||||
|
||||
if ( $this->promote and $this->promote->id )
|
||||
{
|
||||
$textToActuallyUse = $text;
|
||||
$return[] = \IPS\Theme::i()->getTemplate( 'promote' )->promoteDialogTwitterDuplicate();
|
||||
}
|
||||
|
||||
$return[] = new \IPS\Helpers\Form\TextArea( 'promote_social_content_twitter', $textToActuallyUse, FALSE, array( 'maxLength' => 300, 'rows' => 3 ) );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post to Twitter
|
||||
*
|
||||
* @param \IPS\Content\Promote $promote Promote Object
|
||||
* @return void
|
||||
*/
|
||||
public function post( $promote )
|
||||
{
|
||||
$photos = $promote->imageObjects();
|
||||
$mediaIds = array();
|
||||
|
||||
if ( count( $photos ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$done = 0;
|
||||
foreach( $photos as $photo )
|
||||
{
|
||||
/* Twitter can only have a max of 4 images */
|
||||
if ( $done < 4 )
|
||||
{
|
||||
$this->response = $this->twitter()->sendRequest( 'post', 'https://upload.twitter.com/1.1/media/upload.json', array( 'media' => $photo->contents() ), $this->settings['token'], $this->settings['secret'] )->decodeJson();
|
||||
|
||||
if ( isset( $this->response['media_id_string'] ) )
|
||||
{
|
||||
$mediaIds[] = $this->response['media_id_string'];
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Log::log( $this->response, 'twitter' );
|
||||
}
|
||||
|
||||
$done++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e->getMessage(), 'twitter' );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$send = array( 'status' => $promote->text['twitter'] );
|
||||
|
||||
if ( count( $mediaIds ) )
|
||||
{
|
||||
$send['media_ids'] = implode( ',', $mediaIds );
|
||||
}
|
||||
|
||||
$this->response = $this->twitter()->sendRequest( 'post', "https://api.twitter.com/1.1/statuses/update.json", $send, $this->settings['token'], $this->settings['secret'] )->decodeJson();
|
||||
|
||||
if ( isset( $this->response['id_str'] ) )
|
||||
{
|
||||
return $this->response['id_str'];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check for non critical errors we don't need to flag as complete failures */
|
||||
if ( isset( $this->response['errors'] ) )
|
||||
{
|
||||
foreach( $this->response['errors'] as $error )
|
||||
{
|
||||
/* Status is a duplicate */
|
||||
if ( $error['code'] == 187 )
|
||||
{
|
||||
/* Fetch the original string if possible */
|
||||
foreach( $promote->responses('twitter') as $response )
|
||||
{
|
||||
if ( isset( $response['id_str'] ) )
|
||||
{
|
||||
return $response['id_str'];
|
||||
}
|
||||
}
|
||||
|
||||
/* No? Ok */
|
||||
return 'Duplicate status';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e->getMessage(), 'twitter' );
|
||||
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('twitter_publish_exception') );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the published URL
|
||||
*
|
||||
* @param array $data Data returned from a successful POST
|
||||
* @return \IPS\Http\Url
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getUrl( $data )
|
||||
{
|
||||
if ( $data and preg_match( '#^[0-9_]*$#', $data ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( 'https://twitter.com/' . $this->settings['id'] . '/status/' . $data );
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Rating Interface for Content Models/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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 15 Jan 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Reaction Trait
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 Nov 2016
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Trait
|
||||
*/
|
||||
trait Reactable
|
||||
{
|
||||
/**
|
||||
* Reaction type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function reactionType()
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
|
||||
/**
|
||||
* React
|
||||
*
|
||||
* @param \IPS\core\Reaction THe reaction
|
||||
* @param \IPS\Member The member reacting, or NULL
|
||||
* @return void
|
||||
* @throws \DomainException
|
||||
*/
|
||||
public function react( \IPS\Content\Reaction $reaction, \IPS\Member $member = NULL )
|
||||
{
|
||||
/* Did we pass a member? */
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
/* Figure out the owner of this - if it is content, it will be the author. If it is a node, then it will be the person who created it */
|
||||
if ( $this instanceof \IPS\Content )
|
||||
{
|
||||
$owner = $this->author();
|
||||
}
|
||||
else if ( $this instanceof \IPS\Node\Model )
|
||||
{
|
||||
$owner = $this->owner();
|
||||
}
|
||||
|
||||
/* Can we react? */
|
||||
if ( $this->canReact( $member ) === FALSE )
|
||||
{
|
||||
throw new \DomainException( 'cannot_react' );
|
||||
}
|
||||
|
||||
/* Have we hit our limit? Also, why 999 for unlimited? */
|
||||
if ( $member->group['g_rep_max_positive'] !== -1 )
|
||||
{
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_reputation_index', array( 'member_id=? AND rep_date>?', $member->member_id, \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) )->getTimestamp() ) )->first();
|
||||
if ( $count >= $member->group['g_rep_max_positive'] )
|
||||
{
|
||||
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'react_daily_exceeded', FALSE, array( 'sprintf' => array( $member->group['g_rep_max_positive'] ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure out our app - we do it this way as content items and nodes will always have a lowercase namespace for the app, so if the match below fails, then 'core' can be assumed */
|
||||
$app = explode( '\\', get_class( $this ) );
|
||||
if ( \strtolower( $app[1] ) === $app[1] )
|
||||
{
|
||||
$app = $app[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$app = 'core';
|
||||
}
|
||||
|
||||
/* If this is a comment, we need the parent items ID */
|
||||
$itemId = 0;
|
||||
if ( $this instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$item = $this->item();
|
||||
$itemIdColumn = $item::$databaseColumnId;
|
||||
$itemId = $item->$itemIdColumn;
|
||||
}
|
||||
|
||||
/* Have we already reacted? */
|
||||
if ( $this->reacted( $member ) )
|
||||
{
|
||||
$this->removeReaction( $member );
|
||||
}
|
||||
|
||||
/* Actually insert it */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
\IPS\Db::i()->insert( 'core_reputation_index', array(
|
||||
'member_id' => $member->member_id,
|
||||
'app' => $app,
|
||||
'type' => static::reactionType(),
|
||||
'type_id' => $this->$idColumn,
|
||||
'rep_date' => \IPS\DateTime::create()->getTimestamp(),
|
||||
'rep_rating' => $reaction->value,
|
||||
'member_received' => $owner->member_id,
|
||||
'rep_class' => get_class( $this ),
|
||||
'class_type_id_hash' => md5( get_class( $this ) . ':' . $this->$idColumn ),
|
||||
'item_id' => $itemId,
|
||||
'reaction' => $reaction->id
|
||||
) );
|
||||
|
||||
/* Send the notification */
|
||||
if ( $this->author()->member_id AND $this->author() != \IPS\Member::loggedIn() AND $this->canView( $owner ) )
|
||||
{
|
||||
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'new_likes', $this, array( $this, $member ) );
|
||||
$notification->recipients->attach( $owner );
|
||||
$notification->send();
|
||||
}
|
||||
|
||||
if ( $owner->member_id )
|
||||
{
|
||||
$owner->pp_reputation_points += $reaction->value;
|
||||
$owner->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Reaction
|
||||
*
|
||||
* @param \IPS\Member|NULL The member, or NULL
|
||||
* @return void
|
||||
*/
|
||||
public function removeReaction( \IPS\Member $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$rep = \IPS\Db::i()->select( '*', 'core_reputation_index', array( "member_id=? AND rep_class=? AND type=? AND type_id=?", $member->member_id, get_class( $this ), static::reactionType(), $this->$idColumn ) )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$memberReceived = \IPS\Member::load( $rep['member_received'] );
|
||||
$reaction = \IPS\Content\Reaction::load( $rep['reaction'] );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \DomainException;
|
||||
}
|
||||
|
||||
if ( $memberReceived->member_id )
|
||||
{
|
||||
$memberReceived->pp_reputation_points = $memberReceived->pp_reputation_points - $reaction->value;
|
||||
$memberReceived->save();
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_reputation_index', array( "id=?", $rep['id'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can React
|
||||
*
|
||||
* @param \IPS\Member|NULL The member, or NULL for currently logged in
|
||||
* @return bool
|
||||
*/
|
||||
public function canReact( \IPS\Member $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
if ( $this instanceof \IPS\Content )
|
||||
{
|
||||
$owner = $this->author();
|
||||
}
|
||||
else if ( $this instanceof \IPS\Node\Model )
|
||||
{
|
||||
$owner = $this->owner();
|
||||
}
|
||||
|
||||
/* Only members can react */
|
||||
if ( !$member->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( !$owner->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Protected Groups */
|
||||
if ( $owner->inGroup( explode( ',', \IPS\Settings::i()->reputation_protected_groups ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Reactions per day */
|
||||
if ( $member->group['g_rep_max_positive'] == 0 )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* React to own content */
|
||||
if ( !\IPS\Settings::i()->reputation_can_self_vote AND $this->author()->member_id == $member->member_id )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Still here? All good. */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reactions Cache
|
||||
*/
|
||||
protected $_reactions = NULL;
|
||||
|
||||
/**
|
||||
* Reactions
|
||||
*
|
||||
* @param array|NULL $mixInData If the data is already know, it can be passed here to be manually set
|
||||
* @return array
|
||||
*/
|
||||
public function reactions()
|
||||
{
|
||||
if ( $this->_reactionCount === NULL )
|
||||
{
|
||||
$this->_reactionCount = 0;
|
||||
}
|
||||
|
||||
if ( $this->_reactions === NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$this->_reactions = array();
|
||||
|
||||
if ( is_array( $this->reputation ) )
|
||||
{
|
||||
foreach( $this->reputation AS $memberId => $reactionId )
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->_reactionCount += \IPS\Content\Reaction::load( $reactionId )->value;
|
||||
$this->_reactions[ $memberId ][] = $reactionId;
|
||||
}
|
||||
catch( \OutOfRangeException $e ) { }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_reputation_index', $this->getReactionWhereClause() )->join( 'core_reactions', 'reaction=reaction_id' ) AS $reaction )
|
||||
{
|
||||
$this->_reactions[ $reaction['member_id'] ][] = $reaction['reaction'];
|
||||
$this->_reactionCount += $reaction['rep_rating'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_reactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reaction Count
|
||||
*/
|
||||
protected $_reactionCount = NULL;
|
||||
|
||||
/**
|
||||
* Reaction Count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function reactionCount()
|
||||
{
|
||||
$this->reactions();
|
||||
return $this->_reactionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Where Clause
|
||||
*
|
||||
* @param \IPS\Content\Reaction|array|int|NULL This can be any one of the following: An \IPS\Content\Reaction object, an array of \IPS\Content\Reaction objects, and integer, or an array of integers, or NULL
|
||||
* @return array
|
||||
*/
|
||||
public function getReactionWhereClause( $reactions = NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$where = array( array( 'rep_class=? AND type=? AND type_id=? AND reaction_enabled=?', get_class( $this ), static::reactionType(), $this->$idColumn, 1 ) );
|
||||
|
||||
if ( $reactions !== NULL )
|
||||
{
|
||||
if ( !is_array( $reactions ) )
|
||||
{
|
||||
$reactions = array( $reactions );
|
||||
}
|
||||
|
||||
$in = array();
|
||||
foreach( $reactions AS $reaction )
|
||||
{
|
||||
if ( $reaction instanceof \IPS\Content\Reaction )
|
||||
{
|
||||
$in[] = $reaction->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$in[] = $reaction;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $in ) )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( 'reaction', $in ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Table
|
||||
*
|
||||
* @return \IPS\Helpers\Table\Db
|
||||
*/
|
||||
public function reactionTable( $reaction=NULL )
|
||||
{
|
||||
if ( !\IPS\Member::loggedIn()->group['gbw_view_reps'] )
|
||||
{
|
||||
throw new \DomainException;
|
||||
}
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( 'core_reputation_index', $this->url('showReactions'), $this->getReactionWhereClause( $reaction ) );
|
||||
$table->sortBy = 'rep_date';
|
||||
$table->sortDirection = 'desc';
|
||||
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'reactionLogTable' );
|
||||
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' ), 'reactionLog' );
|
||||
$table->joins = array( array( 'from' => 'core_reactions', 'where' => 'reaction=reaction_id' ) );
|
||||
|
||||
$table->rowButtons = function( $row )
|
||||
{
|
||||
return array(
|
||||
'delete' => array(
|
||||
'icon' => 'times-circle',
|
||||
'title' => 'delete',
|
||||
'link' => $this->url( 'unreact' )->csrf()->setQueryString( array( 'member' => $row['member_id'] ) ),
|
||||
'data' => array( 'confirm' => TRUE )
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has reacted?
|
||||
*
|
||||
* @param \IPS\Member|NULL The member, or NULL for currently logged in
|
||||
* @return \IPS\Content\Reaction|FALSE
|
||||
*/
|
||||
public function reacted( \IPS\Member $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
$idColumn = static::$databaseColumnId;
|
||||
try
|
||||
{
|
||||
if ( is_array( $this->reputation ) )
|
||||
{
|
||||
if ( isset( $this->reputation[ $member->member_id ] ) )
|
||||
{
|
||||
return \IPS\Content\Reaction::load( $this->reputation[ $member->member_id ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Content\Reaction::load( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', array( "member_id=? AND rep_class=? AND type=? AND type_id=?", $member->member_id, get_class( $this ), static::reactionType(), $this->$idColumn ) )->first() );
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached React Blurb
|
||||
*/
|
||||
public $reactBlurb = NULL;
|
||||
|
||||
/**
|
||||
* React Blurb
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function reactBlurb()
|
||||
{
|
||||
if ( $this->reactBlurb === NULL )
|
||||
{
|
||||
$this->reactBlurb = array();
|
||||
|
||||
if ( count( $this->reactions() ) )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
/* Figure out our app - we do it this way as content items and nodes will always have a lowercase namespace for the app, so if the match below fails, then 'core' can be assumed */
|
||||
$app = explode( '\\', get_class( $this ) );
|
||||
if ( \strtolower( $app[1] ) === $app[1] )
|
||||
{
|
||||
$app = $app[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$app = 'core';
|
||||
}
|
||||
foreach( \IPS\Db::i()->select( 'reaction', 'core_reputation_index', array( "app=? AND rep_class=? AND type=? AND type_id=?", $app, get_class( $this ), static::reactionType(), $this->$idColumn ) ) AS $rep )
|
||||
{
|
||||
if ( !isset( $this->reactBlurb[ $rep ] ) )
|
||||
{
|
||||
$this->reactBlurb[ $rep ] = 0;
|
||||
}
|
||||
|
||||
$this->reactBlurb[ $rep ]++;
|
||||
}
|
||||
|
||||
/* Error suppressor for https://bugs.php.net/bug.php?id=50688 */
|
||||
@uksort( $this->reactBlurb, function( $a, $b ) {
|
||||
try
|
||||
{
|
||||
$a = \IPS\Content\Reaction::load( $a );
|
||||
$b = \IPS\Content\Reaction::load( $b );
|
||||
}
|
||||
/* One of the reactions does not exist */
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( $a->position < $b->position )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
elseif ( $a->position == $b->position )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
} );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->reactBlurb = array();
|
||||
}
|
||||
}
|
||||
return $this->reactBlurb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached like blurb
|
||||
*/
|
||||
public $likeBlurb = NULL;
|
||||
|
||||
/**
|
||||
* Who Reacted
|
||||
*
|
||||
* @param bool|NULL Use like text instead? NULL to automatically determine
|
||||
* @return string
|
||||
*/
|
||||
public function whoReacted( $isLike = NULL )
|
||||
{
|
||||
if ( $isLike === NULL )
|
||||
{
|
||||
$isLike = \IPS\Content\Reaction::isLikeMode();
|
||||
}
|
||||
|
||||
if( $this->likeBlurb === NULL )
|
||||
{
|
||||
$langPrefix = 'react_';
|
||||
if ( $isLike )
|
||||
{
|
||||
$langPrefix = 'like_';
|
||||
}
|
||||
|
||||
/* Did anyone like it? */
|
||||
$numberOfLikes = $this->reactionCount(); # int
|
||||
if ( $numberOfLikes )
|
||||
{
|
||||
/* Is it just us? */
|
||||
$userLiked = ( $this->reacted() );
|
||||
if ( $userLiked and $numberOfLikes < 2 )
|
||||
{
|
||||
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack("{$langPrefix}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("{$langPrefix}blurb_you_and_others");
|
||||
$andXOthers--;
|
||||
}
|
||||
|
||||
$peopleToDisplayInSecondaryView = array();
|
||||
|
||||
/* Some random names */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$i = 0;
|
||||
$peopleToDisplayInSecondaryView = array();
|
||||
/* Figure out our app - we do it this way as content items and nodes will always have a lowercase namespace for the app, so if the match below fails, then 'core' can be assumed */
|
||||
$app = explode( '\\', get_class( $this ) );
|
||||
if ( \strtolower( $app[1] ) === $app[1] )
|
||||
{
|
||||
$app = $app[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$app = 'core';
|
||||
}
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array( 'app=? AND type=? AND type_id=? AND member_id!=?', $app, static::reactionType(), $this->$idColumn, ( \IPS\Member::loggedIn()->member_id ) ?: 0 ), 'RAND()', $userLiked ? 17 : 18 ) as $rep )
|
||||
{
|
||||
if ( $i < ( $userLiked ? 2 : 3 ) )
|
||||
{
|
||||
$peopleToDisplayInMainView[] = htmlspecialchars( \IPS\Member::load( $rep['member_id'] )->name, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
$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( "{$langPrefix}blurb_others_secondary", FALSE, array( 'pluralize' => array( $andXOthers - count( $peopleToDisplayInSecondaryView ) ) ) );
|
||||
}
|
||||
$peopleToDisplayInMainView[] = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->reputationOthers( $this->url( 'showReactions' ), \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb_others", FALSE, array( 'pluralize' => array( $andXOthers ) ) ), json_encode( $peopleToDisplayInSecondaryView ) );
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
$this->likeBlurb = \IPS\Member::loggedIn()->language()->addToStack( "{$langPrefix}blurb", FALSE, array( 'pluralize' => array( $numberOfLikes ), 'htmlsprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $peopleToDisplayInMainView ) ) ) );
|
||||
}
|
||||
|
||||
}
|
||||
/* Nobody liked it - show nothing */
|
||||
else
|
||||
{
|
||||
$this->likeBlurb = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->likeBlurb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Reaction Model
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 10 Nov 2016
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Model
|
||||
*/
|
||||
class _Reaction extends \IPS\Node\Model
|
||||
{
|
||||
/**
|
||||
* @brief Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_reactions';
|
||||
|
||||
/**
|
||||
* @brief Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'reaction_';
|
||||
|
||||
/**
|
||||
* @brief Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'id';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Map
|
||||
*/
|
||||
protected static $multitonMap = array();
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'reactions';
|
||||
|
||||
/**
|
||||
* @brief [Node] Sortable
|
||||
*/
|
||||
public static $nodeSortable = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Node] Positon Column
|
||||
*/
|
||||
public static $databaseColumnOrder = 'position';
|
||||
|
||||
/**
|
||||
* @brief [Node] Modal Forms because Charles loves them so
|
||||
*/
|
||||
public static $modalForms = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key
|
||||
*/
|
||||
public static $titleLangPrefix = 'reaction_title_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Enabled/Disabled Column
|
||||
*/
|
||||
public static $databaseColumnEnabledDisabled = 'enabled';
|
||||
|
||||
/**
|
||||
* Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Translatable( 'reaction_title', NULL, TRUE, array( 'app' => 'core', 'key' => ( $this->id ? 'reaction_title_' . $this->id : NULL ) ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'reaction_value', $this->id ? $this->value : 1, TRUE, array( 'options' => array( 1 => 'positive', 0 => 'neutral', -1 => 'negative' ) ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Upload( 'reaction_icon', $this->id ? \IPS\File::get( 'core_Reaction', $this->icon ) : NULL, TRUE, array( 'image' => TRUE, 'storageExtension' => 'core_Reaction', 'storageContainer' => 'reactions', 'obscure' => FALSE ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
{
|
||||
if ( !$this->id )
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( 'core', 'reaction_title_' . $this->id, $values['reaction_title'] );
|
||||
unset( $values['reaction_title'] );
|
||||
|
||||
return parent::formatFormValues( $values );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Does the currently logged in user have permission to delete this node?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canDelete()
|
||||
{
|
||||
if ( $this->id === 1 )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Icon
|
||||
*
|
||||
* @return \IPS\File
|
||||
*/
|
||||
public function get__icon()
|
||||
{
|
||||
return \IPS\File::get( 'core_Reaction', $this->_data['icon'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*
|
||||
* @return strong
|
||||
*/
|
||||
public function get__description()
|
||||
{
|
||||
if ( $this->value == 1 )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('positive');
|
||||
}
|
||||
elseif ( $this->value == -1 )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('negative');
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('neutral');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch All Root Nodes
|
||||
*
|
||||
* @param string|NULL $permissionCheck The permission key to check for or NULl to not check permissions
|
||||
* @param \IPS\Member|NULL $member The member to check permissions for or NULL for the currently logged in member
|
||||
* @param mixed $where Additional WHERE clause
|
||||
* @return array
|
||||
*/
|
||||
public static function roots( $permissionCheck='view', $member=NULL, $where=array() )
|
||||
{
|
||||
if ( !count( $where ) )
|
||||
{
|
||||
$return = array();
|
||||
foreach( static::reactionStore() AS $reaction )
|
||||
{
|
||||
$return[ $reaction['reaction_id'] ] = static::constructFromData( $reaction );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return parent::roots( $permissionCheck, $member, $where );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get whether or not this node is enabled
|
||||
*
|
||||
* @note Return value NULL indicates the node cannot be enabled/disabled
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function get__enabled()
|
||||
{
|
||||
if ( $this->id == 1 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parent::get__enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Enabled
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set__enabled( $enabled )
|
||||
{
|
||||
parent::set__enabled( $enabled );
|
||||
|
||||
unset( \IPS\Data\Store::i()->reactions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Store
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function reactionStore()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->reactions ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->reactions = iterator_to_array( \IPS\Db::i()->select( '*', 'core_reactions', NULL, "reaction_position ASC" )->setKeyField( 'reaction_id' ) );
|
||||
}
|
||||
|
||||
return \IPS\Data\Store::i()->reactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Like Mode
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isLikeMode()
|
||||
{
|
||||
$i = 0;
|
||||
foreach( static::roots() AS $row )
|
||||
{
|
||||
if ( $row->_enabled !== FALSE )
|
||||
{
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
return ( $i == 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Record
|
||||
*
|
||||
* @see \IPS\Db::build
|
||||
* @param int|string $id ID
|
||||
* @param string $idField The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId)
|
||||
* @param mixed $extraWhereClause Additional where clause(s) (see \IPS\Db::build for details)
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
public static function load( $id, $idField=NULL, $extraWhereClause=NULL )
|
||||
{
|
||||
if ( ( $idField === NULL or $idField === 'reaction_id' ) and $extraWhereClause === NULL )
|
||||
{
|
||||
$reactions = static::reactionStore();
|
||||
if ( isset( $reactions[ $id ] ) )
|
||||
{
|
||||
return static::constructFromData( $reactions[ $id ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::load( $id, $idField, $extraWhereClause );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
unset( \IPS\Data\Store::i()->reactions );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
parent::save();
|
||||
|
||||
unset( \IPS\Data\Store::i()->reactions );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get buttons to display in tree
|
||||
* Example code explains return value
|
||||
*
|
||||
* @code
|
||||
array(
|
||||
array(
|
||||
'icon' => 'plus-circle', // Name of FontAwesome icon to use
|
||||
'title' => 'foo', // Language key to use for button's title parameter
|
||||
'link' => \IPS\Http\Url::internal( 'app=foo...' ) // URI to link to
|
||||
'class' => 'modalLink' // CSS Class to use on link (Optional)
|
||||
),
|
||||
... // Additional buttons
|
||||
);
|
||||
* @endcode
|
||||
* @param string $url Base URL
|
||||
* @param bool $subnode Is this a subnode?
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons( $url, $subnode=FALSE )
|
||||
{
|
||||
$buttons = parent::getButtons( $url, $subnode );
|
||||
|
||||
if ( $this->canDelete() )
|
||||
{
|
||||
$buttons['delete'] = array(
|
||||
'icon' => 'times-circle',
|
||||
'title' => 'delete',
|
||||
'link' => $url->setQueryString( array( 'do' => 'delete', 'id' => $this->_id ) ),
|
||||
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('delete') ),
|
||||
'hotkey'=> 'd'
|
||||
);
|
||||
}
|
||||
return $buttons;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Read/Unread Tracking Interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Interface for Reportable Comment Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Reputation interface for Content Models
|
||||
* @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 27 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reputation Interface for Content Models
|
||||
*
|
||||
* @note Content classes will gain special functionality by implementing this interface
|
||||
*/
|
||||
interface Reputation
|
||||
{
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Review Model
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 4 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Filter for Search Queries
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 6 Jul 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search;
|
||||
@@ -32,6 +31,12 @@ class _ContentFilter
|
||||
* @brief Classes to include
|
||||
*/
|
||||
public $classes = array();
|
||||
|
||||
/**
|
||||
* @brief Container classes to include
|
||||
* @note NULL means not to filter by container classes
|
||||
*/
|
||||
public $containerClasses = NULL;
|
||||
|
||||
/**
|
||||
* @brief Container ID filter
|
||||
@@ -85,7 +90,7 @@ class _ContentFilter
|
||||
* @param bool $includeItems Include items in results?
|
||||
* @param bool $includeComments Include comments in results?
|
||||
* @param bool $includeReviews Include reviews in results?
|
||||
* @return \IPS\Content\Search\ContainerFilter
|
||||
* @return \IPS\Content\Search\ContentFilter
|
||||
*/
|
||||
public static function init( $itemClass, $includeItems=TRUE, $includeComments=TRUE, $includeReviews=TRUE )
|
||||
{
|
||||
@@ -114,7 +119,7 @@ class _ContentFilter
|
||||
* Only include results in containers
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function onlyInContainers( array $ids )
|
||||
{
|
||||
@@ -128,7 +133,7 @@ class _ContentFilter
|
||||
* Exclude results in containers
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function excludeInContainers( array $ids )
|
||||
{
|
||||
@@ -142,7 +147,7 @@ class _ContentFilter
|
||||
* Only include results in items
|
||||
*
|
||||
* @param array $ids Acceptable item IDs
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function onlyInItems( array $ids )
|
||||
{
|
||||
@@ -156,7 +161,7 @@ class _ContentFilter
|
||||
* Exclude results in items
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function excludeInItems( array $ids )
|
||||
{
|
||||
@@ -170,7 +175,7 @@ class _ContentFilter
|
||||
* Set minimum number of comments
|
||||
*
|
||||
* @param int $minimumComments The minimum number of comments
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function minimumComments( $minimumComments )
|
||||
{
|
||||
@@ -183,7 +188,7 @@ class _ContentFilter
|
||||
* Set minimum number of reviews
|
||||
*
|
||||
* @param int $minimumReviews The minimum number of reviews
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function minimumReviews( $minimumReviews )
|
||||
{
|
||||
@@ -196,7 +201,7 @@ class _ContentFilter
|
||||
* Set minimum number of views
|
||||
*
|
||||
* @param int $minimumViews The minimum number of views
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function minimumViews( $minimumViews )
|
||||
{
|
||||
@@ -208,7 +213,7 @@ class _ContentFilter
|
||||
/**
|
||||
* Only include results which are the first comment on an item requiring a comment
|
||||
*
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function onlyFirstComment()
|
||||
{
|
||||
@@ -220,7 +225,7 @@ class _ContentFilter
|
||||
/**
|
||||
* Only include results which are the last comment on an item requiring a comment
|
||||
*
|
||||
* @return \IPS\Content\Search\ContainerFilter (for daisy chaining)
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
*/
|
||||
public function onlyLastComment()
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Abstract Search Index
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 Aug 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search;
|
||||
@@ -250,12 +249,32 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Club */
|
||||
$container = NULL;
|
||||
$containerId = NULL;
|
||||
$clubId = NULL;
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
$containerId = (int) $object->searchIndexContainer();
|
||||
$container = $object->containerWrapper();
|
||||
}
|
||||
else
|
||||
{
|
||||
$containerId = (int) $object->item()->mapped('container');
|
||||
$container = $object->item()->containerWrapper();
|
||||
}
|
||||
if ( $container and \IPS\IPS::classUsesTrait( $container, 'IPS\Content\ClubContainer' ) )
|
||||
{
|
||||
$clubId = $container->{$container::clubIdColumn()};
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return array(
|
||||
'index_class' => $class,
|
||||
'index_object_id' => $object->$idColumn,
|
||||
'index_item_id' => ( $object instanceof \IPS\Content\Item ) ? $object->$idColumn : $object->mapped('item'),
|
||||
'index_container_class' => ( $object instanceof \IPS\Content\Item ) ? get_class( $object->searchIndexContainerClass() ) : get_class( $object->item()->searchIndexContainerClass() ),
|
||||
'index_container_id' => ( $object instanceof \IPS\Content\Item ) ? (int) $object->searchIndexContainer() : (int) $object->item()->mapped('container'),
|
||||
'index_title' => $title,
|
||||
'index_content' => $content,
|
||||
@@ -268,7 +287,8 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
'index_hidden' => $hiddenStatus,
|
||||
'index_item_index_id' => $itemIndexId,
|
||||
'index_item_author' => intval( ( $object instanceof \IPS\Content\Item ) ? $object->mapped('author') : $object->item()->mapped('author') ),
|
||||
'index_is_last_comment' => $isLastComment
|
||||
'index_is_last_comment' => $isLastComment,
|
||||
'index_club_id' => $clubId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief MySQL Search Index
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 Aug 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search\Mysql;
|
||||
@@ -331,7 +330,7 @@ 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_item_map', array( 'index_class=? and index_item_id IN( ? )', $class, \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 );
|
||||
}
|
||||
@@ -375,6 +374,18 @@ class _Index extends \IPS\Content\Search\Index
|
||||
if ( $newContainer )
|
||||
{
|
||||
$update['index_container_id'] = $newContainer;
|
||||
|
||||
if ( $itemClass = ( in_array( 'IPS\Content\Item', class_parents( $class ) ) ? $class : $class::$itemClass ) and $containerClass = $itemClass::$containerNodeClass and \IPS\IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) and $clubIdColumn = $containerClass::clubIdColumn() )
|
||||
{
|
||||
try
|
||||
{
|
||||
$update['index_club_id'] = $containerClass::load( $newContainer )->$clubIdColumn;
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$update['index_club_id'] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $newItemId )
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief MySQL Search Query
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 Aug 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search\Mysql;
|
||||
@@ -128,6 +127,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$clause[] = \IPS\Db::i()->in( 'index_container_id', $unsearchableIds, TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
/* Are we excluding certain container classes? */
|
||||
if( $filter->containerClasses !== NULL )
|
||||
{
|
||||
$clause[] = \IPS\Db::i()->in( 'index_container_class', $filter->containerClasses );
|
||||
}
|
||||
|
||||
/* Set the item IDs */
|
||||
if ( $filter->itemIdFilter !== NULL )
|
||||
@@ -209,7 +214,32 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by club
|
||||
*
|
||||
* @param \IPS\Member\Club|int|array|null $club The club, or array of club IDs, or NULL to exclude content from clubs
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByClub( $club )
|
||||
{
|
||||
if ( $club === NULL )
|
||||
{
|
||||
$this->where[] = 'index_club_id IS NULL';
|
||||
}
|
||||
if ( is_array( $club ) )
|
||||
{
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_club_id', $club ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->where[] = array( 'index_club_id=?', $club instanceof \IPS\Member\Club ? $club->id : $club );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for profile
|
||||
@@ -253,6 +283,37 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by container class
|
||||
*
|
||||
* @param array $classes Container classes to exclude from results.
|
||||
* @param array $exclude Content classes to exclude from the filter. For cases where multiple content classes may have the same container class
|
||||
* such as Gallery images, comments and reviews.
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByContainerClasses( $classes=array(), $exclude=array() )
|
||||
{
|
||||
if( empty( $exclude ) )
|
||||
{
|
||||
$this->where[] = \IPS\Db::i()->in( 'index_container_class', $classes, TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach( $classes as $i => $class )
|
||||
{
|
||||
$classes[$i] = "'" . \IPS\DB::i()->real_escape_string( $class ) . "'";
|
||||
}
|
||||
foreach( $exclude as $i => $class )
|
||||
{
|
||||
$exclude[$i] = "'" . \IPS\DB::i()->real_escape_string( $class ) . "'";
|
||||
}
|
||||
|
||||
$this->where[] = '( index_container_class NOT IN(' . implode( ',', $classes ) . ') OR index_class IN(' . implode( ',', $exclude ) . ') )';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by content the user follows
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Abstract Search Query
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 Aug 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search;
|
||||
@@ -46,7 +45,7 @@ abstract class _Query
|
||||
* Create new query
|
||||
*
|
||||
* @param \IPS\Member $member The member performing the search (NULL for currently logged in member)
|
||||
* @return \IPS\Content\Search
|
||||
* @return \IPS\Content\Search\Query
|
||||
*/
|
||||
public static function init( \IPS\Member $member = NULL )
|
||||
{
|
||||
@@ -108,6 +107,14 @@ abstract class _Query
|
||||
*/
|
||||
abstract public function filterByAuthor( $author );
|
||||
|
||||
/**
|
||||
* Filter by club
|
||||
*
|
||||
* @param \IPS\Member\Club|int|array $club The club, or array of club IDs
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
abstract public function filterByClub( $club );
|
||||
|
||||
/**
|
||||
* Filter for profile
|
||||
*
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Search Result from Index
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 15 Sep 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search\Result;
|
||||
@@ -63,9 +62,10 @@ class _Content extends \IPS\Content\Search\Result
|
||||
* @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 array $reactions Reaction Data
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating = NULL, $iPostedIn = FALSE )
|
||||
public function __construct( array $indexData, array $authorData, array $itemData, array $containerData = NULL, array $reputationData, $reviewRating = NULL, $iPostedIn = FALSE, $reactions=array() )
|
||||
{
|
||||
$this->createdDate = \IPS\DateTime::ts( $indexData['index_date_created'] );
|
||||
$this->lastUpdatedDate = \IPS\DateTime::ts( $indexData['index_date_updated'] );
|
||||
@@ -76,6 +76,7 @@ class _Content extends \IPS\Content\Search\Result
|
||||
$this->reputationData = $reputationData;
|
||||
$this->reviewRating = $reviewRating;
|
||||
$this->iPostedIn = $iPostedIn;
|
||||
$this->reactions = $reactions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,7 +90,7 @@ class _Content extends \IPS\Content\Search\Result
|
||||
*/
|
||||
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, $canIgnoreComments, $template );
|
||||
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, $this->reactions );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Search Result not from Index
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 15 Sep 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search\Result;
|
||||
@@ -32,6 +31,11 @@ class _Custom extends \IPS\Content\Search\Result
|
||||
* @brief Image
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* @brief Data used for merging multiple extra items
|
||||
*/
|
||||
protected $mergeData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -39,14 +43,39 @@ class _Custom extends \IPS\Content\Search\Result
|
||||
* @param \IPS\DateTime $time The time for this result
|
||||
* @param string $html HTML to display
|
||||
* @param string|NULL $image HTML for image to display
|
||||
* @param string|NULL $mergeData Data for merging
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\DateTime $time, $html, $image = NULL )
|
||||
public function __construct( \IPS\DateTime $time, $html, $image = NULL, $mergeData = NULL )
|
||||
{
|
||||
$this->createdDate = $time;
|
||||
$this->lastUpdatedDate = $time;
|
||||
$this->html = $html;
|
||||
$this->image = $image;
|
||||
$this->mergeData = array( $mergeData );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge in new data
|
||||
*
|
||||
* @param string $html New HTML to merge in as a list
|
||||
* @param string|NULL $mergeData Data for merging
|
||||
* @return void
|
||||
*/
|
||||
public function mergeInData( $html, $mergeData = NULL )
|
||||
{
|
||||
$this->html = $html;
|
||||
$this->mergeData[] = $mergeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data for merging
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMergeData()
|
||||
{
|
||||
return $this->mergeData ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Search Result
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 15 Sep 2015
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Search Results
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 Aug 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\Search;
|
||||
@@ -53,6 +52,11 @@ class _Results extends \ArrayIterator
|
||||
*/
|
||||
protected $reputationData = array();
|
||||
|
||||
/**
|
||||
* @brief Reaction Data
|
||||
*/
|
||||
protected $reactions = array();
|
||||
|
||||
/**
|
||||
* @brief Review Ratings
|
||||
*/
|
||||
@@ -139,7 +143,7 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
}
|
||||
|
||||
if ( in_array( 'IPS\Content\Reputation', class_implements( $result['index_class'] ) ) )
|
||||
if ( \IPS\IPS::classUsesTrait( $result['index_class'], 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
$reputationIds[ $result['index_class'] ][ $result['index_object_id'] ] = $result['index_object_id'];
|
||||
}
|
||||
@@ -153,8 +157,13 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
$itemIndexIds[] = $result['index_item_index_id'];
|
||||
}
|
||||
|
||||
if ( $result['index_club_id'] )
|
||||
{
|
||||
$containersToLoad[ 'IPS\Member\Club' ][ $result['index_club_id'] ] = $result['index_club_id'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Load item data */
|
||||
foreach ( $itemsToLoad as $itemClass => $itemIds )
|
||||
{
|
||||
@@ -162,7 +171,7 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
$this->itemData[ $itemClass ][ $itemId ] = $itemData;
|
||||
|
||||
if ( isset( $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'] ] ) )
|
||||
if ( isset( $itemClass::$databaseColumnMap ) and isset( $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'] ] ) )
|
||||
{
|
||||
$memberId = $itemData[ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'] ];
|
||||
$membersToLoad[ $memberId ] = $memberId;
|
||||
@@ -198,26 +207,23 @@ class _Results extends \ArrayIterator
|
||||
$binds = array();
|
||||
foreach ( $reputationIds as $class => $ids )
|
||||
{
|
||||
$clause[] = "( app=? AND type=? AND " . \IPS\Db::i()->in( 'type_id', $ids ) . " )";
|
||||
$clause[] = "( reaction_enabled=? AND app=? AND type=? AND " . \IPS\Db::i()->in( 'type_id', $ids ) . " )";
|
||||
$binds[] = 1;
|
||||
$binds[] = $class::$application;
|
||||
$binds[] = $class::$reputationType;
|
||||
$binds[] = $class::reactionType();
|
||||
}
|
||||
|
||||
$where = array( array_merge( array( implode( ' OR ', $clause ) ), $binds ) );
|
||||
switch( \IPS\Settings::i()->reputation_point_types )
|
||||
foreach ( \IPS\Db::i()->select( array( 'app', 'type', 'type_id', 'member_id', 'rep_rating', 'reaction' ), 'core_reputation_index', $where )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
|
||||
{
|
||||
case 'positive':
|
||||
case 'like':
|
||||
$where[] = array( 'rep_rating=?', "1" );
|
||||
break;
|
||||
case 'negative':
|
||||
$where[] = array( 'rep_rating=?', "-1" );
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ( \IPS\Db::i()->select( array( 'app', 'type', 'type_id', 'member_id', 'rep_rating' ), 'core_reputation_index', $where ) as $rep )
|
||||
{
|
||||
$this->reputationData[ $rep['app'] ][ $rep['type'] ][ $rep['type_id'] ][ $rep['member_id'] ] = $rep['rep_rating'];
|
||||
$this->reputationData[ $rep['app'] ][ $rep['type'] ][ $rep['type_id'] ][ $rep['member_id'] ] = $rep['reaction'];
|
||||
|
||||
if ( !isset( $this->reactions[ $rep['app'] ][ $rep['type'] ][ $rep['type_id' ] ][ $rep['reaction'] ] ) )
|
||||
{
|
||||
$this->reactions[ $rep['app'] ][ $rep['type'] ][ $rep['type_id' ] ][ $rep['reaction'] ] = 0;
|
||||
}
|
||||
|
||||
$this->reactions[ $rep['app'] ][ $rep['type'] ][ $rep['type_id' ] ][ $rep['reaction'] ]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,9 +264,15 @@ class _Results extends \ArrayIterator
|
||||
$containerClass = isset( $itemClass::$containerNodeClass ) ? $itemClass::$containerNodeClass : NULL;
|
||||
|
||||
$reputationData = array();
|
||||
if ( in_array( 'IPS\Content\Reputation', class_implements( $class ) ) and isset( $this->reputationData[ $class::$application ][ $class::$reputationType ][ $data['index_object_id'] ] ) )
|
||||
if ( \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) and isset( $this->reputationData[ $class::$application ][ $class::reactionType() ][ $data['index_object_id'] ] ) )
|
||||
{
|
||||
$reputationData = $this->reputationData[ $class::$application ][ $class::$reputationType ][ $data['index_object_id'] ];
|
||||
$reputationData = $this->reputationData[ $class::$application ][ $class::reactionType() ][ $data['index_object_id'] ];
|
||||
}
|
||||
|
||||
$reactions = array();
|
||||
if ( \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) and isset( $this->reactions[ $class::$application ][ $class::reactionType() ][ $data['index_object_id'] ] ) )
|
||||
{
|
||||
$reactions = $this->reactions[ $class::$application ][ $class::reactionType() ][ $data['index_object_id'] ];
|
||||
}
|
||||
|
||||
/* Ensure we have an item if results are gathered from the map table */
|
||||
@@ -295,14 +307,25 @@ class _Results extends \ArrayIterator
|
||||
$data['index_prefix'] = $this->prefixes[ $data['index_item_index_id'] ]['index_prefix'];
|
||||
}
|
||||
|
||||
$containerData = NULL;
|
||||
if ( $containerClass and isset( $this->containerData[ $containerClass ] ) and isset( $this->containerData[ $containerClass ][ $data['index_container_id'] ] ) )
|
||||
{
|
||||
$containerData = $this->containerData[ $containerClass ][ $data['index_container_id'] ];
|
||||
if ( $data['index_club_id'] and isset( $this->containerData[ 'IPS\Member\Club' ][ $data['index_club_id'] ] ) )
|
||||
{
|
||||
$containerData['_club'] = $this->containerData[ 'IPS\Member\Club' ][ $data['index_club_id'] ];
|
||||
}
|
||||
}
|
||||
|
||||
return new Result\Content(
|
||||
$data,
|
||||
isset( $this->authorData[ $data['index_author'] ] ) ? $this->authorData[ $data['index_author'] ] : array( 'member_id' => 0, 'name' => \IPS\Member::loggedIn()->language()->addToStack('guest'), 'members_seo_name' => '', 'pp_main_photo' => NULL, 'pp_photo_type' => 'none' ),
|
||||
$itemData,
|
||||
( $containerClass and isset( $this->containerData[ $containerClass ] ) and isset( $this->containerData[ $containerClass ][ $data['index_container_id'] ] ) ) ? $this->containerData[ $containerClass ][ $data['index_container_id'] ] : NULL,
|
||||
$containerData,
|
||||
$reputationData,
|
||||
isset( $this->reviewRatings[ $data['index_class'] ][ $data['index_object_id'] ] ) ? $this->reviewRatings[ $data['index_class'] ][ $data['index_object_id'] ] : NULL,
|
||||
in_array( $data['index_item_index_id'], $this->iPostedIn )
|
||||
in_array( $data['index_item_index_id'], $this->iPostedIn ),
|
||||
$reactions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -383,8 +406,15 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( $results as $result )
|
||||
foreach ( $results as $key => $result )
|
||||
{
|
||||
/* If an item has been removed incorrectly an orphan search index entry can be left. This is already logged in current() so let's not fail catastrophically */
|
||||
if( !$result )
|
||||
{
|
||||
unset( $results[ $key ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$lastTime = $result->createdDate->getTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -481,6 +511,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() ) ) . "')" );
|
||||
|
||||
$lastClass = NULL;
|
||||
$lastMember = NULL;
|
||||
|
||||
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' )
|
||||
@@ -493,66 +526,121 @@ class _Results extends \ArrayIterator
|
||||
else
|
||||
{
|
||||
$class = 'IPS\\' . $follow['follow_app'] . '\\' . ucfirst( $follow['follow_area'] );
|
||||
|
||||
try
|
||||
{
|
||||
if ( class_exists( $class ) )
|
||||
{
|
||||
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
|
||||
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
|
||||
$urlToThing = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingFollowed->url(), FALSE, $thingBeingFollowed instanceof \IPS\Node\Model ? $thingBeingFollowed->_title : $thingBeingFollowed->mapped('title'), FALSE );
|
||||
|
||||
/* Do we need to merge with the last result? */
|
||||
if( $lastClass !== NULL AND $lastClass === $class AND $lastMember === $follow['follow_member_id'] AND count( $extraItems ) )
|
||||
{
|
||||
$extraItem = array_pop( $extraItems );
|
||||
|
||||
$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 ) ) ) )
|
||||
);
|
||||
$mergeList = array_merge( array( $urlToThing ), $extraItem->getMergeData() );
|
||||
$listToDisplay = $mergeList;
|
||||
|
||||
if( count( $mergeList ) > 3 )
|
||||
{
|
||||
$listToDisplay = array_slice( $mergeList, 0, 3 );
|
||||
$listToDisplay[] = \IPS\Member::loggedIn()->language()->addToStack( 'and_x_others', FALSE, array( 'pluralize' => array( count( $mergeList ) - 3 ) ) );
|
||||
}
|
||||
$extraItem->mergeInData( \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\Member::loggedIn()->language()->formatList( $listToDisplay ) ) ) ), $urlToThing );
|
||||
$extraItems[] = $extraItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
$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'] ) ), $urlToThing ) ) ),
|
||||
NULL,
|
||||
$urlToThing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
|
||||
$lastClass = $class;
|
||||
$lastMember = $follow['follow_member_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Likes */
|
||||
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 ) ) )
|
||||
if ( \IPS\Settings::i()->reputation_enabled and \IPS\Member::loggedIn()->group['gbw_view_reps'] and ( in_array( 'like', $extra ) or in_array( 'react', $extra ) ) )
|
||||
{
|
||||
$where = array( array( 'rep_date>?', $lastTime ) );
|
||||
$where[] = array( 'reaction_enabled=?', 1 );
|
||||
if ( $firstTime )
|
||||
{
|
||||
$where[] = array( 'rep_date<?', $firstTime );
|
||||
}
|
||||
if ( !in_array( 'rep_neg', $extra ) or ( \IPS\Settings::i()->reputation_point_types != 'both' and \IPS\Settings::i()->reputation_point_types != 'negative' ) )
|
||||
{
|
||||
$where[] = array( 'rep_rating=1' );
|
||||
}
|
||||
elseif ( !in_array( 'like', $extra ) or ( \IPS\Settings::i()->reputation_point_types == 'negative' ) )
|
||||
{
|
||||
$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 )
|
||||
{
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array_merge( $where, array( array( 'member_id=?', $author->member_id ) ) ), 'rep_date DESC', 10 ) as $rep )
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array_merge( $where, array( array( 'member_id=?', $author->member_id ) ) ), 'rep_date DESC', 10 )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
|
||||
{
|
||||
$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 )
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', array_merge( $where, array( array( 'member_received=?', $author->member_id ) ) ), 'rep_date DESC', 10 )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
|
||||
{
|
||||
$this->addReputationExtraItem( $rep, $extraItems );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'rep_date DESC', 10 ) as $rep )
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_reputation_index', $where, 'rep_date DESC', 10 )->join( 'core_reactions', 'reaction=reaction_id' ) as $rep )
|
||||
{
|
||||
$this->addReputationExtraItem( $rep, $extraItems );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Clubs */
|
||||
if ( \IPS\Settings::i()->clubs and in_array( 'clubs', $extra ) )
|
||||
{
|
||||
$where = array( array( 'created>?', $lastTime ) );
|
||||
if ( $firstTime )
|
||||
{
|
||||
$where[] = array( 'created<?', $firstTime );
|
||||
}
|
||||
if ( $author )
|
||||
{
|
||||
$where[] = array( 'owner=?', $author->member_id );
|
||||
}
|
||||
|
||||
foreach ( \IPS\Member\Club::clubs( \IPS\Member::loggedIn(), NULL, 'created', FALSE, array(), $where ) as $club )
|
||||
{
|
||||
if ( $club->owner AND $club->owner->member_id )
|
||||
{
|
||||
$extraItems[] = new Result\Custom(
|
||||
$club->created,
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_club', FALSE, array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( $club->owner ), \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $club->url(), FALSE, $club->name, FALSE ) ) ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Extensions */
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'StreamItems', TRUE, 'core' ) as $key => $extension )
|
||||
{
|
||||
$settingKey = "all_activity_" . mb_strtolower( $key );
|
||||
|
||||
if( method_exists( $extension, 'extraItems' ) and \IPS\Settings::i()->$settingKey )
|
||||
{
|
||||
$extraItems = array_merge( $extraItems, $extension->extraItems( $author, $lastTime, $firstTime ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Merge them in */
|
||||
if ( !empty( $extraItems ) )
|
||||
{
|
||||
@@ -594,7 +682,7 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
foreach ( $ext->classes as $class )
|
||||
{
|
||||
if ( in_array( 'IPS\Content\Reputation', class_implements( $class ) ) and $class::$reputationType == $rep['type'] )
|
||||
if ( \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) and $class::reactionType() == $rep['type'] )
|
||||
{
|
||||
$thingBeingRepped = $class::loadAndCheckPerms( $rep['type_id'] );
|
||||
break;
|
||||
@@ -602,7 +690,7 @@ class _Results extends \ArrayIterator
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
if ( in_array( 'IPS\Content\Reputation', class_implements( $commentClass ) ) and $commentClass::$reputationType == $rep['type'] )
|
||||
if ( \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) and $commentClass::reactionType() == $rep['type'] )
|
||||
{
|
||||
$thingBeingRepped = $commentClass::loadAndCheckPerms( $rep['type_id'] );
|
||||
break;
|
||||
@@ -611,7 +699,7 @@ class _Results extends \ArrayIterator
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$reviewClass = $class::$reviewClass;
|
||||
if ( in_array( 'IPS\Content\Reputation', class_implements( $reviewClass ) ) and $reviewClass::$reputationType == $rep['type'] )
|
||||
if ( \IPS\IPS::classUsesTrait( $reviewClass, 'IPS\Content\Reactable' ) and $reviewClass::reactionType() == $rep['type'] )
|
||||
{
|
||||
$thingBeingRepped = $reviewClass::loadAndCheckPerms( $rep['type_id'] );
|
||||
break;
|
||||
@@ -624,27 +712,31 @@ class _Results extends \ArrayIterator
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
if ( \IPS\Settings::i()->reputation_point_types == 'like' )
|
||||
if ( \IPS\Content\Reaction::isLikeMode() )
|
||||
{
|
||||
$lang = 'activity_member_liked';
|
||||
}
|
||||
elseif ( $rep['rep_rating'] > 0 )
|
||||
{
|
||||
$lang = 'activity_rep_pos';
|
||||
$extraItems[] = new Result\Custom(
|
||||
\IPS\DateTime::ts( $rep['rep_date'] ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_liked', 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 )
|
||||
) ) )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$lang = 'activity_rep_neg';
|
||||
$reaction = \IPS\Content\Reaction::load( $rep['reaction'] );
|
||||
$extraItems[] = new Result\Custom(
|
||||
\IPS\DateTime::ts( $rep['rep_date'] ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_reacted', FALSE, array( 'htmlsprintf' => array(
|
||||
(string) $reaction->_icon,
|
||||
$reaction->_title,
|
||||
\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 )
|
||||
) ) )
|
||||
);
|
||||
}
|
||||
|
||||
$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 ) { }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Interface for Searchable Content/Comment Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Delicious 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='https://delicious.com/tools'>Delicious button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Digg 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @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)
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Email 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\ShareServices;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Facebook 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='https://developers.facebook.com/docs/reference/plugins/like/'>Facebook like button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Google +1 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='https://developers.google.com/+/web/+1button/'>Google +1 button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Linked In 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='http://developer.linkedin.com/plugins/share-plugin-generator'>Linked In button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 01 Dec 2016
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content\ShareServices;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Reddit 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='http://www.reddit.com/buttons/'>Reddit button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief StumbleUpon 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='http://www.stumbleupon.com/dt/badges/create'>StumbleUpon button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Twitter 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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 11 Sept 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
* @see <a href='https://dev.twitter.com/docs/tweet-button'>Tweet button documentation</a>
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Interface for Shareable Comment Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jul 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Tags interface for Content Models
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 14 Nov 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Interface for Tracking Views
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 8 Jan 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Item Feed Widget
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage forums
|
||||
* @since 16 Oct 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
@@ -151,15 +150,9 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
/* Date restrict */
|
||||
$form->add( new \IPS\Helpers\Form\Number( 'widget_feed_restrict_days', isset( $this->configuration['widget_feed_restrict_days'] ) ? $this->configuration['widget_feed_restrict_days'] : -1, FALSE, array( 'unlimited' => -1 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('widget_feed_restrict_days_suffix') ) );
|
||||
|
||||
/* Sort */
|
||||
$sortOptions = array();
|
||||
foreach ( array( 'updated', 'title', 'num_comments', 'date', 'views', 'rating_average' ) as $k )
|
||||
{
|
||||
if ( isset( $class::$databaseColumnMap[ $k ] ) )
|
||||
{
|
||||
$sortOptions[ $class::$databaseColumnMap[ $k ] ] = 'sort_' . $k;
|
||||
}
|
||||
}
|
||||
/* Sort */
|
||||
$sortOptions = $class::getWidgetSortOptions();
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'widget_feed_sort_on', isset( $this->configuration['widget_feed_sort_on'] ) ? $this->configuration['widget_feed_sort_on'] : $class::$databaseColumnMap['updated'], FALSE, array( 'options' => $sortOptions ) ), NULL, NULL, NULL, 'widget_feed_sort_on' );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'widget_feed_sort_dir', isset( $this->configuration['widget_feed_sort_dir'] ) ? $this->configuration['widget_feed_sort_dir'] : 'desc', FALSE, array(
|
||||
@@ -342,7 +335,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
/* 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_sort_on'] ) and isset( $this->configuration['widget_feed_sort_dir'] ) ) ? ( ( $this->configuration['widget_feed_sort_on'] == '_rand' ) ? $this->configuration['widget_feed_sort_on'] : ( $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 */
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Content Item Feed Widget
|
||||
* @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
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @subpackage forums
|
||||
* @since 16 Oct 2014
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Content;
|
||||
|
||||
Reference in new issue
Block a user