| package org.wordpress.android.ui.prefs; |
| |
| import android.app.Activity; |
| import android.text.TextUtils; |
| |
| import org.wordpress.android.R; |
| import org.wordpress.android.analytics.AnalyticsTracker; |
| import org.wordpress.android.datasets.SiteSettingsTable; |
| import org.wordpress.android.models.Blog; |
| import org.wordpress.android.models.CategoryModel; |
| import org.wordpress.android.models.SiteSettingsModel; |
| import org.wordpress.android.util.LanguageUtils; |
| import org.wordpress.android.util.AnalyticsUtils; |
| import org.wordpress.android.util.AppLog; |
| import org.wordpress.android.util.MapUtils; |
| import org.xmlrpc.android.ApiHelper.Method; |
| import org.xmlrpc.android.XMLRPCCallback; |
| import org.xmlrpc.android.XMLRPCClientInterface; |
| import org.xmlrpc.android.XMLRPCException; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| class SelfHostedSiteSettings extends SiteSettingsInterface { |
| // XML-RPC wp.getOptions keys |
| public static final String PRIVACY_KEY = "blog_public"; |
| public static final String DEF_CATEGORY_KEY = "default_category"; |
| public static final String DEF_POST_FORMAT_KEY = "default_post_format"; |
| public static final String ALLOW_COMMENTS_KEY = "default_comment_status"; |
| public static final String SEND_PINGBACKS_KEY = "default_pingback_flag"; |
| public static final String RECEIVE_PINGBACKS_KEY = "default_ping_status"; |
| public static final String CLOSE_OLD_COMMENTS_KEY = "close_comments_for_old_posts"; |
| public static final String CLOSE_OLD_COMMENTS_DAYS_KEY = "close_comments_days_old"; |
| public static final String THREAD_COMMENTS_KEY = "thread_comments"; |
| public static final String THREAD_COMMENTS_DEPTH_KEY = "thread_comments_depth"; |
| public static final String PAGE_COMMENTS_KEY = "page_comments"; |
| public static final String PAGE_COMMENT_COUNT_KEY = "comments_per_page"; |
| public static final String COMMENT_SORT_ORDER_KEY = "comment_order"; |
| public static final String COMMENT_MODERATION_KEY = "comment_moderation"; |
| public static final String REQUIRE_IDENTITY_KEY = "require_name_email"; |
| public static final String REQUIRE_USER_ACCOUNT_KEY = "comment_registration"; |
| public static final String WHITELIST_KNOWN_USERS_KEY = "comment_whitelist"; |
| public static final String MAX_LINKS_KEY = "comment_max_links"; |
| public static final String MODERATION_KEYS_KEY = "moderation_keys"; |
| public static final String BLACKLIST_KEYS_KEY = "blacklist_keys"; |
| public static final String SOFTWARE_VERSION_KEY = "software_version"; |
| |
| private static final String BLOG_URL_KEY = "blog_url"; |
| private static final String BLOG_TITLE_KEY = "blog_title"; |
| private static final String BLOG_USERNAME_KEY = "username"; |
| private static final String BLOG_PASSWORD_KEY = "password"; |
| private static final String BLOG_TAGLINE_KEY = "blog_tagline"; |
| private static final String BLOG_CATEGORY_ID_KEY = "categoryId"; |
| private static final String BLOG_CATEGORY_PARENT_ID_KEY = "parentId"; |
| private static final String BLOG_CATEGORY_DESCRIPTION_KEY = "categoryDescription"; |
| private static final String BLOG_CATEGORY_NAME_KEY = "categoryName"; |
| |
| // Requires WordPress 4.5.x or higher |
| private static final int REQUIRED_MAJOR_VERSION = 4; |
| private static final int REQUIRED_MINOR_VERSION = 3; |
| |
| private static final String OPTION_ALLOWED = "open"; |
| private static final String OPTION_DISALLOWED = "closed"; |
| |
| SelfHostedSiteSettings(Activity host, Blog blog, SiteSettingsListener listener) { |
| super(host, blog, listener); |
| } |
| |
| @Override |
| public SiteSettingsInterface init(boolean fetch) { |
| super.init(fetch); |
| |
| if (mSettings.defaultCategory == 0) { |
| mSettings.defaultCategory = siteSettingsPreferences(mActivity).getInt(DEF_CATEGORY_PREF_KEY, 0); |
| } |
| if (TextUtils.isEmpty(mSettings.defaultPostFormat) || mSettings.defaultPostFormat.equals("0")) { |
| mSettings.defaultPostFormat = siteSettingsPreferences(mActivity).getString(DEF_FORMAT_PREF_KEY, "0"); |
| } |
| mSettings.language = siteSettingsPreferences(mActivity).getString(LANGUAGE_PREF_KEY, LanguageUtils.getPatchedCurrentDeviceLanguage(null)); |
| |
| return this; |
| } |
| |
| @Override |
| public void saveSettings() { |
| super.saveSettings(); |
| |
| final Map<String, String> params = serializeSelfHostedParams(); |
| if (params == null || params.isEmpty()) return; |
| |
| XMLRPCCallback callback = new XMLRPCCallback() { |
| @Override |
| public void onSuccess(long id, final Object result) { |
| notifySavedOnUiThread(null); |
| mRemoteSettings.copyFrom(mSettings); |
| |
| if (result != null) { |
| HashMap<String, Object> properties = new HashMap<>(); |
| if (result instanceof Map) { |
| Map<String, Object> resultMap = (Map) result; |
| Set<String> keys = resultMap.keySet(); |
| for (String key : keys) { |
| Object currentValue = resultMap.get(key); |
| if (currentValue != null) { |
| properties.put(SAVED_ITEM_PREFIX + key, currentValue); |
| } |
| } |
| } |
| AnalyticsUtils.trackWithCurrentBlogDetails( |
| AnalyticsTracker.Stat.SITE_SETTINGS_SAVED_REMOTELY, properties); |
| } |
| } |
| |
| @Override |
| public void onFailure(long id, final Exception error) { |
| notifySavedOnUiThread(error); |
| } |
| }; |
| final Object[] callParams = { |
| mBlog.getRemoteBlogId(), mSettings.username, mSettings.password, params |
| }; |
| |
| XMLRPCClientInterface xmlrpcInterface = instantiateInterface(); |
| if (xmlrpcInterface == null) return; |
| xmlrpcInterface.callAsync(callback, Method.SET_OPTIONS, callParams); |
| } |
| |
| /** |
| * Request remote site data via XML-RPC. |
| */ |
| @Override |
| protected void fetchRemoteData() { |
| new Thread() { |
| @Override |
| public void run() { |
| Object[] params = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword()}; |
| |
| // Need two interfaces or the first call gets aborted |
| instantiateInterface().callAsync(mOptionsCallback, Method.GET_OPTIONS, params); |
| instantiateInterface().callAsync(mCategoriesCallback, Method.GET_CATEGORIES, params); |
| } |
| }.start(); |
| } |
| |
| /** |
| * Handles response to fetching self-hosted site categories via XML-RPC. |
| */ |
| private final XMLRPCCallback mCategoriesCallback = new XMLRPCCallback() { |
| @Override |
| public void onSuccess(long id, Object result) { |
| if (result instanceof Object[]) { |
| AppLog.d(AppLog.T.API, "Received Categories XML-RPC response."); |
| credentialsVerified(true); |
| |
| mRemoteSettings.localTableId = mBlog.getRemoteBlogId(); |
| deserializeCategoriesResponse(mRemoteSettings, (Object[]) result); |
| mSettings.categories = mRemoteSettings.categories; |
| SiteSettingsTable.saveCategories(mSettings.categories); |
| notifyUpdatedOnUiThread(null); |
| } else { |
| // Response is considered an error if we are unable to parse it |
| AppLog.w(AppLog.T.API, "Error parsing Categories XML-RPC response: " + result); |
| notifyUpdatedOnUiThread(new XMLRPCException("Unknown response object")); |
| } |
| } |
| |
| @Override |
| public void onFailure(long id, Exception error) { |
| AppLog.w(AppLog.T.API, "Error Categories XML-RPC response: " + error); |
| notifyUpdatedOnUiThread(error); |
| } |
| }; |
| |
| /** |
| * Handles response to fetching self-hosted site options via XML-RPC. |
| */ |
| private final XMLRPCCallback mOptionsCallback = new XMLRPCCallback() { |
| @Override |
| public void onSuccess(long id, final Object result) { |
| if (result instanceof Map) { |
| AppLog.d(AppLog.T.API, "Received Options XML-RPC response."); |
| |
| if (!versionSupported((Map) result) && mActivity != null) { |
| notifyUpdatedOnUiThread(new XMLRPCException(mActivity.getString(R.string.site_settings_unsupported_version_error))); |
| return; |
| } |
| |
| credentialsVerified(true); |
| |
| deserializeOptionsResponse(mRemoteSettings, (Map) result); |
| |
| // postFormats setting is not returned by this api call so copy it over |
| final Map<String, String> currentPostFormats = mSettings.postFormats; |
| |
| mSettings.copyFrom(mRemoteSettings); |
| |
| mSettings.postFormats = currentPostFormats; |
| |
| SiteSettingsTable.saveSettings(mSettings); |
| notifyUpdatedOnUiThread(null); |
| } else { |
| // Response is considered an error if we are unable to parse it |
| AppLog.w(AppLog.T.API, "Error parsing Options XML-RPC response: " + result); |
| notifyUpdatedOnUiThread(new XMLRPCException("Unknown response object")); |
| } |
| } |
| |
| @Override |
| public void onFailure(long id, final Exception error) { |
| AppLog.w(AppLog.T.API, "Error Options XML-RPC response: " + error); |
| notifyUpdatedOnUiThread(error); |
| } |
| }; |
| |
| private boolean versionSupported(Map map) { |
| String version = getNestedMapValue(map, SOFTWARE_VERSION_KEY); |
| if (TextUtils.isEmpty(version)) return false; |
| String[] split = version.split("\\."); |
| return split.length > 0 && |
| Integer.valueOf(split[0]) >= REQUIRED_MAJOR_VERSION && |
| Integer.valueOf(split[1]) >= REQUIRED_MINOR_VERSION; |
| } |
| |
| private Map<String, String> serializeSelfHostedParams() { |
| Map<String, String> params = new HashMap<>(); |
| |
| if (mSettings.title != null && !mSettings.title.equals(mRemoteSettings.title)) { |
| params.put(BLOG_TITLE_KEY, mSettings.title); |
| } |
| if (mSettings.tagline != null && !mSettings.tagline.equals(mRemoteSettings.tagline)) { |
| params.put(BLOG_TAGLINE_KEY, mSettings.tagline); |
| } |
| if (mSettings.privacy != mRemoteSettings.privacy) { |
| params.put(PRIVACY_KEY, String.valueOf(mSettings.privacy)); |
| } |
| if (mSettings.defaultCategory != mRemoteSettings.defaultCategory) { |
| params.put(DEF_CATEGORY_KEY, String.valueOf(mSettings.defaultCategory)); |
| } |
| if (mSettings.defaultPostFormat != null && !mSettings.defaultPostFormat.equals(mRemoteSettings.defaultPostFormat)) { |
| params.put(DEF_POST_FORMAT_KEY, mSettings.defaultPostFormat); |
| } |
| if (mSettings.allowComments != mRemoteSettings.allowComments) { |
| params.put(ALLOW_COMMENTS_KEY, String.valueOf(mSettings.allowComments)); |
| } |
| if (mSettings.sendPingbacks != mRemoteSettings.sendPingbacks) { |
| params.put(SEND_PINGBACKS_KEY, mSettings.sendPingbacks ? "1" : "0"); |
| } |
| if (mSettings.receivePingbacks != mRemoteSettings.receivePingbacks) { |
| params.put(RECEIVE_PINGBACKS_KEY, mSettings.receivePingbacks ? OPTION_ALLOWED : OPTION_DISALLOWED); |
| } |
| if (mSettings.commentApprovalRequired != mRemoteSettings.commentApprovalRequired) { |
| params.put(COMMENT_MODERATION_KEY, String.valueOf(mSettings.commentApprovalRequired)); |
| } |
| if (mSettings.closeCommentAfter != mRemoteSettings.closeCommentAfter) { |
| if (mSettings.closeCommentAfter <= 0) { |
| params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(0)); |
| } else { |
| params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(1)); |
| params.put(CLOSE_OLD_COMMENTS_DAYS_KEY, String.valueOf(mSettings.closeCommentAfter)); |
| } |
| } |
| if (mSettings.sortCommentsBy != mRemoteSettings.sortCommentsBy) { |
| if (mSettings.sortCommentsBy == ASCENDING_SORT) { |
| params.put(COMMENT_SORT_ORDER_KEY, "asc"); |
| } else if (mSettings.sortCommentsBy == DESCENDING_SORT) { |
| params.put(COMMENT_SORT_ORDER_KEY, "desc"); |
| } |
| } |
| if (mSettings.threadingLevels != mRemoteSettings.threadingLevels) { |
| if (mSettings.threadingLevels <= 1) { |
| params.put(THREAD_COMMENTS_KEY, String.valueOf(0)); |
| } else { |
| params.put(PAGE_COMMENTS_KEY, String.valueOf(1)); |
| params.put(THREAD_COMMENTS_DEPTH_KEY, String.valueOf(mSettings.threadingLevels)); |
| } |
| } |
| if (mSettings.commentsPerPage != mRemoteSettings.commentsPerPage) { |
| if (mSettings.commentsPerPage <= 0) { |
| params.put(PAGE_COMMENTS_KEY, String.valueOf(0)); |
| } else{ |
| params.put(PAGE_COMMENTS_KEY, String.valueOf(1)); |
| params.put(PAGE_COMMENT_COUNT_KEY, String.valueOf(mSettings.commentsPerPage)); |
| } |
| } |
| if (mSettings.commentsRequireIdentity != mRemoteSettings.commentsRequireIdentity) { |
| params.put(REQUIRE_IDENTITY_KEY, String.valueOf(mSettings.commentsRequireIdentity ? 1 : 0)); |
| } |
| if (mSettings.commentsRequireUserAccount != mRemoteSettings.commentsRequireUserAccount) { |
| params.put(REQUIRE_USER_ACCOUNT_KEY, String.valueOf(mSettings.commentsRequireUserAccount ? 1 : 0)); |
| } |
| if (mSettings.commentAutoApprovalKnownUsers != mRemoteSettings.commentAutoApprovalKnownUsers) { |
| params.put(WHITELIST_KNOWN_USERS_KEY, String.valueOf(mSettings.commentAutoApprovalKnownUsers)); |
| } |
| if (mSettings.maxLinks != mRemoteSettings.maxLinks) { |
| params.put(MAX_LINKS_KEY, String.valueOf(mSettings.maxLinks)); |
| } |
| if (mSettings.holdForModeration != null && !mSettings.holdForModeration.equals(mRemoteSettings.holdForModeration)) { |
| StringBuilder builder = new StringBuilder(); |
| for (String key : mSettings.holdForModeration) { |
| builder.append(key); |
| builder.append("\n"); |
| } |
| if (builder.length() > 1) { |
| params.put(MODERATION_KEYS_KEY, builder.substring(0, builder.length() - 1)); |
| } else { |
| params.put(MODERATION_KEYS_KEY, ""); |
| } |
| } |
| if (mSettings.blacklist != null && !mSettings.blacklist.equals(mRemoteSettings.blacklist)) { |
| StringBuilder builder = new StringBuilder(); |
| for (String key : mSettings.blacklist) { |
| builder.append(key); |
| builder.append("\n"); |
| } |
| if (builder.length() > 1) { |
| params.put(BLACKLIST_KEYS_KEY, builder.substring(0, builder.length() - 1)); |
| } else { |
| params.put(BLACKLIST_KEYS_KEY, ""); |
| } |
| } |
| |
| return params; |
| } |
| |
| /** |
| * Sets values from a self-hosted XML-RPC response object. |
| */ |
| private void deserializeOptionsResponse(SiteSettingsModel model, Map response) { |
| if (mBlog == null || response == null) return; |
| |
| model.username = mBlog.getUsername(); |
| model.password = mBlog.getPassword(); |
| model.address = getNestedMapValue(response, BLOG_URL_KEY); |
| model.title = getNestedMapValue(response, BLOG_TITLE_KEY); |
| model.tagline = getNestedMapValue(response, BLOG_TAGLINE_KEY); |
| model.privacy = Integer.valueOf(getNestedMapValue(response, PRIVACY_KEY)); |
| model.defaultCategory = Integer.valueOf(getNestedMapValue(response, DEF_CATEGORY_KEY)); |
| model.defaultPostFormat = getNestedMapValue(response, DEF_POST_FORMAT_KEY); |
| model.allowComments = OPTION_ALLOWED.equals(getNestedMapValue(response, ALLOW_COMMENTS_KEY)); |
| model.receivePingbacks = OPTION_ALLOWED.equals(getNestedMapValue(response, RECEIVE_PINGBACKS_KEY)); |
| String sendPingbacks = getNestedMapValue(response, SEND_PINGBACKS_KEY); |
| String approvalRequired = getNestedMapValue(response, COMMENT_MODERATION_KEY); |
| String identityRequired = getNestedMapValue(response, REQUIRE_IDENTITY_KEY); |
| String accountRequired = getNestedMapValue(response, REQUIRE_USER_ACCOUNT_KEY); |
| String knownUsers = getNestedMapValue(response, WHITELIST_KNOWN_USERS_KEY); |
| model.sendPingbacks = !TextUtils.isEmpty(sendPingbacks) && Integer.valueOf(sendPingbacks) > 0; |
| model.commentApprovalRequired = !TextUtils.isEmpty(approvalRequired) && Boolean.valueOf(approvalRequired); |
| model.commentsRequireIdentity = !TextUtils.isEmpty(identityRequired) && Integer.valueOf(identityRequired) > 0; |
| model.commentsRequireUserAccount = !TextUtils.isEmpty(accountRequired) && Integer.valueOf(identityRequired) > 0; |
| model.commentAutoApprovalKnownUsers = !TextUtils.isEmpty(knownUsers) && Boolean.valueOf(knownUsers); |
| model.maxLinks = Integer.valueOf(getNestedMapValue(response, MAX_LINKS_KEY)); |
| mRemoteSettings.holdForModeration = new ArrayList<>(); |
| mRemoteSettings.blacklist = new ArrayList<>(); |
| |
| String modKeys = getNestedMapValue(response, MODERATION_KEYS_KEY); |
| if (modKeys.length() > 0) { |
| Collections.addAll(mRemoteSettings.holdForModeration, modKeys.split("\n")); |
| } |
| String blacklistKeys = getNestedMapValue(response, BLACKLIST_KEYS_KEY); |
| if (blacklistKeys.length() > 0) { |
| Collections.addAll(mRemoteSettings.blacklist, blacklistKeys.split("\n")); |
| } |
| |
| String close = getNestedMapValue(response, CLOSE_OLD_COMMENTS_KEY); |
| if (!TextUtils.isEmpty(close) && Boolean.valueOf(close)) { |
| mRemoteSettings.closeCommentAfter = Integer.valueOf(getNestedMapValue(response, CLOSE_OLD_COMMENTS_DAYS_KEY)); |
| } else { |
| mRemoteSettings.closeCommentAfter = 0; |
| } |
| |
| String thread = getNestedMapValue(response, THREAD_COMMENTS_KEY); |
| if (!TextUtils.isEmpty(thread) && Integer.valueOf(thread) > 0) { |
| mRemoteSettings.threadingLevels = Integer.valueOf(getNestedMapValue(response, THREAD_COMMENTS_DEPTH_KEY)); |
| } else { |
| mRemoteSettings.threadingLevels = 0; |
| } |
| |
| String page = getNestedMapValue(response, PAGE_COMMENTS_KEY); |
| if (!TextUtils.isEmpty(page) && Boolean.valueOf(page)) { |
| mRemoteSettings.commentsPerPage = Integer.valueOf(getNestedMapValue(response, PAGE_COMMENT_COUNT_KEY)); |
| } else { |
| mRemoteSettings.commentsPerPage = 0; |
| } |
| |
| if (getNestedMapValue(response, COMMENT_SORT_ORDER_KEY).equals("asc")) { |
| mRemoteSettings.sortCommentsBy = ASCENDING_SORT; |
| } else { |
| mRemoteSettings.sortCommentsBy = DESCENDING_SORT; |
| } |
| } |
| |
| private void deserializeCategoriesResponse(SiteSettingsModel model, Object[] response) { |
| model.categories = new CategoryModel[response.length]; |
| |
| for (int i = 0; i < response.length; ++i) { |
| if (response[i] instanceof Map) { |
| Map category = (Map) response[i]; |
| CategoryModel categoryModel = new CategoryModel(); |
| categoryModel.id = MapUtils.getMapInt(category, BLOG_CATEGORY_ID_KEY); |
| categoryModel.parentId = MapUtils.getMapInt(category, BLOG_CATEGORY_PARENT_ID_KEY); |
| categoryModel.description = MapUtils.getMapStr(category, BLOG_CATEGORY_DESCRIPTION_KEY); |
| categoryModel.name = MapUtils.getMapStr(category, BLOG_CATEGORY_NAME_KEY); |
| model.categories[i] = categoryModel; |
| } |
| } |
| } |
| |
| /** |
| * Helper method to get a value from a nested Map. Used to parse self-hosted response objects. |
| */ |
| private String getNestedMapValue(Map map, String key) { |
| if (map != null && key != null) { |
| return MapUtils.getMapStr((Map) map.get(key), "value"); |
| } |
| |
| return ""; |
| } |
| } |