Seditio Source
Root |
./othercms/ips_4.3.4/applications/core/sources/Reports/Report.php
<?php
/**
 * @brief        Report Index 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        15 Jul 2013
 */

namespace IPS\core\Reports;

/* 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;
}

/**
 * Report Model
 */
class _Report extends \IPS\Content\Item implements \IPS\Content\ReadMarkers
{
   
/**
     * @brief    Const    No type selected
     */
   
const    TYPE_MESSAGE = 0;
     
     
   
/* !\IPS\Patterns\ActiveRecord */
   
    /**
     * @brief    Database Table
     */
   
public static $databaseTable = 'core_rc_index';
   
   
/**
     * @brief    Database Prefix
     */
   
public static $databasePrefix = '';
   
   
/**
     * @brief    Multiton Store
     */
   
protected static $multitons;
   
   
/**
     * @brief    Database ID Fields
     */
   
protected static $databaseIdFields = array( 'content_id' );
   
   
/**
     * @brief    [ActiveRecord] Multiton Map
     */
   
protected static $multitonMap    = array();
   
   
/* !\IPS\Content\Item */
   
    /**
     * @brief    Application
     */
   
public static $application = 'core';
   
   
/**
     * @brief    Application
     */
   
public static $module = 'modcp';

   
/**
     * @brief    Allow the title to be editable via AJAX
     */
   
public $editableTitle    = FALSE;
   
   
/**
     * @brief    Database Column Map
     */
   
public static $databaseColumnMap = array(
       
'date'            => 'first_report_date',
       
'author'        => 'first_report_by',
       
'author_count'    => 'num_reports',
       
'title'            => 'title',
       
'last_comment'    => 'last_updated',
       
'num_comments'    => 'num_comments',
    );
   
   
/**
     * @brief    Language prefix for forms
     */
   
public static $formLangPrefix = 'report_';
   
   
/**
     * @brief    Comment Class
     */
   
public static $commentClass = 'IPS\core\Reports\Comment';
   
   
/**
     * @brief    Title
     */
   
public static $title = 'report';
   
   
/**
     * Should posting this increment the poster's post count?
     *
     * @param    \IPS\Node\Model|NULL    $container    Container
     * @return    void
     */
   
public static function incrementPostCount( \IPS\Node\Model $container = NULL )
    {
        return
FALSE;
    }
   
   
/**
     * Load by class and content_id
     *
     * @return \IPS\core\Reports\Report
     * @throws OutofRangeException
     */
   
public static function loadByClassAndId( $class, $id )
    {
        try
        {
            return static::
constructFromData( \IPS\Db::i()->select( '*', 'core_rc_index', array( 'class=? and content_id=?', $class, $id ) )->first() );
        }
        catch ( \
UnderflowException $e )
        {
            throw new \
OutofRangeException;
        }
    }
   
   
/**
     * Get mapped value
     *
     * @param    string    $key    date,content,ip_address,first
     * @return    mixed
     */
   
public function mapped( $key )
    {
       
/* Get the reported content items title */
       
if ( $key === 'title' )
        {
            try
            {
               
$class = $this->_data['class'];
               
$thing = $class::load( $this->_data['content_id'] );
               
$item = ( $thing instanceof \IPS\Content\Comment ) ? $thing->item() : $thing;

                if( isset(
$item::$databaseColumnMap['content'] ) AND $item::$databaseColumnMap['content'] == $item::$databaseColumnMap['title'] )
                {
                   
$title = trim( mb_substr( strip_tags( $item->mapped( 'title' ) ), 0, 85 ) );
                    return
$title ?: \IPS\Member::loggedIn()->language()->addToStack('report_no_title_available');
                }
                else
                {
                   
$title = trim( strip_tags( $item->mapped( 'title' ) ) );
                    return
$title ?: \IPS\Member::loggedIn()->language()->addToStack('report_no_title_available');
                }
            }
            catch ( \
Exception $e )
            {
                return \
IPS\Member::loggedIn()->language()->addToStack( 'unknown' );
            }
        }

        return
parent::mapped( $key );
    }

   
/**
     * @brief    Cached URLs
     */
   
protected $_url    = array();

   
/**
     * Get URL
     *
     * @param    string|NULL        $action        Action
     * @return    \IPS\Http\Url
     */
   
public function url( $action=NULL )
    {
       
$_key    = md5( $action );

        if( !isset(
$this->_url[ $_key ] ) )
        {
           
$this->_url[ $_key ] = \IPS\Http\Url::internal( "app=core&module=modcp&tab=reports&action=view&id={$this->id}", 'front', 'modcp_report' );
       
            if (
$action )
            {
               
$this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( 'action', $action );
            }
        }
   
        return
$this->_url[ $_key ];
    }
   
   
/* !\IPS\Helpers\Table */
       
    /**
     * Method to add extra data to objects in this
     * class when displaying in a table view
     *
     * @param    array    $rows    Array of objects of this class
     * @return    void
     */
   
public static function tableGetRows( $rows )
    {
       
$types = array();
       
        foreach (
$rows as $row )
        {
           
$types[ $row->class ][ $row->content_id ] = $row;
        }

        foreach (
$types as $class => $objects )
        {
            if (
in_array( 'IPS\Content\Comment', class_parents( $class ) ) )
            {
               
$itemClass = $class::$itemClass;
               
$databaseTable = $class::$databaseTable;
               
$itemDatabaseTable = $itemClass::$databaseTable;
               
$itemTitleField = $itemClass::$databaseColumnMap['title']; # Strange PHP issue can cause this to be lost when added to the query below.
               
               
foreach( \IPS\Db::i()->select(
                   
"{$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnId} as commentId, {$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnMap['item']} AS itemId, {$itemClass::$databaseTable}.{$itemClass::$databasePrefix}{$itemTitleField} AS title",
                   
$databaseTable,
                    \
IPS\Db::i()->in( $databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
                )->
join(
                   
$itemDatabaseTable,
                   
"{$itemDatabaseTable}.{$itemClass::$databasePrefix}{$itemClass::$databaseColumnId}={$databaseTable}.{$class::$databasePrefix}{$class::$databaseColumnMap['item']}"
               
)->setKeyField( 'commentId' ) as $k => $data
               
)
                {
                   
$objects[ $k ]->_data = array_merge( $objects[ $k ]->_data, $data );
                }
            }
            elseif (
in_array( 'IPS\Content\Item', class_parents( $class ) ) )
            {
                foreach( \
IPS\Db::i()->select(
                   
"{$class::$databasePrefix}{$class::$databaseColumnId} as itemId, {$class::$databasePrefix}{$class::$databaseColumnMap['title']} AS title",
                   
$class::$databaseTable,
                    \
IPS\Db::i()->in( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId, array_keys( $objects ) )
                )->
setKeyField( 'itemId' ) as $k => $data
               
)
                {
                   
$objects[ $k ]->_data = array_merge( $objects[ $k ]->_data, $data );
                }
            }
        }
    }
   
   
/**
     * Method to get description for table view
     *
     * @return    string
     */
   
public function tableDescription()
    {
       
$className = $this->class;
        try
        {
           
$reportedContent = $className::load( $this->content_id );

            if(
$reportedContent instanceof \IPS\Content\Comment )
            {
               
$container = ( $reportedContent->item()->containerWrapper() !== NULL ) ? $reportedContent->item()->container() : NULL;
            }
            else
            {
               
$container = ( $reportedContent->containerWrapper() !== NULL ) ? $reportedContent->container() : NULL;
            }
        }
        catch ( \
OutOfRangeException $ex )
        {
           
$container = NULL;
        }

        return \
IPS\Theme::i()->getTemplate( 'tables', 'core', 'front' )->icon( $className, $container );
    }

   
/**
     * Get content table states
     *
     * @return string
     */
   
public function tableStates()
    {
       
$states = explode( ' ', parent::tableStates() );
       
$states[] = "report_status_" . $this->status;

        return
implode( ' ', $states );
    }
   
   
/**
     * Stats for table view
     *
     * @return    array
     */
   
public function stats( $includeFirstCommentInCommentCount=TRUE )
    {
        return
array_merge( parent::stats( $includeFirstCommentInCommentCount ), array( 'num_reports' => $this->num_reports ) );
    }
   
   
/**
     * Icon for table view
     *
     * @return    array
     */
   
public function tableIcon()
    {
        return \
IPS\Theme::i()->getTemplate( 'modcp', 'core', 'front' )->reportToggle( $this );
    }

   
/**
     * Gets a special class for the row
     *
     * @return    string
     */
   
public function tableClass()
    {
        switch (
$this->status )
        {
            case
2:
                return
'warning';
            break;
            case
1:
                return
'new';
            break;
        }

        return
'';
    }
   
   
/**
     * Do Moderator Action
     *
     * @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, $immediately = FALSE )
    {
        if (
mb_substr( $action, 0, -1 ) === 'report_status_' )
        {
           
$this->status = mb_substr( $action, -1 );
           
$this->save();

           
/* Post a comment on the report */
           
$content = \IPS\Member::loggedIn()->language()->addToStack( 'update_report_status_content', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'report_status_' . $this->status ) ) ) );
            \
IPS\Member::loggedIn()->language()->parseOutputForDisplay( $content );

           
$comment = \IPS\core\Reports\Comment::create( $this, $content, TRUE, NULL, NULL, \IPS\Member::loggedIn(), new \IPS\DateTime );
           
$comment->save();

           
/* And add to the moderator log */
           
\IPS\Session::i()->modLog( 'modlog__action_update_report_status', array( $this->url()->__toString() => FALSE ) );
        }
        else
        {
            return
parent::modAction( $action, $member, $reason, $immediately );
        }
    }
   
   
/**
     * Return any custom multimod actions this content item class supports
     *
     * @return    array
     */
   
public function customMultimodActions()
    {
        return
array_diff( array( 'report_status_1', 'report_status_2', 'report_status_3', ), array( 'report_status_' . $this->status ) );
    }

   
/**
     * Return any available custom multimod actions this content item class supports
     *
     * @note    Return in format of array( array( 'action' => ..., 'icon' => ..., 'language' => ... ) )
     * @return    array
     */
   
public static function availableCustomMultimodActions()
    {
        return array(
            array(
               
'groupaction'    => 'report_status',
               
'icon'            => 'flag',
               
'grouplabel'    => 'mark_as',
               
'action'        => array(
                    array(
                       
'action'    => 'report_status_1',
                       
'icon'        => 'flag',
                       
'label'        => 'report_status_1'
                   
),
                    array(
                       
'action'    => 'report_status_2',
                       
'icon'        => 'exclamation-triangle',
                       
'label'        => 'report_status_2'
                   
),
                    array(
                       
'action'    => 'report_status_3',
                       
'icon'        => 'check-circle',
                       
'label'        => 'report_status_3'
                   
)
                )
            )
        );
    }
   
   
/* !\IPS\core\Reports\report */
   
    /**
     * Get reports
     *
     * @return    array
     */
   
public function reports( $filterByType=NULL)
    {
       
$where = array( array( 'rid=?', $this->id ) );
       
        if (
$filterByType )
        {
           
$where[] = array( 'report_type=?', $filterByType );
        }
       
        return
iterator_to_array( \IPS\Db::i()->select( '*', 'core_rc_reports', $where, 'date_reported' ) );
    }
   
   
/**
     * Rebuild
     *
     * @return    void
     */
   
public function rebuild()
    {
       
$numReports = \IPS\Db::i()->select( 'COUNT(*)', 'core_rc_reports', array( 'rid=?', $this->id ) )->first();
        if ( !
$numReports )
        {
           
$this->delete();
        }
       
$this->num_reports = $numReports;
       
       
$numComments = \IPS\Db::i()->select( 'COUNT(*)', 'core_rc_comments', array( 'rid=?', $this->id ) )->first();
       
$this->num_comments = $numComments;
       
       
$this->save();
    }
   
   
/**
     * Delete Report
     *
     * @return    void
     */
   
public function delete()
    {
       
parent::delete();
   
        \
IPS\Db::i()->delete( 'core_rc_reports', array( 'rid=?', $this->id ) );
        \
IPS\Db::i()->delete( 'core_automatic_moderation_pending', array( 'pending_object_class=? and pending_object_id=?', $this->class, $this->content_id ) );
    }
   
   
/**
     * Lock auto moderation to prevent auto moderation from changing the status again
     *
     * @return void
     */
   
public function lockAutoModeration()
    {
       
$this->auto_moderation_exempt = 1;
       
$this->save();
    }
   
   
/**
     * Lock auto moderation to prevent auto moderation from changing the status again
     *
     * @return void
     */
   
public function isAutoModerationLocked()
    {
        return (boolean)
$this->auto_moderation_exempt;
    }
   
   
/**
     * Run any automatic moderation
     *
     * @return void
     */
   
public function runAutomaticModeration()
    {
        if ( ! \
IPS\Settings::i()->automoderation_enabled )
        {
            return
FALSE;
        }
       
       
/* If it is auto moderation locked, skip it */
       
if ( $this->isAutoModerationLocked() )
        {
            return
FALSE;
        }
       
       
$className = $this->class;
        try
        {
           
$reportedContent = $className::load( $this->content_id );
        }
        catch ( \
OutOfRangeException $ex )
        {
           
/* No content, no moderation, no cry */
           
return FALSE;
        }

       
/* Fetch a count of report flags so far */
       
$typeCounts = $this->getReportTypeCounts();
       
$ruleToUse  = NULL;
       
       
/* Loop over all group promotion rules and get the last one that matches us */
       
foreach( \IPS\core\Reports\Rules::roots() as $rule )
        {
            if(
$rule->enabled and $rule->matches( $reportedContent->author(), $typeCounts ) )
            {
               
$ruleToUse = $rule->id;
            }
        }

       
/* If there's no rule, return now */
       
if( $ruleToUse === NULL )
        {
           
/* It is possible a few reports have been removed so the threshold is no longer met, delete any pending rows if this is the case */
           
\IPS\Db::i()->delete( 'core_automatic_moderation_pending', array( 'pending_object_class=? and pending_object_id=?', $className, $this->content_id ) );
        }
        else
        {
           
/* Log the bad boy for actioning later. A small delay allows users to retract their warning */
           
\IPS\Db::i()->replace( 'core_automatic_moderation_pending', array(
               
'pending_object_class' => $className,
               
'pending_object_id'    => $this->content_id,
               
'pending_report_id'       => $this->id,
               
'pending_added'           => time(),
               
'pending_rule_id'       => $ruleToUse
           
) );
        }
    }
   
   
/**
     * Fetch the report type counts
     *
     * @param    boolean    $totalOnly        Return either an int of the total counts, or an array with the breakdown
     * @return array( 1 => 10, 2 => 3 )|INT
     */
   
public function getReportTypeCounts( $totalOnly=false )
    {
       
$typeCounts = array();
       
$total = 0;
       
$seen = array();
        foreach( \
IPS\Db::i()->select( '*', 'core_rc_reports', array( 'rid=? and report_type > 0', $this->id ) ) as $row )
        {
            if ( isset(
$seen[ $row['report_by'] ] ) )
            {
                continue;
            }
           
           
$seen[ $row['report_by'] ] = true;
           
           
$typeCounts[ $row['report_type'] ][ $row['report_by'] ] = true;
        }
       
       
$return = array();
        foreach(
array_keys( \IPS\core\Reports\Types::roots() ) as $type )
        {
            if ( isset(
$typeCounts[ $type ] ) )
            {
               
$return[ $type ] = count( $typeCounts[ $type ] );
               
$total += count( $typeCounts[ $type ] );
            }
            else
            {
               
$return[ $type ] = 0;
            }
        }
       
        return
$totalOnly ? $total : $return;
    }
   
   
/**
     * Return the filters that are available for selecting table rows
     *
     * @return    array
     */
   
public static function getTableFilters()
    {
        return array(
           
'read', 'unread', 'report_status_1', 'report_status_2', 'report_status_3'
       
);
    }
}