############################################################################### # Favorites.pm # # $Date: 01.09.2026 $ # ############################################################################### # YaBB: Yet another Bulletin Board # # Open-Source Community Software for Webmasters # # Version: YaBBForum 3.1 # # Packaged: 01 Sep, 2026 # # Distributed by: https://yabbforum.nz # # =========================================================================== # # Copyright (c) 2000-2026 YaBB (yabbforum.nz) - All Rights Reserved. # # Software by: The YaBB Development Team # # with assistance from the YaBB community. # ############################################################################### # use strict; # use warnings; no warnings qw(uninitialized once redefine); use CGI::Carp qw(fatalsToBrowser); our $VERSION = '3.1'; $favoritespmver = 'YaBBForum 3.1'; if ( $action eq 'detailedversion' ) { return 1; } sub Favorites { LoadLanguage('MessageIndex'); get_micon(); get_template('MyPosts'); my $start = int( $INFO{'start'} ) || 0; my ( @threads, $counter, $pages, $mnum, $msub, $mname, $memail, $mdate, $mreplies, $musername, $micon, $mstate, $dlp ); my $treplies = 0; # grab all relevant info on the favorite thread for this user and check access to them if ( !$maxfavs ) { $maxfavs = 10; } my @favboards; eval { require Variables::Movedthreads }; foreach my $myfav ( split /,/xsm, ${ $uid . $username }{'favorites'} ) { # see if thread exists and search for it if moved if ( exists $moved_file{$myfav} ) { my @moved = ($myfav); while ( exists $moved_file{$myfav} ) { $myfav = $moved_file{$myfav}; unshift @moved, $myfav; } foreach (@moved) { $myfav = $_; if ( $myfav ne $moved[-1] ) { if ( -e "$datadir/$myfav.ctb" ) { RemFav( $moved[-1], 'nonexist' ); AddFav( $myfav, 0, 1 ); last; } } elsif ( !-e "$datadir/$myfav.ctb" ) { RemFav( $myfav, 'nonexist' ); $myfav = 0; } } next if !$myfav; } elsif ( !-e "$datadir/$myfav.ctb" ) { RemFav( $myfav, 'nonexist' ); next; } MessageTotals( 'load', $myfav ); $favoboard = ${$myfav}{'board'}; push @favboards, "$favoboard|$myfav"; } foreach ( sort @favboards ) { ( $loadboard, $loadfav ) = split /\|/xsm, $_; if ( !${ $uid . $loadboard }{'board'} ) { BoardTotals( 'load', $loadboard ); } next if !$iamadmin && AccessCheck( $loadboard, q{}, ( split /\|/xsm, $board{$loadboard} )[1] ) ne 'granted'; next if !$iamadmin && !CatAccess( ( split /\|/xsm, $catinfo{"${$uid.$loadboard}{'cat'}"} )[1] ); fopen( BRDTXT, "$boardsdir/$loadboard.txt" ) or fatal_error( 'cannot_open', "$boardsdir/$currentboard.txt", 1 ); while ( my $brd = ) { if ( ( split /\|/xsm, $brd, 2 )[0] eq $loadfav ) { push @threads, $brd; } } fclose(BRDTXT); } my $curfav = @threads; LoadCensorList(); my %attachments; my $att_length = -s "$vardir/attachments.txt"; if ( ( -s "$vardir/attachments.txt" ) > 5 ) { fopen( ATM, "$vardir/attachments.txt" ); while () { $attachments{ ( split /\|/xsm, $_, 2 )[0] }++; } fclose(ATM); } # Print the header and board info. my $colspan = 7; # Begin printing the message index for current board. $counter = $start; getlog(); my $dmax = $date - ( $max_log_days_old * 86400 ); foreach (@threads) { ( $mnum, $msub, $mname, $memail, $mdate, $mreplies, $musername, $micon, $mstate ) = split /\|/xsm, $_; # Set thread class depending on locked status and number of replies. if ( $mnum eq q{} ) { next; } MessageTotals( 'load', $mnum ); $permlinkboard = ${$mnum}{'board'} eq $annboard ? $annboard : $currentboard; my $permdate = permtimer($mnum); my $message_permalink = qq~$messageindex_txt{'10'}~; $threadclass = 'thread'; if ( $mstate =~ /h/ism ) { $threadclass = 'hide'; } elsif ( $mstate =~ /l/ism ) { $threadclass = 'locked'; } elsif ( $mreplies >= $VeryHotTopic ) { $threadclass = 'veryhotthread'; } elsif ( $mreplies >= $HotTopic ) { $threadclass = 'hotthread'; } elsif ( $mstate eq q{} ) { $threadclass = 'thread'; } if ( $threadclass eq 'hide' && $mstate =~ /s/ism && $mstate !~ /l/ism ) { $threadclass = 'hidesticky'; } elsif ($threadclass eq 'hide' && $mstate =~ /l/ism && $mstate !~ /s/ism ) { $threadclass = 'hidelock'; } elsif ($threadclass eq 'hide' && $mstate =~ /s/ism && $mstate =~ /l/ism ) { $threadclass = 'hidestickylock'; } elsif ($threadclass eq 'locked' && $mstate =~ /s/ism && $mstate !~ /h/ism ) { $threadclass = 'stickylock'; } elsif ( $mstate =~ /s/ism && $mstate !~ /h/ism ) { $threadclass = 'sticky'; } elsif ( ${$mnum}{'board'} eq $annboard && $mstate !~ /h/ism ) { $threadclass = $threadclass eq 'locked' ? 'announcementlock' : 'announcement'; } my $movedFlag; ( undef, $movedFlag ) = Split_Splice_Move( $msub, $mnum ); if ( !$iamguest && $max_log_days_old ) { # Decide if thread should have the "NEW" indicator next to it. # Do this by reading the user's log for last read time on thread, # and compare to the last post time on the thread. $dlp = int( $yyuserlog{$mnum} ) > int( $yyuserlog{"$currentboard--mark"} ) ? int( $yyuserlog{$mnum} ) : int $yyuserlog{"$currentboard--mark"}; if ( $yyuserlog{"$mnum--unread"} || ( !$dlp && $mdate > $dmax ) || ( $dlp > $dmax && $dlp < $mdate ) ) { if ( ${$mnum}{'board'} eq $annboard ) { $new = qq~$micon{'new'}~; } else { $new = qq~$micon{'new'}~; } } else { $new = q{}; } } if ($movedFlag) { $new = q{}; } $micon = $micon{$micon}; $mpoll = q{}; if ( -e "$datadir/$mnum.poll" ) { $mpoll = qq~$messageindex_txt{'15'}: ~; fopen( POLL, "$datadir/$mnum.poll" ); my @poll = ; fclose(POLL); my ( $poll_question, $poll_locked, $poll_uname, $poll_name, $poll_email, $poll_date, $guest_vote, $hide_results, $multi_vote, $poll_mod, $poll_modname, $poll_comment, $vote_limit, $pie_radius, $pie_legends, $poll_end ) = split /\|/xsm, $poll[0]; chomp $poll_end; if ( $poll_end && !$poll_locked && $poll_end < $date ) { $poll_locked = 1; $poll_end = q{}; $poll[0] = "$poll_question|$poll_locked|$poll_uname|$poll_name|$poll_email|$poll_date|$guest_vote|$hide_results|$multi_vote|$poll_mod|$poll_modname|$poll_comment|$vote_limit|$pie_radius|$pie_legends|$poll_end\n"; fopen( POLL, ">$datadir/$mnum.poll" ); print {POLL} @poll or croak "$croak{'print'} POLL"; fclose(POLL); } $micon = qq~$img{'pollicon'}~; if ($poll_locked) { $micon = $img{'polliconclosed'}; } elsif ( $max_log_days_old && $mdate > $dmax ) { if ( $dlp < $createpoll_date ) { $micon = qq~$img{'polliconnew'}~; } else { fopen( POLLED, "$datadir/$mnum.polled" ); my $polled = ; fclose(POLLED); if ( $dlp < ( split /\|/xsm, $polled )[3] ) { $micon = qq~$img{'polliconnew'}~; } } } } # Load the current nickname of the account name of the thread starter. if ( $musername ne 'Guest' ) { LoadUser($musername); if ( ${ $uid . $musername }{'realname'} ) { $mname = qq~$format_unbold{$musername}~; } else { $mname .= qq~ ($messageindex_txt{'470a'})~; } } ( $msub, undef ) = Split_Splice_Move( $msub, 0 ); # Censor the subject of the thread. $msub = Censor($msub); ToChars($msub); # Build the page links list. $pages = q{}; $pagesall; if ($showpageall) { $pagesall = qq~$pidtxt{'01'}~; } $maxmessagedisplay ||= 10; if ( int( ( $mreplies + 1 ) / $maxmessagedisplay ) > 6 ) { $pages = qq~ 1~; $pages .= qq~ 2~; $endpage = int( $mreplies / $maxmessagedisplay ) + 1; $i = ( $endpage - 1 ) * $maxmessagedisplay; $j = $i - $maxmessagedisplay; $k = $endpage - 1; $tmpa = $endpage - 2; $tmpb = $j - $maxmessagedisplay; $pages .= qq~ ...~; $pages .= qq~ $tmpa~; $pages .= qq~ $k~; $pages .= qq~ $endpage~; $pages = qq~
« $messageindex_txt{'139'} $pages $pagesall »~; } elsif ( $mreplies + 1 > $maxmessagedisplay ) { $tmpa = 1; for my $tmpb ( 0 .. $mreplies ) { if ( $tmpb % $maxmessagedisplay == 0 ) { $pages .= qq~$tmpa\n~; ++$tmpa; } } $pages =~ s/\n\Z//xsm; $pages = qq~
« $messageindex_txt{'139'} $pages »~; } $views = ${$mnum}{'views'}; $lastposter = ${$mnum}{'lastposter'}; if ( $lastposter =~ m{\AGuest-(.*)}xsm ) { $lastposter = $1; } elsif ( $lastposter !~ m{Guest}xsm && !( -e "$memberdir/$lastposter.vars" ) ) { $lastposter = $messageindex_txt{'470a'}; } else { if ( ( $lastposter ne $messageindex_txt{'470'} && $lastposter ne $messageindex_txt{'470a'} ) || !-e "$memberdir/$lastposter.vars" ) { LoadUser($lastposter); if ( ${ $uid . $lastposter }{'realname'} ) { $lastposter = qq~$format_unbold{$lastposter}~; } } } $lastpostername = $lastposter || $messageindex_txt{'470'}; $views = $views ? $views - 1 : 0; # Check if the thread contains attachments and create a paper-clip icon if it does $temp_attachment = $attachments{$mnum} ? qq~$messageindex_txt{'3'} $attachments{$mnum} ~
          . (
              $attachments{$mnum} == 1
            ? $messageindex_txt{'5'}
            : $messageindex_txt{'4'}
          )
          . qq~~ : q{}; $mydate = timeformat($mdate); my $threadpic = $micon{$threadclass}; my $msublink = qq~$msub~; if ( !$movedFlag && ${$mnum}{'board'} eq $annboard ) { $msublink = qq~$msub~; } my $lastpostlink = qq~$img{'lastpost'}$mydate~; my $fmreplies = NumberFormat($mreplies); $views = NumberFormat($views); my $tempbar = $threadbar; if ($movedFlag) { $tempbar = $threadbarMoved; } $adminbar = qq~~; $admincol = $admincolumn; $admincol =~ s/{yabb admin}/$adminbar/gsm; $tempbar =~ s/{yabb admin column}/$admincol/gsm; $tempbar =~ s/{yabb threadpic}/$threadpic/gsm; $tempbar =~ s/{yabb icon}/$micon/gsm; $tempbar =~ s/{yabb new}/$new/gsm; $tempbar =~ s/{yabb poll}/$mpoll/gsm; $tempbar =~ s/{yabb favorite}/$favicon{$mnum}/gsm; $tempbar =~ s/{yabb subjectlink}/$msublink/gsm; $tempbar =~ s/{yabb attachmenticon}/$temp_attachment/gsm; $tempbar =~ s/{yabb pages}/$pages/gsm; $tempbar =~ s/{yabb starter}/$mname/gsm; $tempbar =~ s/{yabb replies}/$fmreplies/gsm; $tempbar =~ s/{yabb views}/$views/gsm; $tempbar =~ s/{yabb lastpostlink}/$lastpostlink/gsm; $tempbar =~ s/{yabb lastposter}/$lastpostername/gsm; if ( $accept_permalink == 1 ) { $tempbar =~ s/{yabb permalink}/$message_permalink/gsm; } else { $tempbar =~ s/{yabb permalink}//gsm; } $tmptempbar .= $tempbar; $counter++; $mcount++; $treplies += $mreplies + 1; } # Put a "no messages" message if no threads exist: if ( !$tmptempbar ) { $tmptempbar = $no_favs; } $yabbicons = qq~$micon{'thread'} $messageindex_txt{'457'}
$micon{'sticky'} $messageindex_txt{'779'}
$micon{'locked'} $messageindex_txt{'456'}
$micon{'stickylock'}$messageindex_txt{'780'}
~; if ( $staff && $sessionvalid == 1 ) { $yabbadminicons = qq~$micon{'hide'} $messageindex_txt{'458'}
$micon{'hidesticky'} $messageindex_txt{'459'}
$micon{'hidelock'} $messageindex_txt{'460'}
$micon{'hidestickylock'} $messageindex_txt{'461'}
~; } $yabbadminicons .= qq~$micon{'announcement'} $messageindex_txt{'779a'}
$micon{'announcementlock'} $messageindex_txt{'779b'}
$micon{'hotthread'} $messageindex_txt{'454'} $HotTopic $messageindex_txt{'454a'}
$micon{'veryhotthread'} $messageindex_txt{'455'} $VeryHotTopic $messageindex_txt{'454a'}
~; $formstart = qq~
~; $formend = qq~
~; $adminselector = qq~ ~; $admincheckboxes = q~ ~; $subfooterbar =~ s/{yabb admin selector}/$adminselector/gsm; $subfooterbar =~ s/{yabb admin checkboxes}/$admincheckboxes/gsm; # Template it $adminheader =~ s/{yabb admin}/$messageindex_txt{'2'}/gsm; $favorites_template =~ s/{yabb home}//gsm; $favorites_template =~ s/{yabb category}//gsm; $yynavigation = qq~› $img_txt{'mycenter'} › $img_txt{'70'}~; $favboard = qq~$img_txt{'70'}~; $favorites_template =~ s/{yabb board}/$favboard/gsm; $bdescrip = qq~$messageindex_txt{'75'}
$messageindex_txt{'76'} $curfav $messageindex_txt{'77'} $maxfavs $messageindex_txt{'78'}~; $curfav = NumberFormat($curfav); $treplies = NumberFormat($treplies); $bdpicExt ||= 'gif'; ToChars($bdescrip); $boarddescription =~ s/{yabb boarddescription}/$bdescrip/gsm; $favorites_template =~ s/{yabb description}/$boarddescription/gsm; $bdpic = qq~ $img_txt{'70'} ~; $favorites_template =~ s/{yabb bdpicture}/$bdpic/gsm; $favorites_template =~ s/{yabb threadcount}/$curfav/gsm; $favorites_template =~ s/{yabb messagecount}/$treplies/gsm; $favorites_template =~ s/{yabb colspan}/$colspan/gsm; $favorites_template =~ s/{yabb admin column}/$adminheader/gsm; $favorites_template =~ s/{yabb modupdate}/$formstart/gsm; $favorites_template =~ s/{yabb modupdateend}/$formend/gsm; $favorites_template =~ s/{yabb threadblock}/$tmptempbar/gsm; $favorites_template =~ s/{yabb adminfooter}/$subfooterbar/gsm; $favorites_template =~ s/{yabb icons}/$yabbicons/gsm; $favorites_template =~ s/{yabb admin icons}/$yabbadminicons/gsm; $showFavorites .= qq~$favorites_template~; $showFavorites .= qq~ ~; $yytitle = $img_txt{'70'}; return; } sub AddFav { my @x = @_; my $favo = $INFO{'fav'} || $x[0]; my $goto = $INFO{'start'} || $x[1] || 0; my $return = $x[2]; if ( $favo =~ /\D/xsm ) { fatal_error( 'error_occurred', q{}, 1 ); } my @oldfav = split /,/xsm, ${ $uid . $username }{'favorites'}; if ( @oldfav < ( $maxfavs || 10 ) ) { push @oldfav, $favo; ${ $uid . $username }{'favorites'} = join q{,}, undupe(@oldfav); UserAccount( $username, 'update' ); } if ( !$return ) { if ( $INFO{'oldaddfav'} ) { $yySetLocation = qq~$scripturl?num=$favo/$goto~; redirectexit(); } $elenable = 0; croak q{}; # This is here only to avoid server error log entries! } return; } sub MultiRemFav { while ( $maxfavs >= $count ) { RemFav( $FORM{"admin$count"} ); $count++; } $yySetLocation = qq~$scripturl?action=favorites~; redirectexit(); return; } sub RemFav { my @x = @_; my $favo = $INFO{'fav'} || $x[0]; my $goto = $INFO{'start'} || $x[1]; if ( !$goto ) { $goto = 0; } my @newfav; foreach ( split /,/xsm, ${ $uid . $username }{'favorites'} ) { if ( $favo ne $_ ) { push @newfav, $_; } } ${ $uid . $username }{'favorites'} = join q{,}, undupe(@newfav); UserAccount( $username, 'update' ); return if $x[1] eq 'nonexist'; if ( $INFO{'ref'} ne 'delete' && $action ne 'multiremfav' && $INFO{'oldaddfav'} ) { $yySetLocation = qq~$scripturl?num=$favo/$goto~; redirectexit(); } if ( $action eq 'remfav' ) { $elenable = 0; croak q{}; # This is here only to avoid server error log entries! } return; } sub IsFav { my @x = @_; my $favo = $x[0]; my $goto = $x[1] || 0; my $postcheck = $x[2]; my $addfav = $img{'addfav'}; my $remfav = $img{'remfav'}; if ($useThreadtools) { $addfav =~ s/\[tool=(.+?)\](.+?)\[\/tool\]/$2/gsm; $remfav =~ s/\[tool=(.+?)\](.+?)\[\/tool\]/$2/gsm; } if ( !$postcheck ) { $yyjavascript .= qq~\n var addlink = '$addfav'; var remlink = '$remfav';\n~; } my @oldfav = split /,/xsm, ${ $uid . $username }{'favorites'}; my ( $button, $nofav ); if ( @oldfav < ( $maxfavs || 10 ) ) { $button = qq~$menusep$img{'addfav'}~; $nofav = 1; } else { $nofav = 2; } foreach (@oldfav) { if ( $favo eq $_ ) { $button = qq~$menusep$img{'remfav'}~; $nofav = 0; } } return ( !$postcheck ? $button : $nofav ); } sub IsFav1 { my @x = @_; my $favo = $x[0]; my $goto = $x[1] || 0; my $postcheck = $x[2]; my $addfav = $img{'addfav'}; my $remfav = $img{'remfav'}; if ($useThreadtools) { $addfav =~ s/\[tool=(.+?)\](.+?)\[\/tool\]/$2/gsm; $remfav =~ s/\[tool=(.+?)\](.+?)\[\/tool\]/$2/gsm; } if ( !$postcheck ) { $yyjavascript .= qq~\n addlink = '$addfav'; remlink = '$remfav';\n~; } my @oldfav = split /,/xsm, ${ $uid . $username }{'favorites'}; my ( $button, $nofav ); if ( @oldfav < ( $maxfavs || 10 ) ) { $button = qq~$menusep$img{'addfav'}~; $nofav = 1; } else { $nofav = 2; } foreach (@oldfav) { if ( $favo eq $_ ) { $button = qq~$menusep$img{'remfav'}~; $nofav = 0; } } return ( !$postcheck ? $button : $nofav ); } 1;