Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/xmlrpc/model/_xmlrpcs.funcs.php
<?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 &laquo;%s&raquo; 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;
}

?>