|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/fatshado/cgi-bin/MT/lib/MT/ |
Upload File : |
# Copyright 2001, 2002 Benjamin Trott. This code cannot be redistributed without
# permission from www.movabletype.org.
#
# $Id: XMLRPCServer.pm,v 1.4 2002/09/16 06:28:39 btrott Exp $
package MT::XMLRPCServer;
use strict;
use MT;
use MT::Util qw( first_n_words decode_html );
use MT::ErrorHandler;
BEGIN { @MT::XMLRPCServer::ISA = qw( MT::ErrorHandler ) }
use vars qw( $MT_DIR );
my($HAVE_XML_PARSER);
BEGIN {
eval { require XML::Parser };
$HAVE_XML_PARSER = $@ ? 0 : 1;
}
sub mt_new {
my $cfg = $ENV{MOD_PERL} ?
Apache->request->dir_config('MTConfig') :
$MT_DIR . '/mt.cfg';
my $mt = MT->new( Config => $cfg )
or die MT->errstr;
$mt;
}
## This is sort of a hack. XML::Parser automatically makes everything
## UTF-8, and that is causing severe problems with the serialization
## of database records (what happens is this: we construct a string
## consisting of pack('N', length($string)) . $string. If the $string SV
## is flagged as UTF-8, the packed length is then upgraded to UTF-8,
## which turns characters with values greater than 128 into two bytes,
## like v194.129. And so on. This is obviously now what we want, because
## pack produces a series of bytes, not a string that should be mucked
## about with.)
##
## The following subroutine strips the UTF8 flag from a string, thus
## forcing it into a series of bytes. "pack 'C0'" is a magic way of
## forcing the following string to be packed as bytes, not as UTF8.
sub no_utf8 {
for (@_) {
$_ = pack 'C0A*', $_;
}
}
sub _login {
my $class = shift;
my($user, $pass, $blog_id) = @_;
require MT::Author;
my $author = MT::Author->load({ name => $user }) or return;
$author->is_valid_password($pass) or return;
return $author unless $blog_id;
require MT::Permission;
my $perms = MT::Permission->load({ author_id => $author->id,
blog_id => $blog_id });
($author, $perms);
}
sub _publish {
my $class = shift;
my($mt, $entry) = @_;
require MT::Blog;
my $blog = MT::Blog->load($entry->blog_id);
$mt->rebuild_entry( Entry => $entry, Blog => $blog,
BuildDependencies => 1 )
or return $class->error("Rebuild error: " . $mt->errstr);
$mt->ping(Blog => $blog)
or return $class->error("Ping error: " . $mt->errstr);
1;
}
sub newPost {
my $class = shift;
my($appkey, $blog_id, $user, $pass, $item, $publish);
if ($class eq 'blogger') {
($appkey, $blog_id, $user, $pass, my($content), $publish) = @_;
$item->{description} = $content;
} else {
($blog_id, $user, $pass, $item, $publish) = @_;
}
no_utf8($blog_id, values %$item);
unless ($HAVE_XML_PARSER) {
for my $f (qw( title description mt_text_more mt_excerpt )) {
next unless defined $item->{$f};
$item->{$f} = decode_html($item->{$f});
$item->{$f} =~ s!'!'!g;
}
}
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Blog;
my $blog = MT::Blog->load($blog_id)
or die "Invalid blog ID '$blog_id'\n";
my($author, $perms) = __PACKAGE__->_login($user, $pass, $blog_id);
die "Invalid login\n" unless $author;
die "No posting privileges\n" unless $perms && $perms->can_post;
require MT::Entry;
my $entry = MT::Entry->new;
$entry->blog_id($blog_id);
$entry->author_id($author->id);
## In 2.1 we changed the behavior of the $publish flag. Previously,
## it was used to determine the post status. That was a bad idea.
## So now entries added through XML-RPC are always set to publish,
## *unless* the user has set "NoPublishMeansDraft 1" in mt.cfg, which
## enables the old behavior.
if ($mt->{cfg}->NoPublishMeansDraft) {
$entry->status($publish ? MT::Entry::RELEASE() : MT::Entry::HOLD());
} else {
$entry->status(MT::Entry::RELEASE());
}
$entry->allow_comments($blog->allow_comments_default);
$entry->allow_pings($blog->allow_pings_default);
$entry->convert_breaks($blog->convert_paras);
$entry->title($item->{title} || first_n_words($item->{description}, 5));
$entry->text($item->{description});
for my $field (qw( convert_breaks allow_comments allow_pings )) {
my $val = $item->{"mt_$field"};
next unless defined $val;
die "Value for 'mt_$field' must be either 0 or 1 (was '$val')"
unless $val == 0 || $val == 1;
$entry->$field($val);
}
$entry->excerpt($item->{mt_excerpt}) if $item->{mt_excerpt};
$entry->text_more($item->{mt_text_more}) if $item->{mt_text_more};
if (my $ts = $item->{dateCreated}) {
my($y, $mo, $d, $h, $m, $s) = $ts =~
/(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/
or die "Invalid timestamp format";
$entry->created_on(sprintf "%04d%02d%02d%02d%02d%02d",
$y, $mo, $d, $h, $m, $s);
}
$entry->save;
if ($publish) {
__PACKAGE__->_publish($mt, $entry) or die __PACKAGE__->errstr;
}
SOAP::Data->type(string => $entry->id);
}
sub editPost {
my $class = shift;
my($appkey, $entry_id, $user, $pass, $item, $publish);
if ($class eq 'blogger') {
($appkey, $entry_id, $user, $pass, my($content), $publish) = @_;
$item->{description} = $content;
} else {
($entry_id, $user, $pass, $item, $publish) = @_;
}
no_utf8(values %$item);
unless ($HAVE_XML_PARSER) {
for my $f (qw( title description mt_text_more mt_excerpt )) {
next unless defined $item->{$f};
$item->{$f} = decode_html($item->{$f});
$item->{$f} =~ s!'!'!g;
}
}
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = __PACKAGE__->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "Not privileged to edit entry\n"
unless $perms && $perms->can_edit_entry($entry, $author);
$entry->status(MT::Entry::RELEASE()) if $publish;
$entry->title($item->{title}) if $item->{title};
$entry->text($item->{description});
for my $field (qw( convert_breaks allow_comments allow_pings )) {
my $val = $item->{"mt_$field"};
next unless defined $val;
die "Value for 'mt_$field' must be either 0 or 1 (was '$val')"
unless $val == 0 || $val == 1;
$entry->$field($val);
}
$entry->excerpt($item->{mt_excerpt}) if $item->{mt_excerpt};
$entry->text_more($item->{mt_text_more}) if $item->{mt_text_more};
if (my $ts = $item->{dateCreated}) {
my($y, $mo, $d, $h, $m, $s) = $ts =~
/(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/
or die "Invalid timestamp format";
$entry->created_on(sprintf "%04d%02d%02d%02d%02d%02d",
$y, $mo, $d, $h, $m, $s);
}
$entry->save;
if ($publish) {
__PACKAGE__->_publish($mt, $entry) or die __PACKAGE__->errstr;
}
SOAP::Data->type(boolean => 1);
}
sub getUsersBlogs {
shift if UNIVERSAL::isa($_[0] => __PACKAGE__);
my($appkey, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
my($author) = __PACKAGE__->_login($user, $pass);
die "Invalid login\n" unless $author;
require MT::Permission;
require MT::Blog;
my $iter = MT::Permission->load_iter({ author_id => $author->id });
my @res;
while (my $perms = $iter->()) {
next unless $perms->can_post;
my $blog = MT::Blog->load($perms->blog_id);
push @res, { url => SOAP::Data->type(string => $blog->site_url),
blogid => SOAP::Data->type(string => $blog->id),
blogName => SOAP::Data->type(string => $blog->name) };
}
\@res;
}
sub getUserInfo {
shift if UNIVERSAL::isa($_[0] => __PACKAGE__);
my($appkey, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
my($author) = __PACKAGE__->_login($user, $pass);
die "Invalid login\n" unless $author;
my($fname, $lname) = split /\s+/, $author->name;
$lname ||= '';
{ userid => SOAP::Data->type(string => $author->id),
firstname => SOAP::Data->type(string => $fname),
lastname => SOAP::Data->type(string => $lname),
nickname => SOAP::Data->type(string => $author->nickname),
email => SOAP::Data->type(string => $author->email),
url => SOAP::Data->type(string => $author->url) };
}
sub getRecentPosts {
my $class = shift;
my($blog_id, $user, $pass, $num);
if ($class eq 'blogger') {
(my($appkey), $blog_id, $user, $pass, $num) = @_;
} else {
($blog_id, $user, $pass, $num) = @_;
}
my $mt = mt_new(); ## Will die if MT->new fails.
my($author, $perms) = __PACKAGE__->_login($user, $pass, $blog_id);
die "Invalid login\n" unless $author;
die "No posting privileges\n" unless $perms && $perms->can_post;
require MT::Blog;
my $blog = MT::Blog->load($blog_id);
require MT::Entry;
my $iter = MT::Entry->load_iter({ blog_id => $blog_id },
{ 'sort' => 'created_on',
direction => 'descend',
limit => $num });
my @res;
while (my $entry = $iter->()) {
my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d",
unpack 'A4A2A2A2A2A2', $entry->created_on;
my $row = { dateCreated => SOAP::Data->type(dateTime => $co),
userid => SOAP::Data->type(string => $entry->author_id),
postid => SOAP::Data->type(string => $entry->id), };
if ($class eq 'blogger') {
$row->{content} = SOAP::Data->type(string => $entry->text);
} else {
$row->{title} = SOAP::Data->type(string => $entry->title);
$row->{description} = SOAP::Data->type(string => $entry->text);
my $link = $blog->archive_url;
$link .= '/' unless $link =~ m!/$!;
$link .= $entry->archive_file;
$row->{link} = SOAP::Data->type(string => $link);
$row->{permaLink} = SOAP::Data->type(string => $link),
$row->{mt_allow_comments} = SOAP::Data->type(boolean => $entry->allow_comments);
$row->{mt_allow_pings} = SOAP::Data->type(boolean => $entry->allow_pings);
$row->{mt_convert_breaks} = SOAP::Data->type(boolean => $entry->convert_breaks);
$row->{mt_text_more} = SOAP::Data->type(string => $entry->text_more);
$row->{mt_excerpt} = SOAP::Data->type(string => $entry->excerpt);
}
push @res, $row;
}
\@res;
}
sub deletePost {
shift if UNIVERSAL::isa($_[0] => __PACKAGE__);
my($appkey, $entry_id, $user, $pass, $publish) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = __PACKAGE__->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "Not privileged to delete entry\n"
unless $perms && $perms->can_edit_entry($entry, $author);
$entry->remove;
if ($publish) {
__PACKAGE__->_publish($mt, $entry) or die __PACKAGE__->errstr;
}
SOAP::Data->type(boolean => 1);
}
sub getPost {
my $class = shift;
my($entry_id, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = __PACKAGE__->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "Not privileged to get entry\n"
unless $perms && $perms->can_edit_entry($entry, $author);
my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d",
unpack 'A4A2A2A2A2A2', $entry->created_on;
require MT::Blog;
my $blog = MT::Blog->load($entry->blog_id);
my $link = $blog->archive_url;
$link .= '/' unless $link =~ m!/$!;
$link .= $entry->archive_file;
{
dateCreated => SOAP::Data->type(dateTime => $co),
userid => SOAP::Data->type(string => $entry->author_id),
postid => SOAP::Data->type(string => $entry->id),
description => SOAP::Data->type(string => $entry->text),
title => SOAP::Data->type(string => $entry->title),
link => SOAP::Data->type(string => $link),
permaLink => SOAP::Data->type(string => $link),
mt_allow_comments => SOAP::Data->type(boolean => $entry->allow_comments),
mt_allow_pings => SOAP::Data->type(boolean => $entry->allow_pings),
mt_convert_breaks => SOAP::Data->type(boolean => $entry->convert_breaks),
mt_text_more => SOAP::Data->type(string => $entry->text_more),
mt_excerpt => SOAP::Data->type(string => $entry->excerpt),
}
}
sub supportedMethods {
[ 'blogger.newPost', 'blogger.editPost', 'blogger.getRecentPosts',
'blogger.getUsersBlogs', 'blogger.getUserInfo', 'blogger.deletePost',
'metaWeblog.getPost', 'metaWeblog.newPost', 'metaWeblog.editPost',
'metaWeblog.getRecentPosts',
'mt.getCategoryList', 'mt.setPostCategories', 'mt.getPostCategories',
'mt.getTrackbackPings' ];
}
## getCategoryList, getPostCategories, and setPostCategories were
## originally written by Daniel Drucker with the assistance of
## Benjamin Trott, then later modified by Benjamin Trott.
sub getCategoryList {
my $class = shift;
my($blog_id, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
my($author, $perms) = $class->_login($user, $pass, $blog_id);
die "Invalid login\n" unless $author;
die "Author does not have privileges\n" unless $perms && $perms->can_post;
require MT::Category;
my $iter = MT::Category->load_iter({ blog_id => $blog_id });
my @data;
while (my $cat = $iter->()) {
push @data, {
categoryName => SOAP::Data->type(string => $cat->label),
categoryId => SOAP::Data->type(string => $cat->id)
};
}
\@data;
}
sub getPostCategories {
my $class = shift;
my($entry_id, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = $class->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "No posting privileges\n" unless $perms && $perms->can_post;
my @data;
my $prim = $entry->category;
my $cats = $entry->categories;
for my $cat (@$cats) {
my $is_primary = $cat->id == $prim->id ? 1 : 0;
push @data, {
categoryName => SOAP::Data->type(string => $cat->label),
categoryId => SOAP::Data->type(string => $cat->id),
isPrimary => SOAP::Data->type(boolean => $is_primary),
};
}
\@data;
}
sub setPostCategories {
my $class = shift;
my($entry_id, $user, $pass, $cats) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
require MT::Placement;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = $class->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "No posting privileges\n" unless $perms && $perms->can_post;
my @place = MT::Placement->load({ entry_id => $entry_id });
for my $place (@place) {
$place->remove;
}
## Keep track of which category is named the primary category.
## If the first structure in the array does not have an isPrimary
## key, we just make it the primary category; if it does, we use
## that flag to determine the primary category.
my $is_primary = 1;
for my $cat (@$cats) {
my $place = MT::Placement->new;
$place->entry_id($entry_id);
$place->blog_id($entry->blog_id);
if (defined $cat->{isPrimary} && $is_primary) {
$place->is_primary($cat->{isPrimary});
} else {
$place->is_primary($is_primary);
}
$is_primary = 0;
$place->category_id($cat->{categoryId});
$place->save
or die "Saving placement failed: " . $place->errstr;
}
SOAP::Data->type(boolean => 1);
}
sub getTrackbackPings {
my $class = shift;
my($entry_id) = @_;
require MT::Trackback;
require MT::TBPing;
my $mt = mt_new(); ## Will die if MT->new fails.
my $tb = MT::Trackback->load({ entry_id => $entry_id })
or return [];
my $iter = MT::TBPing->load_iter({ tb_id => $tb->id });
my @data;
while (my $ping = $iter->()) {
push @data, {
pingTitle => SOAP::Data->type(string => $ping->title),
pingURL => SOAP::Data->type(string => $ping->source_url),
pingIP => SOAP::Data->type(string => $ping->ip),
};
}
\@data;
}
sub publishPost {
my $class = shift;
my($entry_id, $user, $pass) = @_;
my $mt = mt_new(); ## Will die if MT->new fails.
require MT::Entry;
my $entry = MT::Entry->load($entry_id)
or die "Invalid entry ID '$entry_id'\n";
my($author, $perms) = __PACKAGE__->_login($user, $pass, $entry->blog_id);
die "Invalid login\n" unless $author;
die "Not privileged to edit entry\n"
unless $perms && $perms->can_edit_entry($entry, $author);
$mt->rebuild_entry( Entry => $entry, BuildDependencies => 1 )
or die "Publish failed: ", $mt->errstr;
SOAP::Data->type(boolean => 1);
}
## getTemplate and setTemplate are not applicable in MT's template
## structure, so they are unimplemented (they return a fault).
## We assign it twice to get rid of "setTemplate used only once" warnings.
sub getTemplate {
die "Template methods are not implemented, due to differences between " .
"the Blogger API and the Movable Type API.\n";
}
*setTemplate = *setTemplate = \&getTemplate;
## The above methods will be called as blogger.newPost, blogger.editPost,
## etc., because we are implementing Blogger's API. Thus, the empty
## subclass.
package blogger;
BEGIN { @blogger::ISA = qw( MT::XMLRPCServer ); }
package metaWeblog;
BEGIN { @metaWeblog::ISA = qw( MT::XMLRPCServer ); }
package mt;
BEGIN { @mt::ISA = qw( MT::XMLRPCServer ); }
1;