package org.wordpress.android.ui.prefs; import android.app.Activity; import com.android.volley.VolleyError; import com.wordpress.rest.RestRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.SiteSettingsTable; import org.wordpress.android.fluxc.model.SiteModel; import org.wordpress.android.models.CategoryModel; import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; class DotComSiteSettings extends SiteSettingsInterface { // WP.com REST keys used in response to a settings GET and POST request public static final String LANGUAGE_ID_KEY = "lang_id"; public static final String PRIVACY_KEY = "blog_public"; public static final String URL_KEY = "URL"; public static final String DEF_CATEGORY_KEY = "default_category"; public static final String DEF_POST_FORMAT_KEY = "default_post_format"; public static final String RELATED_POSTS_ALLOWED_KEY = "jetpack_relatedposts_allowed"; public static final String RELATED_POSTS_ENABLED_KEY = "jetpack_relatedposts_enabled"; public static final String RELATED_POSTS_HEADER_KEY = "jetpack_relatedposts_show_headline"; public static final String RELATED_POSTS_IMAGES_KEY = "jetpack_relatedposts_show_thumbnails"; 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"; // WP.com REST keys used to GET certain site settings public static final String GET_TITLE_KEY = "name"; public static final String GET_DESC_KEY = "description"; // WP.com REST keys used to POST updates to site settings private static final String SET_TITLE_KEY = "blogname"; private static final String SET_DESC_KEY = "blogdescription"; // JSON response keys private static final String SETTINGS_KEY = "settings"; private static final String UPDATED_KEY = "updated"; // WP.com REST keys used in response to a categories GET request private static final String CAT_ID_KEY = "ID"; private static final String CAT_NAME_KEY = "name"; private static final String CAT_SLUG_KEY = "slug"; private static final String CAT_DESC_KEY = "description"; private static final String CAT_PARENT_ID_KEY = "parent"; private static final String CAT_POST_COUNT_KEY = "post_count"; private static final String CAT_NUM_POSTS_KEY = "found"; private static final String CATEGORIES_KEY = "categories"; /** * Only instantiated by {@link SiteSettingsInterface}. */ DotComSiteSettings(Activity host, SiteModel site, SiteSettingsListener listener) { super(host, site, listener); } @Override public void saveSettings() { super.saveSettings(); final Map<String, String> params = serializeDotComParams(); if (params == null || params.isEmpty()) return; WordPress.getRestClientUtils().setGeneralSiteSettings( mSite.getSiteId(), new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { AppLog.d(AppLog.T.API, "Site Settings saved remotely"); notifySavedOnUiThread(null); mRemoteSettings.copyFrom(mSettings); if (response != null) { JSONObject updated = response.optJSONObject(UPDATED_KEY); if (updated == null) return; HashMap<String, Object> properties = new HashMap<>(); Iterator<String> keys = updated.keys(); while (keys.hasNext()) { String currentKey = keys.next(); Object currentValue = updated.opt(currentKey); if (currentValue != null) { properties.put(SAVED_ITEM_PREFIX + currentKey, currentValue); } } AnalyticsUtils.trackWithSiteDetails( AnalyticsTracker.Stat.SITE_SETTINGS_SAVED_REMOTELY, mSite, properties); } } }, new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { AppLog.w(AppLog.T.API, "Error POSTing site settings changes: " + error); notifySavedOnUiThread(error); } }, params); } /** * Request remote site data via the WordPress REST API. */ @Override protected void fetchRemoteData() { fetchCategories(); WordPress.getRestClientUtils().getGeneralSettings( mSite.getSiteId(), new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { AppLog.d(AppLog.T.API, "Received response to Settings REST request."); credentialsVerified(true); mRemoteSettings.localTableId = mSite.getId(); deserializeDotComRestResponse(mSite, response); if (!mRemoteSettings.equals(mSettings)) { // postFormats setting is not returned by this api call so copy it over final Map<String, String> currentPostFormats = mSettings.postFormats; // Local settings boolean location = mSettings.location; boolean optimizedImage = mSettings.optimizedImage; int maxImageWidth = mSettings.maxImageWidth; int imageQualitySetting = mSettings.imageQualitySetting; mSettings.copyFrom(mRemoteSettings); mSettings.postFormats = currentPostFormats; mSettings.location = location; mSettings.optimizedImage = optimizedImage; mSettings.maxImageWidth = maxImageWidth; mSettings.imageQualitySetting = imageQualitySetting; SiteSettingsTable.saveSettings(mSettings); notifyUpdatedOnUiThread(null); } } }, new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { AppLog.w(AppLog.T.API, "Error response to Settings REST request: " + error); notifyUpdatedOnUiThread(error); } }); } /** * Sets values from a .com REST response object. */ public void deserializeDotComRestResponse(SiteModel site, JSONObject response) { if (site == null || response == null) return; JSONObject settingsObject = response.optJSONObject(SETTINGS_KEY); mRemoteSettings.username = site.getUsername(); mRemoteSettings.password = site.getPassword(); mRemoteSettings.address = response.optString(URL_KEY, ""); mRemoteSettings.title = response.optString(GET_TITLE_KEY, ""); mRemoteSettings.tagline = response.optString(GET_DESC_KEY, ""); mRemoteSettings.languageId = settingsObject.optInt(LANGUAGE_ID_KEY, -1); mRemoteSettings.privacy = settingsObject.optInt(PRIVACY_KEY, -2); mRemoteSettings.defaultCategory = settingsObject.optInt(DEF_CATEGORY_KEY, 0); mRemoteSettings.defaultPostFormat = settingsObject.optString(DEF_POST_FORMAT_KEY, "0"); mRemoteSettings.language = languageIdToLanguageCode(Integer.toString(mRemoteSettings.languageId)); mRemoteSettings.allowComments = settingsObject.optBoolean(ALLOW_COMMENTS_KEY, true); mRemoteSettings.sendPingbacks = settingsObject.optBoolean(SEND_PINGBACKS_KEY, false); mRemoteSettings.receivePingbacks = settingsObject.optBoolean(RECEIVE_PINGBACKS_KEY, true); mRemoteSettings.shouldCloseAfter = settingsObject.optBoolean(CLOSE_OLD_COMMENTS_KEY, false); mRemoteSettings.closeCommentAfter = settingsObject.optInt(CLOSE_OLD_COMMENTS_DAYS_KEY, 0); mRemoteSettings.shouldThreadComments = settingsObject.optBoolean(THREAD_COMMENTS_KEY, false); mRemoteSettings.threadingLevels = settingsObject.optInt(THREAD_COMMENTS_DEPTH_KEY, 0); mRemoteSettings.shouldPageComments = settingsObject.optBoolean(PAGE_COMMENTS_KEY, false); mRemoteSettings.commentsPerPage = settingsObject.optInt(PAGE_COMMENT_COUNT_KEY, 0); mRemoteSettings.commentApprovalRequired = settingsObject.optBoolean(COMMENT_MODERATION_KEY, false); mRemoteSettings.commentsRequireIdentity = settingsObject.optBoolean(REQUIRE_IDENTITY_KEY, false); mRemoteSettings.commentsRequireUserAccount = settingsObject.optBoolean(REQUIRE_USER_ACCOUNT_KEY, true); mRemoteSettings.commentAutoApprovalKnownUsers = settingsObject.optBoolean(WHITELIST_KNOWN_USERS_KEY, false); mRemoteSettings.maxLinks = settingsObject.optInt(MAX_LINKS_KEY, 0); mRemoteSettings.holdForModeration = new ArrayList<>(); mRemoteSettings.blacklist = new ArrayList<>(); String modKeys = settingsObject.optString(MODERATION_KEYS_KEY, ""); if (modKeys.length() > 0) { Collections.addAll(mRemoteSettings.holdForModeration, modKeys.split("\n")); } String blacklistKeys = settingsObject.optString(BLACKLIST_KEYS_KEY, ""); if (blacklistKeys.length() > 0) { Collections.addAll(mRemoteSettings.blacklist, blacklistKeys.split("\n")); } if (settingsObject.optString(COMMENT_SORT_ORDER_KEY, "").equals("asc")) { mRemoteSettings.sortCommentsBy = ASCENDING_SORT; } else { mRemoteSettings.sortCommentsBy = DESCENDING_SORT; } if (settingsObject.optBoolean(RELATED_POSTS_ALLOWED_KEY, false)) { mRemoteSettings.showRelatedPosts = settingsObject.optBoolean(RELATED_POSTS_ENABLED_KEY, false); mRemoteSettings.showRelatedPostHeader = settingsObject.optBoolean(RELATED_POSTS_HEADER_KEY, false); mRemoteSettings.showRelatedPostImages = settingsObject.optBoolean(RELATED_POSTS_IMAGES_KEY, false); } } /** * Helper method to create the parameters for the site settings POST request * * Using undocumented endpoint WPCOM_JSON_API_Site_Settings_Endpoint * https://wpcom.trac.automattic.com/browser/trunk/public.api/rest/json-endpoints.php#L1903 */ public Map<String, String> serializeDotComParams() { Map<String, String> params = new HashMap<>(); if (mSettings.title!= null && !mSettings.title.equals(mRemoteSettings.title)) { params.put(SET_TITLE_KEY, mSettings.title); } if (mSettings.tagline != null && !mSettings.tagline.equals(mRemoteSettings.tagline)) { params.put(SET_DESC_KEY, mSettings.tagline); } if (mSettings.languageId != mRemoteSettings.languageId) { params.put(LANGUAGE_ID_KEY, String.valueOf((mSettings.languageId))); } 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.showRelatedPosts != mRemoteSettings.showRelatedPosts || mSettings.showRelatedPostHeader != mRemoteSettings.showRelatedPostHeader || mSettings.showRelatedPostImages != mRemoteSettings.showRelatedPostImages) { params.put(RELATED_POSTS_ENABLED_KEY, String.valueOf(mSettings.showRelatedPosts)); params.put(RELATED_POSTS_HEADER_KEY, String.valueOf(mSettings.showRelatedPostHeader)); params.put(RELATED_POSTS_IMAGES_KEY, String.valueOf(mSettings.showRelatedPostImages)); } if (mSettings.allowComments != mRemoteSettings.allowComments) { params.put(ALLOW_COMMENTS_KEY, String.valueOf(mSettings.allowComments)); } if (mSettings.sendPingbacks != mRemoteSettings.sendPingbacks) { params.put(SEND_PINGBACKS_KEY, String.valueOf(mSettings.sendPingbacks)); } if (mSettings.receivePingbacks != mRemoteSettings.receivePingbacks) { params.put(RECEIVE_PINGBACKS_KEY, String.valueOf(mSettings.receivePingbacks)); } if (mSettings.commentApprovalRequired != mRemoteSettings.commentApprovalRequired) { params.put(COMMENT_MODERATION_KEY, String.valueOf(mSettings.commentApprovalRequired)); } if (mSettings.closeCommentAfter != mRemoteSettings.closeCommentAfter || mSettings.shouldCloseAfter != mRemoteSettings.shouldCloseAfter) { params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(mSettings.shouldCloseAfter)); 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 || mSettings.shouldThreadComments != mRemoteSettings.shouldThreadComments) { params.put(THREAD_COMMENTS_KEY, String.valueOf(mSettings.shouldThreadComments)); params.put(THREAD_COMMENTS_DEPTH_KEY, String.valueOf(mSettings.threadingLevels)); } if (mSettings.commentsPerPage != mRemoteSettings.commentsPerPage || mSettings.shouldPageComments != mRemoteSettings.shouldPageComments) { params.put(PAGE_COMMENTS_KEY, String.valueOf(mSettings.shouldPageComments)); params.put(PAGE_COMMENT_COUNT_KEY, String.valueOf(mSettings.commentsPerPage)); } if (mSettings.commentsRequireIdentity != mRemoteSettings.commentsRequireIdentity) { params.put(REQUIRE_IDENTITY_KEY, String.valueOf(mSettings.commentsRequireIdentity)); } if (mSettings.commentsRequireUserAccount != mRemoteSettings.commentsRequireUserAccount) { params.put(REQUIRE_USER_ACCOUNT_KEY, String.valueOf(mSettings.commentsRequireUserAccount)); } 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; } /** * Request a list of post categories for a site via the WordPress REST API. */ private void fetchCategories() { // TODO: Replace with FluxC (GET_CATEGORIES + TaxonomyStore.getCategoriesForSite()) WordPress.getRestClientUtilsV1_1().getCategories(mSite.getSiteId(), new RestRequest.Listener() { @Override public void onResponse(JSONObject response) { AppLog.d(AppLog.T.API, "Received response to Categories REST request."); credentialsVerified(true); CategoryModel[] models = deserializeJsonRestResponse(response); if (models == null) return; SiteSettingsTable.saveCategories(models); mRemoteSettings.categories = models; mSettings.categories = models; notifyUpdatedOnUiThread(null); } }, new RestRequest.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { AppLog.d(AppLog.T.API, "Error fetching WP.com categories:" + error); } }); } private CategoryModel deserializeCategoryFromJson(JSONObject category) throws JSONException { if (category == null) return null; CategoryModel model = new CategoryModel(); model.id = category.getInt(CAT_ID_KEY); model.name = category.getString(CAT_NAME_KEY); model.slug = category.getString(CAT_SLUG_KEY); model.description = category.getString(CAT_DESC_KEY); model.parentId = category.getInt(CAT_PARENT_ID_KEY); model.postCount = category.getInt(CAT_POST_COUNT_KEY); return model; } private CategoryModel[] deserializeJsonRestResponse(JSONObject response) { try { int num = response.getInt(CAT_NUM_POSTS_KEY); JSONArray categories = response.getJSONArray(CATEGORIES_KEY); CategoryModel[] models = new CategoryModel[num]; for (int i = 0; i < num; ++i) { JSONObject category = categories.getJSONObject(i); models[i] = deserializeCategoryFromJson(category); } AppLog.d(AppLog.T.API, "Successfully fetched WP.com categories"); return models; } catch (JSONException exception) { AppLog.d(AppLog.T.API, "Error parsing WP.com categories response:" + response); return null; } } }