=pod

=head1 NAME

WordPress::XMLRPC - api to wordpress xml rpc calls

=head1 SYNOPSIS

   use WordPress::XMLRPC;
      
   my $o = WordPress:::XMLRPC->new({
     username => 'author1',
     password => 'superpass',
     proxy => 'http://mysite.com/xmlrpc.php',
   });
   
   my $post = $o->getPost(5); # id 5
   
   # let's change the title
   $post->{title} = 'I did not like the old title.';
   
   # let's save the changes back to the server..
   $o->editPost(5, $post, 1); # 1 is publish


=head1 DESCRIPTION

I wanted to interact via the command line to a wordpress blog's xmlrpc.php file.
Bascially this is interaction with xmlrpc.php as client.
This module is not meant for speed, it is meant for convenience.

This is really useful to automate new postings, uploading media, etc.

=head2 WordPress version

This code was built against xmlrpc.php included in WordPress 2.8.4.
As new calls are included in xmlrpc.php, this module is be updated.

=head1 CONSTRUCTOR

=head2 new()

Optional arg is hash ref.

Before we open a connection with xmlrpc, we need to have 
username, password, and proxy in the object's data.
You can provide this in the following ways..

   my $o = WordPress:::XMLRPC->new({
     username => 'author1',
     password => 'superpass',
     proxy => 'http://mysite.com/xmlrpc.php',
   });

Or..

   my $o = WordPress:::XMLRPC->new;  
   
   $o->username('author1');
   $o->password('superpass');
   $o->proxy('http://mysite.com/xmlrpc.php');
   
   $o->server 
      or die( 
         sprintf 'could not connect with %s:%s to %s',
            $self->username,
            $self->password,
            $self->proxy,
         );

=head1 METHODS

=head2 xmlrpc_methods()

Returns array of methods in this package that make calls via xmlrpc.

=head2 server_methods()

Returns array of server methods accessible via xmlrpc.

=head2 username()

Perl set/get method. Argument is string.
If you pass 'username' to constructor, it is prepopulated.

   my $username = $o->username;
   $o->username('bill');

=head2 password()

Perl set/get method. Argument is string.
If you pass 'password' to constructor, it is prepopulated.

   my $pw = $o->password;
   $o->password('jim');

=head2 proxy()

Perl set/get method. Argument is string.
If you pass 'proxy' to constructor, it is prepopulated.

=head2 server()

Returns XMLRPC::Lite object.
proxy() must be set.

=head2 blog_id()

Setget method, set to '1' by default.
This seems unused by wordpress. They have some documentation on this.

=head2 publish()

Many methods use 'publish' boolean value, by default we set to 1.
You can still pass a value for publish such as;

   $o->newPost( $content_hashref, 1 );

But you can also call;

   $o->newPost( $content_hashref );

As we said, by default it is set to 1, if you want to set the default to 0,

   $o->publish(0);

=head2 errstr()

Returns error string if a call fails. 

   $o->newPost(@args) or die($o->errstr);

If the DEBUG flag is on, this warns to STDERR automatically as well.

=cut


=pod

=head2 XML RPC METHODS

These methods specifically mirror the xmlrpc.php file provided by WordPress installations.
This file sits on your website.

=head3 getPage()

Takes 1 args: page_id (number).
Returns page hashref struct(ure).

Example return:

	 $val: {
	         categories => [
	                         'Uncategorized'
	                       ],
	         dateCreated => '20080121T12:38:30',
	         date_created_gmt => '20080121T20:38:30',
	         description => 'These are some interesting resources online.',
	         excerpt => '',
	         link => 'http://leocharre.com/perl-resources/',
	         mt_allow_comments => '0',
	         mt_allow_pings => '0',
	         page_id => '87',
	         page_status => 'publish',
	         permaLink => 'http://leocharre.com/perl-resources/',
	         text_more => '',
	         title => 'Resources',
	         userid => '2',
	         wp_author => 'leocharre',
	         wp_author_display_name => 'leocharre',
	         wp_author_id => '2',
	         wp_page_order => '0',
	         wp_page_parent_id => '0',
	         wp_page_parent_title => '',
	         wp_password => '',
	         wp_slug => 'perl-resources'
	       }

This is the same struct hashref you would send to newPage().

=head3 getPages()

Returns array ref.
Each element is a hash ref same as getPage() returns.
If you want less info, just basic info on each page, use getPageList().

=head3 newPage()

Takes 2 args: page (hashref), publish (boolean).
You can leave out publish, as discussed further in this documentation.
The hashref must have at least a title and description.
Returns page id (number, assigned by server).

=head3 deletePage()

Takes 1 args: page_id (number).
Returns boolean (true or false).

=head3 editPage()

Takes 2 args: page (hashref), publish(boolean).
The page hashref is just as discussed in getPage().

You could use getPage(), edit the returned hashref, and resubmit with editPage().

   my $page_hashref = $o->getPage(5);
   $page_hashref->{title} = 'This is the New Title';

   $o->editPage($page_hashref) or die( $o->errstr );

Obviously the page id is in the page data (hashref), this is there inherently when you
call getPage().

The same would be done with the posts.

=head3 getPageList()

Returns arrayref.
Each element is a hashref.
This is sort of a short version of getPages(), which returns all info for each.

Example return:

	 $return_value: [
	                  {
	                    dateCreated => '20061113T11:08:22',
	                    date_created_gmt => '20061113T19:08:22',
	                    page_id => '2',
	                    page_parent_id => '0',
	                    page_title => 'About Moi'
	                  },
	                  {
	                    dateCreated => '20080105T18:57:24',
	                    date_created_gmt => '20080106T02:57:24',
	                    page_id => '43',
	                    page_parent_id => '74',
	                    page_title => 'tree'
	                  },
	                ]

=cut





=pod

=head3 getAuthors()

Takes no argument.
Returns array ref, each element is a hashref.

	 $return_value: [
	                  {
	                    display_name => 'leo',
	                    user_id => '2',
	                    user_login => 'leo'
	                  },
	                  {
	                    display_name => 'chamon',
	                    user_id => '3',
	                    user_login => 'chamon'
	                  }
	                ]

=cut








=pod

=head3 getCategories()

Takes no argument.
Example return value:

	 $return_value: [
	                  {
	                    categoryId => '4',
	                    categoryName => 'art',
	                    description => 'art',
	                    htmlUrl => 'http://leocharre.com/articles/category/art/',
	                    parentId => '0',
	                    rssUrl => 'http://leocharre.com/articles/category/art/feed/'
	                  },
	                  {
	                    categoryId => '1',
	                    categoryName => 'Uncategorized',
	                    description => 'Uncategorized',
	                    htmlUrl => 'http://leocharre.com/articles/category/uncategorized/',
	                    parentId => '0',
	                    rssUrl => 'http://leocharre.com/articles/category/uncategorized/feed/'
	                  }
	                ]

=head3 newCategory()

Takes 1 args: category struct.
Returns category id (number).

The category struct is a hash ref alike..

   {
      name => 'Ugly houses',
      parent_id => 34, # (if this is a sub category )
      description => 'this is a great category',
   }

The key 'name' must be present or croaks.

=head3 getCategory()

Argument is category id, will return struct (hash ref).

   $got: {
           categoryId => 99,
           categoryName => 'category772',
           description => 'category772',
           htmlUrl => 'http://leocharre.com/articles/category/category772/',
           parentId => '0',
           rssUrl => 'http://leocharre.com/articles/category/category772/feed/'
         }

=over 4

=item CAVEAT 

There seems to be a bug in xmlrpc.php (wordpress v 2.3.2) , that does not fill out 
the categories properly. You can use  newCategory() to insert a description, bu
upon getCategory(), the struct description is replaced by the categoryName field.

=back

=head3 suggestCategories()

Takes 2 optional args: category, max_results.

Returns array ref, each element is a hashref.

Apparently it does a word match on the existing categories, if you provide 
a category string argument.

If you call it as the following..

   $o->suggestCategories('draw');

And you have a 'drawing' category, it returns:

   $r: [
         {
           category_id => '187',
           category_name => 'drawing'
         }
       ]

With no arguments, example return:

   $r: [
         {
           category_id => '4',
           category_name => 'art'
         },
         {
           category_id => '196',
           category_name => 'category528'
         },
         {
           category_id => '197',
           category_name => 'category528 appended'
         },
         {
           category_id => '203',
           category_name => 'category919 appended'
         },
         {
           category_id => '184',
           category_name => 'design'
         },
         {
           category_id => '183',
           category_name => 'dev'
         },
         {
           category_id => '187',
           category_name => 'drawing'
         },
         {
           category_id => '190',
           category_name => 'graphic'
         },
         {
           category_id => '5',
           category_name => 'near life experience'
         },
         {
           category_id => '188',
           category_name => 'painting'
         },
         {
           category_id => '189',
           category_name => 'pinup'
         },
         {
           category_id => '186',
           category_name => 'web'
         }
       ]

=head3 deleteCategory()

Takes 1 args: category_id (number).
Returns boolean.

I suppose you could search for categories with the name 'bad' and delete them as..

   for my $category_href ( @{ $o->suggestCategories('bad') } ){
      $o->deleteCategory($category_href->{category_id})
         or warn("Could not delete $category->{category_name});
   }

=cut

=head3 getComment()

Takes 1 args: comment_id (number).
Returns struct (hashref).

   $o->getComment(2603);

Example return value:
   $r: {
         author => 'santrex sucks',
         author_email => 'webmaster@santrexsucks.com',
         author_ip => '66.165.246.149',
         author_url => 'http://santrexsucks.com',
         comment_id => '2603',
         content => 'santrex is the worst hosting company ive ever used. santrex should be avoided at all costs!',
         date_created_gmt => '20090617T00:17:54',
         link => 'http://leocharre.com/articles/its-on-bitch/comment-page-1/#comment-2603',
         parent => '0',
         post_id => '372',
         post_title => 'IT&#8217;S ON BITCH',
         status => 'approve',
         type => '',
         user_id => '0'
       }

=head3 getComments()

Takes 1 args: struct (hashref).
NOTE: Untested. If you have info on this, send it in.

=head3 deleteComment()

Takes 1 args: comment_id (number).
Returns bool true or false.

   $o->deleteComment(2603);

=head3 editComment()

Takes 2 args: comment_id (number), content_struct (hashref).


=head3 newComment()

Takes 2 args: post id, content_struct (hashref).
Returns new comment id (number).

This will be posted under your login name. The post id is the post the comment is in regards to.

   $o->newComment( 15, { status => 'approve', content => "Hi there, this is a note." } );

=head3 getCommentStatusList()

Takes no argument.
Returns hashref.

Example return value:
   $r: {
         approve => 'Approved',
         hold => 'Unapproved',
         spam => 'Spam'
       }


=head3 getOptions()

Optional arguments are, a list of option names.
If you do not pass a list of options assumes all are chosen.
Returns hash ref. Of which each key is the option name. Each value is a hashref itself.

Return hashref format:

   $options => {

      $option_name => {
         desc        => $string,
         readonly    => $boolean,
         value       => $string
      },
   };

Possible option names (as of wordpress 2.8.4):
   blog_tagline, blog_title, blog_url, date_format, software_name, 
   software_version, time_format, time_zone

Example return value (with no arguments):

   $options: {
               blog_tagline => {
                                 desc => 'Blog Tagline',
                                 readonly => '0',
                                 value => 'pinup art, perl, unix, developer smorgasbord'
                               },
               blog_title => {
                               desc => 'Blog Title',
                               readonly => '0',
                               value => 'leo charre'
                             },
               blog_url => {
                             desc => 'Blog URL',
                             readonly => '1',
                             value => 'http://leocharre.com'
                           },
               date_format => {
                                desc => 'Date Format',
                                readonly => '0',
                                value => 'F j, Y'
                              },
               software_name => {
                                  desc => 'Software Name',
                                  readonly => '1',
                                  value => 'WordPress'
                                },
               software_version => {
                                     desc => 'Software Version',
                                     readonly => '1',
                                     value => '2.8.4'
                                   },
               time_format => {
                                desc => 'Time Format',
                                readonly => '0',
                                value => 'g:i a'
                              },
               time_zone => {
                              desc => 'Time Zone',
                              readonly => '0',
                              value => '-8'
                            }
             }


Example usage:

   my $options = $o->getOptions('software_name', 'time_zone',);
   my $options = $o->getOptions;


=head3 setOptions()

Takes 1 args: options hash ref.
Returns same as getOptions().

Argument is hashref with keys the name of the option, and values the new values.


NOTE: The structure of the hashref to setOptions() is *not* the same as the structure that getOptions()
returns.

NOTE: Also note, some options are set read only, that means they cannot be changed via this method.

Example usage:

   $o->setOptions({ blog_tagline => 'New tagline for this blog, this is the best blog ever' });

This would return:

   $out: {
           blog_tagline => {
                             desc => 'Blog Tagline',
                             readonly => '0',
                             value => 'New tagline for this blog, this is the best blog ever', 
                           }
         }

The value taken by setOptions() should be the same as returned by getOptions(). This is more proof
that php "coders" have no discipline. As if proof were needed. Ok, maybe that's too harsh.

=cut












=pod

=head3 newPost()

Takes 2 args: content_struct, publish.
Returns id number of new post.

=head3 editPost()

Takes 3 args: post_ID, content_struct, publish.
Returns boolean, true or false.

=head3 deletePost()

Argument is post id(number).
Returns boolean.

=head3 getPost()

Takes 1 args: post_ID
Returns post struct, hashref.

	 $example_return_value: {
	                          categories => [
	                                          'Uncategorized'
	                                        ],
	                          dateCreated => '20080130T14:19:05',
	                          date_created_gmt => '20080130T22:19:05',
	                          description => 'test description here',
	                          link => 'http://leocharre.com/articles/test_1201731544/',
	                          mt_allow_comments => '1',
	                          mt_allow_pings => '1',
	                          mt_excerpt => '',
	                          mt_keywords => '',
	                          mt_text_more => '',
	                          permaLink => 'http://leocharre.com/articles/test_1201731544/',
	                          postid => '119',
	                          title => 'test_1201731544',
	                          userid => '2',
	                          wp_author_display_name => 'leocharre',
	                          wp_author_id => '2',
	                          wp_password => '',
	                          wp_slug => 'test_1201731544'
	                        }

=head3 getRecentPosts()

Takes 1 args: num_posts (number, optional).
Returns arrayref.

Each element is hash ref same as getPost() would return.

=head3 uploadFile()

Takes 1 args: data.
Data is a hashref, see WordPress::MediaObject.

=head3 newMediaObject()

Takes 1 args: data (hashref).
The hashref keys and values are bits (Mime::Base64), type (mime type), and name (filename).

=head3 getTemplate()

Takes 1 args: template name (string).
NOTE: This fails. Don't know why. If you have any idea, contact AUTHOR.

=head3 setTemplate()

Takes 2 args: content, template.
NOTE: Untested.

=head3 getPageTemplates()

Takes no args.
Returns hashref.

Example return:

   return: {
             Default => 'default',
             'Link Categories' => 'link_categories.php',
             Links => 'links.php'
           }

=head3 getTags()

Takes no argument.
Returns array ref. Each element is a hashref.

Example return:

   return: [
             {
               count => '1',
               html_url => 'http://leocharre.com/articles/tag/avi/',
               name => 'avi',
               rss_url => 'http://leocharre.com/articles/tag/avi/feed/',
               slug => 'avi',
               tag_id => '158'
             },
             {
               count => '1',
               html_url => 'http://leocharre.com/articles/tag/bugzilla/',
               name => 'bugzilla',
               rss_url => 'http://leocharre.com/articles/tag/bugzilla/feed/',
               slug => 'bugzilla',
               tag_id => '195'
             },
             {
               count => '1',
               html_url => 'http://leocharre.com/articles/tag/callback/',
               name => 'callback',
               rss_url => 'http://leocharre.com/articles/tag/callback/feed/',
               slug => 'callback',
               tag_id => '30'
             },
           ]
        ... .etc.....

=head3 getUsersBlogs()

No argument, returns users blogs.
Example return :

	 $r: [
	       {
	         blogName => 'leo charre',
	         blogid => '1',
	         isAdmin => '1',
	         url => 'http://leocharre.com/'
	       }
	     ]

=head1 DEBUG

This is useful if you get errors..

   $WordPress::XMLRPC::DEBUG = 1;

=head1 WISHLIST

It'd be nice to manage links via xmlrpc.php, but this is up to wordpress devs.

=head1 BUGS

Please submit to AUTHOR

=head1 CAVEATS

This distro is alpha.
Included are the metaWeblog and wp method calls.

=head1 REQUIREMENTS

L<XMLRPC::Lite>

=head1 SEE ALSO

L<XMLRPC::Lite>
L<SOAP::Lite>
WordPress L<http://wordpress.org>

=head1 AUTHOR

Leo Charre leocharre at cpan dot org

=head1 LICENSE

This package is free software; you can redistribute it and/or modify it under the same terms as Perl itself, i.e., under the terms of the "Artistic License" or the "GNU General Public License".

=head1 DISCLAIMER

This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the "GNU General Public License" for more details.

=cut

