# LinkEntryToFile.pl
# LinkEntryToFile plugin for Movable Type
# by Kevin Shay
# http://www.staggernation.com/mtplugins/LinkEntryToFile/
use strict;
package MT::Plugin::LinkEntryToFile;
use base qw(MT::Plugin);
use vars qw( $VERSION );
$VERSION = '1.1';
my $plugin_key = 'LinkEntryToFile';
my $resync_to_db;
require MT::Plugin;
require MT;
my $linkentrytofile = MT::Plugin::LinkEntryToFile->new({
name => "LinkEntryToFile",
description => 'Specify a file to keep in sync with the text of an entry.',
doc_link => 'http://www.staggernation.com/mtplugins/LinkEntryToFile/',
author_name => 'Kevin Shay',
author_link => 'http://www.staggernation.com/',
version => $VERSION,
config_template => \&_config_template,
settings => new MT::PluginSettings([
['enable_links', { Default => 1 }],
['allowed_ext', { Default => 'txt text html htm shtml ssi' }]
])
});
MT->add_plugin($linkentrytofile);
MT->add_callback('MT::App::CMS::AppTemplateSource.edit_entry', 9, $linkentrytofile, \&_template);
MT->add_callback('MT::App::CMS::AppTemplateParam.edit_entry', 9, $linkentrytofile, \&_param);
MT->add_callback('CMSPostSave.entry', 9, $linkentrytofile, \&_app_post);
MT::Entry->add_callback('post_load', 9, $linkentrytofile, \&_entry_post_load);
MT::Entry->add_callback('post_save', 9, $linkentrytofile, \&_entry_post_save);
# cache settings to avoid repeated loading
my $blog_settings = {};
sub _config_template {
return <
checked="checked" />
HTML
}
sub _template {
my ($cb, $app, $template) = @_;
my $settings = blog_settings($app->{'query'}->param('blog_id'));
return 0 unless $settings->{'enable_links'};
my $old = qq{130194" />
};
$old = quotemeta($old);
my $new = <130194" />
HTML
$$template =~ s/$old/$new/s;
}
sub _param {
my ($cb, $app, $param) = @_;
my $settings = blog_settings($param->{'blog_id'});
return 0 unless $settings->{'enable_links'};
if ($param->{'id'}) {
for my $col (qw( text text_more )) {
my $data = load_plugindata($param->{'id'} . '_' . $col);
if ($data && $data->{'file'}) {
$param->{"linkentrytofile_$col"} = $data->{'file'};
if ($data->{'error'}) {
$param->{"linkentrytofile_${col}_err"} = $data->{'error'};
}
}
}
}
}
sub _app_post {
my ($cb, $app, $entry) = @_;
my $settings = blog_settings($app->{'query'}->param('blog_id'));
return 0 unless $settings->{'enable_links'};
for my $col (qw( text text_more )) {
my ($orig_file, $file, $error, $data, $size, $time);
# we handle errors by storing them in the plugindata record,
# so they can be displayed to the user
if ($orig_file = $app->{'query'}->param("linkentrytofile_$col")) {
if (!allowed($orig_file, $settings)) {
$error = 'Filename extension must be one of the following: '
. $settings->{'allowed_ext'};
} else {
$file = file_path($orig_file, $entry->blog_id);
local *FH;
# file exists, field is empty, so load contents into field
if (-e $file && !$entry->$col) {
if (open FH, $file) {
do { local $/; $entry->$col() };
$entry->save;
close FH;
} else {
$error = MT->translate("Opening linked file '[_1]' failed: [_2]", $file, "$!");
}
} else {
my $cfg = MT::ConfigMgr->instance;
my $umask = oct $cfg->HTMLUmask;
my $old = umask($umask);
($file) = $file =~ /(.+)/s;
if (open FH, ">$file") {
print FH $entry->$col;
close FH;
($size, $time) = (stat $file)[7,9];
} else {
$error = MT->translate("Opening linked file '[_1]' failed: [_2]", $file, "$!");
}
umask($old);
}
}
$data = {
'file' => $orig_file,
'size' => $size,
'time' => $time,
'error' => $error
};
save_plugindata($entry->id . '_' . $col, $data);
}
}
}
sub _entry_post_load {
my ($cb, $args, $entry) = @_;
my $settings = blog_settings($entry->blog_id);
return 0 unless $settings->{'enable_links'};
my $changed = 0;
for my $col (qw( text text_more )) {
my $data = load_plugindata($entry->id . '_' . $col);
if ($data && $data->{'file'}) {
next unless (allowed($data->{'file'}, $settings));
my $file = file_path($data->{'file'});
next unless (-e $file);
my ($size, $time) = (stat _)[7,9];
next if (($size == $data->{'size'})
&& (($time == $data->{'time'})));
my $text;
eval {
local($/, *FH) ;
open(FH, $file) || die $!;
$entry->$col();
$changed++;
};
}
}
# if either field of the entry was updated, need to mark it to be
# saved at TakeDown time
if ($changed) {
if (!defined $resync_to_db) {
$resync_to_db = {};
MT->add_callback('TakeDown', 9, $linkentrytofile, \&_resync_to_db);
}
$resync_to_db->{$entry->id} = $entry;
$entry->{'linkentrytofile_needs_db_sync'} = 1;
}
}
sub _entry_post_save {
my ($cb, $entry) = @_;
$entry->{'linkentrytofile_needs_db_sync'} = 0;
}
sub _resync_to_db {
# save any updated entries to the db
# (adapted from MT::Template)
return unless defined $resync_to_db;
return unless %$resync_to_db;
foreach my $id (keys %$resync_to_db) {
my $entry = $resync_to_db->{$id};
next unless $entry->{'linkentrytofile_needs_db_sync'};
$entry->save;
}
$resync_to_db = {};
}
sub apply_default_settings {
my ($plugin, $data, $scope_id) = @_;
if ($scope_id eq 'system') {
return $plugin->SUPER::apply_default_settings($data, $scope_id);
} else {
my $sys;
for my $setting (@{$plugin->{'settings'}}) {
my $key = $setting->[0];
next if exists($data->{$key});
# don't load system settings unless we need to
$sys ||= $plugin->get_config_obj('system')->data;
$data->{$key} = $sys->{$key};
}
}
}
sub blog_settings {
my ($blog_id) = @_;
$blog_settings->{$blog_id} ||=
$linkentrytofile->get_config_hash('blog:' . $blog_id);
return $blog_settings->{$blog_id};
}
sub load_plugindata {
my ($key) = @_;
require MT::PluginData;
my $data = MT::PluginData->load({
plugin => $plugin_key, key => $key
});
return 0 unless $data;
return $data->data;
}
sub save_plugindata {
my ($key, $data) = @_;
require MT::PluginData;
my $plugindata = MT::PluginData->load({
'plugin' => $plugin_key, 'key' => $key
});
if (!$plugindata) {
$plugindata = MT::PluginData->new;
$plugindata->plugin($plugin_key);
$plugindata->key($key);
}
$plugindata->data($data);
$plugindata->save || return 0;
}
sub allowed {
my ($file, $settings) = @_;
my $extensions = $settings->{'allowed_ext'};
for my $ext (split(/ +/, $extensions)) {
if ($file =~ /\.$ext$/) {
return 1;
}
}
return 0;
}
sub file_path {
my ($file, $blog_id) = @_;
require File::Spec;
require MT::Blog;
unless (File::Spec->file_name_is_absolute($file)) {
my $blog = MT::Blog->load($blog_id);
$file = File::Spec->catfile($blog->site_path, $file);
}
return $file;
}
1;