# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: Moderate.pm 3157 2008-07-07 11:05:25Z josh $

package BigMed::App::Web::Moderate;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;

use base qw(BigMed::App::Web::CP);
use BigMed::Comment;
use BigMed::App::Web::PageNav;
use BigMed::Content::Page;
use BigMed::Akismet;
use BigMed::PageAlert;
use BigMed::Search::Scheduler;

sub setup {
    my $app = shift;
    $app->start_mode('menu');
    $app->set_cp_selected_nav('Edit');
    $app->run_modes(
        'AUTOLOAD'  => sub { $_[0]->rm_menu() },
        'menu'      => 'rm_menu',
        'mod'       => 'rm_moderate',
        'spam'      => 'rm_spam',
        'public'    => 'rm_public',
        'view'      => 'rm_view',
        'delete'    => 'rm_delete',
        'approve'   => 'rm_approve',
        'mark-spam' => 'rm_mark_spam',
        'edit'      => 'rm_edit',
        'save'      => 'rm_save',
    );
    return;
}

sub cgiapp_prerun {
    my $app = shift;
    $app->SUPER::cgiapp_prerun;
    $app->require_privilege_level(4);
    return;
}

###########################################################
# RUN MODES
###########################################################

sub rm_menu {
    my $app     = shift;
    my %options = @_;
    my $site    = $app->current_site or return $app->rm_login_site;
    my $site_id = $site->id;

    my $offset = ( $app->path_args )[0] || q{};
    $offset =~ s/\D+//msg;
    $offset ||= '0';

    #get basic search info
    my $s = $app->session;
    my ( $order, $qtype, $filter, $status );
    if ( $app->utf8_param('stat') || $options{status} ) {
        $offset = 0;    #for new search
        my %f = $app->parse_submission(
            { id => 't',    parse_as => 'raw_text' },
            { id => 'q',    parse_as => 'simple_text' },
            { id => 'sort', parse_as => 'raw_text' },
            { id => 'stat', parse_as => 'raw_text' },
        );
        $s->param( 'MODERATE_sort',   ( $order  = $f{'sort'} ) );
        $s->param( 'MODERATE_qtype',  ( $qtype  = $f{'t'} ) );
        $s->param( 'MODERATE_filter', ( $filter = $f{'q'} ) );
        $s->param( 'MODERATE_stat',
            ( $status = $f{'stat'} || $options{status} ) );
    }
    else {
        $order  = $s->param('MODERATE_sort');
        $filter = $s->param('MODERATE_filter');
        $qtype  = $s->param('MODERATE_qtype');
        $status = $s->param('MODERATE_stat');
    }
    $status = 'mod'
      if !$status
      || ( $status ne 'all' && $status ne 'ok' && $status ne 'spam' );
    $order =
        ( !$order && $status && $status eq 'mod' ) ? 'ascend'
      : ( !$order || $order ne 'a' ) ? 'descend'
      : 'ascend';
    $qtype = 'page' if !$qtype || $qtype ne 'name';
    my $search_status = $status eq 'all' ? undef: $status;

    my $find_reg;
    if ( defined $filter && $filter ne q{} ) {
        my $search = $filter;
        $search =~ s/[.,"]+/ /msg;
        $search =~ s/&quot;/ /msg;

        #build the regex; use blocks of anything other than
        #marks, punctuation and separators. Originally split the
        #words by using a regex paren collector:
        #    ( $filter{'q'} =~ /([^\p{M}\p{P}\p{Z}]+)/msg )
        #...but Perl 5.6 mangles that. Splitting does the same thing
        #but works under 5.6.
        #there is still a weird 5.8.0 bug that won't match a utf8 string
        #at the very end of a string. not a prob in 5.6.1 or 5.8.6.
        #looks like a utf8 regex bug in 5.8.0.
        my $find = join( q{|},
            map    { "\Q$_\E" }
              grep { length $_ }
              ( split( /[\p{M}\p{P}\p{Z}]+/, $search ) ) );
        $find_reg = qr/$find/msi;
    }

    my $rid_match;
    if ( $find_reg && $qtype eq 'page' ) {    #gather ids of matching pages
        my $pages =
          BigMed::Content::Page->select(
            { site => $site_id, title => $find_reg } )
          or return $app->error_stop;
        my $p;
        my @id;
        while ( $p = $pages->next ) {
            push @id, $p->id;
        }
        return $app->error_stop if !defined $p;    #error
        @id = (0) if !@id;                         #force false match
        $rid_match = \@id;
        undef $find_reg;
    }

    #get *all* matches to start
    my $rcriteria = {
        site      => $site_id,
        commenter => $find_reg,
        status    => $search_status,
        page      => $rid_match
    };
    my $selection  = BigMed::Comment->select($rcriteria) or $app->error_stop;
    my $full_count = $selection->count;

    my $per_page = $app->env('MENUNUMTODISPLAY') || 100;
    my %param = (
        offset => $offset,
        limit  => $per_page,
        order  => $order,
        sort   => 'post_time',
    );
    $selection = $selection->select( undef, \%param ) or $app->error_stop;
    if ( !$selection->count && $full_count ) {    #back up to last page
        my $npages = int( $full_count / $per_page );
        $npages++ if $full_count % $per_page;
        $offset = $param{offset} = ( $npages - 1 ) * $per_page;
        $selection = BigMed::Comment->select( $rcriteria, \%param )
          or $app->error_stop;
    }

    #navigation bar and browse text
    my $nav_url = $app->build_url(
        script => 'bm-mod.cgi',
        rm     => 'menu',
        site   => $site_id,
    );
    my ( $pagenav_menu, $browse_text ) = $app->appweb_pagenav_menu(
        offset        => $offset,
        this_total    => $selection->count,
        total_records => $full_count,
        per_page      => $per_page,
        url_callback  => sub { return "$nav_url/$_[0]" },
    );

    my $base_edit = $app->build_url(
        script => 'bm-mod.cgi',
        rm     => 'edit',
        site   => $site_id,
    );
    my $base_view = $app->build_url(
        script => 'bm-mod.cgi',
        rm     => 'view',
        site   => $site_id,
    );
    my $page_preview = $app->build_url(
        script => 'bm-editor.cgi',
        rm     => 'preview',
        site   => $site_id,
        args   => ['page'],
    );
    my %stat_text = (
        'ok'   => $app->language('MODERATE_status_ok'),
        'mod'  => $app->language('MODERATE_status_mod'),
        'spam' => $app->language('MODERATE_status_spam'),
    );
    my $view_text = $app->language('MODERATE_View');
    my $site_text = $app->language('MODERATE_Site');
    my $edit_text = $app->language('MODERATE_Edit');

    #build each row for table display
    my %PAGE;
    my $obj;
    my @items;
    while ( $obj = $selection->next ) {
        my ( $sid, $pid ) = ( $obj->site, $obj->page );
        my $page = $PAGE{$pid}
          ||= BigMed::Content::Page->fetch( { site => $sid, id => $pid } )
          or next;
        my $page_url = $page->active_page_url($site) || "$page_preview/$pid";

        my $status = $obj->status || 'mod';
        my $post_time =
          $app->format_time( $obj->post_time || $obj->mod_time );

        my $email = $obj->email     || q{};
        my $url   = $obj->url       || q{};
        my $name  = $obj->commenter || $email || q{};

        my $summary = $app->strip_html_tags( $obj->safe_content );
        $summary = substr( $summary, 0, 300 ) . '...'
          if length $summary > 300;

        push @items,
          { CID         => $obj->id,
            EDIT_URL    => "$base_edit/" . $obj->id,
            NAME        => $name,
            EMAIL       => $email,
            URL         => $url,
            POST_TIME   => $post_time,
            SUMMARY     => $summary,
            PAGE_TITLE  => $page->title,
            PAGE_URL    => $page_url,
            VIEW_URL    => "$base_view/" . $obj->id,
            STATUS_TEXT => $stat_text{$status},
            STATUS      => $status,
            VIEW_TEXT   => $view_text,
            SITE_TEXT   => $site_text,
            EDIT_TEXT   => $edit_text,
            IP          => ($ obj->ip || q{} ),
          };
    }

    #if obj is undefined here, we could have an error... let it slide,
    #since it will be displayed as an error message on the menu page itself

    #page title and message
    my $title_unlocal =
        $status eq 'mod'  ? 'MODERATE_Moderate Menu'
      : $status eq 'spam' ? 'MODERATE_Spam Menu'
      : 'MODERATE_Edit Menu';
    my ( $title, $message ) = $app->title_and_message(
        field_msg => $options{field_msg},
        message   => $options{message},
        title     => $title_unlocal,
    );
    $app->set_cp_breadcrumbs( { bc_label => $title_unlocal } );

    $app->js_add_script( $app->env('BMADMINURL') . '/js/bm-actionmenu.js' );
    $app->js_add_onload('BM.ActionMenu.update("modActionMenu","modMenu");');

    return $app->html_template_screen(
        'screen_moderate_menu.tmpl',
        bmcp_title       => $title,
        message          => $message,
        items            => \@items,
        BMCP_LEFTCOL     => $browse_text,
        navbar           => $pagenav_menu,
        self_url         => $app->build_url( rm => 'menu', site => $site_id ),
        filter           => $filter,
        "select_$status" => ' selected="selected"',
        "select_$qtype"  => ' selected="selected"',
        "select_$order"  => ' selected="selected"',
    );
}

sub rm_public {
    return $_[0]->rm_menu( status => 'ok' );
}

sub rm_moderate {
    return $_[0]->rm_menu( status => 'mod' );
}

sub rm_spam {
    return $_[0]->rm_menu(
        status  => 'spam',
        message => 'MODERATE_Spam removal policy'
    );
}

sub rm_delete {
    my $app = shift;
    $app->require_post() or return $app->error_stop;
    my $site   = $app->current_site           or return $app->rm_login_site;
    my $select = $app->_get_checked_selection or return $app->rm_menu();

    my $comment;
    my %page;
    while ( $comment = $select->next ) {
        $page{ $comment->page } = 1 if $comment->status eq 'ok';
    }
    return $app->rm_menu() if !defined $comment;

    #don't choke on errors; just display 'em on menu page
    my $sid = $site->id;
    $select->trash_all;
    my ($rkids, %reindex);
    foreach my $pid ( keys %page ) {
        my $p = BigMed::Content::Page->fetch( { site => $sid, id => $pid } )
          or next;
        BigMed::Comment->build_page_comments( $site, $p )
          or return $app->rm_menu();

        $rkids ||= { map { $_ => 1 }
              ( $site->homepage_id, $site->all_active_descendants_ids() ) };
        $reindex{ $pid } = 1
          if $p->active_page_url( $site, { rkids => $rkids } );
    }
    schedule_index( $site, [keys %reindex] ) or return $app->rm_menu();

    my $menu = $app->build_url(
        script => 'bm-mod.cgi',
        rm     => 'menu',
        site   => $site,
    );
    return $app->_redirect_to_menu(
        'EDITOR_Items deleted, changes made on live site');
}

sub rm_approve {
    my $app = shift;
    $app->require_post() or return $app->error_stop;
    my $site   = $app->current_site           or return $app->rm_login_site;
    my $select = $app->_get_checked_selection or return $app->rm_menu();

    my $comment;
    my @alerts;
    my %page;
    while ( $comment = $select->next ) {
        next if $comment->status eq 'ok';
        $comment->call_trigger( 'comment_approved', $site );
        $comment->set_status('ok');
        $comment->save or return $app->rm_menu();
        
        my $pid = $comment->page;
        $page{$pid} ||= BigMed::Content::Page->fetch({site=>$site->id, id=>$pid});
        next if !$page{$pid};
        push (@alerts, [$page{$pid},$site,$comment]);
    }
    return $app->rm_menu() if !defined $comment;

    #don't trap errors, let them be displayed on the menu page.
    my ($rkids, %reindex);
    while ( my ($pid, $page) = each %page ) {
        next if !$page;
        BigMed::Comment->build_page_comments( $site, $page )
          or $app->error_stop;
        $rkids ||= { map { $_ => 1 }
              ( $site->homepage_id, $site->all_active_descendants_ids() ) };
        $reindex{ $pid} = 1
          if $page->active_page_url( $site, { rkids => $rkids } );
     }
    foreach my $a (@alerts) {
        BigMed::PageAlert->notify('comment_new',@{ $a });
    }
    schedule_index( $site, [keys %reindex] ) or return $app->rm_menu();
    return $app->_redirect_to_menu('MODERATE_Items approved');
}

sub rm_edit {
    my $app     = shift;
    my %options = @_;
    my $comment = $app->_comment_from_path or return $app->error_stop;
    my $site    = $app->current_site or return $app->rm_login_site;

    my @fields = (
        $app->prompt_field_ref(
            data_class => 'BigMed::Comment',
            column     => 'commenter',
            required   => 1,
            value      => $comment->commenter,
        ),
        $app->prompt_field_ref(
            data_class  => 'BigMed::Comment',
            column      => 'content',
            required    => 1,
            value       => $comment->content,
            description => 'MODERATE_Use Markdown for formatting',
        ),
        $app->prompt_field_ref(
            data_class => 'BigMed::Comment',
            column     => 'email',
            required   => 1,
            value      => $comment->email,
        ),
        $app->prompt_field_ref(
            data_class => 'BigMed::Comment',
            column     => 'url',
            value      => $comment->url,
        ),
        $app->prompt_field_ref(
            data_class => 'BigMed::Comment',
            column     => 'status',
            value      => $comment->status,
            required   => 1,
        ),
    );
    my $submit = $app->prompt_field_ref(
        id        => 'modeditor_submit',
        prompt_as => 'submit',
        value     => $app->language('BM_SUBMIT_LABEL_Save'),
    );
    my @fieldsets = (
        $app->prompt_fieldset_ref(
            fields    => \@fields,
            query     => $app->query,
            field_msg => $options{field_msg},
        ),
        $app->prompt_fieldset_ref( fields => [$submit] ),
    );

    #headline/message text
    my $title_unlocal = 'MODERATE_Edit Comment';
    my ( $title, $message ) = $app->title_and_message(
        field_msg => $options{field_msg},
        message   => $options{message},
        title     => $title_unlocal,
    );
    $app->set_cp_breadcrumbs(
        {   bc_label => 'MODERATE_Edit Menu',
            bc_url   => $app->build_url(
                script => 'bm-mod.cgi',
                site   => $site->id,
                rm     => 'menu'
            )
        },
        { bc_label => $title_unlocal }
    );

    #form url
    my $form_url = $app->build_url(
        script => 'bm-mod.cgi',
        site   => $site->id,
        rm     => 'save',
        args   => $comment->id,
    );

    return $app->html_template_screen(
        'screen_cp_generic.tmpl',
        form_url     => $form_url,
        bmcp_title   => $title,
        message      => $message,
        fieldsets    => \@fieldsets,
        BMCP_LEFTCOL => '',
        ,
    );
}

sub rm_save {
    my $app     = shift;
    my $comment = $app->_comment_from_path or return $app->error_stop;
    my $site    = $app->current_site or return $app->rm_login_site;

    my %field = $app->parse_submission(
        {   data_class => 'BigMed::Comment',
            column     => 'commenter',
            required   => 1,
        },
        {   data_class => 'BigMed::Comment',
            column     => 'content',
            required   => 1,
        },
        {   data_class => 'BigMed::Comment',
            column     => 'email',
            required   => 1,
        },
        {   data_class => 'BigMed::Comment',
            column     => 'url',
        },
        {   data_class => 'BigMed::Comment',
            column     => 'status',
            required   => 1,
        },
    );
    if ( $field{_ERROR} ) {
        return $app->rm_edit(
            field_msg => $field{_ERROR},
            query     => $app->query
        );
    }
    $field{url} = q{} if $field{url} !~ m|^https?://|msi;

    #call triggers on original comment
    my $old_status = $comment->status || q{};
    if ( $old_status ne $field{status} ) {
        $comment->call_trigger( 'comment_approved', $site )
          if $field{status} eq 'ok';
        $comment->call_trigger( 'comment_mark_spam', $site )
          if $field{status} eq 'spam';
    }

    foreach my $f (qw(commenter content email url status)) {
        my $setter = "set_$f";
        $comment->$setter( $field{$f} );
    }
    $comment->save or return $app->rm_edit( query => $app->query );

    if ( $old_status eq 'ok' || $field{status} eq 'ok' ) {
        my $p =
          BigMed::Content::Page->fetch(
            { site => $site->id, id => $comment->page } );
        $comment->build_page_comments( $site, $p ) if $p;

        if ( $p->active_page_url($site) ) {
            schedule_index( $site, $p )
              or return $app->rm_edit( query => $app->query );
        }
    }

    return $app->_redirect_to_menu('BM_Your changes have been saved.');
}

sub rm_view {
    my $app     = shift;
    my $comment = $app->_comment_from_path or return $app->error_stop;
    my $site    = $app->current_site or return $app->rm_login_site;
    my $preview =
      $app->html_template( 'wi_comments_preview.tmpl',
        %{ $comment->prep_tmpl_params($site) } );
    return $app->html_template_screen(
        'screen_web_basic_msg.tmpl',
        message    => $preview,
        bmcp_title => 'Comment Preview',
    );
}

sub rm_mark_spam {
    my $app = shift;
    $app->require_post() or return $app->error_stop;
    my $site   = $app->current_site           or return $app->rm_login_site;
    my $select = $app->_get_checked_selection or return $app->rm_menu();

    my $comment;
    my %build;
    while ( $comment = $select->next ) {
        next if $comment->status eq 'spam';
        $comment->call_trigger( 'comment_mark_spam', $site );
        $build{ $comment->page } = 1 if $comment->status eq 'ok';
        $comment->set_status('spam');
        $comment->save;
    }
    return $app->rm_menu() if !defined $comment;

    #don't trap errors, let them be displayed on the menu page.
    my $sid = $site->id;
    my ($rkids, %reindex);
    foreach my $pid ( keys %build ) {
        my $p = BigMed::Content::Page->fetch( { site => $sid, id => $pid } )
          or next;
        BigMed::Comment->build_page_comments( $site, $p )
          or return $app->rm_menu();

        $rkids ||= { map { $_ => 1 }
              ( $site->homepage_id, $site->all_active_descendants_ids() ) };
        $reindex{ $p->id } = 1
          if $p->active_page_url( $site, { rkids => $rkids } );
    }
    schedule_index( $site, [keys %reindex] ) or return $app->rm_menu();

    return $app->_redirect_to_menu('MODERATE_Items junked');
}

sub _redirect_to_menu {
    my ( $app, $message, $err ) = @_;
    if ( $app->error ) {
        my %err = $app->error_html_hash;
        $message = $err = $err{text};
    }
    $app->set_session_message( $message, $err ) if $message;
    $app->header_type('redirect');

    my $menu = $app->build_url(
        script => 'bm-mod.cgi',
        rm     => 'menu',
        site   => $app->current_site,
    );
    $app->header_props( -url => $menu );
    return "Redirecting to $menu";
}

sub _get_checked_selection {
    my $app     = shift;
    my $site    = $app->current_site or return;
    my $site_id = $site->id;
    my @id      = $app->utf8_param('c');
    return if !@id;

    my $select = BigMed::Comment->select( { site => $site_id, id => \@id } );
    return if !$select || !$select->count;
    return $select;
}

sub _comment_from_path {
    my $app  = shift;
    my $site = $app->current_site or return;
    my $cid  = ( $app->path_args )[0];
    my $comment;
    if (!$cid
        || !(
            $comment =
            BigMed::Comment->fetch( { site => $site->id, id => $cid } )
        )
      )
    {
        return $app->set_error(
            head => 'MODERATE_HEAD_Cannot Find Comment',
            text => 'MODERATE_Cannot Find Comment',
        );
    }
    return $comment;
}

sub left_col_page_status {
    my $app      = shift;
    my $content  = shift;
    my $user     = $app->current_user or return $app->rm_login;
    my $mod_time = $content->mod_time || q{};
    $mod_time &&= $app->format_time($mod_time);

    my $version = $content->version || 0;
    $version++;

    my $create_time = $content->create_time || q{};
    $create_time &&= $app->format_time($create_time);

    #get owner name
    my $owner_id = $content->owner || 0;
    my $owner_name;
    if ( !$owner_id || $owner_id == $user->id ) {
        $owner_name = ( $content->subtype && $content->subtype eq 'section' )
          ? q{--}    #sections have no owners
          : $user->name;
    }
    else {
        defined( my $owner = BigMed::User->fetch($owner_id) )
          or $app->error_stop;

        #if empty user, not found; may have been deleted
        $owner_name = $owner ? $owner->name : '--';
    }

    #get last editor name
    my $last_editor;
    my $editor_id = $content->last_editor;
    if ($editor_id) {
        if ( $editor_id == $owner_id ) {
            $last_editor = $owner_name;
        }
        elsif ( $editor_id == $user->id ) {
            $last_editor = $user->name;
        }
        else {
            defined( my $editor = BigMed::User->fetch($editor_id) )
              or $app->error_stop;
            $last_editor = $editor ? $editor->name : q{};
        }
    }

    return $app->html_template(
        'wi_editor_edit_leftcol.tmpl',
        version     => $version,
        mod_time    => $mod_time,
        owner       => $owner_name,
        last_editor => $last_editor,
        pub_time    => $app->published_text($content),
        create_time => $create_time,
    );
}

1;

__END__

