package org.xmlrpc.android; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.webkit.URLUtil; import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkResponse; import com.android.volley.RedirectError; import com.android.volley.TimeoutError; import com.android.volley.toolbox.RequestFuture; import com.android.volley.toolbox.StringRequest; import com.google.gson.Gson; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.CommentTable; import org.wordpress.android.models.Blog; import org.wordpress.android.models.BlogIdentifier; import org.wordpress.android.models.Comment; import org.wordpress.android.models.CommentList; import org.wordpress.android.models.CommentStatus; import org.wordpress.android.models.FeatureSet; import org.wordpress.android.ui.media.MediaGridFragment.Filter; import org.wordpress.android.ui.stats.StatsUtils; import org.wordpress.android.ui.stats.StatsWidgetProvider; import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.CoreEvents; import org.wordpress.android.util.DateTimeUtils; import org.wordpress.android.util.MapUtils; import org.wordpress.android.util.helpers.MediaFile; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.net.ssl.SSLHandshakeException; import de.greenrobot.event.EventBus; public class ApiHelper { public static final class Method { public static final String GET_MEDIA_LIBRARY = "wp.getMediaLibrary"; public static final String GET_POST_FORMATS = "wp.getPostFormats"; public static final String GET_CATEGORIES = "wp.getCategories"; public static final String GET_MEDIA_ITEM = "wp.getMediaItem"; public static final String GET_COMMENT = "wp.getComment"; public static final String GET_COMMENTS = "wp.getComments"; public static final String GET_BLOGS = "wp.getUsersBlogs"; public static final String GET_OPTIONS = "wp.getOptions"; public static final String GET_PROFILE = "wp.getProfile"; public static final String GET_PAGES = "wp.getPages"; public static final String GET_TERM = "wp.getTerm"; public static final String GET_PAGE = "wp.getPage"; public static final String DELETE_COMMENT = "wp.deleteComment"; public static final String DELETE_PAGE = "wp.deletePage"; public static final String DELETE_POST = "wp.deletePost"; public static final String NEW_CATEGORY = "wp.newCategory"; public static final String NEW_COMMENT = "wp.newComment"; public static final String EDIT_POST = "wp.editPost"; public static final String EDIT_COMMENT = "wp.editComment"; public static final String SET_OPTIONS = "wp.setOptions"; public static final String UPLOAD_FILE = "wp.uploadFile"; public static final String WPCOM_GET_FEATURES = "wpcom.getFeatures"; public static final String LIST_METHODS = "system.listMethods"; } public static final class Param { public static final String SHOW_SUPPORTED_POST_FORMATS = "show-supported"; } public enum ErrorType { NO_ERROR, UNKNOWN_ERROR, INVALID_CURRENT_BLOG, NETWORK_XMLRPC, INVALID_CONTEXT, INVALID_RESULT, NO_UPLOAD_FILES_CAP, CAST_EXCEPTION, TASK_CANCELLED, UNAUTHORIZED } public static final Map<String, String> blogOptionsXMLRPCParameters = new HashMap<String, String>(); static { blogOptionsXMLRPCParameters.put("software_version", "software_version"); blogOptionsXMLRPCParameters.put("post_thumbnail", "post_thumbnail"); blogOptionsXMLRPCParameters.put("jetpack_client_id", "jetpack_client_id"); blogOptionsXMLRPCParameters.put("blog_public", "blog_public"); blogOptionsXMLRPCParameters.put("home_url", "home_url"); blogOptionsXMLRPCParameters.put("admin_url", "admin_url"); blogOptionsXMLRPCParameters.put("login_url", "login_url"); blogOptionsXMLRPCParameters.put("blog_title", "blog_title"); blogOptionsXMLRPCParameters.put("time_zone", "time_zone"); } public static abstract class HelperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> { protected String mErrorMessage; protected ErrorType mErrorType = ErrorType.NO_ERROR; protected Throwable mThrowable; protected void setError(@NonNull ErrorType errorType, String errorMessage) { mErrorMessage = errorMessage; mErrorType = errorType; AppLog.e(T.API, mErrorType.name() + " - " + mErrorMessage); } protected void setError(@NonNull ErrorType errorType, String errorMessage, Throwable throwable) { mErrorMessage = errorMessage; mErrorType = errorType; mThrowable = throwable; AppLog.e(T.API, mErrorType.name() + " - " + mErrorMessage, throwable); } } public interface GenericErrorCallback { public void onFailure(ErrorType errorType, String errorMessage, Throwable throwable); } public interface GenericCallback extends GenericErrorCallback { public void onSuccess(); } public interface DatabasePersistCallback { void onDataReadyToSave(List list); } public static class GetPostFormatsTask extends HelperAsyncTask<Blog, Void, Object> { private Blog mBlog; @Override protected Object doInBackground(Blog... blog) { mBlog = blog[0]; XMLRPCClientInterface client = XMLRPCFactory.instantiate(mBlog.getUri(), mBlog.getHttpuser(), mBlog.getHttppassword()); Object result = null; Object[] params = { mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword(), Param.SHOW_SUPPORTED_POST_FORMATS }; try { result = client.call(Method.GET_POST_FORMATS, params); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } return result; } protected void onPostExecute(Object result) { if (result != null && result instanceof HashMap) { Map<?, ?> postFormats = (HashMap<?, ?>) result; if (postFormats.size() > 0) { Gson gson = new Gson(); String postFormatsJson = gson.toJson(postFormats); if (postFormatsJson != null) { if (mBlog.bsetPostFormats(postFormatsJson)) { WordPress.wpDB.saveBlog(mBlog); } } } } } } public static synchronized void updateBlogOptions(Blog currentBlog, Map<?, ?> blogOptions) { boolean isModified = false; Gson gson = new Gson(); String blogOptionsJson = gson.toJson(blogOptions); if (blogOptionsJson != null) { isModified |= currentBlog.bsetBlogOptions(blogOptionsJson); } // Software version if (!currentBlog.isDotcomFlag()) { Map<?, ?> sv = (Map<?, ?>) blogOptions.get("software_version"); String wpVersion = MapUtils.getMapStr(sv, "value"); if (wpVersion.length() > 0) { isModified |= currentBlog.bsetWpVersion(wpVersion); } } // Featured image support Map<?, ?> featuredImageHash = (Map<?, ?>) blogOptions.get("post_thumbnail"); if (featuredImageHash != null) { boolean featuredImageCapable = MapUtils.getMapBool(featuredImageHash, "value"); isModified |= currentBlog.bsetFeaturedImageCapable(featuredImageCapable); } else { isModified |= currentBlog.bsetFeaturedImageCapable(false); } // Blog name Map<?, ?> blogNameHash = (Map<?, ?>) blogOptions.get("blog_title"); if (blogNameHash != null) { String blogName = MapUtils.getMapStr(blogNameHash, "value"); if (blogName != null && !blogName.equals(currentBlog.getBlogName())) { currentBlog.setBlogName(blogName); isModified = true; } } if (isModified) { WordPress.wpDB.saveBlog(currentBlog); } } /** * Task to refresh blog level information (WP version number) and stuff * related to the active theme (available post types, recent comments, etc). */ public static class RefreshBlogContentTask extends HelperAsyncTask<Boolean, Void, Boolean> { private static HashSet<BlogIdentifier> refreshedBlogs = new HashSet<BlogIdentifier>(); private Blog mBlog; private BlogIdentifier mBlogIdentifier; private GenericCallback mCallback; public RefreshBlogContentTask(Blog blog, GenericCallback callback) { if (blog == null) { cancel(true); return; } mBlogIdentifier = new BlogIdentifier(blog.getUrl(), blog.getRemoteBlogId()); if (refreshedBlogs.contains(mBlogIdentifier)) { cancel(true); } else { refreshedBlogs.add(mBlogIdentifier); } mBlog = blog; mCallback = callback; } private void updateBlogAdmin(Map<String, Object> userInfos) { if (userInfos.containsKey("roles") && ( userInfos.get("roles") instanceof Object[])) { boolean isAdmin = false; Object[] userRoles = (Object[])userInfos.get("roles"); for (int i = 0; i < userRoles.length; i++) { if (userRoles[i].toString().equals("administrator")) { isAdmin = true; break; } } if (mBlog.bsetAdmin(isAdmin)) { WordPress.wpDB.saveBlog(mBlog); EventBus.getDefault().post(new CoreEvents.BlogListChanged()); } } } @Override protected Boolean doInBackground(Boolean... params) { boolean commentsOnly = params[0]; XMLRPCClientInterface client = XMLRPCFactory.instantiate(mBlog.getUri(), mBlog.getHttpuser(), mBlog.getHttppassword()); boolean alreadyTrackedAsJetpackBlog = mBlog.isJetpackPowered(); if (!commentsOnly) { // check the WP number if self-hosted Map<String, String> hPost = ApiHelper.blogOptionsXMLRPCParameters; Object[] vParams = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword(), hPost}; Object versionResult = null; try { versionResult = client.call(Method.GET_OPTIONS, vParams); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); return false; } catch (Exception e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); return false; } if (versionResult != null) { Map<?, ?> blogOptions = (HashMap<?, ?>) versionResult; ApiHelper.updateBlogOptions(mBlog, blogOptions); } if (mBlog.isJetpackPowered() && !alreadyTrackedAsJetpackBlog) { // blog just added to the app, or the value of jetpack_client_id has just changed AnalyticsUtils.trackWithBlogDetails(AnalyticsTracker.Stat.SIGNED_INTO_JETPACK, mBlog); } // get theme post formats new GetPostFormatsTask().execute(mBlog); //Update Stats widgets if necessary String currentBlogID = String.valueOf(mBlog.getRemoteBlogId()); if (StatsWidgetProvider.isBlogDisplayedInWidget(mBlog.getRemoteBlogId())) { AppLog.d(AppLog.T.STATS, "The blog with remoteID " + currentBlogID + " is NOT displayed in a widget. Blog Refresh Task doesn't call an update of the widget."); String currentDate = StatsUtils.getCurrentDateTZ(mBlog.getLocalTableBlogId()); StatsWidgetProvider.enqueueStatsRequestForBlog(WordPress.getContext(), currentBlogID, currentDate); } } // Check if user is an admin Object[] userParams = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword()}; try { Map<String, Object> userInfos = (HashMap<String, Object>) client.call(Method.GET_PROFILE, userParams); updateBlogAdmin(userInfos); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); return false; } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } // refresh the comments Map<String, Object> hPost = new HashMap<String, Object>(); hPost.put("number", 30); Object[] commentParams = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword(), hPost}; try { ApiHelper.refreshComments(mBlog, commentParams, new DatabasePersistCallback() { @Override public void onDataReadyToSave(List list) { int localBlogId = mBlog.getLocalTableBlogId(); CommentTable.deleteCommentsForBlog(localBlogId); CommentTable.saveComments(localBlogId, (CommentList)list); } }); } catch (Exception e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); return false; } return true; } @Override protected void onPostExecute(Boolean success) { if (mCallback != null) { if (success) { mCallback.onSuccess(); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } refreshedBlogs.remove(mBlogIdentifier); } } /** * request deleted comments for passed blog and remove them from local db * @param blog blog to check * @return count of comments that were removed from db */ public static int removeDeletedComments(Blog blog) { if (blog == null) { return 0; } XMLRPCClientInterface client = XMLRPCFactory.instantiate( blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Map<String, Object> hPost = new HashMap<String, Object>(); hPost.put("status", "trash"); Object[] params = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), hPost }; int numDeleted = 0; try { Object[] result = (Object[]) client.call(Method.GET_COMMENTS, params); if (result == null || result.length == 0) { return 0; } Map<?, ?> contentHash; for (Object aComment : result) { contentHash = (Map<?, ?>) aComment; long commentId = Long.parseLong(contentHash.get("comment_id").toString()); if (CommentTable.deleteComment(blog.getLocalTableBlogId(), commentId)) numDeleted++; } if (numDeleted > 0) { AppLog.d(T.COMMENTS, String.format("removed %d deleted comments", numDeleted)); } } catch (XMLRPCException e) { AppLog.e(T.COMMENTS, e); } catch (IOException e) { AppLog.e(T.COMMENTS, e); } catch (XmlPullParserException e) { AppLog.e(T.COMMENTS, e); } return numDeleted; } public static CommentList refreshComments(Blog blog, Object[] commentParams, DatabasePersistCallback dbCallback) throws XMLRPCException, IOException, XmlPullParserException { if (blog == null) { return null; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] result; result = (Object[]) client.call(Method.GET_COMMENTS, commentParams); if (result.length == 0) { return null; } Map<?, ?> contentHash; long commentID, postID; String authorName, content, status, authorEmail, authorURL, postTitle, pubDate; java.util.Date date; CommentList comments = new CommentList(); for (int ctr = 0; ctr < result.length; ctr++) { contentHash = (Map<?, ?>) result[ctr]; content = contentHash.get("content").toString(); status = contentHash.get("status").toString(); postID = Long.parseLong(contentHash.get("post_id").toString()); commentID = Long.parseLong(contentHash.get("comment_id").toString()); authorName = contentHash.get("author").toString(); authorURL = contentHash.get("author_url").toString(); authorEmail = contentHash.get("author_email").toString(); postTitle = contentHash.get("post_title").toString(); date = (java.util.Date) contentHash.get("date_created_gmt"); pubDate = DateTimeUtils.iso8601FromDate(date); Comment comment = new Comment( postID, commentID, authorName, pubDate, content, status, postTitle, authorURL, authorEmail, null); comments.add(comment); } if (dbCallback != null){ dbCallback.onDataReadyToSave(comments); } return comments; } /** * Delete a single post or page via XML-RPC API parameters follow those of FetchSinglePostTask */ public static class DeleteSinglePostTask extends HelperAsyncTask<Object, Boolean, Boolean> { @Override protected Boolean doInBackground(Object... arguments) { Blog blog = (Blog) arguments[0]; if (blog == null) { return false; } String postId = (String) arguments[1]; boolean isPage = (Boolean) arguments[2]; XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] params = {blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), postId}; try { client.call(isPage ? Method.DELETE_PAGE : Method.DELETE_POST, params); return true; } catch (XMLRPCException | IOException | XmlPullParserException e) { mErrorMessage = e.getMessage(); return false; } } } public static class SyncMediaLibraryTask extends HelperAsyncTask<java.util.List<?>, Void, Integer> { public interface Callback extends GenericErrorCallback { public void onSuccess(int results); } private Callback mCallback; private int mOffset; private Filter mFilter; public SyncMediaLibraryTask(int offset, Filter filter, Callback callback) { mOffset = offset; mCallback = callback; mFilter = filter; } @Override protected Integer doInBackground(List<?>... params) { List<?> arguments = params[0]; WordPress.currentBlog = (Blog) arguments.get(0); Blog blog = WordPress.currentBlog; if (blog == null) { setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null"); return 0; } String blogId = String.valueOf(blog.getLocalTableBlogId()); XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Map<String, Object> filter = new HashMap<String, Object>(); filter.put("number", 50); filter.put("offset", mOffset); if (mFilter == Filter.IMAGES) { filter.put("mime_type","image/*"); } else if(mFilter == Filter.UNATTACHED) { filter.put("parent_id", 0); } Object[] apiParams = {blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), filter}; Object[] results = null; try { results = (Object[]) client.call(Method.GET_MEDIA_LIBRARY, apiParams); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); return 0; } catch (XMLRPCException e) { prepareErrorMessage(e); return 0; } catch (IOException e) { prepareErrorMessage(e); return 0; } catch (XmlPullParserException e) { prepareErrorMessage(e); return 0; } if (blogId == null) { setError(ErrorType.INVALID_CURRENT_BLOG, "Invalid blogId"); return 0; } if (results == null) { setError(ErrorType.INVALID_RESULT, "Invalid blogId"); return 0; } Map<?, ?> resultMap; // results returned, so mark everything existing to deleted // since offset is 0, we are doing a full refresh if (mOffset == 0) { WordPress.wpDB.setMediaFilesMarkedForDeleted(blogId); } for (Object result : results) { resultMap = (Map<?, ?>) result; boolean isDotCom = (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isDotcomFlag()); MediaFile mediaFile = new MediaFile(blogId, resultMap, isDotCom); WordPress.wpDB.saveMediaFile(mediaFile); } WordPress.wpDB.deleteFilesMarkedForDeleted(blogId); return results.length; } private void prepareErrorMessage(Exception e) { // user does not have permission to view media gallery if (e.getMessage() != null && e.getMessage().contains("401")) { setError(ErrorType.NO_UPLOAD_FILES_CAP, e.getMessage(), e); } else { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } } @Override protected void onPostExecute(Integer result) { if (mCallback != null) { if (mErrorType == ErrorType.NO_ERROR) { mCallback.onSuccess(result); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } } } public static class EditMediaItemTask extends HelperAsyncTask<List<?>, Void, Boolean> { private GenericCallback mCallback; private String mMediaId; private String mTitle; private String mDescription; private String mCaption; public EditMediaItemTask(String mediaId, String title, String description, String caption, GenericCallback callback) { mMediaId = mediaId; mCallback = callback; mTitle = title; mCaption = caption; mDescription = description; } @Override protected Boolean doInBackground(List<?>... params) { List<?> arguments = params[0]; WordPress.currentBlog = (Blog) arguments.get(0); Blog blog = WordPress.currentBlog; if (blog == null) { setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null"); return null; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Map<String, Object> contentStruct = new HashMap<String, Object>(); contentStruct.put("post_title", mTitle); contentStruct.put("post_content", mDescription); contentStruct.put("post_excerpt", mCaption); Object[] apiParams = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), mMediaId, contentStruct }; Boolean result = null; try { result = (Boolean) client.call(Method.EDIT_POST, apiParams); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } return result; } @Override protected void onPostExecute(Boolean result) { if (mCallback != null) { if (mErrorType == ErrorType.NO_ERROR) { mCallback.onSuccess(); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } } } public static class GetMediaItemTask extends HelperAsyncTask<List<?>, Void, MediaFile> { public interface Callback extends GenericErrorCallback { public void onSuccess(MediaFile results); } private Callback mCallback; private int mMediaId; public GetMediaItemTask(int mediaId, Callback callback) { mMediaId = mediaId; mCallback = callback; } @Override protected MediaFile doInBackground(List<?>... params) { List<?> arguments = params[0]; WordPress.currentBlog = (Blog) arguments.get(0); Blog blog = WordPress.currentBlog; if (blog == null) { setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null"); return null; } String blogId = String.valueOf(blog.getLocalTableBlogId()); XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] apiParams = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), mMediaId }; Map<?, ?> results = null; try { results = (Map<?, ?>) client.call(Method.GET_MEDIA_ITEM, apiParams); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } if (results != null && blogId != null) { boolean isDotCom = (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isDotcomFlag()); MediaFile mediaFile = new MediaFile(blogId, results, isDotCom); WordPress.wpDB.saveMediaFile(mediaFile); return mediaFile; } else { return null; } } @Override protected void onPostExecute(MediaFile result) { if (mCallback != null) { if (result != null) { mCallback.onSuccess(result); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } } } public static class UploadMediaTask extends HelperAsyncTask<List<?>, Void, Map<?, ?>> { public interface Callback extends GenericErrorCallback { void onSuccess(String remoteId, String remoteUrl, String secondaryId); void onProgressUpdate(float progress); } private Callback mCallback; private Context mContext; private MediaFile mMediaFile; public UploadMediaTask(Context applicationContext, MediaFile mediaFile, Callback callback) { mContext = applicationContext; mMediaFile = mediaFile; mCallback = callback; } @Override protected Map<?, ?> doInBackground(List<?>... params) { List<?> arguments = params[0]; WordPress.currentBlog = (Blog) arguments.get(0); Blog blog = WordPress.currentBlog; if (blog == null) { setError(ErrorType.INVALID_CURRENT_BLOG, "current blog is null"); return null; } final XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Map<String, Object> data = new HashMap<String, Object>(); data.put("name", mMediaFile.getFileName()); data.put("type", mMediaFile.getMimeType()); data.put("bits", mMediaFile); data.put("overwrite", true); Object[] apiParams = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), data }; final File tempFile = getTempFile(mContext); if (client instanceof XMLRPCClient) { ((XMLRPCClient) client).setOnBytesUploadedListener(new XMLRPCClient.OnBytesUploadedListener() { @Override public void onBytesUploaded(long uploadedBytes) { if (isCancelled()) { // Stop the upload if the task has been cancelled ((XMLRPCClient) client).cancel(); } if (tempFile == null || tempFile.length() == 0) { return; } float fractionUploaded = uploadedBytes / (float) tempFile.length(); mCallback.onProgressUpdate(fractionUploaded); } }); } if (mContext == null) { return null; } Map<?, ?> resultMap; try { resultMap = (HashMap<?, ?>) client.call(Method.UPLOAD_FILE, apiParams, tempFile); } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, null, cce); return null; } catch (XMLRPCFault e) { if (e.getFaultCode() == 401) { setError(ErrorType.NETWORK_XMLRPC, mContext.getString(R.string.media_error_no_permission_upload), e); } else { // getFaultString() returns the error message from the server without the "[Code 403]" part. setError(ErrorType.NETWORK_XMLRPC, e.getFaultString(), e); } return null; } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, null, e); return null; } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, null, e); return null; } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, null, e); return null; } if (resultMap != null && resultMap.containsKey("id")) { return resultMap; } else { setError(ErrorType.INVALID_RESULT, null); } return null; } // Create a temp file for media upload private File getTempFile(Context context) { String tempFileName = "wp-" + System.currentTimeMillis(); try { context.openFileOutput(tempFileName, Context.MODE_PRIVATE); } catch (FileNotFoundException e) { return null; } return context.getFileStreamPath(tempFileName); } @Override protected void onPostExecute(Map<?, ?> result) { if (mCallback != null) { if (result != null) { String remoteId = (String) result.get("id"); String remoteUrl = (String) result.get("url"); String videoPressId = (String) result.get("videopress_shortcode"); mCallback.onSuccess(remoteId, remoteUrl, videoPressId); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } } } public static class DeleteMediaTask extends HelperAsyncTask<List<?>, Void, Void> { private GenericCallback mCallback; private String mMediaId; public DeleteMediaTask(String mediaId, GenericCallback callback) { mMediaId = mediaId; mCallback = callback; } @Override protected Void doInBackground(List<?>... params) { List<?> arguments = params[0]; Blog blog = (Blog) arguments.get(0); if (blog == null) { setError(ErrorType.INVALID_CONTEXT, "ApiHelper - invalid blog"); return null; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] apiParams = new Object[]{blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), mMediaId}; try { if (client != null) { Boolean result = (Boolean) client.call(Method.DELETE_POST, apiParams); if (!result) { setError(ErrorType.INVALID_RESULT, "wp.deletePost returned false"); } } } catch (ClassCastException cce) { setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce); } catch (XMLRPCException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (IOException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } catch (XmlPullParserException e) { setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e); } return null; } @Override protected void onPostExecute(Void v) { if (mCallback != null) { if (mErrorType == ErrorType.NO_ERROR) { mCallback.onSuccess(); } else { mCallback.onFailure(mErrorType, mErrorMessage, mThrowable); } } } } public static class GetFeatures extends AsyncTask<List<?>, Void, FeatureSet> { public interface Callback { void onResult(FeatureSet featureSet); } private Callback mCallback; public GetFeatures() { } public GetFeatures(Callback callback) { mCallback = callback; } public FeatureSet doSynchronously(List<?>... params) { return doInBackground(params); } @Override protected FeatureSet doInBackground(List<?>... params) { List<?> arguments = params[0]; Blog blog = (Blog) arguments.get(0); if (blog == null) return null; XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] apiParams = new Object[] { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), }; Map<?, ?> resultMap = null; try { resultMap = (HashMap<?, ?>) client.call(Method.WPCOM_GET_FEATURES, apiParams); } catch (ClassCastException cce) { AppLog.e(T.API, "wpcom.getFeatures error", cce); } catch (XMLRPCException e) { AppLog.e(T.API, "wpcom.getFeatures error", e); } catch (IOException e) { AppLog.e(T.API, "wpcom.getFeatures error", e); } catch (XmlPullParserException e) { AppLog.e(T.API, "wpcom.getFeatures error", e); } if (resultMap != null) { return new FeatureSet(blog.getRemoteBlogId(), resultMap); } return null; } @Override protected void onPostExecute(FeatureSet result) { if (mCallback != null) mCallback.onResult(result); } } /** * Synchronous method to fetch the String content at the specified HTTP URL. * * @param stringUrl URL to fetch contents for. * @return content of the resource, or null if URL was invalid or resource could not be retrieved. */ public static String getResponse(final String stringUrl) throws SSLHandshakeException, TimeoutError, TimeoutException { return getResponse(stringUrl, 0); } private static String getRedirectURL(String oldURL, NetworkResponse networkResponse) { if (networkResponse.headers != null && networkResponse.headers.containsKey("Location")) { String newURL = networkResponse.headers.get("Location"); // Relative URL if (newURL != null && newURL.startsWith("/")) { Uri oldUri = Uri.parse(oldURL); if (oldUri.getScheme() == null || oldUri.getAuthority() == null) { return null; } return oldUri.getScheme() + "://" + oldUri.getAuthority() + newURL; } // Absolute URL return newURL; } return null; } public static String getResponse(final String stringUrl, int numberOfRedirects) throws SSLHandshakeException, TimeoutError, TimeoutException { RequestFuture<String> future = RequestFuture.newFuture(); StringRequest request = new StringRequest(stringUrl, future, future); request.setRetryPolicy(new DefaultRetryPolicy(XMLRPCClient.DEFAULT_SOCKET_TIMEOUT_MS, 0, 1)); WordPress.requestQueue.add(request); try { return future.get(XMLRPCClient.DEFAULT_SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { AppLog.e(T.API, e); } catch (ExecutionException e) { if (e.getCause() != null && e.getCause() instanceof RedirectError) { // Maximum 5 redirects or die if (numberOfRedirects > 5) { AppLog.e(T.API, "Maximum of 5 redirects reached, aborting.", e); return null; } // Follow redirect RedirectError re = (RedirectError) e.getCause(); if (re.networkResponse != null) { String newURL = getRedirectURL(stringUrl, re.networkResponse); if (newURL == null) { AppLog.e(T.API, "Invalid server response", e); return null; } // Abort redirect if old URL was HTTPS and not the new one if (URLUtil.isHttpsUrl(stringUrl) && !URLUtil.isHttpsUrl(newURL)) { AppLog.e(T.API, "Redirect from HTTPS to HTTP not allowed.", e); return null; } // Retry getResponse AppLog.i(T.API, "Follow redirect from " + stringUrl + " to " + newURL); return getResponse(newURL, numberOfRedirects + 1); } } else if (e.getCause() != null && e.getCause() instanceof com.android.volley.TimeoutError) { AppLog.e(T.API, e); throw (com.android.volley.TimeoutError) e.getCause(); } else { AppLog.e(T.API, e); } } return null; } /* * fetches a single post saves it to the db - note that this should NOT be called from main thread */ public static boolean updateSinglePost(int localBlogId, String remotePostId, boolean isPage) { Blog blog = WordPress.getBlog(localBlogId); if (blog == null || TextUtils.isEmpty(remotePostId)) { return false; } XMLRPCClientInterface client = XMLRPCFactory.instantiate( blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] apiParams; if (isPage) { apiParams = new Object[]{ blog.getRemoteBlogId(), remotePostId, blog.getUsername(), blog.getPassword() }; } else { apiParams = new Object[]{ remotePostId, blog.getUsername(), blog.getPassword() }; } try { Object result = client.call(isPage ? Method.GET_PAGE : "metaWeblog.getPost", apiParams); if (result != null && result instanceof Map) { Map postMap = (HashMap) result; List<Map<?, ?>> postsList = new ArrayList<>(); postsList.add(postMap); WordPress.wpDB.savePosts(postsList, localBlogId, isPage, true); return true; } else { return false; } } catch (XMLRPCException | IOException | XmlPullParserException e) { AppLog.e(AppLog.T.POSTS, e); return false; } } public static boolean editComment(Blog blog, Comment comment, CommentStatus newStatus) { if (blog == null) { return false; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Map<String, String> postHash = new HashMap<>(); postHash.put("status", CommentStatus.toString(newStatus)); postHash.put("content", comment.getCommentText()); postHash.put("author", comment.getAuthorName()); postHash.put("author_url", comment.getAuthorUrl()); postHash.put("author_email", comment.getAuthorEmail()); Object[] params = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), Long.toString(comment.commentID), postHash}; try { Object result = client.call(Method.EDIT_COMMENT, params); return (result != null && Boolean.parseBoolean(result.toString())); } catch (XMLRPCFault xmlrpcFault) { if (xmlrpcFault.getFaultCode() == 500) { // let's check whether the comment is already marked as _newStatus_ CommentStatus remoteStatus = getCommentStatus(blog, comment); if (remoteStatus != null && remoteStatus.equals(newStatus)) { // Happy days! Remote is already marked as the desired status return true; } } AppLog.e(T.COMMENTS, "Error while editing comment", xmlrpcFault); } catch (XMLRPCException e) { AppLog.e(T.COMMENTS, "Error while editing comment", e); } catch (IOException e) { AppLog.e(T.COMMENTS, "Error while editing comment", e); } catch (XmlPullParserException e) { AppLog.e(T.COMMENTS, "Error while editing comment", e); } return false; } /** * Fetches the status of a comment * @param blog the blog the comment is in * @param comment the comment to fetch its status * @return the status of the comment on the server, null if error */ public static @Nullable CommentStatus getCommentStatus(Blog blog, Comment comment) { if (blog == null || comment == null) { return null; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(), blog.getHttppassword()); Object[] params = { blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), Long.toString(comment.commentID)}; try { Map<?, ?> contentHash = (Map<?, ?>) client.call(Method.GET_COMMENT, params); final Object status = contentHash.get("status"); return status == null ? null : CommentStatus.fromString(status.toString()); } catch (XMLRPCException e) { AppLog.e(T.COMMENTS, "Error while getting comment", e); } catch (IOException e) { AppLog.e(T.COMMENTS, "Error while getting comment", e); } catch (XmlPullParserException e) { AppLog.e(T.COMMENTS, "Error while getting comment", e); } return null; } }