<?php
/**
* @see http://phpxmlrpc.sourceforge.net/
* @see http://xmlrpc.usefulinc.com/doc/
* @copyright Edd Dumbill <edd@usefulinc.com> (C) 1999-2002
*
* @package evocore
* @subpackage xmlrpc
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
if( CANUSEXMLRPC !== TRUE )
{
return;
}
/**
* Include XML-RPC for PHP SERVER library
*/
load_funcs('_ext/xmlrpc/_xmlrpcs.inc.php');
// --------------------------------------- SUPPORT FUNCTIONS ----------------------------------------
/**
* Used for logging, only if {@link $debug_xmlrpc_logging} is true
*
* @return boolean Have we logged?
*/
function logIO( $msg, $newline = false )
{
global $debug_xmlrpc_logging, $basepath, $xmlsrv_subdir;
if( ! $debug_xmlrpc_logging )
{
return false;
}
if( $newline )
{
$msg = "\n\n".date('Y-m-d H:i:s')."\n".$msg;
}
$ok = save_to_file( $msg."\n", $basepath.$xmlsrv_subdir.'xmlrpc.log', 'a+' );
return (bool) $ok;
}
/**
* Returns a string replaced by stars, for passwords.
*
* @param string the source string
* @return string same length, but only stars
*/
function starify( $string )
{
return str_repeat( '*', strlen( $string ) );
}
/**
* metaWeblog.newMediaObject image upload
* wp.uploadFile
*
* Supplied image is encoded into the struct as bits
*
* @see http://www.xmlrpc.com/metaWeblogApi#metaweblognewmediaobject
* @see http://codex.wordpress.org/XML-RPC_wp#wp.uploadFile
*
* @param xmlrpcmsg XML-RPC Message
* 0 blogid (string): Unique identifier of the blog the post will be added to.
* Currently ignored in b2evo, in favor of the category.
* 1 username (string): Login for a Blogger user who has permission to edit the given
* post (either the user who originally created it or an admin of the blog).
* 2 password (string): Password for said username.
* 3 struct (struct)
* - name : filename
* - type : mimetype
* - bits : base64 encoded file
* @return xmlrpcresp XML-RPC Response
*/
function _wp_mw_newmediaobject($m)
{
global $Settings, $Plugins, $force_upload_forbiddenext;
// CHECK LOGIN:
/**
* @var User
*/
if( ! $current_User = & xmlrpcs_login( $m, 1, 2 ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
// GET BLOG:
/**
* @var Blog
*/
if( ! ( $Collection = $Blog = & xmlrpcs_get_Blog( $m, 0 ) ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
// CHECK PERMISSION:
if( ! check_user_perm( 'files', 'add', false, $Blog->ID ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
logIO( 'Permission granted.' );
if( ! $Settings->get('upload_enabled') )
{
return xmlrpcs_resperror( 2, 'Object upload not allowed' );
}
$xcontent = $m->getParam(3);
// Get the main data - and decode it properly for the image - sorry, binary object
logIO( 'Decoding content...' );
$contentstruct = xmlrpc_decode_recurse($xcontent);
$data = $contentstruct['bits'];
$file_mimetype = isset($contentstruct['type']) ? $contentstruct['type'] : '(none)';
logIO( 'Received MIME type: '.$file_mimetype );
$overwrite = false;
if( isset($contentstruct['overwrite']) )
{
$overwrite = (bool) $contentstruct['overwrite'];
}
logIO( 'Overwrite if exists: '.($overwrite ? 'yes' : 'no') );
load_funcs('files/model/_file.funcs.php');
$filesize = evo_bytes( $data );
if( ( $maxfilesize = $Settings->get('upload_maxkb') * 1024 ) && $filesize > $maxfilesize )
{
return xmlrpcs_resperror( 4, sprintf( T_('The file is too large: %s but the maximum allowed is %s.'),
bytesreadable($filesize, false), bytesreadable($maxfilesize, false) ) );
}
logIO( 'File size is OK: '.bytesreadable($filesize, false) );
$FileRootCache = & get_FileRootCache();
$fm_FileRoot = & $FileRootCache->get_by_type_and_ID( 'collection', $Blog->ID, true );
if( ! $fm_FileRoot )
{ // fileRoot not found:
return xmlrpcs_resperror( 14, 'File root not found' );
}
$rf_filepath = $contentstruct['name'];
logIO( 'Received filepath: '.$rf_filepath );
// Split into path + name:
$filepath_parts = explode( '/', $rf_filepath );
$filename = array_pop( $filepath_parts );
logIO( 'Original file name: '.$filename );
// Validate and sanitize filename
if( $error_filename = process_filename( $filename, true ) )
{
return xmlrpcs_resperror( 5, $error_filename );
}
logIO( 'Sanitized file name: '.$filename );
// Check valid path parts:
$rds_subpath = '';
foreach( $filepath_parts as $filepath_part )
{
if( empty($filepath_part) || $filepath_part == '.' )
{ // self ref not useful
continue;
}
if( $error = validate_dirname($filepath_part) )
{ // invalid relative path:
logIO( $error );
syslog_insert( sprintf( 'Invalid name is detected for folder %s', '[['.$filepath_part.']]' ), 'warning', 'file' );
return xmlrpcs_resperror( 6, $error );
}
$rds_subpath .= $filepath_part.'/';
}
logIO( 'Subpath: '.$rds_subpath );
// Create temporary file and insert contents into it.
$tmpfile_name = tempnam(sys_get_temp_dir(), 'fmupload');
if( $tmpfile_name )
{
if( save_to_file( $data, $tmpfile_name, 'wb' ) )
{
$image_info = @getimagesize($tmpfile_name);
}
else
{
return xmlrpcs_resperror( 13, 'Error while writing to temp file.' );
}
}
if( !empty($image_info) )
{ // This is an image file, let's check mimetype and correct extension
if( $image_info['mime'] != $file_mimetype )
{ // Invalid file type
$FiletypeCache = & get_FiletypeCache();
// Get correct file type based on mime type
$correct_Filetype = $FiletypeCache->get_by_mimetype( $image_info['mime'], false, false );
$file_mimetype = $image_info['mime'];
// Check if file type is known by us, and if it is allowed for upload.
// If we don't know this file type or if it isn't allowed we don't change the extension! The current extension is allowed for sure.
if( $correct_Filetype && $correct_Filetype->is_allowed() )
{ // A FileType with the given mime type exists in database and it is an allowed file type for current User
// The "correct" extension is a plausible one, proceed...
$extensions = $correct_Filetype->get_extensions();
$correct_extension = array_shift( $extensions );
$path_info = pathinfo($filename);
$current_extension = $path_info['extension'];
// change file extension to the correct extension, but only if the correct extension is not restricted, this is an extra security check!
if( strtolower($current_extension) != strtolower($correct_extension) && ( !in_array( $correct_extension, $force_upload_forbiddenext ) ) )
{ // change the file extension to the correct extension
$old_filename = $filename;
$filename = $path_info['filename'].'.'.$correct_extension;
}
}
}
}
// Get File object for requested target location:
$FileCache = & get_FileCache();
$newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($rds_subpath).$filename, true );
if( $newFile->exists() )
{
if( $overwrite && $newFile->unlink() )
{ // OK, file deleted
// Delete thumb caches from old location:
logIO( 'Old file deleted' );
$newFile->rm_cache();
}
else
{
return xmlrpcs_resperror( 8, sprintf( T_('The file «%s» already exists.'), $filename ) );
}
}
// Trigger plugin event
if( $Plugins->trigger_event_first_false( 'AfterFileUpload', array(
'File' => & $newFile,
'name' => & $filename,
'type' => & $file_mimetype,
'tmp_name' => & $tmpfile_name,
'size' => & $filesize,
) ) )
{
// Plugin returned 'false'.
// Abort upload for this file:
@unlink($tmpfile_name);
return xmlrpcs_resperror( 16, 'File upload aborted by a plugin.' );
}
if( ! mkdir_r( $newFile->get_dir() ) )
{ // Dir didn't already exist and could not be created
return xmlrpcs_resperror( 9, 'Error creating sub directories: '.$newFile->get_rdfs_rel_path() );
}
if( ! @rename( $tmpfile_name, $newFile->get_full_path() ) )
{
return xmlrpcs_resperror( 13, 'Error while writing to file.' );
}
// chmod the file
$newFile->chmod();
// Initializes file properties (type, size, perms...)
$newFile->load_properties();
// Load meta data AND MAKE SURE IT IS CREATED IN DB:
$newFile->meta == 'unknown';
$newFile->load_meta( true );
// Resize and rotate
logIO( 'Running file post-processing (resize and rotate)...' );
prepare_uploaded_files( array($newFile) );
logIO( 'Done' );
$url = $newFile->get_url();
logIO( 'URL of new file: '.$url );
$struct = new xmlrpcval(array(
'file' => new xmlrpcval($filename, 'string'),
'url' => new xmlrpcval($url, 'string'),
'type' => new xmlrpcval($file_mimetype, 'string'),
), 'struct');
logIO( 'OK.' );
return new xmlrpcresp($struct);
}
/**
* Used in these methods
* - metaweblog.getPost
* - metaWeblog.getRecentPosts
* - wp.getPages
* - wp.getPage
*
* Note: a valid Item object must be supplied
*
*/
function _wp_mw_get_item_struct( & $Item )
{
global $DB;
logIO('Item title: '.$Item->title);
if( is_null($Item->extra_cat_IDs) )
{ // Load extra cats
$Item->extra_cat_IDs = postcats_get_byID($Item->ID);
}
$cat_ids = $Item->extra_cat_IDs;
array_unshift($cat_ids, $Item->main_cat_ID); // Move to top
$cat_ids = array_unique( $cat_ids );
$SQL = 'SELECT cat_name FROM T_categories WHERE cat_ID IN ('.$DB->quote($cat_ids).')';
$cat_names = array();
if( $categories = $DB->get_col($SQL) )
{
foreach( $categories as $cat )
{
$cat_names[] = new xmlrpcval($cat);
}
logIO( 'Categories: '.implode(', ', $categories) );
}
$tag_names_string = '';
if( $tags = $Item->get_tags() )
{
$tag_names_string = implode(', ', $tags);
logIO( 'Tags: '.$tag_names_string );
}
$SQL = 'SELECT * FROM T_items__item_settings WHERE iset_item_ID = '.$DB->quote($Item->ID);
$item_settings = array();
if( $settings = $DB->get_results($SQL) )
{
foreach( $settings as $setting )
{
$item_settings[] = new xmlrpcval(array(
'id' => new xmlrpcval( 0 ),
'key' => new xmlrpcval( $setting->iset_name ),
'value' => new xmlrpcval( $setting->iset_value ),
),'struct');
}
}
// Split item content on before and after [teaserbreak]
// No tag balancing, no pages, no rendering, just raw text
$content_parts = explode( '[teaserbreak]', $Item->content, 2 );
if( !isset($content_parts[1]) ) $content_parts[1] = '';
$parent_title = '';
if( isset($Item->parent_ID) )
{
$ItemCache = & get_ItemCache();
if( $parent_Item = & $ItemCache->get_by_ID( $Item->parent_ID, false, false ) )
{
$parent_title = $parent_Item->title;
}
}
$item_status = wp_or_b2evo_item_status($Item->status, 'wp');
$url = $Item->get_permanent_url();
$data = array(
'page_id' => new xmlrpcval( $Item->ID, 'int' ),
'postid' => new xmlrpcval( $Item->ID, 'int' ), // mw
'userid' => new xmlrpcval( $Item->creator_user_ID, 'int' ),
'page_status' => new xmlrpcval( $item_status ),
'post_status' => new xmlrpcval( $item_status ), // mw
'description' => new xmlrpcval( $content_parts[0] ),
'text_more' => new xmlrpcval( $content_parts[1] ),
'title' => new xmlrpcval( $Item->title ),
'link' => new xmlrpcval( $url ),
'permalink' => new xmlrpcval( $url ), // mw
'permaLink' => new xmlrpcval( $url ),
'categories' => new xmlrpcval( $cat_names, 'array' ),
'excerpt' => new xmlrpcval( $Item->excerpt ),
'mt_excerpt' => new xmlrpcval( $Item->excerpt ),
'mt_allow_comments' => new xmlrpcval( ($Item->can_comment(NULL) ? 1 : 0), 'int' ),
'mt_text_more' => new xmlrpcval( $content_parts[1] ),
'mt_keywords' => new xmlrpcval( $tag_names_string ),
'wp_slug' => new xmlrpcval( $Item->urltitle ),
'wp_author' => new xmlrpcval( $Item->get('t_author') ),
'wp_page_parent_id' => new xmlrpcval( (isset($Item->parent_ID) ? $Item->parent_ID : 0), 'int' ),
'wp_page_parent_title' => new xmlrpcval( $parent_title ),
'wp_page_order' => new xmlrpcval( $Item->get_order() ), // We don't use 'int' here because b2evolution "order" is stored as double while WP uses integer values
'wp_author_id' => new xmlrpcval( $Item->creator_user_ID, 'string' ),
'wp_author_display_name' => new xmlrpcval( $Item->get('t_author') ),
'wp_post_format' => new xmlrpcval( $Item->ityp_ID ),
'date_created_gmt' => new xmlrpcval( datetime_to_iso8601($Item->issue_date, true), 'dateTime.iso8601' ),
'dateCreated' => new xmlrpcval( datetime_to_iso8601($Item->issue_date),'dateTime.iso8601' ),
'custom_fields' => new xmlrpcval( $item_settings, 'array' ),
'wp_page_template' => new xmlrpcval('default'), // n/a
'mt_allow_pings' => new xmlrpcval( 0, 'int' ), // n/a
'wp_password' => new xmlrpcval(''), // n/a
);
return $data;
}
function _wp_or_blogger_getusersblogs( $type, $m )
{
global $xmlsrv_url;
if ( $type == 'wp')
{
$username_index = 0;
$password_index = 1;
}
else
{
$username_index = 1;
$password_index = 2;
}
// CHECK LOGIN:
if( ! $current_User = & xmlrpcs_login( $m, $username_index, $password_index ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
// LOAD BLOGS the user is a member of:
$BlogCache = & get_BlogCache();
$blog_array = $BlogCache->load_user_blogs( 'blog_ismember', 'view', $current_User->ID );
$resp_array = array();
foreach( $blog_array as $l_blog_ID )
{ // Loop through all blogs that match the requested permission:
/**
* @var Blog
*/
$l_Blog = & $BlogCache->get_by_ID( $l_blog_ID );
logIO('Current user IS a member of this blog: '.$l_blog_ID);
$item = array(
'blogid' => new xmlrpcval( $l_blog_ID ),
'blogName' => new xmlrpcval( $l_Blog->get('shortname') ),
'url' => new xmlrpcval( $l_Blog->gen_blogurl() ),
'isAdmin' => new xmlrpcval( check_user_perm( 'blog_admin', 'edit', false, $l_Blog->ID ), 'boolean') );
if ( $type == 'wp')
{
$item['xmlrpc'] = new xmlrpcval ( $xmlsrv_url.'xmlrpc.php' );
}
$resp_array[] = new xmlrpcval( $item, 'struct');
}
logIO( 'OK.' );
return new xmlrpcresp( new xmlrpcval( $resp_array, 'array' ) );
}
/**
* Decode the dateCreated
*
* @param struct
* @return string MYSQL date
*/
function _mw_decode_date( $contentstruct )
{
global $Settings;
$postdate = NULL;
if( !empty($contentstruct['date_created_gmt']) )
{
$postdate = iso8601_to_datetime($contentstruct['date_created_gmt']);
// Add time difference to GMT date
$postdate = date( 'Y-m-d H:i:s', (mysql2timestamp($postdate, true) + $Settings->get('time_difference')) );
logIO( 'Using contentstruct date_created_gmt: '.$postdate );
}
if( empty($postdate) && !empty($contentstruct['dateCreated']) )
{
$postdate = $contentstruct['dateCreated'];
if( strpos($postdate, 'T') > 0 )
{ // Date is in ISO 8601 format
$postdate = iso8601_to_datetime($postdate);
}
logIO( 'Using contentstruct dateCreated: '.$postdate );
}
return $postdate;
}
/**
* Get IDs for requested categories
*
* @param array struct
* @param integer blog ID
* @param boolean Return empty array (instead of error), if no cats given in struct?
* @return array|xmlrpcresp A list of category IDs or xmlrpcresp in case of error.
*/
function _mw_get_cat_IDs( $contentstruct, $Blog, $empty_struct_is_ok = false )
{
global $DB;
$categories = array();
if( isset($contentstruct['categories']) )
{
foreach( $contentstruct['categories'] as $l_catname )
{
$categories[] = trim(strip_tags($l_catname));
}
}
logIO( 'Categories: '.implode( ', ', $categories ) );
if( $empty_struct_is_ok && empty($categories) )
{
return $categories;
}
$cat_IDs = array();
if( ! empty($categories) )
{
// for cross-blog-entries, the cat_blog_ID WHERE clause should be removed (but cats are given by name!)
$SQL = 'SELECT cat_ID
FROM T_categories
WHERE cat_blog_ID = '.$DB->quote($Blog->ID).'
AND cat_name IN (';
foreach( $categories as $l_cat )
{
$SQL .= '"'.$DB->escape($l_cat).'", ';
}
if( ! empty($categories) )
{
$SQL = substr($SQL, 0, -2); // remove ', '
}
$SQL .= ')';
logIO('Loading categories by name');
if( ! $cat_IDs = $DB->get_col($SQL) )
{ // DB error
logIO('Couldn\'t find requested categories');
}
}
if( empty($cat_IDs) )
{
// No category given/valid - use default for this blog:
logIO('Using default category');
if( ! $default_cat = $Blog->get_default_cat_ID() )
{
logIO( 'There are no categories in this blog yet');
return xmlrpcs_resperror( 5, 'There are no categories in this blog yet' ); // user error 5
}
$cat_IDs = array($default_cat);
}
return $cat_IDs;
}
function add_to_categories_data( $Chapter )
{
global $categories_data, $categoryIdName;
$categories_data[] = new xmlrpcval( array(
$categoryIdName => new xmlrpcval( $Chapter->ID ),
'categoryName' => new xmlrpcval( $Chapter->name )
), 'struct' );
}
/**
* Helper for {@link b2_getcategories()} and {@link mt_getPostCategories()}, because they differ
* only in the "categoryId" case ("categoryId" (b2) vs "categoryID" (MT))
*
* @param string Type, either "b2" or "mt"
* @param xmlrpcmsg XML-RPC Message
* 0 blogid (string): Unique identifier of the blog to query
* 1 username (string): Login for a Blogger user who is member of the blog.
* 2 password (string): Password for said username.
* @return xmlrpcresp XML-RPC Response
*/
function _b2_or_mt_get_categories( $type, $m )
{
global $DB;
/**
* @var User
*/
if( ! $current_User = & xmlrpcs_login( $m, 1, 2 ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
// GET BLOG:
/**
* @var Blog
*/
if( ! ( $Collection = $Blog = & xmlrpcs_get_Blog( $m, 0 ) ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
global $categories_data, $categoryIdName;
$categories_data = array();
$categoryIdName = ( $type == 'b2' ? 'categoryID' : 'categoryId' );
$aggregate_coll_IDs = $Blog->get_aggregate_coll_IDs();
$callbacks = array( 'line' => 'add_to_categories_data' );
$ChapterCache = & get_ChapterCache();
$ChapterCache->recurse( $callbacks, $aggregate_coll_IDs, NULL, 0, 0, array( 'sorted' => true ) );
logIO( 'Categories: '.count($ChapterCache->cache) );
logIO( 'OK.' );
$result = new xmlrpcresp( new xmlrpcval($categories_data, 'array') );
unset( $categories_data );
return $result;
}
/**
* metaWeblog.getCategories
*
* @see http://www.xmlrpc.com/metaWeblogApi#metawebloggetcategories
*
* @param xmlrpcmsg XML-RPC Message
* 0 blogid (string): Unique identifier of the blog the post will be added to.
* Currently ignored in b2evo, in favor of the category.
* 1 username (string): Login for a Blogger user who has permission to edit the given
* post (either the user who originally created it or an admin of the blog).
* 2 password (string): Password for said username.
* @param array of params to narrow category selection
*/
function _wp_mw_getcategories( $m, $params = array() )
{
global $DB;
// CHECK LOGIN:
/**
* @var User
*/
if( ! $current_User = & xmlrpcs_login( $m, 1, 2 ) )
{ // Login failed, return (last) error:
return xmlrpcs_resperror();
}
// GET BLOG:
/**
* @var Blog
*/
if( ! ( $Collection = $Blog = & xmlrpcs_get_Blog( $m, 0 ) ) )
{ // Not found the selected blog, return (last) error:
return xmlrpcs_resperror();
}
$SQL = new SQL();
$SQL->SELECT( 'cat_ID, cat_name, cat_order' );
$SQL->FROM( 'T_categories' );
$SQL->WHERE( $Blog->get_sql_where_aggregate_coll_IDs('cat_blog_ID') );
if( !empty($params['search']) )
{ // Category name starts with 'search'
$SQL->WHERE_and( 'cat_name LIKE "'.$DB->like_escape( $params['search'] ).'%"' );
}
// TODO: asimo>fp How should we order categories from multiple blogs?
$SQL->ORDER_BY( 'cat_name' );
$rows = $DB->get_results( $SQL->get() );
if( $DB->error )
{ // DB error
return xmlrpcs_resperror( 99, 'DB error: '.$DB->last_error ); // user error 9
}
$total_rows = count($rows);
logIO( 'Categories: '.$total_rows );
$ChapterCache = & get_ChapterCache();
$data = array();
for( $i=0; $i<$total_rows; $i++ )
{
if( !empty($params['limit']) && $i >= $params['limit'] )
{ // We found enough, exit the loop
break;
}
$Chapter = & $ChapterCache->get_by_ID( $rows[$i]->cat_ID, false, false );
if( ! $Chapter )
{
continue;
}
if( isset($params['search']) )
{ // wp.suggestCategories
$data[] = new xmlrpcval( array(
'category_id' => new xmlrpcval( intval($Chapter->ID) ),
'category_name' => new xmlrpcval( $Chapter->name ),
),'struct');
}
else
{
$data[] = new xmlrpcval( array(
'categoryId' => new xmlrpcval( intval($Chapter->ID) ), // not in RFC (http://www.xmlrpc.com/metaWeblogApi)
'parentId' => new xmlrpcval( intval($Chapter->parent_ID) ), // not in RFC
'description' => new xmlrpcval( $Chapter->name ),
'categoryDescription' => new xmlrpcval( $Chapter->description ), // not in RFC
'categoryName' => new xmlrpcval( $Chapter->name ), // not in RFC
'htmlUrl' => new xmlrpcval( $Chapter->get_permanent_url() ),
'rssUrl' => new xmlrpcval( url_add_param($Chapter->get_permanent_url(), 'tempskin=_rss2') )
),'struct');
}
}
logIO( 'OK.' );
return new xmlrpcresp( new xmlrpcval($data, 'array') );
}
/**
* Get current_User for an XML-RPC request - Includes login (password) check.
*
* @param xmlrpcmsg XML-RPC Message
* @param integer idx of login param in XML-RPC Message
* @param integer idx of pass param in XML-RPC Message
* @return User or NULL
*/
function & xmlrpcs_login( $m, $login_param, $pass_param )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
$username = $m->getParam( $login_param );
$username = $username->scalarval();
$password = $m->getParam( $pass_param );
$password = $password->scalarval();
/**
* @var UserCache
*/
$UserCache = & get_UserCache();
$current_User = & $UserCache->get_by_login( $username );
if( empty( $current_User ) || ! $current_User->check_password( $password, false ) )
{ // User not found or password doesn't match
$xmlrpcs_errcode = $xmlrpcerruser+1;
$xmlrpcs_errmsg = 'Wrong username/password combination: '.$username.' / '.starify($password);
$r = NULL;
return $r;
}
// This may be needed globally for status permissions in ItemList2, etc..
$GLOBALS['current_User'] = & $current_User;
// Check here ability to use APIs
$group = $current_User->get_Group();
if( ! $group->check_perm('perm_api', 'always') )
{ // Permission denied
$xmlrpcs_errcode = $xmlrpcerruser+1;
$xmlrpcs_errmsg = 'User has no permission to use this API: '.$username.' / '.starify($password);
$r = NULL;
return $r;
}
logIO( 'Login OK - User: '.$current_User->ID.' - '.$current_User->login );
return $current_User;
}
/**
* Get current Blog for an XML-RPC request.
*
* @param xmlrpcmsg XML-RPC Message
* @param integer idx of blog ID param
* @return Blog or NULL
*/
function & xmlrpcs_get_Blog( $m, $id_param )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
$blog = $m->getParam( $id_param );
$blog = $blog->scalarval();
// waltercruz> qtm: http://qtm.blogistan.co.uk/ inserts some spacing before/after blogID.
$blog = (int) trim($blog);
/**
* @var BlogCache
*/
$BlogCache = & get_BlogCache();
/**
* @var Blog
*/
$Collection = $Blog = & $BlogCache->get_by_ID( $blog, false, false );
if( empty( $Blog ) )
{ // Blog not found
$xmlrpcs_errcode = $xmlrpcerruser+2;
$xmlrpcs_errmsg = 'Requested blog/Collection ('.$blog.') does not exist.';
$r = NULL;
return $r;
}
logIO( 'Requested Blog: '.$Blog->ID.' - '.$Blog->name );
return $Blog;
}
/**
* Get current Item for an XML-RPC request.
*
* @param xmlrpcmsg XML-RPC Message
* @param integer idx of item ID param
* @return Item or NULL
*/
function & xmlrpcs_get_Item( $m, $id_param )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
$postid = $m->getParam( $id_param );
$postid = $postid->scalarval();
/**
* @var ItemCache
*/
$ItemCache = & get_ItemCache();
/**
* @var Item
*/
$edited_Item = & $ItemCache->get_by_ID( $postid, false, false );
if( empty( $edited_Item ) )
{ // Item not found
$xmlrpcs_errcode = $xmlrpcerruser+6;
$xmlrpcs_errmsg = 'Requested post/Item ('.$postid.') does not exist.';
$r = NULL;
return $r;
}
logIO( 'Requested Item: '.$edited_Item->ID.' - '.$edited_Item->title );
return $edited_Item;
}
/**
* Get current Chapter for an XML-RPC request.
*
* @param xmlrpcmsg XML-RPC Message
* @param integer idx of chapter ID param
* @return Chapter or NULL
*/
function & xmlrpcs_get_Chapter( $m, $id_param )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
$id = $m->getParam( $id_param );
$id = $id->scalarval();
/**
* @var ChapterCache
*/
$ChapterCache = & get_ChapterCache();
/**
* @var Chapter
*/
$edited_Chapter = & $ChapterCache->get_by_ID( $id, false, false );
if( empty( $edited_Chapter ) )
{ // Chapter not found
$xmlrpcs_errcode = $xmlrpcerruser+9;
$xmlrpcs_errmsg = 'Requested chapter ('.$id.') does not exist.';
$r = NULL;
return $r;
}
logIO( 'Requested Chapter: '.$edited_Chapter->ID.' - '.strmaxlen($edited_Chapter->name, 30) );
return $edited_Chapter;
}
/**
* Get current Comment for an XML-RPC request.
*
* @param xmlrpcmsg XML-RPC Message
* @param integer idx of comment ID param
* @return Comment or NULL
*/
function & xmlrpcs_get_Comment( $m, $id_param )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
$id = $m->getParam( $id_param );
$id = $id->scalarval();
/**
* @var CommentCache
*/
$CommentCache = & get_CommentCache();
/**
* @var Comment
*/
$edited_Comment = & $CommentCache->get_by_ID( $id, false, false );
if( empty( $edited_Comment ) )
{ // Comment not found
$xmlrpcs_errcode = $xmlrpcerruser+9;
$xmlrpcs_errmsg = 'Requested comment ('.$id.') does not exist.';
$r = NULL;
return $r;
}
logIO( 'Requested Comment: '.$edited_Comment->ID.' - '.strmaxlen($edited_Comment->content, 30) );
return $edited_Comment;
}
/**
* If no errcode or errmsg given, will use the last one that has been set previously.
*
* @param integer
* @param string
* @return xmlrpcresp
*/
function xmlrpcs_resperror( $errcode = NULL, $errmsg = NULL )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
if( !empty($errcode) )
{ // Transform into user error code
$xmlrpcs_errcode = $xmlrpcerruser + $errcode;
}
if( !empty($errmsg) )
{ // Custom message
$xmlrpcs_errmsg = $errmsg;
}
elseif( empty( $xmlrpcs_errmsg ) )
{ // Use a standard messsage
switch( $errcode )
{
case 3:
$xmlrpcs_errmsg = 'Permission denied.';
break;
case 11:
$xmlrpcs_errmsg = 'Requested category not found in requested blog.';
break;
case 12:
$xmlrpcs_errmsg = 'No default category found for requested blog.';
break;
case 21:
$xmlrpcs_errmsg = 'Invalid post title.';
break;
case 22:
$xmlrpcs_errmsg = 'Invalid post contents.';
break;
case 99:
$xmlrpcs_errmsg = 'Database error.';
break;
default:
$xmlrpcs_errmsg = 'Unknown error.';
}
}
logIO( 'ERROR: '.$xmlrpcs_errcode.' - '.$xmlrpcs_errmsg );
return new xmlrpcresp( 0, $xmlrpcs_errcode, $xmlrpcs_errmsg );
}
/**
* Create a new Comment and return an XML-RPC response
*
* @param array of params
* - Item (object)
* - User (object) Can be NULL for anonymous comments
* - password (string)
* - username (string)
* - comment_parent (int)
* - content (string)
* - author (string)
* - author_url (string)
* - author_email (string)
* @return xmlrpcmsg
*/
function xmlrpcs_new_comment( $params = array(), & $commented_Item )
{
global $DB, $Plugins, $Messages, $Hit, $localtimenow;
$params = array_merge( array(
'password' => '',
'username' => '',
'content' => '',
'comment_parent' => 0,
'author' => '',
'author_url' => '',
'author_email' => '',
), $params);
$comment = $params['content'] = trim($params['content']);
if( ! $commented_Item->can_comment(NULL) )
{
return xmlrpcs_resperror( 5, T_('You cannot leave comments on this post!') );
}
$commented_Item->load_Blog(); // Make sure Blog is loaded (will be needed whether logged in or not)
if( empty($params['username']) && empty($params['password']) )
{ // Anonymous comment
// NO permission to edit!
$perm_comment_edit = false;
$User = NULL;
$author = trim($params['author']);
$email = utf8_strtolower( trim($params['author_email']) );
if( $commented_Item->Blog->get_setting('allow_anon_url') )
{
$url = trim($params['author_url']);
}
else
{
$url = NULL;
}
// We need some id info from the anonymous user:
if( $commented_Item->Blog->get_setting( 'require_anon_name' ) && empty( $author ) )
{ // Author name is required for anonymous users:
return xmlrpcs_resperror( 5, T_('Please fill in your name.') );
}
if( $commented_Item->Blog->get_setting( 'require_anon_email' ) && empty( $email ) )
{ // Author email address is required for anonymous users:
return xmlrpcs_resperror( 5, T_('Please fill in your email.') );
}
if( !empty($author) && ( $block = antispam_check( $author ) ) )
{
// Log incident in system log
syslog_insert( sprintf( 'Antispam: Supplied name "%s" contains blacklisted word "%s".', $author, $block ), 'error', 'comment', $commented_Item->ID );
return xmlrpcs_resperror( 5, T_('Supplied name is invalid.') );
}
if( !empty($email)
&& ( !is_email($email)|| ( $block = antispam_check( $email ) ) ) )
{
// Log incident in system log
syslog_insert( sprintf( 'Antispam: Supplied email address "%s" contains blacklisted word "%s".', $email, $block ), 'error', 'comment', $commented_Item->ID );
return xmlrpcs_resperror( 5, T_('Supplied email address is invalid.') );
}
if( !stristr($url, '://') && !stristr($url, '@') )
{ // add 'http://' if no protocol defined for URL; but not if the user seems to be entering an email address alone
$url = 'http://'.$url;
}
if( strlen($url) <= 8 )
{ // ex: https:// is 8 chars
$url = '';
}
// Note: as part of the validation we require the url to be absolute; otherwise we cannot detect bozos typing in
// a title for their comment or whatever...
if( $error = validate_url( $url, 'commenting' ) )
{
return xmlrpcs_resperror( 5, T_('Supplied website address is invalid: ').$error );
}
}
else
{
$User = & $params['User'];
$perm_comment_edit = $User->check_perm( 'blog_comment!published', 'edit', false, $commented_Item->Blog->ID );
$author = $User->ID;
$url = $User->url;
$email = $User->email;
}
// Following call says "WARNING: this does *NOT* (necessarilly) make the HTML code safe.":
$comment = check_html_sanity( $comment, $perm_comment_edit ? 'posting' : 'commenting', $User );
if( $comment === false )
{ // ERROR! Restore original comment for further editing:
$comment = $params['content'];
}
if( empty($comment) )
{ // comment should not be empty!
return xmlrpcs_resperror( 5, T_('Please do not send empty comments.') );
}
$now = date2mysql( $localtimenow );
/**
* Create comment object. Gets validated, before recording it into DB:
*/
$Comment = new Comment();
$Comment->set( 'type', 'comment' );
$Comment->set_Item( $commented_Item );
if( $User )
{ // User is logged in, we'll use his ID
$Comment->set_author_User( $User );
}
else
{ // User is not logged in:
$Comment->set( 'author', $author );
$Comment->set( 'author_email', $email );
$Comment->set( 'author_url', $url );
}
if( !empty($params['comment_parent']) )
{
$Comment->set( 'in_reply_to_cmt_ID', intval($params['comment_parent']) );
}
$Comment->set( 'author_IP', $Hit->IP );
$Comment->set( 'date', $now );
$Comment->set( 'content', $comment );
if( $perm_comment_edit )
{ // User has perm to moderate comments, publish automatically:
$Comment->set( 'status', 'published' );
}
else
{ // Assign default status for new comments:
$Comment->set( 'status', $commented_Item->Blog->get_setting('new_feedback_status') );
}
$action = 'submit_comment_post_'.$commented_Item->ID;
// Trigger event: a Plugin could add a $category="error" message here..
$Plugins->trigger_event('BeforeCommentFormInsert', array(
'Comment' => & $Comment,
'original_comment' => $params['content'],
'is_preview' => false,
'action' => & $action
) );
// Validate first enabled captcha plugin:
$Plugins->trigger_event_first_return( 'ValidateCaptcha', array(
'form_type' => 'comment',
'Comment' => & $Comment,
'is_preview' => ( $action == 'preview' ),
) );
if( $Messages->has_errors() )
{
return xmlrpcs_resperror( 5, $Messages->get_string( 'Cannot create comment, please correct these errors:'."\n", '', " // \n", 'xmlrpc' ) );
}
$Comment->dbinsert();
if( $Comment->ID )
{ // comment has not been deleted
// Trigger event: a Plugin should cleanup any temporary data here..
$Plugins->trigger_event( 'AfterCommentFormInsert', array( 'Comment' => & $Comment, 'original_comment' => $params['content'] ) );
/*
* --------------------------
* New comment notifications:
* --------------------------
*/
// TODO: dh> this should only send published feedback probably and should also use "outbound_notifications_mode"
// fp> yes for general users, but comment moderators need to receive notifications for new unpublished comments
// asimo> this handle moderators and general users as well and use "outbound_notifications_mode" in case of general users
// Moderators will get emails about every new comment
// Subscribed user will only get emails about new published comments
$executed_by_userid = empty( $User ) ? NULL : $User->ID;
$Comment->handle_notifications( $executed_by_userid, true );
}
else
{
return xmlrpcs_resperror( 99, 'Error while inserting comment: '.$DB->last_error );
}
return new xmlrpcresp( new xmlrpcval($Comment->ID, 'int') );
}
/**
* Edit a Comment and return an XML-RPC response
*
* @param array of params
* - status (string)
* - date_created_gmt (string)
* - content (string)
* - author (string)
* - author_url (string)
* - author_email (string)
* @return xmlrpcmsg
*/
function xmlrpcs_edit_comment( $params = array(), & $edited_Comment )
{
global $DB, $current_User, $Messages;
$params = array_merge( array(
'status' => '',
'date' => '',
'content' => '',
'author' => '',
'author_url' => '',
'author_email' => '',
), $params);
$comment = trim($params['content']);
$edited_Comment_Item = $edited_Comment->get_Item();
$edited_Comment_Item->load_Blog();
$perm_comment_edit = check_user_perm( 'blog_comment!published', 'edit', false, $edited_Comment_Item->Blog->ID );
// CHECK HTML SANITY:
// Following call says "WARNING: this does *NOT* (necessarilly) make the HTML code safe.":
$comment = check_html_sanity( $comment, $perm_comment_edit ? 'posting' : 'commenting', $current_User );
if( $comment === false )
{ // ERROR! Restore original comment for further editing:
$comment = trim($params['content']);
}
if( empty($comment) )
{ // comment should not be empty!
return xmlrpcs_resperror( 5, T_('Please do not send empty comments.') );
}
// UPDATE COMMENT IN DB:
$edited_Comment->set( 'content', $comment );
$edited_Comment->set( 'status', $params['status'] );
if( !empty($date) )
{
$edited_Comment->set( 'date', $date );
}
if( ! $edited_Comment->get_author_User() )
{ // If this is not a member comment
$edited_Comment->set( 'author', $params['author'] );
$edited_Comment->set( 'author_url', $params['author_url'] );
$edited_Comment->set( 'author_email', $params['author_email'] );
}
$edited_Comment->dbupdate();
if( $DB->error )
{ // DB error
return xmlrpcs_resperror( 99, 'Error while updating comment: '.$DB->last_error );
}
// Execute or schedule notifications & pings:
logIO( 'Handling notifications...' );
$edited_Comment->handle_notifications();
logIO( 'OK.' );
return new xmlrpcresp( new xmlrpcval( true, 'boolean' ) );
}
/**
* Create a new Item and return an XML-RPC response
*
* @param array Item properties
* @param object Blog where we are going to create a new Item
* @return xmlrpcmsg
*/
function xmlrpcs_new_item( $params, & $Blog = NULL )
{
global $current_User, $Settings, $Messages, $DB;
$params = array_merge( array(
'title' => '',
'content' => '',
'date' => '',
'main_cat_ID' => 0,
'extra_cat_IDs' => array(),
'cat_IDs' => array(), // we may use this to set main and extra cats
'status' => 'published',
'tags' => '',
'excerpt' => '',
'item_typ_ID' => 1,
'comment_status' => 'open',
'urltitle' => '',
'featured' => 0,
'custom_fields' => array(),
'order' => '',
'parent_ID' => '',
), $params );
if( empty($Blog) && !empty($params['main_cat_ID']) )
{ // Get the blog by main category ID
// Check if category exists and can be used
$ChapterCache = & get_ChapterCache();
$main_Chapter = & $ChapterCache->get_by_ID( $params['main_cat_ID'], false, false );
if( empty($main_Chapter) )
{ // Cat does not exist:
return xmlrpcs_resperror( 11 ); // User error 11
}
$BlogCache = & get_BlogCache();
$Collection = $Blog = & $BlogCache->get_by_ID( $main_Chapter->blog_ID, false, false );
logIO( 'Requested Blog: '.$Blog->ID.' - '.$Blog->name );
}
if( empty($Blog) )
{ // Blog does not exist:
return xmlrpcs_resperror();
}
if( empty($params['main_cat_ID']) )
{
if( is_array($params['cat_IDs']) && count($params['cat_IDs']) > 0 )
{ // Let's use first cat for MAIN and others for EXTRA
$params['main_cat_ID'] = array_shift($params['cat_IDs']);
$params['extra_cat_IDs'] = $params['cat_IDs'];
}
else
{
if( ! $main_cat = $Blog->get_default_cat_ID() )
{ // No default category found for requested blog
return xmlrpcs_resperror( 12 ); // User error 12
}
$params['main_cat_ID'] = $main_cat;
}
}
logIO( 'Main cat ID: '.$params['main_cat_ID'] );
logIO( 'Extra cat IDs: '.implode( ', ', $params['extra_cat_IDs'] ) );
if( empty($params['main_cat_ID']) )
{ // Main category does not exist:
return xmlrpcs_resperror( 11 ); // User error 11
}
// Check if category exists and can be used
if( ! xmlrpcs_check_cats( $params['main_cat_ID'], $Blog, $params['extra_cat_IDs'] ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
/*
* CHECK PERMISSION: (we need perm on all categories, especially if they are in different blogs)
* NOTE: extra_cat_IDs array now includes main_cat_ID too, so we are actually checking ALL categories below
*/
if( ! check_user_perm( 'cats_post!'.$params['status'], 'edit', false, $params['extra_cat_IDs'] ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
if( ! empty( $params['item_typ_ID'] ) )
{
if( ! preg_match( '~^[0-9]+$~', $params['item_typ_ID'] ) )
{ // Only accept numeric values, switch to default value
$params['item_typ_ID'] = 1;
}
$ItemTypeCache = & get_ItemTypeCache();
$ItemType = & $ItemTypeCache->get_by_ID( $params['item_typ_ID'], false, false );
// Check permission for this post type
if( $ItemType && ! check_user_perm( 'cats_item_type_'.$ItemType->perm_level, 'edit', false, $params['extra_cat_IDs'] ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
}
logIO( 'Post type: '.$params['item_typ_ID'] );
logIO( 'Permission granted.' );
// CHECK HTML SANITY:
if( ($params['title'] = check_html_sanity( $params['title'], 'xmlrpc_posting' )) === false )
{
return xmlrpcs_resperror( 21, $Messages->get_string( 'Invalid post title, please correct these errors:', '' ) );
}
if( ($params['content'] = check_html_sanity( $params['content'], 'xmlrpc_posting' )) === false )
{
return xmlrpcs_resperror( 22, $Messages->get_string( 'Invalid post contents, please correct these errors:'."\n", '', " // \n", 'xmlrpc' ) );
}
if( empty($params['date']) )
{
$params['date'] = date( 'Y-m-d H:i:s', (time() + $Settings->get('time_difference')) );
}
// INSERT NEW POST INTO DB:
load_class( 'items/model/_item.class.php', 'Item' );
$edited_Item = new Item();
$edited_Item->set( 'title', $params['title'] );
$edited_Item->set( 'content', $params['content'] );
$edited_Item->set( 'issue_date', $params['date'] );
$edited_Item->set( 'main_cat_ID', $params['main_cat_ID'] );
$edited_Item->set( 'extra_cat_IDs', $params['extra_cat_IDs'] );
$edited_Item->set( 'status', $params['status'] );
$edited_Item->set( 'ityp_ID', $params['item_typ_ID'] );
$edited_Item->set( 'featured', $params['featured'] );
$edited_Item->set_tags_from_string( $params['tags'] );
$edited_Item->set( 'locale', $current_User->locale );
$edited_Item->set_creator_User( $current_User );
if( $params['excerpt'] != '' ) $edited_Item->set( 'excerpt', $params['excerpt'] );
if( $params['urltitle'] != '' ) $edited_Item->set( 'urltitle', $params['urltitle'] );
if( $params['parent_ID'] != '' ) $edited_Item->set( 'parent_ID', $params['parent_ID'] );
if( !empty($params['order']) ) $edited_Item->set( 'order', $params['order'] ); // Do not set if order is 0
if( $edited_Item->allow_comment_statuses() )
{ // Comment status
$edited_Item->set( 'comment_status', $params['comment_status'] );
}
$edited_Item->dbinsert();
if( empty( $edited_Item->ID ) )
{
return xmlrpcs_resperror( 99, 'Error while inserting item: '.$DB->last_error );
}
logIO( 'Posted with ID: '.$edited_Item->ID );
if( !empty($params['custom_fields']) && is_array($params['custom_fields']) && count($params['custom_fields']) > 0 )
{ // TODO sam2kb> Add custom fields
foreach( $params['custom_fields'] as $field )
{ // id, key, value
logIO( 'Custom field: '.var_export($field, true) );
}
}
// Execute or schedule notifications & pings:
logIO( 'Handling notifications...' );
$edited_Item->handle_notifications( NULL, true );
logIO( 'OK.' );
return new xmlrpcresp(new xmlrpcval($edited_Item->ID));
}
/**
* Edit an Item and return an XML-RPC response
*
* @param Item
* @param array Item properties
* @param object Blog where we are going to create a new Item
* @return xmlrpcmsg
*/
function xmlrpcs_edit_item( & $edited_Item, $params )
{
global $current_User, $Messages, $DB;
$params = array_merge( array(
'title' => NULL,
'content' => NULL,
'date' => '',
'main_cat_ID' => NULL,
'extra_cat_IDs' => NULL,
'cat_IDs' => array(), // we may use this to set main and extra cats
'status' => '',
'tags' => NULL,
'excerpt' => NULL,
'item_typ_ID' => NULL,
'comment_status' => '',
'urltitle' => NULL,
'featured' => NULL,
'custom_fields' => NULL,
'order' => NULL,
'parent_ID' => NULL,
'author_ID' => NULL,
'locale' => '',
), $params );
$Collection = $Blog = & $edited_Item->get_Blog();
logIO( 'Requested Blog: '.$Blog->ID.' - '.$Blog->name );
if( empty($Blog) )
{ // Blog does not exist:
return xmlrpcs_resperror();
}
if( is_array($params['cat_IDs']) && count($params['cat_IDs']) > 0 )
{ // Let's use first cat for MAIN and others for EXTRA
$params['main_cat_ID'] = array_shift($params['cat_IDs']);
$params['extra_cat_IDs'] = $params['cat_IDs'];
}
if( !is_null($params['main_cat_ID']) && is_array($params['extra_cat_IDs']) )
{ // Check new categories
logIO( 'Main cat ID: '.$params['main_cat_ID'] );
logIO( 'Extra cat IDs: '.implode( ', ', $params['extra_cat_IDs'] ) );
// Check if category exists and can be used
if( ! xmlrpcs_check_cats( $params['main_cat_ID'], $Blog, $params['extra_cat_IDs'] ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
/*
* CHECK PERMISSION: (we need perm on all categories, especially if they are in different blogs)
* NOTE: extra_cat_IDs array now includes main_cat_ID too, so we are actually checking ALL categories below
*/
if( ! check_user_perm( 'cats_post!'.$params['status'], 'edit', false, $params['extra_cat_IDs'] ) )
{
}
}
if( ! is_null( $params['item_typ_ID'] ) )
{
if( ! preg_match( '~^[0-9]+$~', $params['item_typ_ID'] ) )
{ // Only accept numeric values, switch to default value
$params['item_typ_ID'] = NULL;
}
$ItemTypeCache = & get_ItemTypeCache();
$ItemType = & $ItemTypeCache->get_by_ID( $params['item_typ_ID'], false, false );
// Check permission for this post type
if( $ItemType && ! check_user_perm( 'cats_item_type_'.$ItemType->perm_level, 'edit', false, $params['extra_cat_IDs'] ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
}
logIO( 'Post type: '.$params['item_typ_ID'] );
logIO( 'Permission granted.' );
// CHECK HTML SANITY:
if( ($params['title'] = check_html_sanity( $params['title'], 'xmlrpc_posting' )) === false )
{
return xmlrpcs_resperror( 21, $Messages->get_string( 'Invalid post title, please correct these errors:', '' ) );
}
if( ($params['content'] = check_html_sanity( $params['content'], 'xmlrpc_posting' )) === false )
{
return xmlrpcs_resperror( 22, $Messages->get_string( 'Invalid post contents, please correct these errors:'."\n", '', " // \n", 'xmlrpc' ) );
}
if( !is_null($params['title']) )
{
$edited_Item->set( 'title', $params['title'] );
}
if( !is_null($params['content']) )
{
$edited_Item->set( 'content', $params['content'] );
}
if( !is_null($params['urltitle']) )
{
$edited_Item->set( 'urltitle', $params['urltitle'] );
}
if( !is_null($params['main_cat_ID']) && !is_null($params['extra_cat_IDs']) )
{
$edited_Item->set('main_cat_ID', $params['main_cat_ID']);
$edited_Item->set('extra_cat_IDs', $params['extra_cat_IDs']);
}
if( !is_null($params['item_typ_ID']) )
{
$edited_Item->set('ityp_ID', $params['item_typ_ID']);
}
if( !is_null($params['featured']) )
{
$edited_Item->set('featured', $params['featured']);
}
if( !is_null($params['order']) )
{
if( ! (empty($params['order']) && ! $edited_Item->get_order()) )
{ // Do not allow 0 order if there was no order set before
$edited_Item->set('order', $params['order']);
}
}
if( !is_null($params['parent_ID']) )
{
$edited_Item->set('parent_ID', $params['parent_ID']);
}
if( !is_null($params['author_ID']) && $params['author_ID'] != $this->creator_user_ID )
{ // We have already checked perms to edit items created by other users
$edited_Item->set( 'lastedit_user_ID', $params['parent_ID']);
}
if( !is_null($params['tags']) )
{
$edited_Item->set_tags_from_string( $params['tags'] );
}
if( !is_null($params['excerpt']) )
{
$edited_Item->set( 'excerpt', $params['excerpt'] );
}
if( !empty($params['comment_status']) && $edited_Item->allow_comment_statuses() )
{ // Comment status
$edited_Item->set( 'comment_status', $params['comment_status'] );
}
if( !empty($params['status']) )
{
$edited_Item->set( 'status', $params['status'] );
}
if( !empty($params['date']) )
{
$edited_Item->set( 'issue_date', $params['date'] );
}
if( !empty($params['locale']) )
{
$edited_Item->set('locale', $params['locale']);
}
logIO( var_export($edited_Item->dbchanges, true) );
// UPDATE POST IN DB:
$edited_Item->dbupdate();
if( $DB->error )
{
return xmlrpcs_resperror( 99, 'Error while updating item: '.$DB->last_error );
}
if( !is_null($params['custom_fields']) )
{ // TODO sam2kb> Add custom fields
if( is_array($params['custom_fields']) && count($params['custom_fields']) > 0 )
{
logIO( 'Modifying custom fields...' );
foreach( $params['custom_fields'] as $field )
{ // id, key, value
logIO( 'Custom field: '.var_export($field, true) );
}
}
else
{
logIO( 'Deleting custom fields...' );
}
}
// Execute or schedule notifications & pings:
logIO( 'Handling notifications...' );
$edited_Item->handle_notifications();
logIO( 'OK.' );
return new xmlrpcresp( new xmlrpcval( 1, 'boolean' ) );
}
/**
* Check that an User can view a specific Item.
*
* @param object The Item (by reference).
* @param object The User (by reference).
* @return boolean True if permission granted, false otherwise.
*/
function xmlrpcs_can_view_item( & $Item, & $current_User )
{
$can_view_post = false;
switch( $Item->status )
{
case 'published':
case 'redirected':
$can_view_post = true;
break;
case 'protected':
case 'draft':
case 'deprecated':
$can_view_post = check_user_perm( 'blog_ismember', 'view', false, $Item->get_blog_ID() );
break;
case 'private':
$can_view_post = ( $Item->creator_user_ID == $current_User->ID );
break;
}
logIO( 'xmlrpcs_can_view_item(): Post status: '.$Item->status );
logIO( 'xmlrpcs_can_view_item( Item(#'.$Item->ID.'), User(#'.$current_User->ID.') ): Permission '.( $can_view_post ? 'granted' : 'DENIED' ) );
return $can_view_post;
}
/**
* Check whether the main category and the extra categories are valid
* in a Blog's context and try to fix errors.
*
* @author Tilman BLUMENBACH / Tblue
*
* @param integer The main category to check (by reference).
* @param object The Blog to which the category is supposed to belong to (by reference).
* @param array Extra categories for the post (by reference).
*
* @return boolean False on error (use xmlrpcs_resperror() to return it), true on success.
*/
function xmlrpcs_check_cats( & $maincat, & $Blog, & $extracats )
{
global $xmlrpcs_errcode, $xmlrpcs_errmsg, $xmlrpcerruser;
// Trim $maincat and $extracats (qtm sends whitespace before the cat IDs):
$maincat = trim( $maincat );
$extracats = array_map( 'trim', $extracats );
$ChapterCache = & get_ChapterCache();
// ---- CHECK MAIN CATEGORY ----
if( $ChapterCache->get_by_ID( $maincat, false ) === false )
{ // Category does not exist!
// Remove old category from extra cats:
if( ( $key = array_search( $maincat, $extracats ) ) !== false )
{
unset( $extracats[$key] );
}
// Set new category (blog default):
$maincat = $Blog->get_default_cat_ID();
logIO( 'Invalid main cat ID - new ID: '.$maincat );
}
else if( get_allow_cross_posting() < 2 && get_catblog( $maincat ) != $Blog->ID )
{ // We cannot use a maincat of another blog than the current one:
$xmlrpcs_errcode = $xmlrpcerruser + 11;
$xmlrpcs_errmsg = 'Current crossposting setting does not allow moving posts to a different blog.';
return false;
}
// ---- CHECK EXTRA CATEGORIES ----
foreach( $extracats as $ecat )
{
if( $ecat == $maincat )
{ // We already checked the maincat above (or reset it):
continue;
}
logIO( 'Checking extra cat: '.$ecat );
if( $ChapterCache->get_by_ID( $ecat, false ) === false )
{ // Extra cat does not exist:
$xmlrpcs_errcode = $xmlrpcerruser + 11;
$xmlrpcs_errmsg = 'Extra category '.(int)$ecat.' not found in requested blog.';
return false;
}
}
if( ! in_array( $maincat, $extracats ) )
{
logIO( '$maincat was not found in $extracats array - adding.' );
$extracats[] = $maincat;
}
return true;
}
/**
* Get array of latest items
*
* @param array of params
* - limit (int) the number of items to return
* - post_ID (int) return specified item or NULL to return all available
* @return xmlrpcmsg
*/
function xmlrpc_get_items( $params, & $Blog )
{
$params = array_merge( array(
'limit' => 0,
'item_ID' => 0,
'types' => '', // all post types
'itemtype_usage' => '', // all post types
), $params);
// Protected and private get checked by statuses_where_clause().
$statuses = array( 'published', 'redirected', 'protected', 'private' );
if( check_user_perm( 'blog_ismember', 'view', false, $Blog->ID ) )
{ // These statuses require member status:
$statuses = array_merge( $statuses, array( 'draft', 'deprecated' ) );
}
logIO( 'Statuses: '.implode(', ', $statuses) );
if( !empty($params['item_ID']) )
{
logIO('Getting item #'.$params['item_ID']);
$filters = array(
'visibility_array' => $statuses,
'types' => NULL,
'itemtype_usage' => NULL,
'post_ID' => $params['item_ID'],
);
}
else
{
logIO( sprintf('Trying to get latest items (%s)', ($params['limit'] ? $params['limit'] : 'all')) );
$filters = array(
'visibility_array' => $statuses,
'types' => $params['types'],
'itemtype_usage' => $params['itemtype_usage'],
'order' => 'DESC',
'unit' => 'posts',
);
}
// Get the pages to display:
load_class( 'items/model/_itemlist.class.php', 'ItemList2' );
$ItemList = new ItemList2( $Blog, NULL, NULL, $params['limit'] );
$ItemList->set_filters( $filters, false );
// Run the query:
$ItemList->query();
logIO( 'Items found: '.$ItemList->result_num_rows );
$data = array();
while( $Item = & $ItemList->get_item() )
{
$data[] = _wp_mw_get_item_struct( $Item );
}
return $data;
}
/**
* Get array of latest comments
*
* @param array of params
* - Blog (object)
* - User (object)
* - limit (int) the number of comments to return
* - comment_ID (int) return specified comment or NULL to return all available
* - item_ID (int) return comments for specified item only
* @return xmlrpcmsg
*/
function xmlrpc_get_comments( $params, & $Blog )
{
global $DB;
$params = array_merge( array(
'limit' => 0,
'comment_ID' => 0,
'item_ID' => 0,
'statuses' => '',
'types' => array( 'comment', 'trackback', 'pingback', 'webmention' ),
), $params );
$params['comment_ID'] = abs( intval($params['comment_ID']) );
$params['item_ID'] = abs( intval($params['item_ID']) );
if( empty($params['statuses']) )
{ // Return all except 'trash'
$params['statuses'] = array('published', 'deprecated', 'draft');
}
if( !empty($params['comment_ID']) )
{
logIO('Getting comment #'.$params['comment_ID']);
$filters = array(
'comment_ID'=> $params['comment_ID'],
'types' => $params['types'],
'statuses' => $params['statuses'],
);
}
elseif( !empty($params['item_ID']) )
{
logIO('Getting comments to item #'.$params['item_ID']);
$ItemCache = & get_ItemCache();
$Item = & $ItemCache->get_by_ID( $params['item_ID'], false, false );
if( empty( $Item ) )
{ // Item not found
return xmlrpcs_resperror( 5, 'Requested post/Item ('.$params['item_ID'].') does not exist.' );
}
if( ! $Item->can_see_comments() )
{ // Cannot see comments
return xmlrpcs_resperror( 5, 'You are not allowed to view comments for this post/Item ('.$params['item_ID'].').' );
}
$filters = array(
'post_ID' => $Item->ID,
'types' => $params['types'],
'statuses' => $params['statuses'],
'comments' => $params['limit'],
'order' => 'DESC',
);
}
else
{
logIO( sprintf('Trying to get latest comments (%s)', ($params['limit'] ? $params['limit'] : 'all')) );
$filters = array(
'types' => $params['types'],
'statuses' => $params['statuses'],
'comments' => $params['limit'],
'order' => 'DESC',
);
}
//logIO( "Filters:\n".var_export($filters, true) );
$CommentList = new CommentList2( $Blog );
// Filter list:
$CommentList->set_filters( $filters, false );
// Get ready for display (runs the query):
$CommentList->display_init();
logIO( 'Comments found: '.$CommentList->result_num_rows );
$data = array();
if( $CommentList->result_num_rows )
{
while( $Comment = & $CommentList->get_next() )
{ // Loop through comments:
$Comment->get_Item();
$data[] = array(
'dateCreated' => new xmlrpcval( datetime_to_iso8601($Comment->date, true), 'dateTime.iso8601' ), // Force GMT date
'date_created_gmt' => new xmlrpcval( datetime_to_iso8601($Comment->date, true), 'dateTime.iso8601' ),
'user_id' => new xmlrpcval( intval($Comment->author_user_ID) ),
'comment_id' => new xmlrpcval($Comment->ID),
'parent' => new xmlrpcval( intval($Comment->in_reply_to_cmt_ID) ),
'status' => new xmlrpcval( wp_or_b2evo_comment_status($Comment->status, 'wp') ),
'content' => new xmlrpcval($Comment->content),
'link' => new xmlrpcval($Comment->get_permanent_url()),
'post_id' => new xmlrpcval($Comment->Item->ID),
'post_title' => new xmlrpcval($Comment->Item->title),
'author' => new xmlrpcval($Comment->get_author_name()),
'author_url' => new xmlrpcval($Comment->get_author_url()),
'author_email' => new xmlrpcval($Comment->get_author_email()),
'author_ip' => new xmlrpcval($Comment->author_IP),
'type' => new xmlrpcval( (($Comment->type == 'comment') ? '' : $Comment->type) ) // empty string for 'comment'
);
}
}
return $data;
}
/**
* Deletes given Item
*
* @return xmlrpcresp XML-RPC Response (bool)
*/
function xmlrpcs_delete_item( & $edited_Item )
{
global $DB;
// CHECK PERMISSION:
if( ! check_user_perm( 'item_post!CURSTATUS', 'delete', false, $edited_Item ) )
{ // Permission denied
return xmlrpcs_resperror( 3 ); // User error 3
}
logIO( 'Permission granted.' );
// DELETE POST FROM DB:
$edited_Item->dbdelete();
if( $DB->error )
{
return xmlrpcs_resperror( 99, 'DB error: '.$DB->last_error ); // user error 9
}
logIO( 'OK.' );
return new xmlrpcresp(new xmlrpcval(1, 'boolean'));
}
function wp_or_b2evo_comment_status( $raw_status, $convert_to = 'b2evo' )
{
$status = '';
if( $convert_to == 'b2evo' )
{
switch( $raw_status )
{ // Map WP statuses to b2evo
// Keep native b2evo statuses
case 'published':
case 'deprecated':
case 'draft':
case 'trash':
$status = $raw_status;
break;
case 'hold':
$status = 'draft';
break;
case 'spam':
$status = 'deprecated';
break;
case 'approve':
$status = 'published';
break;
default:
$status = NULL;
}
}
elseif( $convert_to == 'wp' )
{
switch( $raw_status )
{ // Map b2evo statuses to WP
case 'deprecated':
$status = 'spam';
break;
case 'draft':
$status = 'hold';
break;
case 'trash':
$status = 'trash';
break;
default:
$status = 'approve';
break;
}
}
return $status;
}
function wp_or_b2evo_item_status( $raw_status, $convert_to = 'b2evo' )
{
$status = '';
if( $convert_to == 'b2evo' )
{
switch( $raw_status )
{ // Map WP statuses to b2evo
// Note: we drop 'inherit' status because b2evo doesn't support it
// Keep native b2evo statuses
case 'published':
case 'deprecated':
case 'protected':
case 'private':
case 'draft':
case 'redirected':
$status = $raw_status;
break;
case 'auto-draft':
case 'pending':
$status = 'draft';
break;
case 'publish':
case 'future':
$status = 'published';
break;
case 'trash':
$status = 'deprecated';
break;
}
}
elseif( $convert_to == 'wp' )
{
switch( $raw_status )
{ // Map b2evo statuses to WP
case 'private':
case 'draft':
$status = $raw_status;
break;
case 'deprecated':
$status = 'trash';
break;
case 'protected':
$status = 'private';
break;
case 'published':
$status = 'publish';
break;
case 'redirected':
$status = 'published';
break;
default:
$status = 'approve';
break;
}
}
return $status;
}
?>