# 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" />
ERROR:
HTML $$template =~ s/$old/$new/s; $old = qq{162" /> }; $old = quotemeta($old); $new = <162" />
ERROR:
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;