Advertisement
Guest User

Untitled

a guest
Mar 16th, 2017
201
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 44.47 KB | None | 0 0
  1. <?php
  2. /**
  3.  * @brief       Content Comment Model
  4.  * @author      <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
  5.  * @copyright   (c) 2001 - 2016 Invision Power Services, Inc.
  6.  * @license     http://www.invisionpower.com/legal/standards/
  7.  * @package     IPS Community Suite
  8.  * @since       8 Jul 2013
  9.  * @version     SVN_VERSION_NUMBER
  10.  */
  11.  
  12. namespace IPS\Content;
  13.  
  14. /* To prevent PHP errors (extending class does not exist) revealing path */
  15. if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
  16. {
  17.     header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
  18.     exit;
  19. }
  20.  
  21. /**
  22.  * Content Comment Model
  23.  */
  24. abstract class _Comment extends \IPS\Content
  25. {
  26.     /**
  27.      * @brief   [Content\Comment]   Comment Template
  28.      */
  29.     public static $commentTemplate = array( array( 'global', 'core', 'front' ), 'commentContainer' );
  30.    
  31.     /**
  32.      * @brief   [Content\Comment]   Form Template
  33.      */
  34.     public static $formTemplate = array( array( 'forms', 'core', 'front' ), 'commentTemplate' );
  35.    
  36.     /**
  37.      * @brief   [Content\Comment]   The ignore type
  38.      */
  39.     public static $ignoreType = 'topics';
  40.    
  41.     /**
  42.      * @brief   [Content\Item]  Sharelink HTML
  43.      */
  44.     protected $sharelinks = array();
  45.    
  46.     /**
  47.      * Create comment
  48.      *
  49.      * @param   \IPS\Content\Item       $item               The content item just created
  50.      * @param   string                  $comment            The comment
  51.      * @param   bool                    $first              Is the first comment?
  52.      * @param   string                  $guestName          If author is a guest, the name to use
  53.      * @param   bool|NULL               $incrementPostCount Increment post count? If NULL, will use static::incrementPostCount()
  54.      * @param   \IPS\Member|NULL        $member             The author of this comment. If NULL, uses currently logged in member.
  55.      * @param   \IPS\DateTime|NULL      $time               The time
  56.      * @param   string|NULL             $ipAddress          The IP address or NULL to detect automatically
  57.      * @param   int|NULL                $hiddenStatus       NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
  58.      * @return  static
  59.      */
  60.     public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
  61.     {
  62.         if ( $member === NULL )
  63.         {
  64.             $member = \IPS\Member::loggedIn();
  65.         }
  66.  
  67.         /* Create the object */
  68.         $obj = new static;
  69.         foreach ( array( 'item', 'date', 'author', 'author_name', 'content', 'ip_address', 'first', 'approved', 'hidden' ) as $k )
  70.         {
  71.             if ( isset( static::$databaseColumnMap[ $k ] ) )
  72.             {
  73.                 $val = NULL;
  74.                 switch ( $k )
  75.                 {
  76.                     case 'item':
  77.                         $idColumn = $item::$databaseColumnId;
  78.                         $val = $item->$idColumn;
  79.                         break;
  80.                    
  81.                     case 'date':
  82.                         $val = ( $time ) ? $time->getTimestamp() : time();
  83.                         break;
  84.                    
  85.                     case 'author':
  86.                         $val = (int) $member->member_id;
  87.                         break;
  88.                        
  89.                     case 'author_name':
  90.                         $val = ( $member->member_id ) ? $member->name : ( $guestName ?: '' );
  91.                         break;
  92.                        
  93.                     case 'content':
  94.                         $val = $comment;
  95.                         break;
  96.                        
  97.                     case 'ip_address':
  98.                         $val = $ipAddress ?: \IPS\Request::i()->ipAddress();
  99.                         break;
  100.                    
  101.                     case 'first':
  102.                         $val = $first;
  103.                         break;
  104.                        
  105.                     case 'approved':
  106.                         if ( $first ) // If this is the first post within an item, don't mark it hidden, otherwise the count of unapproved comments/items will include both the comment and the item when really only the item is hidden
  107.                         {
  108.                             $val = TRUE;
  109.                         }
  110.                         elseif ( $hiddenStatus === NULL )
  111.                         {
  112.                             if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  113.                             {
  114.                                 $val = $item->moderateNewReviews( $member ) ? 0 : 1;
  115.                             }
  116.                             else
  117.                             {
  118.                                 $val = $item->moderateNewComments( $member ) ? 0 : 1;
  119.                             }
  120.                         }
  121.                         else
  122.                         {
  123.                             switch ( $hiddenStatus )
  124.                             {
  125.                                 case 0:
  126.                                     $val = 1;
  127.                                     break;
  128.                                 case 1:
  129.                                     $val = 0;
  130.                                     break;
  131.                                 case -1:
  132.                                     $val = -1;
  133.                                     break;
  134.                             }
  135.                         }
  136.                         break;
  137.                    
  138.                     case 'hidden':
  139.                         if ( $first )
  140.                         {
  141.                             $val = FALSE; // If this is the first post within an item, don't mark it hidden, otherwise the count of unapproved comments/items will include both the comment and the item when really only the item is hidden
  142.                         }
  143.                         elseif ( $item->approvedButHidden() )
  144.                         {
  145.                             $val = 2;
  146.                         }
  147.                         elseif ( $hiddenStatus === NULL )
  148.                         {
  149.                             if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  150.                             {
  151.                                 $val = $item->moderateNewReviews( $member ) ? 1 : 0;
  152.                             }
  153.                             else
  154.                             {
  155.                                 $val = $item->moderateNewComments( $member ) ? 1 : 0;
  156.                             }
  157.                         }
  158.                         else
  159.                         {
  160.                             $val = $hiddenStatus;
  161.                         }
  162.                         break;
  163.                 }
  164.                
  165.                 foreach ( is_array( static::$databaseColumnMap[ $k ] ) ? static::$databaseColumnMap[ $k ] : array( static::$databaseColumnMap[ $k ] ) as $column )
  166.                 {
  167.                     $obj->$column = $val;
  168.                 }
  169.             }
  170.         }
  171.         $obj->save();
  172.        
  173.         /* Pass this through our profanity filters to see if it needs to be mod queued */
  174.         try
  175.         {
  176.             $hiddenByFilter = FALSE;
  177.             if ( !$obj->hidden() AND ( ! $item::$firstCommentRequired or ( $item::$firstCommentRequired and !$obj->isFirst() ) ) AND !$member->group['g_bypass_badwords'] )
  178.             {
  179.                 $hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $comment );
  180.             }
  181.         }
  182.         catch( \BadMethodCallException $e ) { }
  183.        
  184.         if ( $hiddenByFilter === TRUE )
  185.         {
  186.             if ( isset( static::$databaseColumnMap['approved'] ) )
  187.             {
  188.                 /* 'approved' is easy, clear and concise */
  189.                 $column = static::$databaseColumnMap['approved'];
  190.                 $obj->$column = 0;
  191.             }
  192.             else if ( isset( static::$databaseColumnMap['hidden'] ) )
  193.             {
  194.                 /* 'hidden' is backwards */
  195.                 $column = static::$databaseColumnMap['hidden'];
  196.                 $obj->$column = 1;
  197.             }
  198.            
  199.             $obj->save();
  200.         }
  201.        
  202.         /* Increment post count */
  203.         try
  204.         {
  205.             if ( !$obj->hidden() and ( $incrementPostCount === TRUE or ( $incrementPostCount === NULL and static::incrementPostCount( $item->container() ) ) ) )
  206.             {
  207.                 $obj->author()->member_posts++;
  208.             }
  209.         }
  210.         catch( \BadMethodCallException $e ) { }
  211.        
  212.         /* Update the container */
  213.         if ( !$obj->hidden() AND !$item->hidden() )
  214.         {
  215.             try
  216.             {
  217.                 $container = $item->container();
  218.                 if ( $container->_comments !== NULL )
  219.                 {
  220.                     $container->setLastComment( $obj );
  221.                     $container->save();
  222.                 }
  223.             }
  224.             catch ( \BadMethodCallException $e ) { }
  225.         }
  226.  
  227.         /* Update member's last post and daily post limits */
  228.         if( $obj->author()->member_id )
  229.         {
  230.             $obj->author()->member_last_post = time();
  231.            
  232.             /* Update posts per day limits */
  233.             if ( $obj->author()->group['g_ppd_limit'] )
  234.             {
  235.                 $current = $obj->author()->members_day_posts;
  236.                
  237.                 $current[0] += 1;
  238.                 if ( $current[1] == 0 )
  239.                 {
  240.                     $current[1] = \IPS\DateTime::create()->getTimestamp();
  241.                 }
  242.                
  243.                 $obj->author()->members_day_posts = $current;
  244.             }
  245.            
  246.             $obj->author()->save();
  247.         }
  248.        
  249.         /* Send notifications */
  250.         if ( !in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  251.         {
  252.             if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
  253.             {
  254.                 $obj->sendNotifications();
  255.             }
  256.             else if( $obj->hidden() === 1 )
  257.             {
  258.                 $obj->sendUnapprovedNotification();
  259.             }
  260.         }
  261.        
  262.         /* Update item */
  263.         $obj->postCreate();
  264.  
  265.         /* Add to search index */
  266.         if ( $obj instanceof \IPS\Content\Searchable )
  267.         {
  268.             \IPS\Content\Search\Index::i()->index( $obj );
  269.         }
  270.  
  271.         /* Return */
  272.         return $obj;
  273.     }
  274.    
  275.     /**
  276.      * Join profile fields when loading comments?
  277.      */
  278.     public static $joinProfileFields = FALSE;
  279.    
  280.     /**
  281.      * Joins (when loading comments)
  282.      *
  283.      * @param   \IPS\Content\Item   $item           The item
  284.      * @return  array
  285.      */
  286.     public static function joins( \IPS\Content\Item $item )
  287.     {
  288.         $return = array();
  289.        
  290.         /* Author */
  291.         $authorColumn = static::$databasePrefix . static::$databaseColumnMap['author'];
  292.         $return['author'] = array(
  293.             'select'    => 'author.*',
  294.             'from'      => array( 'core_members', 'author' ),
  295.             'where'     => array( 'author.member_id = ' . static::$databaseTable . '.' . $authorColumn )
  296.         );
  297.        
  298.         /* Author profile fields */
  299.         if ( static::$joinProfileFields and \IPS\core\ProfileFields\Field::fieldsForContentView() )
  300.         {
  301.             $return['author_pfields'] = array(
  302.                 'select'    => 'author_pfields.*',
  303.                 'from'      => array( 'core_pfields_content', 'author_pfields' ),
  304.                 'where'     => array( 'author_pfields.member_id=author.member_id' )
  305.             );
  306.         }
  307.                
  308.         return $return;
  309.     }
  310.    
  311.     /**
  312.      * Do stuff after creating (abstracted as comments and reviews need to do different things)
  313.      *
  314.      * @return  void
  315.      */
  316.     public function postCreate()
  317.     {
  318.         $item = $this->item();
  319.        
  320.         $item->resyncCommentCounts();
  321.            
  322.         if( isset( static::$databaseColumnMap['date'] ) )
  323.         {
  324.             if( is_array( static::$databaseColumnMap['date'] ) )
  325.             {
  326.                 $postDateColumn = static::$databaseColumnMap['date'][0];
  327.             }
  328.             else
  329.             {
  330.                 $postDateColumn = static::$databaseColumnMap['date'];
  331.             }
  332.         }
  333.  
  334.         if ( !$this->hidden() or $item->approvedButHidden() )
  335.         {
  336.             if ( isset( $item::$databaseColumnMap['last_comment'] ) )
  337.             {
  338.                 $lastCommentField = $item::$databaseColumnMap['last_comment'];
  339.                 if ( is_array( $lastCommentField ) )
  340.                 {
  341.                     foreach ( $lastCommentField as $column )
  342.                     {
  343.                         $item->$column = ( isset( $postDateColumn ) ) ? $this->$postDateColumn : time();
  344.                     }
  345.                 }
  346.                 else
  347.                 {
  348.                     $item->$lastCommentField = ( isset( $postDateColumn ) ) ? $this->$postDateColumn : time();
  349.                 }
  350.             }
  351.             if ( isset( $item::$databaseColumnMap['last_comment_by'] ) )
  352.             {
  353.                 $lastCommentByField = $item::$databaseColumnMap['last_comment_by'];
  354.                 $item->$lastCommentByField = (int) $this->author()->member_id;
  355.             }
  356.             if ( isset( $item::$databaseColumnMap['last_comment_name'] ) )
  357.             {
  358.                 $lastCommentNameField = $item::$databaseColumnMap['last_comment_name'];
  359.                 $item->$lastCommentNameField = $this->mapped('author_name');
  360.             }
  361.            
  362.             $item->save();
  363.            
  364.             if ( !$item->hidden() and ! $item->approvedButHidden() and $item->containerWrapper() and $item->container()->_comments !== NULL )
  365.             {
  366.                 $item->container()->_comments = ( $item->container()->_comments + 1 );
  367.                 $item->container()->setLastComment( $this );
  368.                 $item->container()->save();
  369.             }
  370.         }
  371.         else
  372.         {
  373.             $item->save();
  374.  
  375.             if ( $item->containerWrapper() AND !$item->approvedButHidden() AND $item->container()->_unapprovedComments !== NULL )
  376.             {
  377.                 $item->container()->_unapprovedComments = $item->container()->_unapprovedComments + 1;
  378.                 $item->container()->save();
  379.             }
  380.         }
  381.     }
  382.  
  383.     /**
  384.      * Get URL
  385.      *
  386.      * @param   string|NULL     $action     Action
  387.      * @return  \IPS\Http\Url
  388.      */
  389.     public function url( $action='find' )
  390.     {
  391.         $idColumn = static::$databaseColumnId;
  392.        
  393.         return $this->item()->url()->setQueryString( array(
  394.             'do'        => $action . 'Comment',
  395.             'comment'   => $this->$idColumn
  396.         ) );
  397.     }
  398.  
  399.     /**
  400.      * Get containing item
  401.      *
  402.      * @return  \IPS\Content\Item
  403.      */
  404.     public function item()
  405.     {
  406.         $itemClass = static::$itemClass;
  407.         return $itemClass::load( $this->mapped( 'item' ) );
  408.     }
  409.    
  410.     /**
  411.      * Is first message?
  412.      *
  413.      * @return  bool
  414.      */
  415.     public function isFirst()
  416.     {
  417.         if ( isset( static::$databaseColumnMap['first'] ) )
  418.         {
  419.             if ( $this->mapped('first') )
  420.             {
  421.                 return TRUE;
  422.             }
  423.         }
  424.         return FALSE;
  425.     }
  426.    
  427.     /**
  428.      * Get permission index ID
  429.      *
  430.      * @return  int|NULL
  431.      */
  432.     public function permId()
  433.     {
  434.         return $this->item()->permId();
  435.     }
  436.    
  437.     /**
  438.      * Can view?
  439.      *
  440.      * @param   \IPS\Member|NULL    $member The member to check for or NULL for the currently logged in member
  441.      * @return  bool
  442.      */
  443.     public function canView( $member=NULL )
  444.     {
  445.         if( $member === NULL )
  446.         {
  447.             $member = \IPS\Member::loggedIn();
  448.         }
  449.                
  450.         if ( $this instanceof \IPS\Content\Hideable and $this->hidden() and !$this->item()->canViewHiddenComments( $member ) and ( $this->hidden() !== 1 or $this->author() !== $member ) )
  451.         {
  452.             return FALSE;
  453.         }
  454.  
  455.         return $this->item()->canView( $member );
  456.     }
  457.    
  458.     /**
  459.      * Can edit?
  460.      *
  461.      * @param   \IPS\Member|NULL    $member The member to check for (NULL for currently logged in member)
  462.      * @return  bool
  463.      */
  464.     public function canEdit( $member=NULL )
  465.     {
  466.         $member = $member ?: \IPS\Member::loggedIn();
  467.                
  468.         if ( $member->member_id )
  469.         {
  470.             $item = $this->item();
  471.            
  472.             /* Do we have moderator permission to edit stuff in the container? */
  473.             if ( static::modPermission( 'edit', $member, $item->containerWrapper() ) )
  474.             {
  475.                 return TRUE;
  476.             }
  477.            
  478.             /* Can the member edit their own content? */
  479.             if ( $member->member_id == $this->author()->member_id and $member->group['g_edit_posts'] and ( !( $item instanceof \IPS\Content\Lockable ) or !$item->locked() ) )
  480.             {
  481.                 if ( !$member->group['g_edit_cutoff'] )
  482.                 {
  483.                     return TRUE;
  484.                 }
  485.                 else
  486.                 {
  487.                     if( \IPS\DateTime::ts( $this->mapped('date') )->add( new \DateInterval( "PT{$member->group['g_edit_cutoff']}M" ) ) > \IPS\DateTime::create() )
  488.                     {
  489.                         return TRUE;
  490.                     }
  491.                 }
  492.             }
  493.         }
  494.        
  495.         return FALSE;
  496.     }
  497.    
  498.     /**
  499.      * Can hide?
  500.      *
  501.      * @param   \IPS\Member|NULL    $member The member to check for (NULL for currently logged in member)
  502.      * @return  bool
  503.      */
  504.     public function canHide( $member=NULL )
  505.     {
  506.         $member = $member ?: \IPS\Member::loggedIn();
  507.  
  508.         $container = NULL;
  509.         try
  510.         {
  511.             $container = $this->item()->container();
  512.         }
  513.         catch ( \BadMethodCallException $e ) { }
  514.  
  515.         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'] ) ) );
  516.     }
  517.    
  518.     /**
  519.      * Can unhide?
  520.      *
  521.      * @param   \IPS\Member|NULL    $member The member to check for (NULL for currently logged in member)
  522.      * @return  boolean
  523.      */
  524.     public function canUnhide( $member=NULL )
  525.     {
  526.         $member = $member ?: \IPS\Member::loggedIn();
  527.  
  528.         $container = NULL;
  529.         try
  530.         {
  531.             $container = $this->item()->container();
  532.         }
  533.         catch ( \BadMethodCallException $e ) { }
  534.  
  535.         $hiddenByItem = FALSE;
  536.         if ( isset( static::$databaseColumnMap['hidden'] ) )
  537.         {
  538.             $column = static::$databaseColumnMap['hidden'];
  539.             $hiddenByItem = (boolean) ( $this->$column === 2 );
  540.         }
  541.  
  542.         return ( !$this->isFirst() and ! $hiddenByItem and static::modPermission( 'unhide', $member, $container ) );
  543.     }
  544.    
  545.     /**
  546.      * Can delete?
  547.      *
  548.      * @param   \IPS\Member|NULL    $member The member to check for (NULL for currently logged in member)
  549.      * @return  bool
  550.      */
  551.     public function canDelete( $member=NULL )
  552.     {
  553.         $member = $member ?: \IPS\Member::loggedIn();
  554.  
  555.         $container = NULL;
  556.         try
  557.         {
  558.             $container = $this->item()->container();
  559.         }
  560.         catch ( \BadMethodCallException $e ) { }
  561.  
  562.         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'] ) ) );
  563.     }
  564.    
  565.     /**
  566.      * Can split this comment off?
  567.      *
  568.      * @param   \IPS\Member|NULL    $member The member to check for (NULL for currently logged in member)
  569.      * @return  bool
  570.      */
  571.     public function canSplit( $member=NULL )
  572.     {
  573.         $itemClass = static::$itemClass;
  574.  
  575.         if ( $itemClass::$firstCommentRequired )
  576.         {
  577.             $container = NULL;
  578.             try
  579.             {
  580.                 $container = $this->item()->container();
  581.             }
  582.             catch ( \BadMethodCallException $e ) { }
  583.  
  584.             if ( !$this->isFirst() )
  585.             {
  586.                 $member = $member ?: \IPS\Member::loggedIn();
  587.                 return $itemClass::modPermission( 'split_merge', $member, $container );
  588.             }
  589.         }
  590.         return FALSE;
  591.     }
  592.    
  593.     /**
  594.      * Search Index Permissions
  595.      *
  596.      * @return  string  Comma-delimited values or '*'
  597.      *  @li         Number indicates a group
  598.      *  @li         Number prepended by "m" indicates a member
  599.      *  @li         Number prepended by "s" indicates a social group
  600.      */
  601.     public function searchIndexPermissions()
  602.     {
  603.         try
  604.         {
  605.             return $this->item()->searchIndexPermissions();
  606.         }
  607.         catch ( \BadMethodCallException $e )
  608.         {
  609.             return '*';
  610.         }
  611.     }
  612.    
  613.     /**
  614.      * Should this comment be ignored?
  615.      *
  616.      * @param   \IPS\Member|null    $member The member to check for - NULL for currently logged in member
  617.      * @return  bool
  618.      */
  619.     public function isIgnored( $member=NULL )
  620.     {
  621.         if ( $member === NULL )
  622.         {
  623.             $member = \IPS\Member::loggedIn();
  624.         }
  625.                
  626.         return $member->isIgnoring( $this->author(), static::$ignoreType );
  627.     }
  628.    
  629.     /**
  630.      * Get date line
  631.      *
  632.      * @return  string
  633.      */
  634.     public function dateLine()
  635.     {
  636.         if( $this->mapped('first') )
  637.         {
  638.             return \IPS\Member::loggedIn()->language()->addToStack( static::$formLangPrefix . 'date_started', FALSE, array( 'htmlsprintf' => array( \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ) ) );
  639.         }
  640.         else
  641.         {
  642.             return \IPS\Member::loggedIn()->language()->addToStack( static::$formLangPrefix . 'date_replied', FALSE, array( 'htmlsprintf' => array( \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ) ) );
  643.         }
  644.     }
  645.    
  646.     /**
  647.      * Edit Comment Contents - Note: does not add edit log
  648.      *
  649.      * @param   string  $newContent New content
  650.      * @return  string|NULL
  651.      */
  652.     public function editContents( $newContent )
  653.     {
  654.         /* Do it */
  655.         $valueField = static::$databaseColumnMap['content'];
  656.         $oldValue = $this->$valueField;
  657.         $this->$valueField = $newContent;
  658.         $this->save();
  659.        
  660.         /* Send any new mention/quote notifications */
  661.         $this->sendAfterEditNotifications( $oldValue );
  662.        
  663.         /* Reindex */
  664.         if ( $this instanceof \IPS\Content\Searchable )
  665.         {
  666.             \IPS\Content\Search\Index::i()->index( $this );
  667.         }
  668.     }
  669.    
  670.     /**
  671.      * Get edit line
  672.      *
  673.      * @return  string|NULL
  674.      */
  675.     public function editLine()
  676.     {
  677.         if ( $this instanceof \IPS\Content\EditHistory and $this->mapped('edit_time') and ( $this->mapped('edit_show') or \IPS\Member::loggedIn()->modPermission('can_view_editlog') ) and \IPS\Settings::i()->edit_log )
  678.         {
  679.             return \IPS\Theme::i()->getTemplate( 'global', 'core' )->commentEditLine( $this, ( isset( static::$databaseColumnMap['edit_reason'] ) and $this->mapped('edit_reason') ) );
  680.         }
  681.         return NULL;
  682.     }
  683.    
  684.     /**
  685.      * Get edit history
  686.      *
  687.      * @param   bool    $staff      Set true for moderators who have permission to view the full log which will show edits not made by the author and private edits
  688.      * @return  \IPS\Db\Select
  689.      */
  690.     public function editHistory( $staff=FALSE )
  691.     {
  692.         $idColumn = static::$databaseColumnId;
  693.         $where = array( array( 'class=? AND comment_id=?', get_called_class(), $this->$idColumn ) );
  694.         if ( !$staff )
  695.         {
  696.             $where[] = array( 'member=? AND public=1', $this->author()->member_id );
  697.         }
  698.         return \IPS\Db::i()->select( '*', 'core_edit_history', $where, 'time DESC' );
  699.     }
  700.    
  701.     /**
  702.      * Get HTML
  703.      *
  704.      * @return  string
  705.      */
  706.     public function html()
  707.     {
  708.         $template = static::$commentTemplate[1];
  709.         return call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), static::$commentTemplate[0] )->$template( $this->item(), $this );
  710.     }
  711.        
  712.     /**
  713.      * Users to receive immediate notifications
  714.      *
  715.      * @param   int|array       $limit      LIMIT clause
  716.      * @param   string|NULL     $extra      Additional data
  717.      * @param   boolean         $countOnly  Just return the count
  718.      * @return \IPS\Db\Select
  719.      */
  720.     public function notificationRecipients( $limit=array( 0, 25 ), $extra=NULL, $countOnly=FALSE )
  721.     {
  722.         $memberFollowers = $this->author()->followers( 3, array( 'immediate' ), $this->mapped('date'), NULL, NULL, NULL );
  723.        
  724.         if( count( $memberFollowers ) )
  725.         {
  726.             $unions = array(
  727.                 $this->item()->followers( static::FOLLOW_PUBLIC + static::FOLLOW_ANONYMOUS, array( 'immediate' ), $this->mapped('date'), NULL, NULL, 0 ),
  728.                 $memberFollowers
  729.             );
  730.        
  731.             if ( $countOnly )
  732.             {
  733.                 $return = 0;
  734.                 foreach ( $unions as $query )
  735.                 {
  736.                     $return += $query->count();
  737.                 }
  738.                 return $return;
  739.             }
  740.             else
  741.             {
  742.                 return \IPS\Db::i()->union( $unions, 'follow_added', $limit, NULL, FALSE, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
  743.             }
  744.         }
  745.         else
  746.         {
  747.             $query = $this->item()->followers( static::FOLLOW_PUBLIC + static::FOLLOW_ANONYMOUS, array( 'immediate' ), $this->mapped('date'), $limit, 'follow_added', \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
  748.            
  749.             if ( $countOnly )
  750.             {
  751.                 return $query->count();
  752.             }
  753.             else
  754.             {
  755.                 return $query;
  756.             }
  757.         }      
  758.     }
  759.    
  760.     /**
  761.      * Create Notification
  762.      *
  763.      * @param   string|NULL     $extra      Additional data
  764.      * @return  \IPS\Notification
  765.      */
  766.     protected function createNotification( $extra=NULL )
  767.     {
  768.         return new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_comment', $this->item(), array( $this ) );
  769.     }
  770.    
  771.     /**
  772.      * Syncing to run when hiding
  773.      *
  774.      * @param   \IPS\Member|NULL|FALSE  $member The member doing the action (NULL for currently logged in member, FALSE for no member)
  775.      * @return  void
  776.      */
  777.     public function onHide( $member )
  778.     {
  779.         $item = $this->item();
  780.        
  781.         /* Remove any notifications */
  782.         $idColumn = static::$databaseColumnId;
  783.         \IPS\Db::i()->delete( 'core_notifications', array( 'item_sub_class=? AND item_sub_id=?', (string) get_called_class(), (int) $this->$idColumn ) );
  784.        
  785.         $item->resyncCommentCounts();
  786.         $item->resyncLastComment();
  787.         $item->save();
  788.  
  789.         /* We have to do this *after* updating the last comment data for the item, because that uses the cached data from the item (i.e. topic) */
  790.         try
  791.         {
  792.             if ( $item->container()->_comments !== NULL )
  793.             {
  794.                 $item->container()->setLastComment();
  795.                 $item->container()->resetCommentCounts();
  796.                 $item->container()->save();
  797.             }
  798.         } catch ( \BadMethodCallException $e ) {}
  799.     }
  800.    
  801.     /**
  802.      * Syncing to run when unhiding
  803.      *
  804.      * @param   bool                    $approving  If true, is being approved for the first time
  805.      * @param   \IPS\Member|NULL|FALSE  $member The member doing the action (NULL for currently logged in member, FALSE for no member)
  806.      * @return  void
  807.      */
  808.     public function onUnhide( $approving, $member )
  809.     {
  810.         $item = $this->item();
  811.  
  812.         if ( $approving )
  813.         {
  814.             /* We should only do this if it is an actual account, and not a guest. */
  815.             if ( $this->author()->member_id )
  816.             {
  817.                 try
  818.                 {
  819.                     if ( static::incrementPostCount( $item->container() ) )
  820.                     {
  821.                         $this->author()->member_posts++;
  822.                         $this->author()->save();
  823.                     }
  824.                 }
  825.                 catch( \BadMethodCallException $e ) { }
  826.             }
  827.            
  828.         }
  829.        
  830.         $item->resyncCommentCounts();
  831.         $item->resyncLastComment();
  832.         $item->save();
  833.  
  834.         /* We have to do this *after* updating the last comment data for the item, because that uses the cached data from the item (i.e. topic) */
  835.         try
  836.         {
  837.             if ( $item->container()->_comments !== NULL )
  838.             {
  839.                 $item->container()->setLastComment();
  840.                 $item->container()->resetCommentCounts();
  841.                 $item->container()->save();
  842.             }
  843.         } catch ( \BadMethodCallException $e ) {}
  844.     }
  845.    
  846.     /**
  847.      * Move Comment to another item
  848.      *
  849.      * @param   \IPS\Content\Item   $item The item to move this comment too.
  850.      * @return  void
  851.      */
  852.     public function move( \IPS\Content\Item $item )
  853.     {
  854.         $oldItem = $this->item();
  855.        
  856.         $idColumn = $item::$databaseColumnId;
  857.         $itemColumn = static::$databaseColumnMap['item'];
  858.         $commentIdColumn = static::$databaseColumnId;
  859.         $this->$itemColumn = $item->$idColumn;
  860.         $this->save();
  861.        
  862.         /* The new item needs to re-claim any attachments associated with this comment */
  863.         \IPS\Db::i()->update( 'core_attachments_map', array( 'id1' => $item->$idColumn ), array( "location_key=? AND id1=? AND id2=?", $oldItem::$application . '_' . ucfirst( $oldItem::$module ), $oldItem->$idColumn, $this->$commentIdColumn ) );
  864.  
  865.         /* Update notifications */
  866.         \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 ) );
  867.        
  868.         /* Update reputation */
  869.         if ( $this instanceof \IPS\Content\Reputation )
  870.         {
  871.             \IPS\Db::i()->update( 'core_reputation_index', array( 'item_id' => $item->$idColumn ), array( 'rep_class=? and item_id=?', get_class( $this ), $oldItem->$idColumn ) );
  872.         }
  873.        
  874.         $oldItem->rebuildFirstAndLastCommentData();
  875.         $item->rebuildFirstAndLastCommentData();
  876.  
  877.         /* Add to search index */
  878.         if ( $this instanceof \IPS\Content\Searchable )
  879.         {
  880.             \IPS\Content\Search\Index::i()->index( $this );
  881.         }
  882.     }
  883.    
  884.     /**
  885.      * Get container
  886.      *
  887.      * @return  \IPS\Node\Model
  888.      * @note    Certain functionality requires a valid container but some areas do not use this functionality (e.g. messenger)
  889.      * @note    Some functionality refers to calls to the container when managing comments (e.g. deleting a comment and decrementing content counts). In this instance, load the parent items container.
  890.      * @throws  \OutOfRangeException|\BadMethodCallException
  891.      */
  892.     public function container()
  893.     {
  894.         $container = NULL;
  895.        
  896.         try
  897.         {
  898.             $container = $this->item()->container();
  899.         }
  900.         catch( \BadMethodCallException $e ) {}
  901.        
  902.         return $container;
  903.     }
  904.            
  905.     /**
  906.      * Delete Comment
  907.      *
  908.      * @return  void
  909.      */
  910.     public function delete()
  911.     {
  912.         /* Remove from search index first */
  913.         if ( $this instanceof \IPS\Content\Searchable )
  914.         {
  915.             \IPS\Content\Search\Index::i()->removeFromSearchIndex( $this );
  916.         }
  917.  
  918.         /* Init */
  919.         $idColumn = static::$databaseColumnId;
  920.         $itemClass = static::$itemClass;
  921.         $itemIdColumn = $itemClass::$databaseColumnId;
  922.  
  923.         /* It is possible to delete a comment that is orphaned, so let's try to protect against that */
  924.         try
  925.         {
  926.             $item   = $this->item();
  927.             $itemId = $this->item()->$itemIdColumn;
  928.         }
  929.         catch( \OutOfRangeException $e )
  930.         {
  931.             $item   = NULL;
  932.             $itemId = $this->mapped('item');
  933.         }
  934.        
  935.         /* Unclaim attachments */
  936.         \IPS\File::unclaimAttachments( $itemClass::$application . '_' . ucfirst( $itemClass::$module ), $itemId, $this->$idColumn );
  937.        
  938.         /* Reduce the number of comment/reviews count on the item but only if the item is unapproved or visible
  939.          * - hidden as opposed to unapproved items do not get included in either of the unapproved_comments/num_comments columns */
  940.         if( $this->hidden() !== -1 )
  941.         {
  942.             $columnName = ( $this->hidden() === 1 ) ? 'unapproved_comments' : 'num_comments';
  943.             if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  944.             {
  945.                 $columnName = ( $this->hidden() === 1 ) ? 'unapproved_reviews' : 'num_reviews';
  946.             }
  947.             if ( isset( $itemClass::$databaseColumnMap[$columnName] ) AND $item !== NULL )
  948.             {
  949.                 $column = $itemClass::$databaseColumnMap[$columnName];
  950.  
  951.                 if ( $item->$column > 0 )
  952.                 {
  953.                     $item->$column--;
  954.                     $item->save();
  955.                 }
  956.             }
  957.         }
  958.         else
  959.         {
  960.             if ( in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  961.             {
  962.                 if( isset( $itemClass::$databaseColumnMap['hidden_reviews'] ) AND $item !== NULL )
  963.                 {
  964.                     $column = $itemClass::$databaseColumnMap['hidden_reviews'];
  965.  
  966.                     if ( $item->$column > 0 )
  967.                     {
  968.                         $item->$column--;
  969.                         $item->save();
  970.                     }
  971.                 }
  972.             }
  973.             else
  974.             {
  975.                 if( isset( $itemClass::$databaseColumnMap['hidden_comments'] ) AND $item !== NULL )
  976.                 {
  977.                     $column = $itemClass::$databaseColumnMap['hidden_comments'];
  978.  
  979.                     if ( $item->$column > 0 )
  980.                     {
  981.                         $item->$column--;
  982.                         $item->save();
  983.                     }
  984.                 }
  985.             }
  986.         }
  987.        
  988.         /* Delete any notifications telling people about this */
  989.         $memberIds  = array();
  990.  
  991.         foreach( \IPS\Db::i()->select( 'member', 'core_notifications', array( 'item_sub_class=? AND item_sub_id=?', (string) get_called_class(), (int) $this->$idColumn ) ) as $member )
  992.         {
  993.             $memberIds[ $member ]   = $member;
  994.         }
  995.  
  996.         \IPS\Db::i()->delete( 'core_notifications', array( 'item_sub_class=? AND item_sub_id=?', (string) get_called_class(), (int) $this->$idColumn ) );
  997.  
  998.         foreach( $memberIds as $member )
  999.         {
  1000.             \IPS\Member::load( $member )->recountNotifications();
  1001.         }
  1002.        
  1003.         /* Actually delete */
  1004.         parent::delete();
  1005.        
  1006.         /* Update last comment/review data for container and item */
  1007.         try
  1008.         {
  1009.             if ( $item !== NULL AND in_array( 'IPS\Content\Review', class_parents( get_called_class() ) ) )
  1010.             {
  1011.                 if ( $item->container()->_reviews !== NULL )
  1012.                 {
  1013.                     $item->container()->_reviews = ( $item->container()->_reviews - 1 );
  1014.                     $item->resyncLastReview();
  1015.                     $item->save();
  1016.                     $item->container()->setLastReview();
  1017.                 }
  1018.                 if ( $item->container()->_unapprovedReviews !== NULL )
  1019.                 {
  1020.                     $item->container()->_unapprovedReviews = ( $item->container()->_unapprovedReviews > 0 ) ? ( $item->container()->_unapprovedReviews - 1 ) : 0;
  1021.                 }
  1022.                 $item->container()->save();
  1023.             }
  1024.             else if ( $item !== NULL AND $item->container() !== NULL )
  1025.             {
  1026.                 if ( $item->container()->_comments !== NULL )
  1027.                 {
  1028.                     if ( !$this->hidden() )
  1029.                     {
  1030.                         $item->container()->_comments = ( $item->container()->_comments > 0 ) ? ( $item->container()->_comments - 1 ) : 0;
  1031.                     }
  1032.                    
  1033.                     $item->resyncLastComment();
  1034.                     $item->save();
  1035.                     $item->container()->setLastComment();
  1036.                 }
  1037.                 if ( $item->container()->_unapprovedComments !== NULL and $this->hidden() === 1 )
  1038.                 {
  1039.                     $item->container()->_unapprovedComments = ( $item->container()->_unapprovedComments > 0 ) ? ( $item->container()->_unapprovedComments - 1 ) : 0;
  1040.                 }
  1041.                 $item->container()->save();
  1042.             }
  1043.         }
  1044.         catch ( \BadMethodCallException $e ) {}
  1045.     }
  1046.    
  1047.     /**
  1048.      * Change Author
  1049.      *
  1050.      * @param   \IPS\Member $newAuthor  The new author
  1051.      * @return  void
  1052.      */
  1053.     public function changeAuthor( \IPS\Member $newAuthor )
  1054.     {
  1055.         $oldAuthor = $this->author();
  1056.        
  1057.         /* Update the row */
  1058.         parent::changeAuthor( $newAuthor );
  1059.        
  1060.         /* Adjust post counts */
  1061.         if ( static::incrementPostCount( $this->item()->containerWrapper() ) )
  1062.         {
  1063.             if( $oldAuthor->member_id )
  1064.             {
  1065.                 $oldAuthor->member_posts--;
  1066.                 $oldAuthor->save();
  1067.             }
  1068.            
  1069.             if( $newAuthor->member_id )
  1070.             {
  1071.                 $newAuthor->member_posts++;
  1072.                 $newAuthor->save();
  1073.             }
  1074.         }
  1075.        
  1076.         /* Last comment */
  1077.         $this->item()->resyncLastComment();
  1078.         $this->item()->resyncLastReview();
  1079.         $this->item()->save();
  1080.         if ( $container = $this->item()->containerWrapper() )
  1081.         {
  1082.             $container->setLastComment();
  1083.             $container->setLastReview();
  1084.             $container->save();
  1085.         }
  1086.  
  1087.         /* Update search index */
  1088.         if ( $this instanceof \IPS\Content\Searchable )
  1089.         {
  1090.             \IPS\Content\Search\Index::i()->index( $this );
  1091.         }
  1092.     }
  1093.    
  1094.     /**
  1095.      * Get template for content tables
  1096.      *
  1097.      * @return  callable
  1098.      */
  1099.     public static function contentTableTemplate()
  1100.     {
  1101.         return array( \IPS\Theme::i()->getTemplate( 'tables', 'core', 'front' ), 'commentRows' );
  1102.     }
  1103.    
  1104.     /**
  1105.      * Get content for header in content tables
  1106.      *
  1107.      * @return  callable
  1108.      */
  1109.     public function contentTableHeader()
  1110.     {
  1111.         return \IPS\Theme::i()->getTemplate( 'global', static::$application )->commentTableHeader( $this, $this->item() );
  1112.     }
  1113.  
  1114.     /**
  1115.      * Get comments based on some arbitrary parameters
  1116.      *
  1117.      * @param   array       $where                  Where clause
  1118.      * @param   string      $order                  MySQL ORDER BY clause (NULL to order by date)
  1119.      * @param   int|array   $limit                  Limit clause
  1120.      * @param   string|NULL $permissionKey          A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index, or NULL to ignore permissions
  1121.      * @param   mixed       $includeHiddenComments  Include hidden comments? NULL to detect if currently logged in member has permission, -1 to return public content only, TRUE to return unapproved content and FALSE to only return unapproved content the viewing member submitted
  1122.      * @param   int         $queryFlags             Select bitwise flags
  1123.      * @param   \IPS\Member $member                 The member (NULL to use currently logged in member)
  1124.      * @param   bool        $joinContainer          If true, will join container data (set to TRUE if your $where clause depends on this data)
  1125.      * @param   bool        $joinComments           If true, will join comment data (set to TRUE if your $where clause depends on this data)
  1126.      * @param   bool        $joinReviews            If true, will join review data (set to TRUE if your $where clause depends on this data)
  1127.      * @param   bool        $countOnly              If true will return the count
  1128.      * @param   array|null  $joins                  Additional arbitrary joins for the query
  1129.      * @return  array|NULL|\IPS\Content\Comment     If $limit is 1, will return \IPS\Content\Comment or NULL for no results. For any other number, will return an array.
  1130.      */
  1131.     public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenComments=\IPS\Content\Hideable::FILTER_AUTOMATIC, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL )
  1132.     {
  1133.         /* Get the item class - we need it later */
  1134.         $itemClass  = static::$itemClass;
  1135.        
  1136.         $itemWhere = array();
  1137.         $containerWhere = array();
  1138.        
  1139.         /* Queries are always more efficient when the WHERE clause is added to the ON */
  1140.         if ( is_array( $where ) )
  1141.         {
  1142.             foreach( $where as $key => $value )
  1143.             {
  1144.                 if ( $key ==='item' )
  1145.                 {
  1146.                     $itemWhere = array_merge( $itemWhere, $value );
  1147.                    
  1148.                     unset( $where[ $key ] );
  1149.                 }
  1150.                
  1151.                 if ( $key === 'container' )
  1152.                 {
  1153.                     $containerWhere = array_merge( $containerWhere, $value );
  1154.                     unset( $where[ $key ] );
  1155.                 }
  1156.             }
  1157.         }
  1158.        
  1159.         /* Work out the order */
  1160.         $order = $order ?: ( static::$databasePrefix . static::$databaseColumnMap['date'] . ' DESC' );
  1161.        
  1162.         /* Exclude hidden comments */
  1163.         if( $includeHiddenComments === \IPS\Content\Hideable::FILTER_AUTOMATIC )
  1164.         {
  1165.             if( static::modPermission( 'view_hidden', $member ) )
  1166.             {
  1167.                 $includeHiddenComments = \IPS\Content\Hideable::FILTER_SHOW_HIDDEN;
  1168.             }
  1169.             else
  1170.             {
  1171.                 $includeHiddenComments = \IPS\Content\Hideable::FILTER_OWN_HIDDEN;
  1172.             }
  1173.         }
  1174.        
  1175.         if ( in_array( 'IPS\Content\Hideable', class_implements( get_called_class() ) ) and $includeHiddenComments === \IPS\Content\Hideable::FILTER_ONLY_HIDDEN )
  1176.         {
  1177.             /* If we can't view hidden stuff, just return an empty array now */
  1178.             if( !static::modPermission( 'view_hidden', $member ) )
  1179.             {
  1180.                 return array();
  1181.             }
  1182.  
  1183.             if ( isset( static::$databaseColumnMap['approved'] ) )
  1184.             {
  1185.                 $where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 0 );
  1186.             }
  1187.             elseif ( isset( static::$databaseColumnMap['hidden'] ) )
  1188.             {
  1189.                 $where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 1 );
  1190.             }
  1191.         }
  1192.         elseif ( in_array( 'IPS\Content\Hideable', class_implements( get_called_class() ) ) and ( $includeHiddenComments === \IPS\Content\Hideable::FILTER_OWN_HIDDEN OR $includeHiddenComments === \IPS\Content\Hideable::FILTER_PUBLIC_ONLY ) )
  1193.         {
  1194.             if ( isset( static::$databaseColumnMap['approved'] ) )
  1195.             {
  1196.                 $where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 1 );
  1197.             }
  1198.             elseif ( isset( static::$databaseColumnMap['hidden'] ) )
  1199.             {
  1200.                 $where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 0 );
  1201.             }
  1202.         }
  1203.        
  1204.         /* Exclude hidden items. We don't check FILTER_ONLY_HIDDEN because we should return hidden comments in both approved and unapproved topics. */
  1205.         if ( in_array( 'IPS\Content\Hideable', class_implements( $itemClass ) ) and ( $includeHiddenComments === \IPS\Content\Hideable::FILTER_OWN_HIDDEN OR $includeHiddenComments === \IPS\Content\Hideable::FILTER_PUBLIC_ONLY ) )
  1206.         {
  1207.             $member = $member ?: \IPS\Member::loggedIn();
  1208.             $authorCol = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['author'];
  1209.             if ( isset( $itemClass::$databaseColumnMap['approved'] ) )
  1210.             {
  1211.                 $col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['approved'];
  1212.                 if ( $member->member_id AND $includeHiddenComments !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
  1213.                 {
  1214.                     $itemWhere[] = array( "( {$col}=1 OR ( {$col}=0 AND {$authorCol}=" . $member->member_id . " ) )" );
  1215.                 }
  1216.                 else
  1217.                 {
  1218.                     $itemWhere[] = array( "{$col}=1" );
  1219.                 }
  1220.             }
  1221.             elseif ( isset( $itemClass::$databaseColumnMap['hidden'] ) )
  1222.             {
  1223.                 $col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['hidden'];
  1224.                 if ( $member->member_id AND $includeHiddenComments !== \IPS\Content\Hideable::FILTER_PUBLIC_ONLY )
  1225.                 {
  1226.                     $itemWhere[] = array( "( {$col}=0 OR ( {$col}=1 AND {$authorCol}=" . $member->member_id . " ) )" );
  1227.                 }
  1228.                 else
  1229.                 {
  1230.                     $itemWhere[] = array( "{$col}=0" );
  1231.                 }
  1232.             }
  1233.         }
  1234.         else
  1235.         {
  1236.             /* Legacy items pending deletion in 3.x at time of upgrade may still exist */
  1237.             $col    = null;
  1238.  
  1239.             if ( isset( $itemClass::$databaseColumnMap['approved'] ) )
  1240.             {
  1241.                 $col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['approved'];
  1242.             }
  1243.             else if( isset( $itemClass::$databaseColumnMap['hidden'] ) )
  1244.             {
  1245.                 $col = $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['hidden'];
  1246.             }
  1247.  
  1248.             if( $col )
  1249.             {
  1250.                 $itemWhere[] = array( "{$col} < 2" );
  1251.             }
  1252.         }
  1253.        
  1254.         if ( $joinContainer AND isset( $itemClass::$containerNodeClass ) )
  1255.         {
  1256.             $containerClass = $itemClass::$containerNodeClass;
  1257.             $joins[] = array(
  1258.                 'from'  =>  $containerClass::$databaseTable,
  1259.                 'where' => array_merge( array( array( $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . '=' . $containerClass::$databaseTable . '.' . $containerClass::$databasePrefix . $containerClass::$databaseColumnId ) ), $containerWhere )
  1260.             );
  1261.         }
  1262.        
  1263.         /* Build the select clause */
  1264.         if( $countOnly )
  1265.         {
  1266.             if ( in_array( 'IPS\Content\Permissions', class_implements( $itemClass ) ) AND $permissionKey !== NULL )
  1267.             {
  1268.                 $member = $member ?: \IPS\Member::loggedIn();
  1269.                
  1270.                 $containerClass = $itemClass::$containerNodeClass;
  1271.  
  1272.                 $select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, NULL, NULL, $queryFlags )
  1273.                     ->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' )
  1274.                     ->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . ' AND (' . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->groups ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . '=? )', $containerClass::$permApp, $containerClass::$permType, '*' ), 'STRAIGHT_JOIN' );
  1275.             }
  1276.             else
  1277.             {
  1278.                 $select = \IPS\Db::i()->select( 'COUNT(*) as cnt', static::$databaseTable, $where, NULL, NULL, NULL, NULL, $queryFlags )
  1279.                     ->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' );
  1280.             }
  1281.  
  1282.             if ( count( $joins ) )
  1283.             {
  1284.                 foreach( $joins as $join )
  1285.                 {
  1286.                     $select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), ( isset( $join['type'] ) ) ? $join['type'] : 'LEFT' );
  1287.                 }
  1288.             }
  1289.             return $select->first();
  1290.         }
  1291.  
  1292.         $selectClause = static::$databaseTable . '.*';
  1293.         if ( count( $joins ) )
  1294.         {
  1295.             foreach( $joins as $join )
  1296.             {
  1297.                 if ( isset( $join['select'] ) )
  1298.                 {
  1299.                     $selectClause .= ', ' . $join['select'];
  1300.                 }
  1301.             }
  1302.         }
  1303.        
  1304.         if ( in_array( 'IPS\Content\Permissions', class_implements( $itemClass ) ) AND $permissionKey !== NULL )
  1305.         {
  1306.             $member = $member ?: \IPS\Member::loggedIn();
  1307.            
  1308.             $containerClass = $itemClass::$containerNodeClass;
  1309.            
  1310.             $selectClause .= ', ' . $itemClass::$databaseTable . '.*';
  1311.  
  1312.             $select = \IPS\Db::i()->select( $selectClause, static::$databaseTable, $where, $order, $limit, NULL, NULL, $queryFlags )
  1313.                 ->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' )
  1314.                 ->join( 'core_permission_index', array( "core_permission_index.app=? AND core_permission_index.perm_type=? AND core_permission_index.perm_type_id=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['container'] . ' AND (' . \IPS\Db::i()->findInSet( 'perm_' . $containerClass::$permissionMap[ $permissionKey ], $member->groups ) . ' OR ' . 'perm_' . $containerClass::$permissionMap[ $permissionKey ] . '=? )', $containerClass::$permApp, $containerClass::$permType, '*' ), 'STRAIGHT_JOIN' );
  1315.         }
  1316.         else
  1317.         {
  1318.             $select = \IPS\Db::i()->select( $selectClause, static::$databaseTable, $where, $order, $limit, NULL, NULL, $queryFlags )
  1319.                 ->join( $itemClass::$databaseTable, array_merge( array( array( static::$databaseTable . "." . static::$databasePrefix . static::$databaseColumnMap['item'] . "=" . $itemClass::$databaseTable . "." . $itemClass::$databasePrefix . $itemClass::$databaseColumnId ) ), $itemWhere ), 'STRAIGHT_JOIN' );
  1320.         }
  1321.                        
  1322.         if ( count( $joins ) )
  1323.         {
  1324.             foreach( $joins as $join )
  1325.             {
  1326.                 $select->join( $join['from'], ( isset( $join['where'] ) ? $join['where'] : null ), ( isset( $join['type'] ) ) ? $join['type'] : 'LEFT' );
  1327.             }
  1328.         }
  1329.                
  1330.         /* Return */
  1331.         return new \IPS\Patterns\ActiveRecordIterator( $select, get_called_class() );
  1332.     }
  1333.    
  1334.     /**
  1335.      * Warning Reference Key
  1336.      *
  1337.      * @return  string
  1338.      */
  1339.     public function warningRef()
  1340.     {
  1341.         /* If the member cannot warn, return NULL so we're not adding ugly parameters to the profile URL unnecessarily */
  1342.         if ( !\IPS\Member::loggedIn()->modPermission('mod_can_warn') )
  1343.         {
  1344.             return NULL;
  1345.         }
  1346.        
  1347.         $itemClass = static::$itemClass;
  1348.         $idColumn = static::$databaseColumnId;
  1349.         return base64_encode( json_encode( array( 'app' => $itemClass::$application, 'module' => $itemClass::$module . '-comment' , 'id_1' => $this->mapped('item'), 'id_2' => $this->$idColumn ) ) );
  1350.     }
  1351.    
  1352.     /**
  1353.      * Get attachment IDs
  1354.      *
  1355.      * @return  array
  1356.      */
  1357.     public function attachmentIds()
  1358.     {
  1359.         $item = $this->item();
  1360.         $idColumn = $item::$databaseColumnId;
  1361.         $commentIdColumn = static::$databaseColumnId;
  1362.         return array( $this->item()->$idColumn, $this->$commentIdColumn );
  1363.     }
  1364.    
  1365.     /**
  1366.      * @brief   Existing warning
  1367.      */
  1368.     public $warning;
  1369.        
  1370.     /**
  1371.      * Can Share
  1372.      *
  1373.      * @return  boolean
  1374.      */
  1375.     public function canShare()
  1376.     {
  1377.         return ( $this->canView( \IPS\Member::load( 0 ) ) and in_array( 'IPS\Content\Shareable', class_implements( get_called_class() ) ) );
  1378.     }
  1379.    
  1380.     /**
  1381.      * Return sharelinks for this item
  1382.      *
  1383.      * @return array
  1384.      */
  1385.     public function sharelinks()
  1386.     {
  1387.         if( !count( $this->sharelinks ) )
  1388.         {
  1389.             if ( $this instanceof Shareable and $this->canShare() )
  1390.             {
  1391.                 $idColumn = static::$databaseColumnId;
  1392.                 $shareUrl = $this->url( 'find' )->setQueryString( 'comment', $this->$idColumn );
  1393.                
  1394.                 $this->sharelinks = \IPS\core\ShareLinks\Service::getAllServices( $shareUrl, $this->item()->mapped('title'), NULL, $this->item() );
  1395.             }
  1396.         }
  1397.  
  1398.         return $this->sharelinks;
  1399.     }
  1400.  
  1401.     /**
  1402.      * Addition where needed for fetching comments
  1403.      *
  1404.      * @return  array|NULL
  1405.      */
  1406.     public static function commentWhere()
  1407.     {
  1408.         return NULL;
  1409.     }
  1410.    
  1411.     /**
  1412.      * Get output for API
  1413.      *
  1414.      * @return  array
  1415.      * @apiresponse int         id          ID number
  1416.      * @apiresponse int         item_id     The ID number of the item this belongs to
  1417.      * @apiresponse \IPS\Member author      Author
  1418.      * @apiresponse datetime    date        Date
  1419.      * @apiresponse string      content     The content
  1420.      * @apiresponse bool        hidden      Is hidden?
  1421.      * @apiresponse string      url         URL to content
  1422.      */
  1423.     public function apiOutput()
  1424.     {
  1425.         $idColumn = static::$databaseColumnId;
  1426.         $itemColumn = static::$databaseColumnMap['item'];
  1427.         return array(
  1428.             'id'        => $this->$idColumn,
  1429.             'item_id'   => $this->$itemColumn,
  1430.             'author'    => $this->author()->apiOutput(),
  1431.             'date'      => \IPS\DateTime::ts( $this->mapped('date') )->rfc3339(),
  1432.             'content'   => $this->content(),
  1433.             'hidden'    => (bool) $this->hidden(),
  1434.             'url'       => (string) $this->url()
  1435.         );
  1436.     }
  1437.    
  1438.     /* !Embeddable */
  1439.    
  1440.     /**
  1441.      * Get content for embed
  1442.      *
  1443.      * @param   array   $params Additional parameters to add to URL
  1444.      * @return  string
  1445.      */
  1446.     public function embedContent( $params )
  1447.     {
  1448.         return \IPS\Theme::i()->getTemplate( 'global', 'core' )->embedComment( $this->item(), $this, $this->url()->setQueryString( $params ), $this->item()->embedImage() );
  1449.     }
  1450. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement