| 1 | # Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd. |
|---|
| 2 | # This program is distributed under the terms of the |
|---|
| 3 | # GNU General Public License, version 2. |
|---|
| 4 | # |
|---|
| 5 | # $Id$ |
|---|
| 6 | |
|---|
| 7 | package MT::XMLRPCServer::Util; |
|---|
| 8 | use strict; |
|---|
| 9 | use Time::Local qw( timegm ); |
|---|
| 10 | use MT::Util qw( offset_time_list ); |
|---|
| 11 | use MT::I18N qw( encode_text first_n_text const ); |
|---|
| 12 | |
|---|
| 13 | sub mt_new { |
|---|
| 14 | my $cfg = $ENV{MOD_PERL} ? |
|---|
| 15 | Apache->request->dir_config('MTConfig') : |
|---|
| 16 | $MT::XMLRPCServer::MT_DIR . '/mt-config.cgi'; |
|---|
| 17 | my $mt = MT->new( Config => $cfg ) |
|---|
| 18 | or die MT::XMLRPCServer::_fault(MT->errstr); |
|---|
| 19 | # we need to be UTF-8 here no matter which PublishCharset |
|---|
| 20 | $main::server->serializer->encoding('UTF-8'); |
|---|
| 21 | $mt->run_callbacks('init_app', $mt, {App => 'xmlrpc'}); |
|---|
| 22 | $mt; |
|---|
| 23 | } |
|---|
| 24 | |
|---|
| 25 | sub iso2ts { |
|---|
| 26 | my($blog, $iso) = @_; |
|---|
| 27 | die MT::XMLRPCServer::_fault(MT->translate("Invalid timestamp format")) |
|---|
| 28 | unless $iso =~ /^(\d{4})(?:-?(\d{2})(?:-?(\d\d?)(?:T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})?)?)?)?/; |
|---|
| 29 | my($y, $mo, $d, $h, $m, $s, $offset) = |
|---|
| 30 | ($1, $2 || 1, $3 || 1, $4 || 0, $5 || 0, $6 || 0, $7); |
|---|
| 31 | if ($offset && !MT->config->IgnoreISOTimezones) { |
|---|
| 32 | $mo--; |
|---|
| 33 | $y -= 1900; |
|---|
| 34 | my $time = timegm($s, $m, $h, $d, $mo, $y); |
|---|
| 35 | ## If it's not already in UTC, first convert to UTC. |
|---|
| 36 | if ($offset ne 'Z') { |
|---|
| 37 | my($sign, $h, $m) = $offset =~ /([+-])(\d{2}):(\d{2})/; |
|---|
| 38 | $offset = $h * 3600 + $m * 60; |
|---|
| 39 | $offset *= -1 if $sign eq '-'; |
|---|
| 40 | $time -= $offset; |
|---|
| 41 | } |
|---|
| 42 | ## Now apply the offset for this weblog. |
|---|
| 43 | ($s, $m, $h, $d, $mo, $y) = offset_time_list($time, $blog); |
|---|
| 44 | $mo++; |
|---|
| 45 | $y += 1900; |
|---|
| 46 | } |
|---|
| 47 | sprintf "%04d%02d%02d%02d%02d%02d", $y, $mo, $d, $h, $m, $s; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | sub ts2iso { |
|---|
| 51 | my ($blog, $ts) = @_; |
|---|
| 52 | my ($yr, $mo, $dy, $hr, $mn, $sc) = unpack('A4A2A2A2A2A2A2', $ts); |
|---|
| 53 | $ts = timegm($sc, $mn, $hr, $dy, $mo, $yr); |
|---|
| 54 | ($sc, $mn, $hr, $dy, $mo, $yr) = offset_time_list($ts, $blog, '-'); |
|---|
| 55 | $yr += 1900; |
|---|
| 56 | sprintf("%04d-%02d-%02d %02d:%02d:%02d", $yr, $mo, $dy, $hr, $mn, $sc); |
|---|
| 57 | } |
|---|
| 58 | |
|---|
| 59 | package MT::XMLRPCServer; |
|---|
| 60 | use strict; |
|---|
| 61 | |
|---|
| 62 | use MT; |
|---|
| 63 | use MT::Util qw( first_n_words decode_html start_background_task archive_file_for ); |
|---|
| 64 | use MT::I18N qw( encode_text first_n_text const ); |
|---|
| 65 | use base qw( MT::ErrorHandler ); |
|---|
| 66 | |
|---|
| 67 | our $MT_DIR; |
|---|
| 68 | |
|---|
| 69 | my($HAVE_XML_PARSER); |
|---|
| 70 | BEGIN { |
|---|
| 71 | eval { require XML::Parser }; |
|---|
| 72 | $HAVE_XML_PARSER = $@ ? 0 : 1; |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | sub _fault { |
|---|
| 76 | my $mt = MT::XMLRPCServer::Util::mt_new(); |
|---|
| 77 | my $enc = $mt->config('PublishCharset'); |
|---|
| 78 | SOAP::Fault->faultcode(1)->faultstring( |
|---|
| 79 | SOAP::Data->type( |
|---|
| 80 | string => encode_text($_[0], $enc, 'utf-8'))); |
|---|
| 81 | } |
|---|
| 82 | |
|---|
| 83 | ## This is sort of a hack. XML::Parser automatically makes everything |
|---|
| 84 | ## UTF-8, and that is causing severe problems with the serialization |
|---|
| 85 | ## of database records (what happens is this: we construct a string |
|---|
| 86 | ## consisting of pack('N', length($string)) . $string. If the $string SV |
|---|
| 87 | ## is flagged as UTF-8, the packed length is then upgraded to UTF-8, |
|---|
| 88 | ## which turns characters with values greater than 128 into two bytes, |
|---|
| 89 | ## like v194.129. And so on. This is obviously now what we want, because |
|---|
| 90 | ## pack produces a series of bytes, not a string that should be mucked |
|---|
| 91 | ## about with.) |
|---|
| 92 | ## |
|---|
| 93 | ## The following subroutine strips the UTF8 flag from a string, thus |
|---|
| 94 | ## forcing it into a series of bytes. "pack 'C0'" is a magic way of |
|---|
| 95 | ## forcing the following string to be packed as bytes, not as UTF8. |
|---|
| 96 | |
|---|
| 97 | sub no_utf8 { |
|---|
| 98 | for (@_) { |
|---|
| 99 | next if ref; |
|---|
| 100 | $_ = pack 'C0A*', $_; |
|---|
| 101 | } |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | sub _make_token { |
|---|
| 105 | my @alpha = ('a'..'z', 'A'..'Z', 0..9); |
|---|
| 106 | my $token = join '', map $alpha[rand @alpha], 1..40; |
|---|
| 107 | $token; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | sub _login { |
|---|
| 111 | my $class = shift; |
|---|
| 112 | my($user, $pass, $blog_id) = @_; |
|---|
| 113 | no_utf8($user, $pass); |
|---|
| 114 | my $mt = MT::XMLRPCServer::Util::mt_new(); |
|---|
| 115 | my $enc = $mt->config('PublishCharset'); |
|---|
| 116 | $user = encode_text($user, 'utf-8', $enc); |
|---|
| 117 | $pass = encode_text($pass, 'utf-8', $enc); |
|---|
| 118 | require MT::Author; |
|---|
| 119 | my $author = MT::Author->load({ name => $user, type => 1 }) or return; |
|---|
| 120 | die _fault(MT->translate("No web services password assigned. Please see your user profile to set it.")) unless $author->api_password; |
|---|
| 121 | die _fault(MT->translate("Failed login attempt by disabled user '[_1]'")) unless $author->is_active; |
|---|
| 122 | my $auth = $author->api_password eq $pass; |
|---|
| 123 | $auth ||= crypt($pass, $author->api_password) eq $author->api_password; |
|---|
| 124 | return unless $auth; |
|---|
| 125 | return $author unless $blog_id; |
|---|
| 126 | require MT::Permission; |
|---|
| 127 | my $perms = MT::Permission->load({ author_id => $author->id, |
|---|
| 128 | blog_id => $blog_id }); |
|---|
| 129 | |
|---|
| 130 | ## update session so the user will be counted as active |
|---|
| 131 | require MT::Session; |
|---|
| 132 | my $sess_active = MT::Session->load( { kind => 'UA', name => $author->id } ); |
|---|
| 133 | if (!$sess_active) { |
|---|
| 134 | $sess_active = MT::Session->new; |
|---|
| 135 | $sess_active->id(_make_token()); |
|---|
| 136 | $sess_active->kind('UA'); # UA == User Activation |
|---|
| 137 | $sess_active->name($author->id); |
|---|
| 138 | } |
|---|
| 139 | $sess_active->start(time); |
|---|
| 140 | $sess_active->save; |
|---|
| 141 | |
|---|
| 142 | ($author, $perms); |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | sub _publish { |
|---|
| 146 | my $class = shift; |
|---|
| 147 | my($mt, $entry, $no_ping) = @_; |
|---|
| 148 | require MT::Blog; |
|---|
| 149 | my $blog = MT::Blog->load($entry->blog_id); |
|---|
| 150 | $mt->rebuild_entry( Entry => $entry, Blog => $blog, |
|---|
| 151 | BuildDependencies => 1 ) |
|---|
| 152 | or return $class->error("Publish error: " . $mt->errstr); |
|---|
| 153 | unless ($no_ping) { |
|---|
| 154 | $mt->ping_and_save(Blog => $blog, Entry => $entry) |
|---|
| 155 | or return $class->error("Ping error: " . $mt->errstr); |
|---|
| 156 | } |
|---|
| 157 | 1; |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | sub _apply_basename { |
|---|
| 161 | my $class = shift; |
|---|
| 162 | my ($entry, $item, $param) = @_; |
|---|
| 163 | |
|---|
| 164 | my $basename = $item->{mt_basename} || $item->{wp_slug}; |
|---|
| 165 | if ($param->{page} && $item->{permaLink}) { |
|---|
| 166 | local $entry->{column_values}->{basename} = '##s##'; |
|---|
| 167 | my $real_url = $entry->archive_url(); |
|---|
| 168 | my ($pre, $post) = split /##s##/, $real_url, 2; |
|---|
| 169 | |
|---|
| 170 | my $req_url = $item->{permaLink}; |
|---|
| 171 | if ($req_url =~ m{ \A \Q$pre\E (.*) \Q$post\E \z }xms) { |
|---|
| 172 | my $req_base = $1; |
|---|
| 173 | my @folders = split /\//, $req_base; |
|---|
| 174 | $basename = pop @folders; |
|---|
| 175 | $param->{__permaLink_folders} = \@folders; |
|---|
| 176 | } |
|---|
| 177 | else { |
|---|
| 178 | die _fault(MT->translate("Requested permalink '[_1]' is not available for this page", |
|---|
| 179 | $req_url)); |
|---|
| 180 | } |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | if (defined $basename) { |
|---|
| 184 | # Ensure this basename is unique. |
|---|
| 185 | my $entry_class = ref $entry; |
|---|
| 186 | my $basename_uses = $entry_class->exist({ |
|---|
| 187 | blog_id => $entry->blog_id, |
|---|
| 188 | basename => $basename, |
|---|
| 189 | ($entry->id ? ( id => { op => '!=', value => $entry->id } ) : ()), |
|---|
| 190 | }); |
|---|
| 191 | if ($basename_uses) { |
|---|
| 192 | $basename = MT::Util::make_unique_basename($entry); |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | $entry->basename($basename); |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | 1; |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | sub _save_placements { |
|---|
| 202 | my $class = shift; |
|---|
| 203 | my ($entry, $item, $param) = @_; |
|---|
| 204 | |
|---|
| 205 | my @categories; |
|---|
| 206 | my $changed = 0; |
|---|
| 207 | |
|---|
| 208 | if ($param->{page}) { |
|---|
| 209 | if (my $folders = $param->{__permaLink_folders}) { |
|---|
| 210 | my $parent_id = 0; |
|---|
| 211 | my $folder; |
|---|
| 212 | require MT::Folder; |
|---|
| 213 | for my $basename (@$folders) { |
|---|
| 214 | $folder = MT::Folder->load({ |
|---|
| 215 | blog_id => $entry->blog_id, |
|---|
| 216 | parent => $parent_id, |
|---|
| 217 | basename => $basename, |
|---|
| 218 | }); |
|---|
| 219 | |
|---|
| 220 | if (!$folder) { |
|---|
| 221 | # Autovivify the folder tree. |
|---|
| 222 | $folder = MT::Folder->new; |
|---|
| 223 | $folder->blog_id($entry->blog_id); |
|---|
| 224 | $folder->parent($parent_id); |
|---|
| 225 | $folder->basename($basename); |
|---|
| 226 | $folder->label($basename); |
|---|
| 227 | $changed = 1; |
|---|
| 228 | $folder->save |
|---|
| 229 | or die _fault(MT->translate("Saving folder failed: [_1]", |
|---|
| 230 | $folder->errstr)); |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | $parent_id = $folder->id; |
|---|
| 234 | } |
|---|
| 235 | @categories = ($folder) if $folder; |
|---|
| 236 | } |
|---|
| 237 | } |
|---|
| 238 | elsif (my $cats = $item->{categories}) { |
|---|
| 239 | if (@$cats) { |
|---|
| 240 | my $cat_class = MT->model('category'); |
|---|
| 241 | # The spec says to ignore invalid category names. |
|---|
| 242 | @categories = grep { defined } $cat_class->search({ |
|---|
| 243 | blog_id => $entry->blog_id, |
|---|
| 244 | label => $cats, |
|---|
| 245 | }); |
|---|
| 246 | } |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | require MT::Placement; |
|---|
| 250 | my $is_primary_placement = 1; |
|---|
| 251 | CATEGORY: for my $category (@categories) { |
|---|
| 252 | my $place; |
|---|
| 253 | if ($is_primary_placement) { |
|---|
| 254 | $place = MT::Placement->load({ |
|---|
| 255 | entry_id => $entry->id, |
|---|
| 256 | is_primary => 1, |
|---|
| 257 | }); |
|---|
| 258 | } |
|---|
| 259 | if (!$place) { |
|---|
| 260 | $place = MT::Placement->new; |
|---|
| 261 | $place->blog_id($entry->blog_id); |
|---|
| 262 | $place->entry_id($entry->id); |
|---|
| 263 | $place->is_primary($is_primary_placement ? 1 : 0); |
|---|
| 264 | } |
|---|
| 265 | $place->category_id($category->id); |
|---|
| 266 | $place->save |
|---|
| 267 | or die _fault(MT->translate("Saving placement failed: [_1]", |
|---|
| 268 | $place->errstr)); |
|---|
| 269 | |
|---|
| 270 | if ($is_primary_placement) { |
|---|
| 271 | # Delete all the secondary placements, so each of the remaining |
|---|
| 272 | # iterations of the loop make a brand new placement. |
|---|
| 273 | my @old_places = MT::Placement->load({ |
|---|
| 274 | entry_id => $entry->id, |
|---|
| 275 | is_primary => 0, |
|---|
| 276 | }); |
|---|
| 277 | for my $place (@old_places) { |
|---|
| 278 | $place->remove; |
|---|
| 279 | } |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | $is_primary_placement = 0; |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | $changed; |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | sub _new_entry { |
|---|
| 289 | my $class = shift; |
|---|
| 290 | my %param = @_; |
|---|
| 291 | my($blog_id, $user, $pass, $item, $publish) = |
|---|
| 292 | @param{qw( blog_id user pass item publish )}; |
|---|
| 293 | my $obj_type = $param{page} ? 'page' : 'entry'; |
|---|
| 294 | die _fault(MT->translate("No blog_id")) unless $blog_id; |
|---|
| 295 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 296 | no_utf8($blog_id, values %$item); |
|---|
| 297 | for my $f (qw( title description mt_text_more |
|---|
| 298 | mt_excerpt mt_keywords mt_tags mt_basename wp_slug )) { |
|---|
| 299 | next unless defined $item->{$f}; |
|---|
| 300 | my $enc = $mt->{cfg}->PublishCharset; |
|---|
| 301 | $item->{$f} = encode_text($item->{$f}, 'utf-8', $enc); |
|---|
| 302 | unless ($HAVE_XML_PARSER) { |
|---|
| 303 | $item->{$f} = decode_html($item->{$f}); |
|---|
| 304 | $item->{$f} =~ s!'!'!g; #' |
|---|
| 305 | } |
|---|
| 306 | } |
|---|
| 307 | require MT::Blog; |
|---|
| 308 | my $blog = MT::Blog->load($blog_id) |
|---|
| 309 | or die _fault(MT->translate("Invalid blog ID '[_1]'", $blog_id)); |
|---|
| 310 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 311 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 312 | die _fault(MT->translate("Permission denied.")) unless $perms && $perms->can_create_post; |
|---|
| 313 | my $entry = MT->model($obj_type)->new; |
|---|
| 314 | my $orig_entry = $entry->clone; |
|---|
| 315 | $entry->blog_id($blog_id); |
|---|
| 316 | $entry->author_id($author->id); |
|---|
| 317 | |
|---|
| 318 | ## In 2.1 we changed the behavior of the $publish flag. Previously, |
|---|
| 319 | ## it was used to determine the post status. That was a bad idea. |
|---|
| 320 | ## So now entries added through XML-RPC are always set to publish, |
|---|
| 321 | ## *unless* the user has set "NoPublishMeansDraft 1" in mt.cfg, which |
|---|
| 322 | ## enables the old behavior. |
|---|
| 323 | if ($mt->{cfg}->NoPublishMeansDraft) { |
|---|
| 324 | $entry->status($publish && $perms->can_publish_post ? MT::Entry::RELEASE() : MT::Entry::HOLD()); |
|---|
| 325 | } else { |
|---|
| 326 | $entry->status($perms->can_publish_post ? MT::Entry::RELEASE() : MT::Entry::HOLD() ); |
|---|
| 327 | } |
|---|
| 328 | $entry->allow_comments($blog->allow_comments_default); |
|---|
| 329 | $entry->allow_pings($blog->allow_pings_default); |
|---|
| 330 | $entry->convert_breaks(defined $item->{mt_convert_breaks} ? $item->{mt_convert_breaks} : $blog->convert_paras); |
|---|
| 331 | $entry->allow_comments($item->{mt_allow_comments}) |
|---|
| 332 | if exists $item->{mt_allow_comments}; |
|---|
| 333 | $entry->title($item->{title}) |
|---|
| 334 | if exists $item->{title}; |
|---|
| 335 | |
|---|
| 336 | $class->_apply_basename($entry, $item, \%param); |
|---|
| 337 | |
|---|
| 338 | $entry->text($item->{description}); |
|---|
| 339 | for my $field (qw( allow_pings )) { |
|---|
| 340 | my $val = $item->{"mt_$field"}; |
|---|
| 341 | next unless defined $val; |
|---|
| 342 | die _fault(MT->translate("Value for 'mt_[_1]' must be either 0 or 1 (was '[_2]')", $field, $val)) |
|---|
| 343 | unless $val == 0 || $val == 1; |
|---|
| 344 | $entry->$field($val); |
|---|
| 345 | } |
|---|
| 346 | $entry->excerpt($item->{mt_excerpt}) if $item->{mt_excerpt}; |
|---|
| 347 | $entry->text_more($item->{mt_text_more}) if $item->{mt_text_more}; |
|---|
| 348 | $entry->keywords($item->{mt_keywords}) if $item->{mt_keywords}; |
|---|
| 349 | |
|---|
| 350 | if (my $tags = $item->{mt_tags}) { |
|---|
| 351 | require MT::Tag; |
|---|
| 352 | my $tag_delim = chr($author->entry_prefs->{tag_delim}); |
|---|
| 353 | my @tags = MT::Tag->split($tag_delim, $tags); |
|---|
| 354 | $entry->set_tags(@tags); |
|---|
| 355 | } |
|---|
| 356 | if (my $urls = $item->{mt_tb_ping_urls}) { |
|---|
| 357 | no_utf8(@$urls); |
|---|
| 358 | $entry->to_ping_urls(join "\n", @$urls); |
|---|
| 359 | } |
|---|
| 360 | if (my $iso = $item->{dateCreated}) { |
|---|
| 361 | $entry->authored_on(MT::XMLRPCServer::Util::iso2ts($blog, $iso)) |
|---|
| 362 | || die MT::XMLRPCServer::_fault(MT->translate("Invalid timestamp format")); |
|---|
| 363 | require MT::DateTime; |
|---|
| 364 | $entry->status(MT::Entry::FUTURE()) |
|---|
| 365 | if MT::DateTime->compare( |
|---|
| 366 | blog => $blog, |
|---|
| 367 | a => $entry->authored_on, |
|---|
| 368 | b => { value => time(), type => 'epoch' } ) > 0; |
|---|
| 369 | } |
|---|
| 370 | $entry->discover_tb_from_entry(); |
|---|
| 371 | |
|---|
| 372 | MT->run_callbacks("api_pre_save.$obj_type", $mt, $entry, $orig_entry) |
|---|
| 373 | || die MT::XMLRPCServer::_fault(MT->translate("PreSave failed [_1]", MT->errstr)); |
|---|
| 374 | |
|---|
| 375 | $entry->save; |
|---|
| 376 | |
|---|
| 377 | my $changed = $class->_save_placements($entry, $item, \%param); |
|---|
| 378 | |
|---|
| 379 | my @types = ( $obj_type ); |
|---|
| 380 | if ($changed) { |
|---|
| 381 | push @types, 'folder'; # folders are the only type that can be |
|---|
| 382 | # created in _save_placements |
|---|
| 383 | } |
|---|
| 384 | $blog->touch( @types ); |
|---|
| 385 | $blog->save; |
|---|
| 386 | |
|---|
| 387 | MT->run_callbacks("api_post_save.$obj_type", $mt, $entry, $orig_entry); |
|---|
| 388 | |
|---|
| 389 | require MT::Log; |
|---|
| 390 | $mt->log({ |
|---|
| 391 | message => $mt->translate("User '[_1]' (user #[_2]) added [lc,_4] #[_3]", $author->name, $author->id, $entry->id, $entry->class_label), |
|---|
| 392 | level => MT::Log::INFO(), |
|---|
| 393 | class => $obj_type, |
|---|
| 394 | category => 'new', |
|---|
| 395 | metadata => $entry->id |
|---|
| 396 | }); |
|---|
| 397 | |
|---|
| 398 | if ($publish) { |
|---|
| 399 | $class->_publish($mt, $entry) or die _fault($class->errstr); |
|---|
| 400 | } |
|---|
| 401 | delete $ENV{SERVER_SOFTWARE}; |
|---|
| 402 | SOAP::Data->type(string => $entry->id); |
|---|
| 403 | } |
|---|
| 404 | |
|---|
| 405 | sub newPost { |
|---|
| 406 | my $class = shift; |
|---|
| 407 | my($appkey, $blog_id, $user, $pass, $item, $publish); |
|---|
| 408 | if ($class eq 'blogger') { |
|---|
| 409 | ($appkey, $blog_id, $user, $pass, my($content), $publish) = @_; |
|---|
| 410 | $item->{description} = $content; |
|---|
| 411 | } else { |
|---|
| 412 | ($blog_id, $user, $pass, $item, $publish) = @_; |
|---|
| 413 | } |
|---|
| 414 | $class->_new_entry( blog_id => $blog_id, user => $user, pass => $pass, |
|---|
| 415 | item => $item, publish => $publish ); |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | sub newPage { |
|---|
| 419 | my $class = shift; |
|---|
| 420 | my ($blog_id, $user, $pass, $item, $publish) = @_; |
|---|
| 421 | $class->_new_entry( blog_id => $blog_id, user => $user, pass => $pass, |
|---|
| 422 | item => $item, publish => $publish, page => 1 ); |
|---|
| 423 | } |
|---|
| 424 | |
|---|
| 425 | sub _edit_entry { |
|---|
| 426 | my $class = shift; |
|---|
| 427 | my %param = @_; |
|---|
| 428 | my ($blog_id, $entry_id, $user, $pass, $item, $publish) = |
|---|
| 429 | @param{qw( blog_id entry_id user pass item publish )}; |
|---|
| 430 | my $obj_type = $param{page} ? 'page' : 'entry'; |
|---|
| 431 | die _fault(MT->translate("No entry_id")) unless $entry_id; |
|---|
| 432 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 433 | no_utf8(values %$item); |
|---|
| 434 | for my $f (qw( title description mt_text_more |
|---|
| 435 | mt_excerpt mt_keywords mt_tags mt_basename wp_slug )) { |
|---|
| 436 | next unless defined $item->{$f}; |
|---|
| 437 | my $enc = $mt->config('PublishCharset'); |
|---|
| 438 | $item->{$f} = encode_text($item->{$f}, 'utf-8', $enc); |
|---|
| 439 | unless ($HAVE_XML_PARSER) { |
|---|
| 440 | $item->{$f} = decode_html($item->{$f}); |
|---|
| 441 | $item->{$f} =~ s!'!'!g; #' |
|---|
| 442 | } |
|---|
| 443 | } |
|---|
| 444 | my $entry = MT->model($obj_type)->load($entry_id) |
|---|
| 445 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 446 | if ($blog_id && $blog_id != $entry->blog_id) { |
|---|
| 447 | die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 448 | } |
|---|
| 449 | require MT::Blog; |
|---|
| 450 | my $blog = MT::Blog->load($blog_id) |
|---|
| 451 | or die _fault(MT->translate("Invalid blog ID '[_1]'", $blog_id)); |
|---|
| 452 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 453 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 454 | die _fault(MT->translate("Not privileged to edit entry")) |
|---|
| 455 | unless $perms && $perms->can_edit_entry($entry, $author); |
|---|
| 456 | my $orig_entry = $entry->clone; |
|---|
| 457 | $entry->status(MT::Entry::RELEASE()) if $publish; |
|---|
| 458 | $entry->title($item->{title}) if $item->{title}; |
|---|
| 459 | |
|---|
| 460 | $class->_apply_basename($entry, $item, \%param); |
|---|
| 461 | |
|---|
| 462 | $entry->text($item->{description}); |
|---|
| 463 | $entry->convert_breaks($item->{mt_convert_breaks}) |
|---|
| 464 | if exists $item->{mt_convert_breaks}; |
|---|
| 465 | $entry->allow_comments($item->{mt_allow_comments}) |
|---|
| 466 | if exists $item->{mt_allow_comments}; |
|---|
| 467 | for my $field (qw( allow_pings )) { |
|---|
| 468 | my $val = $item->{"mt_$field"}; |
|---|
| 469 | next unless defined $val; |
|---|
| 470 | die _fault(MT->translate("Value for 'mt_[_1]' must be either 0 or 1 (was '[_2]')", $field, $val)) |
|---|
| 471 | unless $val == 0 || $val == 1; |
|---|
| 472 | $entry->$field($val); |
|---|
| 473 | } |
|---|
| 474 | $entry->excerpt($item->{mt_excerpt}) if defined $item->{mt_excerpt}; |
|---|
| 475 | $entry->text_more($item->{mt_text_more}) if defined $item->{mt_text_more}; |
|---|
| 476 | $entry->keywords($item->{mt_keywords}) if defined $item->{mt_keywords}; |
|---|
| 477 | if (my $tags = $item->{mt_tags}) { |
|---|
| 478 | require MT::Tag; |
|---|
| 479 | my $tag_delim = chr($author->entry_prefs->{tag_delim}); |
|---|
| 480 | my @tags = MT::Tag->split($tag_delim, $tags); |
|---|
| 481 | $entry->set_tags(@tags); |
|---|
| 482 | } |
|---|
| 483 | if (my $urls = $item->{mt_tb_ping_urls}) { |
|---|
| 484 | no_utf8(@$urls); |
|---|
| 485 | $entry->to_ping_urls(join "\n", @$urls); |
|---|
| 486 | } |
|---|
| 487 | if (my $iso = $item->{dateCreated}) { |
|---|
| 488 | $entry->authored_on(MT::XMLRPCServer::Util::iso2ts($entry->blog_id, $iso)) |
|---|
| 489 | || die MT::XMLRPCServer::_fault(MT->translate("Invalid timestamp format")); |
|---|
| 490 | require MT::DateTime; |
|---|
| 491 | $entry->status(MT::Entry::FUTURE()) |
|---|
| 492 | if MT::DateTime->compare( |
|---|
| 493 | a => $entry->authored_on, |
|---|
| 494 | b => { value => time(), type => 'epoch' } ) > 0; |
|---|
| 495 | } |
|---|
| 496 | $entry->discover_tb_from_entry(); |
|---|
| 497 | |
|---|
| 498 | MT->run_callbacks("api_pre_save.$obj_type", $mt, $entry, |
|---|
| 499 | $orig_entry) |
|---|
| 500 | || die MT::XMLRPCServer::_fault(MT->translate("PreSave failed [_1]", MT->errstr)); |
|---|
| 501 | |
|---|
| 502 | $entry->save; |
|---|
| 503 | |
|---|
| 504 | my $changed = $class->_save_placements($entry, $item, \%param); |
|---|
| 505 | my @types = ( $obj_type ); |
|---|
| 506 | if ($changed) { |
|---|
| 507 | push @types, 'folder'; # folders are the only type that can be |
|---|
| 508 | # created in _save_placements |
|---|
| 509 | } |
|---|
| 510 | $blog->touch( @types ); |
|---|
| 511 | |
|---|
| 512 | MT->run_callbacks("api_post_save.$obj_type", $mt, $entry, |
|---|
| 513 | $orig_entry); |
|---|
| 514 | |
|---|
| 515 | require MT::Log; |
|---|
| 516 | $mt->log({ |
|---|
| 517 | message => $mt->translate("User '[_1]' (user #[_2]) edited [lc,_4] #[_3]", $author->name, $author->id, $entry->id, $entry->class_label), |
|---|
| 518 | level => MT::Log::INFO(), |
|---|
| 519 | class => $obj_type, |
|---|
| 520 | category => 'new', |
|---|
| 521 | metadata => $entry->id |
|---|
| 522 | }); |
|---|
| 523 | |
|---|
| 524 | if ($publish) { |
|---|
| 525 | $class->_publish($mt, $entry) or die _fault($class->errstr); |
|---|
| 526 | } |
|---|
| 527 | SOAP::Data->type(boolean => 1); |
|---|
| 528 | } |
|---|
| 529 | |
|---|
| 530 | sub editPost { |
|---|
| 531 | my $class = shift; |
|---|
| 532 | my($entry_id, $user, $pass, $item, $publish); |
|---|
| 533 | if ($class eq 'blogger') { |
|---|
| 534 | (my($appkey), $entry_id, $user, $pass, my($content), $publish) = @_; |
|---|
| 535 | $item->{description} = $content; |
|---|
| 536 | } |
|---|
| 537 | else { |
|---|
| 538 | ($entry_id, $user, $pass, $item, $publish) = @_; |
|---|
| 539 | } |
|---|
| 540 | $class->_edit_entry( entry_id => $entry_id, user => $user, pass => $pass, |
|---|
| 541 | item => $item, publish => $publish ); |
|---|
| 542 | } |
|---|
| 543 | |
|---|
| 544 | sub editPage { |
|---|
| 545 | my $class = shift; |
|---|
| 546 | my($blog_id, $entry_id, $user, $pass, $item, $publish) = @_; |
|---|
| 547 | $class->_edit_entry( blog_id => $blog_id, entry_id => $entry_id, |
|---|
| 548 | user => $user, pass => $pass, item => $item, publish => $publish, |
|---|
| 549 | page => 1 ); |
|---|
| 550 | } |
|---|
| 551 | |
|---|
| 552 | sub getUsersBlogs { |
|---|
| 553 | my $class; |
|---|
| 554 | if (UNIVERSAL::isa($_[0] => __PACKAGE__)) { |
|---|
| 555 | $class = shift; |
|---|
| 556 | } else { |
|---|
| 557 | $class = __PACKAGE__; |
|---|
| 558 | } |
|---|
| 559 | my($appkey, $user, $pass) = @_; |
|---|
| 560 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 561 | my($author) = $class->_login($user, $pass); |
|---|
| 562 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 563 | require MT::Permission; |
|---|
| 564 | require MT::Blog; |
|---|
| 565 | my $iter = MT::Permission->load_iter({ author_id => $author->id }); |
|---|
| 566 | my @res; |
|---|
| 567 | while (my $perms = $iter->()) { |
|---|
| 568 | next unless $perms->can_create_post; |
|---|
| 569 | my $blog = MT::Blog->load($perms->blog_id); |
|---|
| 570 | next unless $blog; |
|---|
| 571 | push @res, { url => SOAP::Data->type(string => $blog->site_url), |
|---|
| 572 | blogid => SOAP::Data->type(string => $blog->id), |
|---|
| 573 | blogName => SOAP::Data->type(string => encode_text($blog->name, undef, 'utf-8')) }; |
|---|
| 574 | } |
|---|
| 575 | \@res; |
|---|
| 576 | } |
|---|
| 577 | |
|---|
| 578 | sub getUserInfo { |
|---|
| 579 | my $class; |
|---|
| 580 | if (UNIVERSAL::isa($_[0] => __PACKAGE__)) { |
|---|
| 581 | $class = shift; |
|---|
| 582 | } else { |
|---|
| 583 | $class = __PACKAGE__; |
|---|
| 584 | } |
|---|
| 585 | my($appkey, $user, $pass) = @_; |
|---|
| 586 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 587 | my($author) = $class->_login($user, $pass); |
|---|
| 588 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 589 | my($fname, $lname) = map { encode_text($_, undef, 'utf-8') } split /\s+/, $author->name; |
|---|
| 590 | $lname ||= ''; |
|---|
| 591 | { userid => SOAP::Data->type(string => $author->id), |
|---|
| 592 | firstname => SOAP::Data->type(string => $fname), |
|---|
| 593 | lastname => SOAP::Data->type(string => $lname), |
|---|
| 594 | nickname => SOAP::Data->type(string => encode_text($author->nickname, undef, 'utf-8')), |
|---|
| 595 | email => SOAP::Data->type(string => $author->email), |
|---|
| 596 | url => SOAP::Data->type(string => $author->url) }; |
|---|
| 597 | } |
|---|
| 598 | |
|---|
| 599 | sub _get_entries { |
|---|
| 600 | my $class = shift; |
|---|
| 601 | my %param = @_; |
|---|
| 602 | my($blog_id, $user, $pass, $num, $titles_only) = |
|---|
| 603 | @param{qw( blog_id user pass num titles_only )}; |
|---|
| 604 | my $obj_type = $param{page} ? 'page' : 'entry'; |
|---|
| 605 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 606 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 607 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 608 | die _fault(MT->translate("Permission denied.")) unless $perms && $perms->can_create_post; |
|---|
| 609 | my $iter = MT->model($obj_type)->load_iter({ blog_id => $blog_id }, |
|---|
| 610 | { 'sort' => 'authored_on', |
|---|
| 611 | direction => 'descend', |
|---|
| 612 | limit => $num }); |
|---|
| 613 | my @res; |
|---|
| 614 | while (my $entry = $iter->()) { |
|---|
| 615 | my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d", |
|---|
| 616 | unpack 'A4A2A2A2A2A2', $entry->authored_on; |
|---|
| 617 | my $row = { dateCreated => SOAP::Data->type(dateTime => $co), |
|---|
| 618 | userid => SOAP::Data->type(string => $entry->author_id) }; |
|---|
| 619 | $row->{ $param{page} ? 'page_id' : 'postid' } = |
|---|
| 620 | SOAP::Data->type(string => $entry->id); |
|---|
| 621 | if ($class eq 'blogger') { |
|---|
| 622 | $row->{content} = SOAP::Data->type(string => encode_text($entry->text, undef, 'utf-8')); |
|---|
| 623 | } else { |
|---|
| 624 | $row->{title} = SOAP::Data->type(string => encode_text($entry->title, undef, 'utf-8')); |
|---|
| 625 | unless ($titles_only) { |
|---|
| 626 | require MT::Tag; |
|---|
| 627 | my $tag_delim = chr($author->entry_prefs->{tag_delim}); |
|---|
| 628 | my $tags = MT::Tag->join($tag_delim, $entry->tags); |
|---|
| 629 | $row->{description} = SOAP::Data->type(string => encode_text($entry->text, undef, 'utf-8')); |
|---|
| 630 | my $link = $entry->permalink; |
|---|
| 631 | $row->{link} = SOAP::Data->type(string => $link); |
|---|
| 632 | $row->{permaLink} = SOAP::Data->type(string => $link), |
|---|
| 633 | $row->{mt_basename} = SOAP::Data->type(string => encode_text($entry->basename, undef, 'utf-8')); |
|---|
| 634 | $row->{mt_allow_comments} = SOAP::Data->type(int => $entry->allow_comments); |
|---|
| 635 | $row->{mt_allow_pings} = SOAP::Data->type(int => $entry->allow_pings); |
|---|
| 636 | $row->{mt_convert_breaks} = SOAP::Data->type(string => $entry->convert_breaks); |
|---|
| 637 | $row->{mt_text_more} = SOAP::Data->type(string => encode_text($entry->text_more, undef, 'utf-8')); |
|---|
| 638 | $row->{mt_excerpt} = SOAP::Data->type(string => encode_text($entry->excerpt, undef, 'utf-8')); |
|---|
| 639 | $row->{mt_keywords} = SOAP::Data->type(string => encode_text($entry->keywords, undef, 'utf-8')); |
|---|
| 640 | $row->{mt_tags} = SOAP::Data->type(string => encode_text($tags, undef, 'utf-8')); |
|---|
| 641 | } |
|---|
| 642 | } |
|---|
| 643 | push @res, $row; |
|---|
| 644 | } |
|---|
| 645 | \@res; |
|---|
| 646 | } |
|---|
| 647 | |
|---|
| 648 | sub getRecentPosts { |
|---|
| 649 | my $class = shift; |
|---|
| 650 | my ($blog_id, $user, $pass, $num); |
|---|
| 651 | if ($class eq 'blogger') { |
|---|
| 652 | (my($appkey), $blog_id, $user, $pass, $num) = @_; |
|---|
| 653 | } |
|---|
| 654 | else { |
|---|
| 655 | ($blog_id, $user, $pass, $num) = @_; |
|---|
| 656 | } |
|---|
| 657 | $class->_get_entries( blog_id => $blog_id, user => $user, |
|---|
| 658 | pass => $pass, num => $num ); |
|---|
| 659 | } |
|---|
| 660 | |
|---|
| 661 | sub getRecentPostTitles { |
|---|
| 662 | my $class = shift; |
|---|
| 663 | my ($blog_id, $user, $pass, $num) = @_; |
|---|
| 664 | $class->_get_entries( blog_id => $blog_id, user => $user, |
|---|
| 665 | pass => $pass, num => $num, titles_only => 1 ); |
|---|
| 666 | } |
|---|
| 667 | |
|---|
| 668 | sub getPages { |
|---|
| 669 | my $class = shift; |
|---|
| 670 | my ($blog_id, $user, $pass) = @_; |
|---|
| 671 | $class->_get_entries( blog_id => $blog_id, user => $user, |
|---|
| 672 | pass => $pass, page => 1 ); |
|---|
| 673 | } |
|---|
| 674 | |
|---|
| 675 | sub _delete_entry { |
|---|
| 676 | my $class = shift; |
|---|
| 677 | my %param = @_; |
|---|
| 678 | my ($blog_id, $entry_id, $user, $pass, $publish) = |
|---|
| 679 | @param{qw( blog_id entry_id user pass publish )}; |
|---|
| 680 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 681 | my $obj_type = $param{page} ? 'page' : 'entry'; |
|---|
| 682 | my $entry = MT->model($obj_type)->load($entry_id) |
|---|
| 683 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 684 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 685 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 686 | die _fault(MT->translate("Permission denied.")) |
|---|
| 687 | unless $perms && $perms->can_edit_entry($entry, $author); |
|---|
| 688 | $entry->remove; |
|---|
| 689 | |
|---|
| 690 | $mt->log({ |
|---|
| 691 | message => $mt->translate("Entry '[_1]' ([lc,_5] #[_2]) deleted by '[_3]' (user #[_4]) from xml-rpc", $entry->title, $entry->id, $author->name, $author->id, $entry->class_label), |
|---|
| 692 | level => MT::Log::INFO(), |
|---|
| 693 | class => 'system', |
|---|
| 694 | category => 'delete' |
|---|
| 695 | }); |
|---|
| 696 | |
|---|
| 697 | if ($publish) { |
|---|
| 698 | $class->_publish($mt, $entry, 1) or die _fault($class->errstr); |
|---|
| 699 | } |
|---|
| 700 | SOAP::Data->type(boolean => 1); |
|---|
| 701 | } |
|---|
| 702 | |
|---|
| 703 | sub deletePost { |
|---|
| 704 | my $class; |
|---|
| 705 | if (UNIVERSAL::isa($_[0] => __PACKAGE__)) { |
|---|
| 706 | $class = shift; |
|---|
| 707 | } else { |
|---|
| 708 | $class = __PACKAGE__; |
|---|
| 709 | } |
|---|
| 710 | my($appkey, $entry_id, $user, $pass, $publish) = @_; |
|---|
| 711 | $class->_delete_entry( entry_id => $entry_id, user => $user, |
|---|
| 712 | pass => $pass, publish => $publish ); |
|---|
| 713 | } |
|---|
| 714 | |
|---|
| 715 | sub deletePage { |
|---|
| 716 | my $class = shift; |
|---|
| 717 | my ($blog_id, $user, $pass, $entry_id) = @_; |
|---|
| 718 | $class->_delete_entry( blog_id => $blog_id, entry_id => $entry_id, |
|---|
| 719 | user => $user, pass => $pass, publish => 1, page => 1 ); |
|---|
| 720 | } |
|---|
| 721 | |
|---|
| 722 | sub _get_entry { |
|---|
| 723 | my $class = shift; |
|---|
| 724 | my %param = @_; |
|---|
| 725 | my($blog_id, $entry_id, $user, $pass) = |
|---|
| 726 | @param{qw( blog_id entry_id user pass )}; |
|---|
| 727 | my $obj_type = $param{page} ? 'page' : 'entry'; |
|---|
| 728 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 729 | my $entry = MT->model($obj_type)->load($entry_id) |
|---|
| 730 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 731 | if ($blog_id && $blog_id != $entry->blog_id) { |
|---|
| 732 | die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 733 | } |
|---|
| 734 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 735 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 736 | die _fault(MT->translate("Not privileged to get entry")) |
|---|
| 737 | unless $perms && $perms->can_edit_entry($entry, $author); |
|---|
| 738 | my $co = sprintf "%04d%02d%02dT%02d:%02d:%02d", |
|---|
| 739 | unpack 'A4A2A2A2A2A2', $entry->authored_on; |
|---|
| 740 | my $link = $entry->permalink; |
|---|
| 741 | require MT::Tag; |
|---|
| 742 | my $delim = chr($author->entry_prefs->{tag_delim}); |
|---|
| 743 | my $tags = MT::Tag->join($delim, $entry->tags); |
|---|
| 744 | |
|---|
| 745 | my $cats = []; |
|---|
| 746 | my $cat_data = $entry->__load_category_data(); |
|---|
| 747 | if (scalar @$cat_data) { |
|---|
| 748 | my ($first_cat) = grep { $_->[1] } @$cat_data; |
|---|
| 749 | my @cat_ids = grep { !$_->[1] } @$cat_data; |
|---|
| 750 | unshift @cat_ids, $first_cat if $first_cat; |
|---|
| 751 | $cats = MT->model('category')->lookup_multi( |
|---|
| 752 | [ map { $_->[0] } @cat_ids ]); |
|---|
| 753 | } |
|---|
| 754 | |
|---|
| 755 | my $basename = SOAP::Data->type(string => encode_text($entry->basename, undef, 'utf-8')); |
|---|
| 756 | { |
|---|
| 757 | dateCreated => SOAP::Data->type(dateTime => $co), |
|---|
| 758 | userid => SOAP::Data->type(string => $entry->author_id), |
|---|
| 759 | ($param{page} ? 'page_id' : 'postid') => SOAP::Data->type(string => $entry->id), |
|---|
| 760 | description => SOAP::Data->type(string => encode_text($entry->text, undef, 'utf-8')), |
|---|
| 761 | title => SOAP::Data->type(string => encode_text($entry->title, undef, 'utf-8')), |
|---|
| 762 | mt_basename => $basename, |
|---|
| 763 | wp_slug => $basename, |
|---|
| 764 | link => SOAP::Data->type(string => $link), |
|---|
| 765 | permaLink => SOAP::Data->type(string => $link), |
|---|
| 766 | categories => [ map { SOAP::Data->type(string => $_->label) } @$cats ], |
|---|
| 767 | mt_allow_comments => SOAP::Data->type(int => $entry->allow_comments), |
|---|
| 768 | mt_allow_pings => SOAP::Data->type(int => $entry->allow_pings), |
|---|
| 769 | mt_convert_breaks => SOAP::Data->type(string => $entry->convert_breaks), |
|---|
| 770 | mt_text_more => SOAP::Data->type(string => encode_text($entry->text_more, undef, 'utf-8')), |
|---|
| 771 | mt_excerpt => SOAP::Data->type(string => encode_text($entry->excerpt, undef, 'utf-8')), |
|---|
| 772 | mt_keywords => SOAP::Data->type(string => encode_text($entry->keywords, undef, 'utf-8')), |
|---|
| 773 | mt_tags => SOAP::Data->type(string => encode_text($tags, undef, 'utf-8')), |
|---|
| 774 | } |
|---|
| 775 | } |
|---|
| 776 | |
|---|
| 777 | sub getPost { |
|---|
| 778 | my $class = shift; |
|---|
| 779 | my($entry_id, $user, $pass) = @_; |
|---|
| 780 | $class->_get_entry( entry_id => $entry_id, user => $user, pass => $pass ); |
|---|
| 781 | } |
|---|
| 782 | |
|---|
| 783 | sub getPage { |
|---|
| 784 | my $class = shift; |
|---|
| 785 | my ($blog_id, $entry_id, $user, $pass) = @_; |
|---|
| 786 | $class->_get_entry( blog_id => $blog_id, entry_id => $entry_id, |
|---|
| 787 | user => $user, pass => $pass, page => 1 ); |
|---|
| 788 | } |
|---|
| 789 | |
|---|
| 790 | sub supportedMethods { |
|---|
| 791 | [ 'blogger.newPost', 'blogger.editPost', 'blogger.getRecentPosts', |
|---|
| 792 | 'blogger.getUsersBlogs', 'blogger.getUserInfo', 'blogger.deletePost', |
|---|
| 793 | 'metaWeblog.getPost', 'metaWeblog.newPost', 'metaWeblog.editPost', |
|---|
| 794 | 'metaWeblog.getRecentPosts', 'metaWeblog.newMediaObject', |
|---|
| 795 | 'metaWeblog.getCategories', 'metaWeblog.deletePost', |
|---|
| 796 | 'metaWeblog.getUsersBlogs', |
|---|
| 797 | 'wp.newPage', 'wp.getPages', 'wp.getPage', 'wp.editPage', |
|---|
| 798 | 'wp.deletePage', |
|---|
| 799 | # not yet supported: metaWeblog.getTemplate, metaWeblog.setTemplate |
|---|
| 800 | 'mt.getCategoryList', 'mt.setPostCategories', 'mt.getPostCategories', |
|---|
| 801 | 'mt.getTrackbackPings', 'mt.supportedTextFilters', |
|---|
| 802 | 'mt.getRecentPostTitles', 'mt.publishPost', 'mt.getTagList' ]; |
|---|
| 803 | } |
|---|
| 804 | |
|---|
| 805 | sub supportedTextFilters { |
|---|
| 806 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 807 | my $filters = $mt->all_text_filters; |
|---|
| 808 | my @res; |
|---|
| 809 | for my $filter (keys %$filters) { |
|---|
| 810 | my $label = $filters->{$filter}{label}; |
|---|
| 811 | if ('CODE' eq ref($label)) { |
|---|
| 812 | $label = $label->(); |
|---|
| 813 | } |
|---|
| 814 | push @res, { |
|---|
| 815 | key => SOAP::Data->type(string => $filter), |
|---|
| 816 | label => SOAP::Data->type(string => $label) |
|---|
| 817 | }; |
|---|
| 818 | } |
|---|
| 819 | \@res; |
|---|
| 820 | } |
|---|
| 821 | |
|---|
| 822 | ## getCategoryList, getPostCategories, and setPostCategories were |
|---|
| 823 | ## originally written by Daniel Drucker with the assistance of |
|---|
| 824 | ## Six Apart, then later modified by Six Apart. |
|---|
| 825 | |
|---|
| 826 | sub getCategoryList { |
|---|
| 827 | my $class = shift; |
|---|
| 828 | my($blog_id, $user, $pass) = @_; |
|---|
| 829 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 830 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 831 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 832 | die _fault(MT->translate("Permission denied.")) |
|---|
| 833 | unless $perms && $perms->can_create_post; |
|---|
| 834 | require MT::Category; |
|---|
| 835 | my $iter = MT::Category->load_iter({ blog_id => $blog_id }); |
|---|
| 836 | my @data; |
|---|
| 837 | while (my $cat = $iter->()) { |
|---|
| 838 | push @data, { |
|---|
| 839 | categoryName => SOAP::Data->type(string => encode_text($cat->label, undef, 'utf-8')), |
|---|
| 840 | categoryId => SOAP::Data->type(string => $cat->id) |
|---|
| 841 | }; |
|---|
| 842 | } |
|---|
| 843 | \@data; |
|---|
| 844 | } |
|---|
| 845 | |
|---|
| 846 | sub getCategories { |
|---|
| 847 | my $class = shift; |
|---|
| 848 | my($blog_id, $user, $pass) = @_; |
|---|
| 849 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 850 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 851 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 852 | die _fault(MT->translate("Permission denied.")) |
|---|
| 853 | unless $perms && $perms->can_create_post; |
|---|
| 854 | require MT::Category; |
|---|
| 855 | my $iter = MT::Category->load_iter({ blog_id => $blog_id }); |
|---|
| 856 | my @data; |
|---|
| 857 | my $blog = MT::Blog->load($blog_id); |
|---|
| 858 | require File::Spec; |
|---|
| 859 | while (my $cat = $iter->()) { |
|---|
| 860 | my $url = File::Spec->catfile($blog->site_url, archive_file_for( undef, $blog, 'Category', $cat )); |
|---|
| 861 | push @data, { |
|---|
| 862 | categoryId => SOAP::Data->type(string => encode_text($cat->id, undef, 'utf-8')), |
|---|
| 863 | parentId => ($cat->parent_category ? SOAP::Data->type(string => encode_text($cat->parent_category->id, undef, 'utf-8')) : undef), |
|---|
| 864 | categoryName => SOAP::Data->type(string => encode_text($cat->label, undef, 'utf-8')), |
|---|
| 865 | title => SOAP::Data->type(string => encode_text($cat->label, undef, 'utf-8')), |
|---|
| 866 | description => SOAP::Data->type(string => encode_text($cat->description, undef, 'utf-8')), |
|---|
| 867 | htmlUrl => SOAP::Data->type(string => encode_text($url, undef, 'utf-8')), |
|---|
| 868 | }; |
|---|
| 869 | } |
|---|
| 870 | \@data; |
|---|
| 871 | } |
|---|
| 872 | |
|---|
| 873 | sub getTagList { |
|---|
| 874 | my $class = shift; |
|---|
| 875 | my($blog_id, $user, $pass) = @_; |
|---|
| 876 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 877 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 878 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 879 | die _fault(MT->translate("Permission denied.")) |
|---|
| 880 | unless $perms && $perms->can_create_post; |
|---|
| 881 | require MT::Tag; |
|---|
| 882 | require MT::ObjectTag; |
|---|
| 883 | my $iter = MT::Tag->load_iter(undef, { join => ['MT::ObjectTag', 'tag_id', { blog_id => $blog_id }, { unique => 1 } ] } ); |
|---|
| 884 | my @data; |
|---|
| 885 | while (my $tag = $iter->()) { |
|---|
| 886 | push @data, { |
|---|
| 887 | tagName => SOAP::Data->type(string => encode_text($tag->name, undef, 'utf-8')), |
|---|
| 888 | tagId => SOAP::Data->type(string => $tag->id) |
|---|
| 889 | }; |
|---|
| 890 | } |
|---|
| 891 | \@data; |
|---|
| 892 | } |
|---|
| 893 | |
|---|
| 894 | sub getPostCategories { |
|---|
| 895 | my $class = shift; |
|---|
| 896 | my($entry_id, $user, $pass) = @_; |
|---|
| 897 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 898 | require MT::Entry; |
|---|
| 899 | my $entry = MT::Entry->load($entry_id) |
|---|
| 900 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 901 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 902 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 903 | die _fault(MT->translate("Permission denied.")) unless $perms && $perms->can_create_post; |
|---|
| 904 | my @data; |
|---|
| 905 | my $prim = $entry->category; |
|---|
| 906 | my $cats = $entry->categories; |
|---|
| 907 | for my $cat (@$cats) { |
|---|
| 908 | my $is_primary = $prim && $cat->id == $prim->id ? 1 : 0; |
|---|
| 909 | push @data, { |
|---|
| 910 | categoryName => SOAP::Data->type(string => encode_text($cat->label, undef, 'utf-8')), |
|---|
| 911 | categoryId => SOAP::Data->type(string => $cat->id), |
|---|
| 912 | isPrimary => SOAP::Data->type(boolean => $is_primary), |
|---|
| 913 | }; |
|---|
| 914 | } |
|---|
| 915 | \@data; |
|---|
| 916 | } |
|---|
| 917 | |
|---|
| 918 | sub setPostCategories { |
|---|
| 919 | my $class = shift; |
|---|
| 920 | my($entry_id, $user, $pass, $cats) = @_; |
|---|
| 921 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 922 | require MT::Entry; |
|---|
| 923 | require MT::Placement; |
|---|
| 924 | my $entry = MT::Entry->load($entry_id) |
|---|
| 925 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 926 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 927 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 928 | die _fault(MT->translate("Not privileged to set entry categories")) |
|---|
| 929 | unless $perms && $perms->can_edit_entry($entry, $author); |
|---|
| 930 | my @place = MT::Placement->load({ entry_id => $entry_id }); |
|---|
| 931 | for my $place (@place) { |
|---|
| 932 | $place->remove; |
|---|
| 933 | } |
|---|
| 934 | ## Keep track of which category is named the primary category. |
|---|
| 935 | ## If the first structure in the array does not have an isPrimary |
|---|
| 936 | ## key, we just make it the primary category; if it does, we use |
|---|
| 937 | ## that flag to determine the primary category. |
|---|
| 938 | my $is_primary = 1; |
|---|
| 939 | for my $cat (@$cats) { |
|---|
| 940 | my $place = MT::Placement->new; |
|---|
| 941 | $place->entry_id($entry_id); |
|---|
| 942 | $place->blog_id($entry->blog_id); |
|---|
| 943 | if (defined $cat->{isPrimary} && $is_primary) { |
|---|
| 944 | $place->is_primary($cat->{isPrimary}); |
|---|
| 945 | } else { |
|---|
| 946 | $place->is_primary($is_primary); |
|---|
| 947 | } |
|---|
| 948 | ## If we just set the is_primary flag to 1, we don't want to |
|---|
| 949 | ## make any other categories primary. |
|---|
| 950 | $is_primary = 0 if $place->is_primary; |
|---|
| 951 | $place->category_id($cat->{categoryId}); |
|---|
| 952 | $place->save |
|---|
| 953 | or die _fault(MT->translate("Saving placement failed: [_1]", $place->errstr)); |
|---|
| 954 | } |
|---|
| 955 | SOAP::Data->type(boolean => 1); |
|---|
| 956 | } |
|---|
| 957 | |
|---|
| 958 | sub getTrackbackPings { |
|---|
| 959 | my $class = shift; |
|---|
| 960 | my($entry_id) = @_; |
|---|
| 961 | require MT::Trackback; |
|---|
| 962 | require MT::TBPing; |
|---|
| 963 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 964 | my $tb = MT::Trackback->load({ entry_id => $entry_id }) |
|---|
| 965 | or return []; |
|---|
| 966 | my $iter = MT::TBPing->load_iter({ tb_id => $tb->id }); |
|---|
| 967 | my @data; |
|---|
| 968 | while (my $ping = $iter->()) { |
|---|
| 969 | push @data, { |
|---|
| 970 | pingTitle => SOAP::Data->type(string => encode_text($ping->title, undef, 'utf-8')), |
|---|
| 971 | pingURL => SOAP::Data->type(string => $ping->source_url), |
|---|
| 972 | pingIP => SOAP::Data->type(string => $ping->ip), |
|---|
| 973 | }; |
|---|
| 974 | } |
|---|
| 975 | \@data; |
|---|
| 976 | } |
|---|
| 977 | |
|---|
| 978 | sub publishPost { |
|---|
| 979 | my $class = shift; |
|---|
| 980 | my($entry_id, $user, $pass) = @_; |
|---|
| 981 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 982 | require MT::Entry; |
|---|
| 983 | my $entry = MT::Entry->load($entry_id) |
|---|
| 984 | or die _fault(MT->translate("Invalid entry ID '[_1]'", $entry_id)); |
|---|
| 985 | my($author, $perms) = $class->_login($user, $pass, $entry->blog_id); |
|---|
| 986 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 987 | die _fault(MT->translate("Not privileged to edit entry")) |
|---|
| 988 | unless $perms && $perms->can_edit_entry($entry, $author); |
|---|
| 989 | $mt->rebuild_entry( Entry => $entry, BuildDependencies => 1 ) |
|---|
| 990 | or die _fault(MT->translate("Publish failed: [_1]", $mt->errstr)); |
|---|
| 991 | SOAP::Data->type(boolean => 1); |
|---|
| 992 | } |
|---|
| 993 | |
|---|
| 994 | sub runPeriodicTasks { |
|---|
| 995 | my $class = shift; |
|---|
| 996 | my ($user, $pass) = @_; |
|---|
| 997 | |
|---|
| 998 | my $mt = MT::XMLRPCServer::Util::mt_new(); |
|---|
| 999 | my $author = $class->_login($user, $pass); |
|---|
| 1000 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 1001 | |
|---|
| 1002 | $mt->run_tasks; |
|---|
| 1003 | |
|---|
| 1004 | { responseCode => 'success' } |
|---|
| 1005 | } |
|---|
| 1006 | |
|---|
| 1007 | sub publishScheduledFuturePosts { |
|---|
| 1008 | my $class = shift; |
|---|
| 1009 | my ($blog_id, $user, $pass) = @_; |
|---|
| 1010 | |
|---|
| 1011 | my $mt = MT::XMLRPCServer::Util::mt_new(); |
|---|
| 1012 | my $author = $class->_login($user, $pass); |
|---|
| 1013 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 1014 | my $blog = MT::Blog->load($blog_id) |
|---|
| 1015 | or die _fault(MT->translate('Can\'t load blog #[_1].', $blog_id)); |
|---|
| 1016 | |
|---|
| 1017 | my $now = time; |
|---|
| 1018 | # Convert $now to user's timezone, which is how future post dates |
|---|
| 1019 | # are stored. |
|---|
| 1020 | $now = MT::Util::offset_time($now); |
|---|
| 1021 | $now = strftime("%Y%m%d%H%M%S", gmtime($now)); |
|---|
| 1022 | |
|---|
| 1023 | my $iter = MT::Entry->load_iter({ |
|---|
| 1024 | blog_id => $blog->id, |
|---|
| 1025 | class => '*', |
|---|
| 1026 | status => MT::Entry::FUTURE()}, |
|---|
| 1027 | {'sort' => 'authored_on', |
|---|
| 1028 | direction => 'descend' |
|---|
| 1029 | }); |
|---|
| 1030 | my @queue; |
|---|
| 1031 | while (my $i = $iter->()) { |
|---|
| 1032 | push @queue, $i->id(); |
|---|
| 1033 | } |
|---|
| 1034 | |
|---|
| 1035 | my $changed = 0; |
|---|
| 1036 | my $total_changed = 0; |
|---|
| 1037 | my @results; |
|---|
| 1038 | my %types; |
|---|
| 1039 | foreach my $entry_id (@queue) { |
|---|
| 1040 | my $entry = MT::Entry->load($entry_id); |
|---|
| 1041 | if ($entry && $entry->authored_on <= $now) { |
|---|
| 1042 | $entry->status(MT::Entry::RELEASE()); |
|---|
| 1043 | $entry->discover_tb_from_entry(); |
|---|
| 1044 | $entry->save |
|---|
| 1045 | or die $entry->errstr; |
|---|
| 1046 | |
|---|
| 1047 | $types{$entry->class} = 1; |
|---|
| 1048 | start_background_task(sub { |
|---|
| 1049 | $mt->rebuild_entry( Entry => $entry, Blog => $blog ) |
|---|
| 1050 | or die $mt->errstr; |
|---|
| 1051 | }); |
|---|
| 1052 | $changed++; |
|---|
| 1053 | $total_changed++; |
|---|
| 1054 | } |
|---|
| 1055 | } |
|---|
| 1056 | $blog->touch( keys %types ) if $changed; |
|---|
| 1057 | $blog->save if $changed && (keys %types); |
|---|
| 1058 | |
|---|
| 1059 | if ($changed) { |
|---|
| 1060 | $mt->rebuild_indexes( Blog => $blog ) |
|---|
| 1061 | or die $mt->errstr; |
|---|
| 1062 | } |
|---|
| 1063 | { responseCode => 'success', |
|---|
| 1064 | publishedCount => $total_changed, |
|---|
| 1065 | }; |
|---|
| 1066 | } |
|---|
| 1067 | |
|---|
| 1068 | sub getNextScheduled { |
|---|
| 1069 | my $class = shift; |
|---|
| 1070 | my ($user, $pass) = @_; |
|---|
| 1071 | |
|---|
| 1072 | my $mt = MT::XMLRPCServer::Util::mt_new(); |
|---|
| 1073 | my $author = $class->_login($user, $pass); |
|---|
| 1074 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 1075 | |
|---|
| 1076 | my $next_scheduled = MT::get_next_sched_post_for_user($author->id()); |
|---|
| 1077 | |
|---|
| 1078 | { nextScheduledTime => $next_scheduled }; |
|---|
| 1079 | } |
|---|
| 1080 | |
|---|
| 1081 | sub setRemoteAuthToken { |
|---|
| 1082 | my $class = shift; |
|---|
| 1083 | my ($user, $pass, $remote_auth_username, $remote_auth_token) = @_; |
|---|
| 1084 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 1085 | my($author) = $class->_login($user, $pass); |
|---|
| 1086 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 1087 | $author->remote_auth_username($remote_auth_username); |
|---|
| 1088 | $author->remote_auth_token($remote_auth_token); |
|---|
| 1089 | $author->save(); |
|---|
| 1090 | 1; |
|---|
| 1091 | } |
|---|
| 1092 | |
|---|
| 1093 | sub newMediaObject { |
|---|
| 1094 | my $class = shift; |
|---|
| 1095 | my($blog_id, $user, $pass, $file) = @_; |
|---|
| 1096 | my $mt = MT::XMLRPCServer::Util::mt_new(); ## Will die if MT->new fails. |
|---|
| 1097 | my($author, $perms) = $class->_login($user, $pass, $blog_id); |
|---|
| 1098 | die _fault(MT->translate("Invalid login")) unless $author; |
|---|
| 1099 | die _fault(MT->translate("Not privileged to upload files")) |
|---|
| 1100 | unless $perms && $perms->can_upload; |
|---|
| 1101 | require MT::Blog; |
|---|
| 1102 | require File::Spec; |
|---|
| 1103 | my $blog = MT::Blog->load($blog_id) |
|---|
| 1104 | or die _fault(MT->translate('Can\'t load blog #[_1].', $blog_id)); |
|---|
| 1105 | |
|---|
| 1106 | my $fname = $file->{name} or die _fault(MT->translate("No filename provided")); |
|---|
| 1107 | if ($fname =~ m!\.\.|\0|\|!) { |
|---|
| 1108 | die _fault(MT->translate("Invalid filename '[_1]'", $fname)); |
|---|
| 1109 | } |
|---|
| 1110 | my $local_file = File::Spec->catfile($blog->site_path, $file->{name}); |
|---|
| 1111 | my $fmgr = $blog->file_mgr; |
|---|
| 1112 | my($vol, $path, $name) = File::Spec->splitpath($local_file); |
|---|
| 1113 | $path =~ s!/$!! unless $path eq '/'; ## OS X doesn't like / at the end in mkdir(). |
|---|
| 1114 | unless ($fmgr->exists($path)) { |
|---|
| 1115 | $fmgr->mkpath($path) |
|---|
| 1116 | or die _fault(MT->translate("Error making path '[_1]': [_2]", $path, $fmgr->errstr)); |
|---|
| 1117 | } |
|---|
| 1118 | defined(my $bytes = $fmgr->put_data($file->{bits}, $local_file, 'upload')) |
|---|
| 1119 | or die _fault(MT->translate("Error writing uploaded file: [_1]", $fmgr->errstr)); |
|---|
| 1120 | my $url = $blog->site_url . $fname; |
|---|
| 1121 | |
|---|
| 1122 | require File::Basename; |
|---|
| 1123 | my $local_basename = File::Basename::basename($local_file); |
|---|
| 1124 | my $ext = |
|---|
| 1125 | ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2]; |
|---|
| 1126 | eval { require Image::Size; }; |
|---|
| 1127 | die _fault(MT->translate("Perl module Image::Size is required to determine width and height of uploaded images.")) if $@; |
|---|
| 1128 | my ( $w, $h, $id ) = Image::Size::imgsize($local_file); |
|---|
| 1129 | |
|---|
| 1130 | require MT::Asset; |
|---|
| 1131 | my $asset_pkg = MT::Asset->handler_for_file($local_basename); |
|---|
| 1132 | my $is_image = defined($w) |
|---|
| 1133 | && defined($h) |
|---|
| 1134 | && $asset_pkg->isa('MT::Asset::Image'); |
|---|
| 1135 | my $asset; |
|---|
| 1136 | if (!($asset = $asset_pkg->load( |
|---|
| 1137 | { file_path => $local_file, blog_id => $blog_id }))) |
|---|
| 1138 | { |
|---|
| 1139 | $asset = $asset_pkg->new(); |
|---|
| 1140 | $asset->file_path($local_file); |
|---|
| 1141 | $asset->file_name($local_basename); |
|---|
| 1142 | $asset->file_ext($ext); |
|---|
| 1143 | $asset->blog_id($blog_id); |
|---|
| 1144 | $asset->created_by( $author->id ); |
|---|
| 1145 | } |
|---|
| 1146 | else { |
|---|
| 1147 | $asset->modified_by( $author->id ); |
|---|
| 1148 | } |
|---|
| 1149 | my $original = $asset->clone; |
|---|
| 1150 | $asset->url($url); |
|---|
| 1151 | if ($is_image) { |
|---|
| 1152 | $asset->image_width($w); |
|---|
| 1153 | $asset->image_height($h); |
|---|
| 1154 | } |
|---|
| 1155 | $asset->mime_type($file->{type}); |
|---|
| 1156 | $asset->save; |
|---|
| 1157 | |
|---|
| 1158 | $blog->touch('asset'); |
|---|
| 1159 | $blog->save; |
|---|
| 1160 | |
|---|
| 1161 | MT->run_callbacks( |
|---|
| 1162 | 'api_upload_file.' . $asset->class, |
|---|
| 1163 | File => $local_file, file => $local_file, |
|---|
| 1164 | Url => $url, url => $url, |
|---|
| 1165 | Size => $bytes, size => $bytes, |
|---|
| 1166 | Asset => $asset, asset => $asset, |
|---|
| 1167 | Type => $asset->class, type => $asset->class, |
|---|
| 1168 | Blog => $blog,blog => $blog); |
|---|
| 1169 | if ($is_image) { |
|---|
| 1170 | MT->run_callbacks( |
|---|
| 1171 | 'api_upload_image', |
|---|
| 1172 | File => $local_file, file => $local_file, |
|---|
| 1173 | Url => $url, url => $url, |
|---|
| 1174 | Size => $bytes, size => $bytes, |
|---|
| 1175 | Asset => $asset, asset => $asset, |
|---|
| 1176 | Height => $h, height => $h, |
|---|
| 1177 | Width => $w, width => $w, |
|---|
| 1178 | Type => 'image', type => 'image', |
|---|
| 1179 | ImageType => $id, image_type => $id, |
|---|
| 1180 | Blog => $blog, blog => $blog); |
|---|
| 1181 | } |
|---|
| 1182 | |
|---|
| 1183 | { url => SOAP::Data->type(string => $url) }; |
|---|
| 1184 | } |
|---|
| 1185 | |
|---|
| 1186 | ## getTemplate and setTemplate are not applicable in MT's template |
|---|
| 1187 | ## structure, so they are unimplemented (they return a fault). |
|---|
| 1188 | ## We assign it twice to get rid of "setTemplate used only once" warnings. |
|---|
| 1189 | |
|---|
| 1190 | sub getTemplate { |
|---|
| 1191 | die _fault(MT->translate( |
|---|
| 1192 | "Template methods are not implemented, due to differences between the Blogger API and the Movable Type API.")); |
|---|
| 1193 | } |
|---|
| 1194 | *setTemplate = *setTemplate = \&getTemplate; |
|---|
| 1195 | |
|---|
| 1196 | ## The above methods will be called as blogger.newPost, blogger.editPost, |
|---|
| 1197 | ## etc., because we are implementing Blogger's API. Thus, the empty |
|---|
| 1198 | ## subclass. |
|---|
| 1199 | package blogger; |
|---|
| 1200 | BEGIN { @blogger::ISA = qw( MT::XMLRPCServer ); } |
|---|
| 1201 | |
|---|
| 1202 | package metaWeblog; |
|---|
| 1203 | BEGIN { @metaWeblog::ISA = qw( MT::XMLRPCServer ); } |
|---|
| 1204 | |
|---|
| 1205 | package mt; |
|---|
| 1206 | BEGIN { @mt::ISA = qw( MT::XMLRPCServer ); } |
|---|
| 1207 | |
|---|
| 1208 | package wp; |
|---|
| 1209 | BEGIN { @wp::ISA = qw( MT::XMLRPCServer ); } |
|---|
| 1210 | |
|---|
| 1211 | 1; |
|---|
| 1212 | __END__ |
|---|
| 1213 | |
|---|
| 1214 | =head1 NAME |
|---|
| 1215 | |
|---|
| 1216 | MT::XMLRPCServer |
|---|
| 1217 | |
|---|
| 1218 | =head1 SYNOPSIS |
|---|
| 1219 | |
|---|
| 1220 | An XMLRPC API interface for communicating with Movable Type. |
|---|
| 1221 | |
|---|
| 1222 | =head1 CALLBACKS |
|---|
| 1223 | |
|---|
| 1224 | =over 4 |
|---|
| 1225 | |
|---|
| 1226 | =item api_pre_save.entry |
|---|
| 1227 | =item api_pre_save.page |
|---|
| 1228 | |
|---|
| 1229 | callback($eh, $mt, $entry, $original_entry) |
|---|
| 1230 | |
|---|
| 1231 | Called before saving a new or existing entry. If saving a new entry, the |
|---|
| 1232 | $original_entry will have an unassigned 'id'. This callback is executed |
|---|
| 1233 | as a filter, so your handler must return 1 to allow the entry to be saved. |
|---|
| 1234 | |
|---|
| 1235 | =item api_post_save.entry |
|---|
| 1236 | =item api_post_save.page |
|---|
| 1237 | |
|---|
| 1238 | callback($eh, $mt, $entry, $original_entry) |
|---|
| 1239 | |
|---|
| 1240 | Called after saving a new or existing entry. If saving a new entry, the |
|---|
| 1241 | $original_entry will have an unassigned 'id'. |
|---|
| 1242 | |
|---|
| 1243 | =item api_upload_file |
|---|
| 1244 | |
|---|
| 1245 | callback($eh, %params) |
|---|
| 1246 | |
|---|
| 1247 | This callback is invoked for each file the user uploads to the weblog. |
|---|
| 1248 | This callback is similar to the CMSUploadFile callback found in |
|---|
| 1249 | C<MT::App::CMS>. |
|---|
| 1250 | |
|---|
| 1251 | =back |
|---|
| 1252 | |
|---|
| 1253 | =head2 Parameters |
|---|
| 1254 | |
|---|
| 1255 | =over 4 |
|---|
| 1256 | |
|---|
| 1257 | =item File |
|---|
| 1258 | |
|---|
| 1259 | The full physical file path of the uploaded file. |
|---|
| 1260 | |
|---|
| 1261 | =item Url |
|---|
| 1262 | |
|---|
| 1263 | The full URL to the file that has been uploaded. |
|---|
| 1264 | |
|---|
| 1265 | =item Type |
|---|
| 1266 | |
|---|
| 1267 | For this callback, this value is currently always 'file'. |
|---|
| 1268 | |
|---|
| 1269 | =item Blog |
|---|
| 1270 | |
|---|
| 1271 | The C<MT::Blog> object associated with the newly uploaded file. |
|---|
| 1272 | |
|---|
| 1273 | =back |
|---|
| 1274 | |
|---|
| 1275 | =cut |
|---|