# 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: Update.pm 3335 2008-09-15 13:35:43Z josh $

package BigMed::Update;
use strict;
use warnings;
use utf8;
use Carp;
use BigMed::DiskUtil
  qw(bm_file_path bm_write_file bm_delete_file bm_copy_dir bm_move_file
  bm_dir_permissions bm_move_dir bm_load_file bm_untaint_filepath);
use BigMed::Site;
use BigMed::CSS;
use BigMed::Builder;
use BigMed::Log;
use BigMed::Status;
use BigMed::Plugin;
use File::Find;
use BigMed::NoBots qw(clear_captcha_html);

sub is_update {
    my ( $class, $bigmed ) = @_;

    #if this is not an update request, show unavailable message
    my $requesting_update;
    $requesting_update = $bigmed->app->param('BMUPDATE') if $bigmed->app;
    if ( !$requesting_update ) {    #show message that update required
        return $bigmed->set_error(
            head => 'UPDATE_HEAD_Temporarily_unavailable',
            text => 'UPDATE_Temporarily_unavailable',
        );
    }

    return 1;
}

sub do_update_statusbar {
    my ( $class, $bigmed ) = @_;
    my $statusbar = BigMed::Status->new() or return;

    #skip it if there's no version (not configured yet) or already current
    my $this_version      = $bigmed->env('VERSION');
    my $installed_version = $bigmed->VERSION;
    if ( !$this_version || $this_version >= $installed_version ) {
        $class->log( info =>
              'Update: Version update requested, but already up-to-date' );
        $statusbar->mark_done();
        exit(0);
    }

    #make sure there's only one updater running at a time
    if ( !create_lock_file($bigmed) ) {
        $statusbar->send_error();
    }

    #handle legacy "dot" env variable
    if ( $this_version < 2.004 ) {
        $bigmed->set_env( 'DOT', '~' );
    }

    #do the update
    $class->log( notice => 'Update: Starting version update' );

    #background updating begins
    my $updated;
    if ( $this_version < 0.018 ) {
        $updated = update_to_2_0b18( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.020 ) {
        $updated = update_to_2_0b20( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.037 ) {
        $updated = update_to_2_0b37( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.050 ) {
        $updated = update_to_2_0b50( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.051 ) {
        $updated = update_to_2_0b51( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.054 ) {
        $updated = update_to_2_0rc2( $bigmed, $statusbar );
    }
    elsif ( $this_version < 0.057 ) {
        $updated = update_to_2_0rc5( $bigmed, $statusbar );
    }
    elsif ( $this_version < 2.004 ) {
        $updated = update_to_2_0_4( $bigmed, $statusbar );
    }
    elsif ( $this_version < 2.008 ) {
        $updated = update_to_2_0_8( $bigmed, $statusbar );
    }
    else {
        $updated = 1;    #no changes required
    }

    if ( !$updated ) {
        $class->log( error =>
              'Update: Encountered error during version update, stopping' );
        $statusbar->send_error();
    }

    $bigmed->set_env( 'VERSION', $installed_version );
    if ( !$bigmed->save_config() ) {
        $class->log( error =>
              'Update: Encountered error updating config file, stopping' );
        $statusbar->send_error();
    }
    if ( !remove_lock_file($bigmed) ) {
        $statusbar->send_error();
    }
    $class->log( notice => 'Update: Update to version '
          . $bigmed->version
          . ' successful.' );
    $statusbar->mark_done();
    exit 0;
}

# VERSION-SPECIFIC UPDATERS ---------------------------

sub update_to_2_0b18 {
    my ( $bigmed, $statusbar ) = @_;
    $bigmed->set_env( 'MENUNUMTODISPLAY', 30 );
    update_file_types($bigmed);

    require BigMed::Library;
    my $lib_steps = ( my @libtypes = BigMed::Library->library_types );

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 9;
    my $all_steps      = ( $site_count * $steps_per_site ) + $lib_steps;
    $statusbar->update_status( steps => $all_steps ) or return;

    #do library index
    reindex_library($statusbar) or return;

    my $progress = 0;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;
        my $site_count = "$i/" . $sites->count;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', $site_count]
        ) or return;
        update_xhtml_doctype($site) or return;
        fix_sort_order( $bigmed, $site ) or return;
        remove_pullwidth($site) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', $site_count]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site images', $site_count]
        ) or return;
        rebuild_thumbnails( $site, $statusbar, $site_count ) or return;

        #image update can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        #statusbar message for versioning handled in routine
        $statusbar->update_status( progress => $progress++ ) or return;
        init_versioning( $site, $statusbar, $site_count ) or return;

        #version init can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', $site_count]
        ) or return;
        rebuild_site( $site, $statusbar, $site_count ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0b20 {
    my ( $bigmed, $statusbar ) = @_;
    $bigmed->set_env( 'MENUNUMTODISPLAY', 30 );

    #update the assets and rebuild sites
    #(adding new prototype-scriptaculous file, new image sizes)

    require BigMed::Library;
    my $lib_steps = ( my @libtypes = BigMed::Library->library_types );

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 9;
    my $all_steps      = ( $site_count * $steps_per_site ) + $lib_steps;
    $statusbar->update_status( steps => $all_steps ) or return;

    #do library index
    reindex_library($statusbar) or return;

    my $progress = 0;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;
        my $site_count = "$i/" . $sites->count;

        update_dot_directories( $site, $statusbar, $site_count )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', $site_count]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, $site_count )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', $site_count]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site images', $site_count]
        ) or return;
        rebuild_thumbnails( $site, $statusbar, $site_count ) or return;

        #image update can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', $site_count]
        ) or return;
        update_xhtml_doctype($site) or return;

        #statusbar message for versioning handled in routine
        $statusbar->update_status( progress => $progress++ ) or return;
        init_versioning( $site, $statusbar, $site_count ) or return;

        #version init can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, $site_count ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', $site_count]
        ) or return;
        rebuild_site( $site, $statusbar, $site_count ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0b37 {
    my ( $bigmed, $statusbar ) = @_;
    $bigmed->set_env( 'MENUNUMTODISPLAY', 30 );

    require BigMed::Library;
    my $lib_steps = ( my @libtypes = BigMed::Library->library_types );

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 9;
    my $all_steps      = ( $site_count * $steps_per_site ) + $lib_steps;
    $statusbar->update_status( steps => $all_steps ) or return;

    #do library index
    reindex_library($statusbar) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Cleaning up orphans', "$i/$site_count"]
        ) or return;
        kill_library_orphans( $site, $statusbar ) or return;
        update_lock_file( $bigmed, $site ) or return;

        #statusbar message for versioning handled in routine
        $statusbar->update_status( progress => $progress++ ) or return;
        init_versioning( $site, $statusbar, "$i/$site_count" ) or return;

        #version init can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;

    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0b50 {
    my ( $bigmed, $statusbar ) = @_;
    $bigmed->set_env( 'MENUNUMTODISPLAY', 30 );

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 10;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Cleaning up orphans', "$i/$site_count"]
        ) or return;
        kill_library_orphans( $site, $statusbar ) or return;
        update_lock_file( $bigmed, $site ) or return;

        #statusbar message for versioning handled in routine
        $statusbar->update_status( progress => $progress++ ) or return;
        init_versioning( $site, $statusbar, "$i/$site_count" ) or return;

        #version init can take a while, touch the lock file
        update_lock_file( $bigmed, $site ) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => [
                'UPDATE_Correcting invalid publication time',
                "$i/$site_count", q{}
            ],
        ) or return;
        fix_pub_time( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0b51 {
    my ( $bigmed, $statusbar ) = @_;

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 9;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Cleaning up orphans', "$i/$site_count"]
        ) or return;
        kill_library_orphans( $site, $statusbar ) or return;
        update_lock_file( $bigmed, $site ) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => [
                'UPDATE_Correcting invalid publication time',
                "$i/$site_count", q{}
            ],
        ) or return;
        fix_pub_time( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0rc2 {
    my ( $bigmed, $statusbar ) = @_;

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 9;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        )                             or return;
        update_style_selectors($site) or return;
        rebuild_css_sheet($site)      or return;

        $statusbar->update_status(
            progress => $progress++,
            message =>
              ['UPDATE_Removing search-index orphans', "$i/$site_count", q{}],
        ) or return;
        remove_searchindex_orphans($site) or return;

        #message set in update_index
        $statusbar->update_status( progress => $progress++ )
          or return;
        update_index( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => [
                'UPDATE_Correcting invalid publication time',
                "$i/$site_count", q{}
            ],
        ) or return;
        fix_pub_time( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        BigMed::Update->clear_captcha_html($site) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0rc5 {
    my ( $bigmed, $statusbar ) = @_;

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 8;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        ) or return;
        rebuild_css_sheet($site) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => [
                'UPDATE_Correcting invalid publication time',
                "$i/$site_count", q{}
            ],
        ) or return;
        fix_pub_time( $site, $statusbar, "$i/$site_count" ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message =>
              ['UPDATE_Removing search-index orphans', "$i/$site_count", q{}],
        ) or return;
        remove_searchindex_orphans($site) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        BigMed::Update->clear_captcha_html($site) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0_4 {
    my ( $bigmed, $statusbar ) = @_;

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 6;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Making preference fixes', "$i/$site_count"]
        ) or return;
        update_xhtml_doctype($site) or return;

        update_dot_directories( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        update_dot_css_html( $site, $statusbar, "$i/$site_count" )
          or return;
        $progress++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        ) or return;
        rebuild_css_sheet($site) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        BigMed::Update->clear_captcha_html($site) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

sub update_to_2_0_8 {
    my ( $bigmed, $statusbar ) = @_;

    my $sites =
      BigMed::Site->select( undef, { sort => 'id', order => 'ascend' } )
      or return;
    my $site_count = $sites->count;

    my $steps_per_site = 3;
    my $all_steps      = ( $site_count * $steps_per_site );
    $statusbar->update_status( steps => $all_steps ) or return;

    #update sites
    my $progress = $statusbar->progress;
    my $i        = 0;
    my $site;
    while ( $site = $sites->next ) {
        BigMed::Update->log( info => 'Update: Starting '
              . BigMed::Update->log_data_tag($site) );
        update_lock_file( $bigmed, $site ) or return;

        $i++;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating site assets', "$i/$site_count"]
        ) or return;
        update_assets( $bigmed, $site ) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Updating style sheet', "$i/$site_count"]
        ) or return;
        rebuild_css_sheet($site) or return;

        $statusbar->update_status(
            progress => $progress++,
            message  => ['UPDATE_Rebuilding pages', "$i/$site_count"]
        ) or return;
        rebuild_site( $site, $statusbar, "$i/$site_count" ) or return;
    }
    return if !defined $site;

    return 1;
}

# FILE-LOCKING ROUTINES ---------------------------

sub lock_file_path {
    my $bigmed = shift;
    return bm_file_path( $bigmed->env('MOXIEDATA'), 'bm-updating.txt' );
}

sub create_lock_file {
    my $bigmed    = shift;
    my $lock_file = lock_file_path($bigmed);

    #if there's a lock file and it was updated within the last minute,
    #then we'll assume that there's an update underway
    if ( -e $lock_file && -M $lock_file < 0.0007 ) {
        BigMed::Update->log( notice =>
              'Version update requested, but update already underway' );
        return $bigmed->set_error(
            head => 'UPDATE_HEAD_System Update Already Underway',
            text => 'UPDATE_System Update Already Underway',
        );
    }

    #otherwise it's old or failed, start fresh
    if ( !bm_write_file( $lock_file, '0' ) ) {
        BigMed::Update->log( error =>
              'Could not create lock file for version update, stopping' );
        return;
    }
    return 1;
}

sub update_lock_file {
    my ( $bigmed, $site ) = @_;
    my $lock_file = lock_file_path($bigmed);
    if ( !bm_write_file( $lock_file, $site->id ) ) {
        BigMed::Update->log( error =>
              'Could not update lock file for version update, stopping' );
        return;
    }
    return 1;
}

sub remove_lock_file {
    my $bigmed    = shift;
    my $lock_file = lock_file_path($bigmed);
    if ( !bm_delete_file($lock_file) ) {
        BigMed::Update->log( error =>
              'Could not delete lock file for version update, stopping' );
        return;
    }
    return 1;
}

# UPDATE HELPER ROUTINES ---------------------------

sub update_file_types {
    my $bigmed = shift;

    BigMed::Update->log(
        info => 'Updating allowed file types to include all opendoc types' );
    my $rfile_types = [
        'wk1',  'wk3',  'wk4',  'xls', 'xlw',  'doc',  'pps',  'ppt',
        'mpp',  'pub',  'wks',  'wps', 'wri',  'pdf',  'rtf',  'sda',
        'sdc',  'sdd',  'sdp',  'sdw', 'sgl',  'stcm', 'std',  'sti',
        'stw',  'sxc',  'sxd',  'sxg', 'sxi',  'sxm',  'sxw',  'asc',
        'csv',  'txt',  'wpd',  'xml', 'bmp',  'eps',  'gif',  'jfif',
        'jpeg', 'jpg',  'png',  'psd', 'tif',  'tiff', 'aac',  'm4p',
        'aif',  'aifc', 'aiff', 'avi', 'mid',  'midi', 'rmi',  'm4a',
        'mp3',  'mpu',  'mp4',  'mpe', 'mpeg', 'mpg',  'swf',  'moov',
        'mov',  'qt',   'qtm',  'wma', 'wmv',  'wav',  'hqx',  'gz',
        'gzip', 'taz',  'tgz',  'bin', 'dmg',  'sit',  'sitx', 'tar',
        'zip'
    ];
    $bigmed->set_env( 'FILETYPES', $rfile_types );
    return;
}

sub update_assets {
    my ( $bigmed, $site ) = @_;
    my $sys_assets =
      bm_file_path( $bigmed->env('MOXIEDATA'), 'support', 'assets' );
    my $site_assets = bm_file_path( $site->html_dir, 'bm.assets' );
    bm_copy_dir( $sys_assets, $site_assets ) or return;
    return bm_dir_permissions($site_assets);
}

sub rebuild_css_sheet {
    my $site = shift;
    return BigMed::CSS->build_sheet($site);
}

sub rebuild_thumbnails {
    my ( $site, $statusbar, $site_count ) = @_;
    require BigMed::Media::Image;
    return 1 if !BigMed::Media::Image->can_thumbnail;
    my $images = BigMed::Media::Image->select( { site => $site->id } )
      or return;
    my $img;

    my $img_count = $images->count;
    my $i         = 0;
    my $img_dir   = $site->image_path;
    while ( $img = $images->next ) {
        $i++;
        $statusbar->update_status(
            message => [
                'UPDATE_Updating site image num of num',
                $site_count, $i, $img_count
            ]
        ) or return;
        $img->generate_thumbnails($site) or return;
    }
    return if !defined $img;    #i/o error
    return 1;
}

sub rebuild_site {
    my ( $site, $statusbar, $site_count ) = @_;
    my $builder = BigMed::Builder->new( site => $site ) or return;

    my $rupdate_section = sub {
        my $builder = shift;
        my $cx      = $builder->context or return 1;
        my $count   = $cx->content->count;
        my $section = $cx->section || $cx->site->homepage_obj or return 1;
        my $name    = $section->name;
        return $statusbar->update_status(
            message => ['UPDATE_Building section', $site_count, $name, $count]
        );
    };
    $builder->add_trigger( 'fresh_section_context', $rupdate_section );

    my $bigmed = BigMed->bigmed;
    my $rping  = sub {
        update_lock_file( $bigmed, $site ) or return;
        return $statusbar->ping();
    };
    $builder->add_trigger( 'level_midbuild', $rping );
    return $builder->build();
}

sub update_index {
    my ( $site, $statusbar, $site_count ) = @_;

    require BigMed::Content::Page;
    my @active = ( $site->homepage_id, $site->all_active_descendants_ids() );
    my $all_active = BigMed::Content::Page->select(
        {   site       => $site->id,
            pub_status => 'published',
            sections   => \@active
        }
    ) or return;

    require BigMed::Search;
    require BigMed::Format::HTML;    #for language pref
    my $lang   = $site->get_pref_value('html_htmlhead_lang') || 'en-us';
    my $total  = $all_active->count;
    my $bigmed = BigMed->bigmed;
    my $rping  = sub {
        my $indexer = shift;
        $statusbar->update_status(
            message => [
                'SEARCH_Updating search index', $site_count,
                $indexer->page_progress,        $total,
                $indexer->word_progress,
            ]
        );
        update_lock_file( $bigmed, $site ) or return;
        return 1;
    };
    my $search = BigMed::Search->new( locale => $lang ) or return;
    $search->indexer->add_trigger( 'mid_index', $rping );
    return $search->index_page($all_active);
}

sub init_versioning {
    my ( $site, $statusbar, $site_count ) = @_;
    require BigMed::Content::Page;
    my $all_pages = BigMed::Content::Page->select( { site => $site->id } )
      or return;
    my $total = $all_pages->count;

    #get any current page versions in case we're restarting an update
    my $all_version = BigMed::PageVersion->select( { site => $site->id } )
      or return;

    my $page;
    my $count = 0;
    while ( $page = $all_pages->next ) {
        if ( !( $count % 100 ) ) {
            $statusbar->update_status(
                message => [
                    'UPDATE_Initializing version info', $site_count,
                    "$count/$total"
                ],
            );
        }
        $count++;
        my $already_exists =
          $all_version->fetch( { site => $site->id, page => $page->id } );
        next if $already_exists;
        $page->save_version or return;
    }
    return if !defined $page;
    return 1;
}

#problem should only exist for v2.0b37 or later, after intro of library editor
sub kill_library_orphans {
    my ( $site, $statusbar ) = @_;
    my $site_id = $site->id;
    my $app     = BigMed->bigmed->app;

    my @sources =
      map { $_->data_source } BigMed::Plugin->load_library_types();

    require BigMed::Pointer;
    my $pointers = BigMed::Pointer->select(
        {   site         => $site_id,
            target_table => \@sources,
            source_table => \@sources
        },
        { any => ['target_table', 'source_table'] }
    ) or return;
    my $count = 0;

    my @delete;
    my $targets =
      $pointers->select( { site => $site_id, target_table => \@sources } )
      or return;
    my $site_tag = $app->log_data_tag($site);

    #gather target orphans
    my $p;
    while ( $p = $targets->next ) {
        $statusbar->ping() if $count % 500 == 0;
        $count++;
        defined( my $obj = $p->fetch_target ) or return;
        next if $obj;
        push @delete, $p->id;
        $app->log( info => "Update: $site_tag Orphan target pointer #"
              . $p->id
              . ' points to: '
              . $p->target_table . ' #'
              . $p->target_id );
    }
    return if !defined $p;

    #gather source orphans
    my $sources =
      $pointers->select( { site => $site_id, source_table => \@sources } )
      or return;
    while ( $p = $sources->next ) {
        $statusbar->ping() if $count % 500 == 0;
        $count++;
        defined( my $obj = $p->fetch_source ) or return;
        push @delete, $p->id if !$obj;
        $app->log( info => "Update: $site_tag Orphan source pointer #"
              . $p->id
              . ' points from: '
              . $p->source_table . ' #'
              . $p->source_id );
    }
    return   if !defined $p;
    return 1 if !@delete;

    $pointers = $pointers->select( { site => $site_id, id => \@delete } )
      or return;
    $pointers->trash_all or return;
    $app->log( warning => "Update: Deleted orphan pointers for $site_tag" );
    return 1;
}

sub fix_sort_order {
    my ( $bigmed, $site ) = @_;

    my @prefs_to_fix = qw(
      html_link_sort_order   html_quicktease_sort_order
      html_latest_sort_order html_news_sort_order
      html_tip_sort_order    html_annc_sort_order
    );
    my $select =
      BigMed::Prefs->select( { site => $site->id, name => \@prefs_to_fix } )
      or return;
    my @delete;
    my $pref;

    while ( $pref = $select->next ) {
        push @delete, $pref->id if $pref->pref_value eq q{|};
    }
    return if !defined $pref;
    my $delete = $select->select( { site => $site->id, id => \@delete } )
      or return;
    return $delete->trash_all;
}

sub remove_pullwidth {    #pullwidth preference is defunct, moved to css
    my ($site) = @_;
    my $select = BigMed::Prefs->select(
        { site => $site->id, name => 'html_content_pullwidth' } )
      or return;
    return $select->trash_all;
}

sub update_style_selectors {
    my ($site) = @_;

    #change introduced in 2.0b37
    my $select = BigMed::CSS->select(
        { site => $site->id, selector => 'div.bmw_navigation li li' } )
      or return;

    my $css;
    while ( $css = $select->next ) {
        $css->set_selector('div.bmw_navigation li ul li');
        $css->save or return;
    }
    return if !defined $css;

    #change introduced in 2.0rc1 (aka b53)
    $select = BigMed::CSS->select(
        { site => $site->id, selector => qr/div[.]bmw_morelinksLinks/ } )
      or return;
    while ( $css = $select->next ) {
        ( my $sel = $css->selector ) =~ s/morelinksLinks/moreLinks/;
        $css->set_selector($sel);
        $css->save or return;
    }
    return if !defined $css;
    return 1;
}

sub reindex_library {
    my ($statusbar) = @_;
    my $progress = $statusbar->progress;
    foreach my $class ( BigMed::Library->library_classes ) {
        my $label = $class->data_label;
        my $msg   = "UPDATE_Reindexing $label library for site";

        $progress++;
        $class->add_trigger(
            'before_reindex',
            sub {
                my ( $class, $site, $rcols ) = @_;
                $statusbar->update_status(
                    message  => [$msg, $site->name],
                    progress => $progress
                );
                return 1;
            }
        );
        BigMed::Update->log( info => "Update: Reindexing $label library" );

        #owner/shared is not populated at all in person; not ideal,
        #but just assign owner to 1. almost certainly an admin.
        my $rvalue =
          $class eq 'BigMed::Person'
          ? { in_lib => 1, owner => 1, shared => 1 }
          : { in_lib => 1 };
        $class->reindex($rvalue) or return;
    }
    return 1;
}

sub fix_pub_time {
    my ( $site, $statusbar, $site_count ) = @_;

 #cleans up bug 43765, introduced in 2.0b45, where copying a published page to
 #another site in draft status sets the correct status but leaves the
 #pub_time. find all non-published pages with a pub_time and clear the
 #pub_time.
    require BigMed::Content::Page;
    my $bad_dates = BigMed::Content::Page->select(
        {   site       => $site->id,
            pub_status => ['draft', 'edit', 'ready'],
            pub_time => { from => '1' },
        }
    ) or return;

    my $total = $bad_dates->count;
    my $page;
    my $count = 0;
    while ( $page = $bad_dates->next ) {
        if ( !( $count % 100 ) ) {
            $statusbar->update_status(
                message => [
                    'UPDATE_Correcting invalid publication time',
                    $site_count, "$count/$total"
                ],
            );
        }
        $count++;
        $page->set_pub_time(undef);
        $page->save or return;
    }
    return if !defined $page;
    return 1;
}

sub remove_searchindex_orphans {
    my ($site) = @_;

    #cleans up bug 50046, which prevented the janitor from cleaning
    #up deleted pages in the search index.
    my $site_tag = BigMed::Update->log_data_tag($site);
    BigMed::Update->log(
        info => "Update: Looking for search-index orphans in $site_tag" );

    #get all published pages and put their ids into a hash
    my %page_exists;
    {
        require BigMed::Content::Page;
        my $published = BigMed::Content::Page->select(
            {   site       => $site->id,
                pub_status => 'published',
            }
        ) or return;
        while ( my $rindex = $published->next_index ) {
            $page_exists{ $rindex->{id} } = 1;
        }
    }

    #gather all page-indices for pages that no longer exist
    my @bad_pages;
    require BigMed::Search::PageIndex;
    my $indices = BigMed::Search::PageIndex->select( { site => $site->id } )
      or return;
    while ( my $rindex = $indices->next_index ) {
        my $page_id = $rindex->{page} or next;
        push @bad_pages, $page_id if !$page_exists{$page_id};
    }
    return 1 if !@bad_pages;

    BigMed::Update->log(
        info => "Update: Found search-engine orphans in $site_tag: "
          . join( ', ', @bad_pages ) );

    my $lang = $site->get_pref_value('html_htmlhead_lang') || 'en';
    my $search = BigMed::Search->new( locale => $lang );
    return $search->remove_page(
        { site => $site->id, pages => \@bad_pages } );
}

sub update_dot_directories {
    my ( $site, $statusbar, $site_count ) = @_;

    my $site_tag = BigMed::Update->log_data_tag($site);
    foreach my $dir ( 'bm~assets', 'bm~theme', 'bm~comments' ) {
        $statusbar->update_status(
            message => ['UPDATE_Renaming dot directory', $site_count, $dir,],
        );
        my $orig = bm_file_path( $site->html_dir, $dir );
        if ( -d $orig ) {
            ( my $tdir = $dir ) =~ s/~/./;
            my $target = bm_file_path( $site->html_dir, $tdir );
            bm_move_dir( $orig, $target ) or return;
            BigMed::Update->log( info => "Update: Moved $dir for $site_tag" );
        }
    }
    return 1;

}

sub update_dot_css_html {
    my ( $site, $statusbar, $site_count ) = @_;
    my $tdir = bm_file_path(
        BigMed->bigmed->env('MOXIEDATA'), 'templates_custom',
        'site_templates',                 'site' . $site->id
    );
    return 1 if !-d $tdir;

    _do_dot_file_update( bm_file_path( $tdir, 'theme.css' ) );

    my $html = bm_untaint_filepath( bm_file_path( $tdir, 'HTML' ) )
      or return;
    return 1 if !-d $html;

    my $rupdate = sub {
        $File::Find::prune = 1
          if -d && length > 1 && substr( $_, 0, 1 ) eq '.';
        return if -d || substr( $_, 0, 1 ) eq '.';
        $File::Find::prune = 0;
        _do_dot_file_update($File::Find::name);
    };
    find(
        {   wanted          => $rupdate,
            untaint         => 1,
            untaint_pattern => qr{\A([^\|;<>\`\*\(\)\[\]\{\}\$\n\r]+)\z},
        },
        $html
    );
    return 1;
}

sub _do_dot_file_update {
    my $filepath = shift;
    return if !-e $filepath;
    my $text = join( "\n", bm_load_file($filepath) );
    my $changed = ( $text =~ s/bm~(assets|theme)/bm.$1/msg ) or return;
    BigMed::Update->log(
        info => "Update: Updating 'dot' directories in $filepath" );
    return bm_write_file( $filepath, $text, { data => 1 } );
}

sub update_xhtml_doctype {
    my $site = shift;
    my $rprefs = $site->ref_to_preference_hash() or return;

    require BigMed::Format::HTML;
    require BigMed::Prefs;

    my $select = BigMed::Prefs->select(
        { site => $site->id, name => 'html_htmlhead_doctype' } )
      or return;
    my $pref;

    while ( $pref = $select->next ) {
        my $value = ( $pref->pref_value )[0];
        if ( $value =~ s{2000/REC\-xhtml1\-20000126}{xhtml1}ms ) {
            $pref->set_pref_value( [$value] );
            $pref->save or return;
            $rprefs->{'html_htmlhead_doctype'}->{ $pref->section } = [$value];
        }
    }
    return if !defined $pref;
    return 1;
}

1;

