# DynamicPreview - (c) 2014-2017 Juxtaposition. All Rights Reserved.
# This code cannot be redistributed without permission from juxtaposition.jp
# For more information, consult your DynamicPreview license.
#
package DynamicPreview;

use strict;
use warnings;
use utf8;
use Carp;
use MT;
use MT::Plugin;
use base qw( MT::Plugin );
use File::Basename;
use File::Spec;
use Data::Dumper;

our $PLUGIN_NAME = 'DynamicPreview(Trial)';
our $VERSION = '4.00';

use constant {
    REPLACE_PREVIEW_BUTTON => 0,
    MULTI_PREVIEW => 1,
    ENABLE_EXTRA => 0,
};

BEGIN {
    my $has_plugin_extra = eval { require DynamicPreview::Extra; 1 } ? 1 : 0;
    sub has_extra { 
        return ENABLE_EXTRA && is_mt52_or_later() && $has_plugin_extra; 
    }
}

sub is_mt52_or_later {
    return (substr(MT->version_number, 0, 3) eq '5.2')
        || (substr(MT->version_number, 0, 1) >= 6);
}

sub is_mt7_or_later {
    return (substr(MT->version_number, 0, 2) eq '7.')
        || (substr(MT->version_number, 0, 1) >= 7);
}

my $default_settings = [
    ['dynamic_preview_enabled', { Default => 1 }],
    ['dynamic_preview_ssi_include', { Default => 1 }],
    ['dynamic_preview_document_root', { Default => '' }],
    ['dynamic_preview_convert_url', { Default => 1 }], # NOTE: not used
    ['dynamic_preview_base_url', { Default => '' }],
    ['dynamic_preview_replace_url', { Default => '' }],
    ['dynamic_preview_replace_string', { Default => '' }],
    ['dynamic_preview_index_template_ids', { Default => '' }],
    ['dynamic_preview_include_hold_entry', { Default => 0 }],
    ['dynamic_preview_include_future_entry', { Default => 0 }],
    ['dynamic_preview_exclude_archive_template_ids', { Default => '' }],
    ['dynamic_preview_index_template_ids_for_cd', { Default => '' }],
    ['dynamic_preview_exclude_archive_template_ids_for_cd', { Default => '' }],
    ['dynamic_preview_load_latest_revision', { Default => 0, Scope => 'system' }],
    ['dynamic_preview_basic_auth_for_image_url', { Default => '' }]
];

my $defaults = {
    id => 'dynamicpreview',
    key => __PACKAGE__,
    name => "<__trans phrase='$PLUGIN_NAME'>",
    version => $VERSION,
    description => "<MT_TRANS phrase='This plugin allows you to preview an entry/page/template/content data dynamically.'>",
    author_name => 'Juxtaposition',
    author_link => 'http://www.lat43n.com/',
    l10n_class => 'DynamicPreview::L10N',
    blog_config_template => \&blog_config_template,
    system_config_template => has_extra() ? 'system_config.tmpl' : undef,
    settings => new MT::PluginSettings(
        has_extra() ? DynamicPreview::Extra::default_settings($default_settings) : $default_settings
    ),
    registry => {
        callbacks =>  {
            'init_app' => '$DynamicPreview::DynamicPreview::Callbacks::init_app',
            'template_source.edit_entry'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_entry_source',
            'template_param.edit_entry'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_entry_param',
            'template_source.edit_template'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_template_source',
            'template_param.edit_category'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_category_param',
            'template_source.edit_content_data'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_content_data_source',
            'template_param.edit_content_data'
                => '$DynamicPreview::DynamicPreview::Callbacks::edit_content_data_param',
            'cms_pre_preview.template'
                => '$DynamicPreview::DynamicPreview::Callbacks::cms_pre_preview_template',
        },
        applications => {
            cms => {
                methods => {
                    dynamic_preview => '$DynamicPreview::DynamicPreview::CMS::show_preview',
                    dynamic_preview_cd => '$DynamicPreview::DynamicPreview::CMS::show_preview_cd',
                    dynamic_preview_edit_mail => '$DynamicPreview::DynamicPreview::CMS::edit_mail', 
                    dynamic_preview_send_mail => '$DynamicPreview::DynamicPreview::CMS::send_mail', 
                    dynamic_preview_entry => '$DynamicPreview::DynamicPreview::CMS::preview_entry',
                    dynamic_preview_template => '$DynamicPreview::DynamicPreview::CMS::preview_template',
                    dynamic_preview_cd_template => '$DynamicPreview::DynamicPreview::CMS::preview_cd_template',
                    dynamic_preview_create_role => '$DynamicPreview::DynamicPreview::CMS::create_role',
                    dynamic_preview_content_data => '$DynamicPreview::DynamicPreview::CMS::preview_content_data',
                    dynamic_preview_load_image => '$DynamicPreview::DynamicPreview::CMS::load_image',
                },
            },
        },
        permissions => has_extra() ? DynamicPreview::Extra::permissions() : undef,
    },
};

sub new {
    my $class = shift;
    my $self = bless { %$defaults }, $class;
    return $self->init(@_);
}

sub init {
    my $self = shift;
    my ($args) = @_;
    return $self unless $args;
    croak 'Invalid args' unless ref $args eq 'HASH';
    while(my ($key, $value) = each %$args) {
        $self->{$key} = $value;
    }
    return $self;
}

sub doLog {
    my $plugin = shift;
    my ($msg) = @_; 
    return unless defined($msg);

    require MT::Log;
    my $log = MT::Log->new;
    $log->message($msg) ;
    $log->save or die $log->errstr;
    return;
}

sub blog_config_template {
    my ($plugin, $param, $scope) = @_;
    my ($id) = $scope =~ m!blog:(\d+)!;
    my $blog = MT::Blog->load($id)
        or return;
    my $tmpl;
    if($blog->is_blog) {
        $tmpl = $plugin->load_tmpl('blog_config.tmpl');
    }
    else {
        $tmpl = $plugin->load_tmpl('website_config.tmpl');
    }
    return $tmpl;
}

sub load_config {
    my $plugin = shift;
    my ($param, $scope) = @_;
    $plugin->SUPER::load_config(@_);
    if (defined $scope && $scope =~ m/blog:(\d+)/) {
        my $blog_id = $1;
        $param->{blog_id} = $blog_id;
    }
    if ($plugin->has_extra) {
        $param->{dynamic_preview_has_extra} = 1;
    }
}

sub dynamic_preview_enabled {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_enabled};
    return $value;
}

sub dynamic_preview_ssi_include {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_ssi_include};
    return $value;
}

sub dynamic_preview_document_root {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_document_root};
    unless ($value) {
        my $blog = MT::Blog->load($blog_id)
            or return $value;
        my $website = $blog->website;
        if ($website) {
            $plugin->dynamic_preview_enabled($website->id)
                or return $value;
            $plugin->load_config(\%plugin_param, 'blog:'.$website->id); 
            $value = $plugin_param{dynamic_preview_document_root};
        }
    }
    return $value;
}

sub dynamic_preview_convert_url {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_convert_url};
    return $value;
}

sub dynamic_preview_base_url {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_base_url};
    return $value;
}

sub dynamic_preview_replace_url {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_replace_url};
    unless ($value) {
        my $blog = MT::Blog->load($blog_id)
            or return $value;
        my $website = $blog->website;
        if ($website) {
            $plugin->dynamic_preview_enabled($website->id)
                or return $value;
            $plugin->load_config(\%plugin_param, 'blog:'.$website->id); 
            $value = $plugin_param{dynamic_preview_replace_url};
        }
    }
    return $value;
}

sub dynamic_preview_replace_string {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_replace_string};
    unless ($value) {
        my $blog = MT::Blog->load($blog_id)
            or return $value;
        my $website = $blog->website;
        if ($website) {
            $plugin->dynamic_preview_enabled($website->id)
                or return $value;
            $plugin->load_config(\%plugin_param, 'blog:'.$website->id); 
            $value = $plugin_param{dynamic_preview_replace_string};
        }
    }
    return $value;
}

sub dynamic_preview_index_template_ids {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_index_template_ids};
    return $value;
}

sub dynamic_preview_include_hold_entry {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_include_hold_entry};
    return $value;
}

sub dynamic_preview_include_future_entry {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_include_future_entry};
    return $value;
}

sub dynamic_preview_mail_subject {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_mail_subject};
    return $value;
}

sub dynamic_preview_mail_template {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_mail_template};
    return $value;
}

sub dynamic_preview_exclude_archive_template_ids {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_exclude_archive_template_ids};
    return $value;
}

sub dynamic_preview_index_template_ids_for_cd {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_index_template_ids_for_cd};
    return $value;
}

sub dynamic_preview_exclude_archive_template_ids_for_cd {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_exclude_archive_template_ids_for_cd};
    return $value;
}

sub dynamic_preview_load_latest_revision {
    my $plugin = shift;
    my %plugin_param;

    $plugin->load_config(\%plugin_param);
    my $value = $plugin_param{dynamic_preview_load_latest_revision};
    return $value;
}

sub dynamic_preview_basic_auth_for_image_url {
    my $plugin = shift;
    my ($blog_id) = @_;
    my %plugin_param;

    $plugin->load_config(\%plugin_param, 'blog:'.$blog_id);
    my $value = $plugin_param{dynamic_preview_basic_auth_for_image_url};
    unless ($value) {
        my $blog = MT::Blog->load($blog_id)
            or return $value;
        my $website = $blog->website;
        if ($website) {
            $plugin->dynamic_preview_enabled($website->id)
                or return $value;
            $plugin->load_config(\%plugin_param, 'blog:'.$website->id);
            $value = $plugin_param{dynamic_preview_basic_auth_for_image_url};
        }
    }
    return $value;
}

sub save_config {
    my $plugin = shift;
    my $app = MT->instance;
    if($app && $app->can('reboot')) {
        $app->reboot;
    }
    return $plugin->SUPER::save_config(@_);
}

sub include_virtual {
    my ($plugin, $blog_id, $html, $doc_file) = @_;
    
    return $html unless $plugin->dynamic_preview_ssi_include($blog_id);

    my $doc_root = $plugin->dynamic_preview_document_root($blog_id)
        or return $html;

    $html =~ s{<!--#include\s+virtual="([^"]+)"\s*-->}
              {_read_virtual($doc_root, $doc_file, $1)}ge;

    return $html;
}

sub _read_virtual {
    my ($doc_root, $doc_file, $vir_path) = @_;
    return '' unless $doc_root && $doc_file && $vir_path;
    my $doc_dir = '';
    if($vir_path !~ m!^/!) {
        $doc_dir = dirname($doc_file);
        $doc_dir =~ s!$doc_root!!;
    }
    $vir_path =~ s!^/!!;
    return '' unless $vir_path;
    my $filepath = File::Spec->catfile($doc_root, $doc_dir, $vir_path);
    my $content = '';
    -e $filepath or return '[not found]';
    my $length = -s $filepath or return '[empty]';
    open my $fh, "<", $filepath or return "[open error: $!]";
    sysread $fh, $content, $length
        or return "[read error: $!]";
    close $fh;

    if($content) {
        my $config = MT->config;
        my $enc = $config->PublishCharset || 'utf8';
        $content = Encode::decode($enc, $content);
    }

    return $content;
}

sub _abs2rel {
    my ($base_url, $path, $match, $pre, $post) = @_;
    return $match if $path =~ m!^https?://!;
    $base_url =~ s!/$!!g;
    $path = '/' . $path unless $path =~ m!^/!;
    return $pre . $base_url . $path . $post;
}

sub insert_base_tag {
    my ($plugin, $blog_id, $html, $doc_file, $entry) = @_;
    return $plugin->has_extra ? DynamicPreview::Extra::insert_base_tag(@_) : $html;
}

sub replace_url {
    my ($plugin, $blog_id, $html, $doc_file) = @_;
    return $plugin->has_extra ? DynamicPreview::Extra::replace_url(@_) : $html;
}

sub replace_string {
    my ($plugin, $blog_id, $html, $doc_file) = @_;
    return $plugin->has_extra ? DynamicPreview::Extra::replace_string(@_) : $html;
}

sub entry_pre_search {
    my ($plugin, $cb, $class, $terms, $args) = @_;
    return 1 if defined $terms && ref $terms eq 'HASH' && exists $terms->{status} && ref $terms->{status};
    require MT::Entry;
    my $status = [ MT::Entry::RELEASE() ];
    if(MT->request('dynamic_preview_include_hold_entry')) {
        push @$status, MT::Entry::HOLD();
    }
    if(MT->request('dynamic_preview_include_future_entry')) {
        push @$status, MT::Entry::FUTURE();
    }
    if(@$status > 1) {
        $terms->{status} = $status;
    }
    return 1;
}

sub content_data_pre_search {
    my ($plugin, $cb, $class, $terms, $args) = @_;
    return 1
        if defined $terms
            && ref $terms eq 'HASH'
            && exists $terms->{status}
            && ref $terms->{status};
    require MT::ContentStatus;
    my $status = [ MT::ContentStatus::RELEASE() ];
    if(MT->request('dynamic_preview_include_hold_entry')) {
        push @$status, MT::ContentStatus::HOLD();
    }
    if(MT->request('dynamic_preview_include_future_entry')) {
        push @$status, MT::ContentStatus::FUTURE();
    }
    if(@$status > 1) {
        $terms->{status} = $status;
    }
    return 1;
}

sub latest_revision {
    my $plugin = shift;

    return unless $plugin->has_extra;

    my ($type, $id) = @_;
    my $terms = {
        entry_id => $id,
    };
    my $args = {
        sort => 'rev_number',
        direction => 'descend',
        limit => 1,
    };
    my @revs = MT->model($type.':revision')->load($terms, $args);
    my $rev= shift @revs;
    return $rev;
}

1;
__END__


