# $id$ package MT::Plugin::PostVox; use strict; use warnings; use MT 4.0; use XML::Atom; use constant NS_DC => 'http://purl.org/dc/elements/1.1/'; use base 'MT::Plugin'; our $VERSION = '0.08'; my $plugin = __PACKAGE__->new( { name => 'Post to Vox', description => 'Automatic cross-posting to Vox. (This version supports only MT4.)', author_name => 'Six Apart, Ltd.', author_link => 'http://www.sixapart.com/', version => $VERSION, settings => new MT::PluginSettings( [ ['vox_username'], ['vox_password'], ['vox_url'], ['always_post'], ['excerpt_only'], ] ), blog_config_template => 'config.tmpl', } ); MT->add_plugin($plugin); MT->add_callback( 'MT::App::CMS::template_param.edit_entry', 9, $plugin, \&add_input_field ); MT->add_callback( 'cms_post_save.entry', 9, $plugin, \&hdlr_post_save ); MT->add_callback( 'api_post_save.entry', 9, $plugin, \&hdlr_api_post_save ); sub add_input_field { my ( $eh, $app, $param, $tmpl ) = @_; return unless UNIVERSAL::isa( $tmpl, 'MT::Template' ); my $q = $app->param; my $blog_id = $q->param('blog_id'); my $config = $plugin->get_config_hash( 'blog:' . $blog_id . ':user:' . $app->user->id ); return unless ( $config->{vox_username} || $config->{vox_password} ); my $entry_class = MT->model('entry'); my $entry_id = $app->param('id'); my $entry; if ($entry_id) { $entry = $entry_class->load( $entry_id, { cached_ok => 1 } ); } my $innerHTML; my $already_posted = $entry && ( ( $entry->tangent_cache || '' ) =~ m!http://www.vox.com! ); if ($already_posted) { if ( $entry->tangent_cache =~ m!(http://www.vox.com\S+)!i ) { my @parts = split '/', $1; my $asset_id = pop @parts; $innerHTML = "Update Vox post"; } } else { my $checked = ''; $checked = "checked=\"checked\"" if $config->{always_post}; $innerHTML = " Cross post to Vox"; } my $host_node = $tmpl->getElementById('status') or return $app->error('cannot get the status block'); my $block_node = $tmpl->createElement( 'app:setting', { id => 'allow_postvox', label => 'Cross Posting', } ) or return $app->error('cannot create the element'); $block_node->innerHTML($innerHTML); $tmpl->insertBefore( $block_node, $host_node ) or return $app->error('failed to insertBefore.'); } sub hdlr_api_post_save { return $plugin->_cross_post(@_); } sub hdlr_post_save { my ( $cb, $app, $obj, $orig ) = @_; return $obj unless $app->param('allow_postvox'); return $plugin->_cross_post( $cb, $app, $obj, $orig ); } sub _cross_post { my $self = shift; my ( $cb, $app, $obj, $orig ) = @_; my $blog_id = $obj->blog_id; my $user_id = $obj->author_id; my $config = $plugin->get_config_hash( 'blog:' . $blog_id . ':user:' . $user_id ); require MT::Blog; my $blog = MT::Blog->load($blog_id); return $obj unless ( $config->{vox_username} && $config->{vox_password} && $config->{vox_url} ); my $entry_class = MT->model('entry'); return $obj if $obj->status != MT::Entry::RELEASE(); # APILINK my $apilink = $config->{vox_apilink}; my $new_post = 1; if ( $obj->tangent_cache && $obj->tangent_cache =~ m!(http://www.vox.com\S+)!i ) { $apilink = $1; $new_post = 0; } elsif ( !$apilink || $apilink eq '' ) { my $url = $config->{vox_url}; if ( $url !~ m!^http://!i ) { $url = 'http://' . $url; } $apilink = $plugin->_find_apilink($url); return $obj unless $apilink; $config->{vox_apilink} = $apilink; $plugin->set_config_value( 'vox_apilink', $apilink, 'blog:' . $blog_id . ':user:' . $app->user->id ); } # ENTRY require XML::Atom::Entry; require MT::I18N; my $enc = MT->instance->config('PublishCharset') || undef; my $entry = XML::Atom::Entry->new; $entry->title( MT::I18N::encode_text( $obj->title, $enc, 'utf-8' ) ); my $text = ''; my $excerpt_only = $config->{excerpt_only} || 0; if ($excerpt_only) { # Uses entry excerpt only my $words = $blog->words_in_excerpt; $words = 40 unless defined $words && $words ne ''; $text = $obj->get_excerpt($words); $text .= "

read more..."; } else { # Apply filters to entry text and extended entry if available $text = $obj->text; my $text_more = $obj->text_more; $text = '' unless defined $text; $text_more = '' unless defined $text_more; my $filters = $obj->text_filters; push @$filters, '__default__' unless @$filters; $text = MT->apply_text_filters( $text, $filters ); $text_more = MT->apply_text_filters( $text_more, $filters ) if $text_more ne ''; $text .= "\n\nread more..." if $text_more ne ''; } $entry->content( MT::I18N::encode_text( $text, $enc, 'utf-8' ) ); my @tags = $obj->tags; my $dc = XML::Atom::Namespace->new( dc => NS_DC ); foreach my $tag (@tags) { $entry->add( $dc, 'subject', MT::I18N::encode_text( $tag, $enc, 'utf-8' ) ); } # CLIENT require XML::Atom::Client; my $api = XML::Atom::Client->new; $api->username( $config->{vox_username} ); $api->password( $config->{vox_password} ); # SEND if ($new_post) { my $edit_uri = $api->createEntry( $apilink, $entry ) or $config->{vox_apilink} = '', $plugin->set_config_value( 'vox_apilink', '', 'blog:' . $blog_id . ':user:' . $app->user->id ), return MT->log( { message => $api->errstr, } ); # Save EditURI my @t = split /\s+/, ( $obj->tangent_cache || '' ); push @t, $edit_uri; $obj->tangent_cache( join ' ', @t ); $obj->save; } else { my $ret = $api->updateEntry( $apilink, $entry ) or return MT->log( { message => $api->errstr, } ); } return $obj; } #TODO: other API support sub _find_apilink { my $self = shift; my ($uri) = @_; require XML::Atom::Feed; my $feed = XML::Atom::Feed->new( URI->new($uri) ); return undef unless $feed; my @links = $feed->link; for my $link (@links) { if ( $link->rel eq 'service.post' ) { return $link->href; } } return undef; } # Since these settings are blog *and* user specific, automatically assign # attach the user id to the scope element of all load/save/reset operations # that operate on the blog scope. sub load_config { my $plugin = shift; my $app = MT->instance; return unless $app->can('user'); my ( $param, $scope ) = @_; $scope .= ':user:' . $app->user->id if $scope =~ m/^blog:/; $plugin->SUPER::load_config( $param, $scope ); } sub save_config { my $plugin = shift; my $app = MT->instance; return unless $app->can('user'); my ( $param, $scope ) = @_; $scope .= ':user:' . $app->user->id if $scope =~ m/^blog:/; $plugin->SUPER::save_config( $param, $scope ); } sub reset_config { my $plugin = shift; my $app = MT->instance; return unless $app->can('user'); my ($scope) = @_; $scope .= ':user:' . $app->user->id if $scope =~ m/^blog:/; $plugin->SUPER::reset_config($scope); } 1;