DIR : /home/kozerus/public_html/dgs/dgs/forum/class_forum_read.php

/home/kozerus/public_html/dgs/dgs/forum

<?php
/*
Dragon Go Server
Copyright (C) 2001-  Erik Ouchterlony, Rod Ival, Jens-Uwe Gaspar

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*!
 * \file forum_functions.php
 *
 * \brief Helper-class to manage forum-reads NEW-flag
 */

$TranslateGroups[] = "Forum"; //local use

require_once 'include/std_classes.php';
require_once 'include/connect2mysql.php';
require_once 'forum/class_forum_options.php';



define('CACHEFILE_FORUM_GLOBAL', CACHE_FOLDER.'cache_forum_global_updated');

 /*!
  * \class ForumRead
  *
  * \brief Class to help with handling forum-reads and cope with 'new'-flag.
  *
  * \note Dates are stored in UNIX-time
  * \note expected row-fields: Forum_ID, Thread_ID, X_Time, HasNew
  * \note need combined index on IDs for correct working of update-funcs
  */
class ForumRead
{
   public $uid;
   public $fid;
   public $tid;
   public $min_date; // posts older (or equal) than min_date are considered as read

   private $reads = array(); // [ "fid,tid" => unix-time ]

   /*! \brief Constructs a ForumRead-object with specified args. */
   public function __construct( $user_id, $forum_id=0, $thread_id=0 )
   {
      $this->uid = $user_id;
      $this->fid = $forum_id;
      $this->tid = $thread_id;
      $this->min_date = self::get_min_date();
   }

   /*!
    * \brief Returns true, if specified check-date is older than read-date
    *        stored for specified forum and thread.
    *        So is a read (and no "new") post.
    */
   public function has_newer_read_date( $chkdate, $forum_id, $thread_id )
   {
      $time = (int)@$this->reads["$forum_id,$thread_id"];
      return ( $time >= $chkdate );
   }

   /*! \brief Loads forum-reads for thread-post view into this ForumRead-object. */
   public function load_forum_reads()
   {
      $qsql = self::build_query_sql( $this->uid );
      $qsql->add_part( SQLP_WHERE,
         'Forum_ID='.$this->fid,
         'Thread_ID='.$this->tid );
      $query = $qsql->get_select();
      $result = db_query( "ForumRead.load_forum_reads({$this->uid},{$this->fid},{$this->tid})", $query );

      while ( $row = mysql_fetch_array( $result ) )
      {
         $key = $row['Forum_ID'] . ',' . $row['Thread_ID'];
         $this->reads[$key] = $row['X_Time'];
      }
      mysql_free_result($result);
   }

   /*!
    * \brief Replaces Forumreads-db-entry with time and new-flag for specified fid/tid-key.
    * \note IMPORTANT NOTE: caller needs to open TA with HOT-section if used with other db-writes!!
    */
   public function replace_row_forumread( $dbgmsg, $fid, $tid, $time, $has_new=false )
   {
      // IMPORTANT NOTE:
      // - 'INSERT .. ON DUPLICATE UPDATE' need at least 5.0.38 for replication-bugfix,
      //   can also be used for multi-row inserts.
      // - REPLACE is slower as it would be a combined DELETE + INSERT
      //   with full index-updating on both steps.
      $new_val = ($has_new) ? 1 : 0;
      db_query( $dbgmsg."({$this->uid},$fid,$tid)",
         "INSERT INTO Forumreads (User_ID,Forum_ID,Thread_ID,Time,HasNew) "
            . "VALUES ({$this->uid},$fid,$tid,FROM_UNIXTIME($time),$new_val) "
            . "ON DUPLICATE KEY UPDATE Time=VALUES(Time), HasNew=VALUES(HasNew)" );
   }

   /*!
    * \brief Marks posts in thread as read that are older than specified (read-)time.
    * \see specs/forums.txt (use-case U03)
    */
   public function mark_thread_read( $thread_id, $mark_time )
   {
      if ( $this->fid > 0 && $thread_id > 0 && $mark_time >= $this->min_date )
      {
         ta_begin();
         {//HOT-section to mark thread read
            $this->replace_row_forumread( "ForumRead.mark_thread_read",
               $this->fid, $thread_id, $mark_time );
            self::trigger_recalc_forum_read_update( $this->uid, $this->fid );
            self::trigger_recalc_global_read_update( $this->uid );
         }
         ta_end();
      }
   }

   /*!
    * \brief Marks threads in forum-list as read that are older than specified (read-)time.
    * \see specs/forums.txt (use-case U09)
    */
   public function mark_forum_read( $mark_time, $is_moderator )
   {
      if ( $this->fid <= 0 || $mark_time < $this->min_date )
         return;

      $min_date = self::get_min_date();
      $qsql = new QuerySQL();
      $qsql->add_part( SQLP_FIELDS,
         'P.Thread_ID',
         'UNIX_TIMESTAMP(P.Lastchanged) AS X_Lastchanged',
         'IF(ISNULL(FR.User_ID),0,UNIX_TIMESTAMP(FR.Time)) AS FR_X_Lastread' );
      $qsql->add_part( SQLP_FROM,
         'Posts AS P',
         "LEFT JOIN Forumreads AS FR ON FR.User_ID='{$this->uid}' AND FR.Forum_ID='{$this->fid}' "
            . 'AND FR.Thread_ID=P.Thread_ID' );
      $qsql->add_part( SQLP_WHERE,
         "P.Forum_ID=".$this->fid,
         'P.Parent_ID=0', 'P.Thread_ID=P.ID', //=thread (better query with thread=id for Forumreads)
         "P.Lastchanged >= FROM_UNIXTIME($min_date)" );
      if ( !$is_moderator )
         $qsql->add_part( SQLP_WHERE, 'P.PostsInThread>0' );
      $query = $qsql->get_select();
      $result = db_query( "ForumRead.mark_forum_read.load({$this->uid},{$this->fid})", $query );

      $upd_arr = array();
      $ins_arr = array();
      while ( $row = mysql_fetch_array( $result ) )
      {
         if ( $row['FR_X_Lastread'] > 0 )
         {
            if ( $row['X_Lastchanged'] > $row['FR_X_Lastread'] )
               $upd_arr[] = $row['Thread_ID'];
         }
         else
            $ins_arr[] = $row['Thread_ID'];
      }
      mysql_free_result($result);

      ta_begin();
      {//HOT-section to mark forum as read
         if ( count($upd_arr) ) // update existing Forumreads-entries
         {
            $upd_threads = implode(',', $upd_arr);
            db_query( "ForumRead.mark_forum_read.update(({$this->uid},{$this->fid})",
               "UPDATE Forumreads SET Time=GREATEST(Time,FROM_UNIXTIME($mark_time)) " .
               "WHERE User_ID={$this->uid} AND Forum_ID={$this->fid} AND Thread_ID>0 AND " .
                  "Thread_ID IN ($upd_threads)" );
         }

         if ( count($ins_arr) ) // insert new Forumreads-entries for NEW-threads
         {
            $query = "INSERT INTO Forumreads (User_ID,Forum_ID,Thread_ID,Time) VALUES ";

            $first = true;
            foreach ( $ins_arr as $thread_id )
            {
               if ( !$first ) $query .= ', ';
               $first = false;
               $query .= "({$this->uid},{$this->fid},$thread_id,FROM_UNIXTIME($mark_time))";
            }

            // use ON-DUPLICATE to avoid race-condiditions
            db_query( "ForumRead.mark_forum_read.insert(({$this->uid},{$this->fid})",
               $query . " ON DUPLICATE KEY UPDATE Time=GREATEST(Time,VALUES(Time))" );
         }

         self::trigger_recalc_forum_read_update( $this->uid, $this->fid );
         self::trigger_recalc_global_read_update( $this->uid );
      }
      ta_end();
   } //mark_forum_read

   /*! \brief Returns string-representation of this object (for debugging purposes). */
   public function to_string()
   {
      $reads = '';
      foreach ( $this->reads as $k => $time )
         $reads .= sprintf( "\n{ [$k]=[%s] },", date(DATE_FMT_QUICK, $time));
      return "ForumRead(uid={$this->uid},fid={$this->fid},tid={$this->tid}): "
         . "min_date=[{$this->min_date}], "
         . $reads;
   }


   // ---------- Static Class functions ----------------------------

   /*! \brief Builds basic QuerySQL to load forum-reads. */
   public static function build_query_sql( $uid )
   {
      // Forumreads: User_ID, Forum_ID, Thread_ID, Time
      $qsql = new QuerySQL();
      $qsql->add_part( SQLP_FIELDS, 'FR.*', 'UNIX_TIMESTAMP(FR.Time) AS X_Time' );
      $qsql->add_part( SQLP_FROM, 'Forumreads AS FR' );
      $qsql->add_part( SQLP_WHERE, "FR.User_ID='$uid'" );
      return $qsql;
   }

   /*! \brief Updates global forum-read for user. */
   public static function update_global_forum_read( $dbgmsg, $uid, $time, $has_new=false )
   {
      $new_val = ($has_new) ? 1 : 0;
      return db_query( "ForumRead:update_global_forum_read.$dbgmsg($uid,$time,$new_val)",
         "UPDATE Players SET ForumReadTime=FROM_UNIXTIME($time), ForumReadNew=$new_val " .
         "WHERE ID=$uid LIMIT 1" );
   }

   /*!
    * \brief Returns true, if there is at least one new post in all forums or in specific forum.
    * \param $forum_id if 0 check for all forums, otherwise check for given forum
    */
   public static function has_new_posts_in_forums( $uid, $forum_id )
   {
      $qsql = new QuerySQL();
      $qsql->add_part( SQLP_FIELDS,
         'P.Lastchanged',
         "IF(ISNULL(FR.User_ID),'0000-00-00 00:00:00',FR.Time) AS FR_Lastread" );
      $qsql->add_part( SQLP_FROM,
         "Posts AS P",
         "LEFT JOIN Forumreads AS FR " .
            "ON FR.Forum_ID=P.Forum_ID AND FR.Thread_ID=P.Thread_ID AND FR.User_ID='$uid'" );
      $qsql->add_part( SQLP_WHERE,
         'P.ID=P.Thread_ID',
         'P.Parent_ID=0', // redundant, but sometimes query is faster (intersect-index-merge)
         'P.PostsInThread>0',
         'P.Lastchanged >= FROM_UNIXTIME('.self::get_min_date().')' );
      if ( $forum_id >= 0 )
         $qsql->add_part( SQLP_WHERE, 'P.Forum_ID='.$forum_id );
      $qsql->add_part( SQLP_HAVING, 'P.Lastchanged > FR_Lastread' );
      $qsql->add_part( SQLP_LIMIT, '1' );

      $query = $qsql->get_select();
      $has_new = mysql_exists_row( "ForumRead:has_new_posts_in_forums($uid,$forum_id)", $query );
      return $has_new;
   }//has_new_posts_in_forums

   /*!
    * \brief Returns true, if there is at least one new post in all forums user can view.
    * \param $forum_opts ForumOptions-object with user-id
    */
   public static function has_new_posts_in_all_forums( ForumOptions $forum_opts )
   {
      $uid = $forum_opts->uid;
      $exclude_fopts = (int)$forum_opts->build_db_options_exclude();

      $qsql = new QuerySQL();
      $qsql->add_part( SQLP_FIELDS,
         'P.Lastchanged',
         "IF(ISNULL(FR.User_ID),'0000-00-00 00:00:00',FR.Time) AS FR_Lastread" );
      $qsql->add_part( SQLP_FROM,
         "Posts AS P",
         "INNER JOIN Forums ON Forums.ID=P.Forum_ID",
         "LEFT JOIN Forumreads AS FR " .
            "ON FR.Forum_ID=P.Forum_ID AND FR.Thread_ID=P.Thread_ID AND FR.User_ID='$uid'" );
      if ( $exclude_fopts > 0 )
         $qsql->add_part( SQLP_WHERE, "Forums.Options & $exclude_fopts = 0" );
      $qsql->add_part( SQLP_WHERE,
         'P.ID=P.Thread_ID',
         'P.Parent_ID=0', // redundant, but sometimes query is faster (intersect-index-merge)
         'P.PostsInThread>0',
         'P.Lastchanged >= FROM_UNIXTIME('.self::get_min_date().')' );
      $qsql->add_part( SQLP_HAVING, 'P.Lastchanged > FR_Lastread' );
      $qsql->add_part( SQLP_LIMIT, '1' );

      $query = $qsql->get_select();
      $has_new = mysql_exists_row( "ForumRead:has_new_posts_in_all_forums($uid)", $query );
      return $has_new;
   }//has_new_posts_in_all_forums

   /*!
    * \brief Returns boolean if forums have new posts for specified user-id (in ForumOptions).
    * \param $forum_opts ForumOptions-object for given user-id
    * \note stores result for user in Forumreads-table
    */
   public static function load_global_new( ForumOptions $forum_opts )
   {
      global $player_row;

      $user_id = $forum_opts->uid;
      $global_updated = self::load_global_updated();

      // load (from player_row) and check against global forum-read for user
      $fr_new = (int)@$player_row['ForumReadNew']; // recalc if <0
      $fr_time = (int)@$player_row['X_ForumReadTime'];
      if ( $fr_new < 0 || $fr_time <= 0 || $global_updated > $fr_time )
      {
         $global_has_new = self::has_new_posts_in_all_forums( $forum_opts );
         $success_update = self::update_global_forum_read( "load_global_new", // no TA
            $user_id, $global_updated, $global_has_new );
         if ( $success_update )
         {
            $player_row['X_ForumReadTime'] = $global_updated;
            $player_row['ForumReadNew'] = ($global_has_new) ? 1 : 0;
         }
      }
      else
         $global_has_new = (bool)$player_row['ForumReadNew'];

      return $global_has_new;
   }//load_global_new

   /*! \brief Returns non-0 global forum Updated date (creates one if not existing). */
   public static function load_global_updated( $need_update=true )
   {
      // assume updated=true if no file-cache (inefficient but functionality available)
      if ( (string)CACHE_FOLDER == '' )
         return true;

      // read from file-cache
      clearstatcache(); //FIXME since PHP5.3.0 with filename
      $updated = ( file_exists(CACHEFILE_FORUM_GLOBAL) )
         ? (int)@filemtime(CACHEFILE_FORUM_GLOBAL)
         : -1;

      if ( $need_update && $updated <= 0 ) // need-update ?
      {
         global $NOW;
         $updated = $NOW;
         self::trigger_recalc_global_post_update( $updated );
      }
      return $updated;
   }//load_global_updated

   /*! \brief Updates global-forum Updated-date because of change to post (stored in file-system). */
   public static function trigger_recalc_global_post_update( $time )
   {
      if ( (string)CACHE_FOLDER != '' )
      {
         $updated = self::load_global_updated( false );
         if ( $updated < 0 || $time > $updated ) // update only if newer
            touch(CACHEFILE_FORUM_GLOBAL, $time );
      }
   }

   /*!
    * \brief Updates Forumreads to initiate recalculation for global-forum NEW-flag-state.
    * \note IMPORTANT NOTE: caller needs to open TA with HOT-section if used with other db-writes!!
    */
   public static function trigger_recalc_global_read_update( $uid )
   {
      // force trigger updating with older updated-date on next load-global
      $global_updated = self::load_global_updated( false );
      return self::update_global_forum_read( "trigger_recalc_global_read_update",
         $uid, $global_updated - SECS_PER_DAY, false );
   }

   /*! \brief Updates Forumreads to initiate recalculation for forum NEW-flag-state. */
   public static function trigger_recalc_forum_read_update( $uid, $forum_id )
   {
      // force trigger updating with older updated-date on next load-global
      global $NOW;
      $row = mysql_single_col( "ForumRead:trigger_recalc_forum_read_update.read_forum($uid,$forum_id)",
         "SELECT UNIX_TIMESTAMP(Updated) AS X_Updated FROM Forums WHERE ID='$forum_id' LIMIT 1" );
      $rowval = ( $row ) ? $row[0] : 0;
      $forum_updated = ($rowval > 0) ? $rowval : $NOW;

      $fread = new ForumRead( $uid, $forum_id );
      $fread->replace_row_forumread( "ForumRead:trigger_recalc_forum_read_update.upd",
         $forum_id, 0, $forum_updated - SECS_PER_DAY );
   }

   public static function get_min_date()
   {
      return $GLOBALS['NOW'] - FORUM_SECS_NEW_END;
   }

} // end of 'ForumRead'

?>
//


koh5_pano



Your browser does not support the HTML5 canvas element.


Drag mouse to navigate.

Navigation





17.Aug.2010, Martin Wengenmayer