package org.wikipedia.descriptions; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import org.json.JSONArray; import org.wikipedia.WikipediaApp; import org.wikipedia.dataclient.WikiSite; import org.wikipedia.dataclient.mwapi.MwException; import org.wikipedia.dataclient.mwapi.MwServiceError; import org.wikipedia.dataclient.retrofit.MwCachedService; import org.wikipedia.dataclient.retrofit.RetrofitException; import org.wikipedia.dataclient.retrofit.WikiCachedService; import org.wikipedia.login.User; import org.wikipedia.page.Page; import org.wikipedia.page.PageProperties; import org.wikipedia.page.PageTitle; import org.wikipedia.util.ReleaseUtil; import java.util.Arrays; import retrofit2.Call; import retrofit2.Response; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; /** * Data Client to submit a new or updated description to wikidata.org. */ public class DescriptionEditClient { private static final String ABUSEFILTER_DISALLOWED = "abusefilter-disallowed"; private static final String ABUSEFILTER_WARNING = "abusefilter-warning"; public interface Callback { void success(@NonNull Call<DescriptionEdit> call); void abusefilter(@NonNull Call<DescriptionEdit> call, @Nullable String code, @Nullable String info); void invalidLogin(@NonNull Call<DescriptionEdit> call, @NonNull Throwable caught); void failure(@NonNull Call<DescriptionEdit> call, @NonNull Throwable caught); } public static boolean isEditAllowed(@NonNull Page page) { PageProperties props = page.getPageProperties(); return !TextUtils.isEmpty(props.getWikiBaseItem()) && (!isLanguageBlacklisted(page.getTitle().getWikiSite().languageCode()) || ReleaseUtil.isPreBetaRelease()); } private static boolean isLanguageBlacklisted(@NonNull String lang) { JSONArray blacklist = WikipediaApp.getInstance().getRemoteConfig().getConfig() .optJSONArray("descriptionEditLangBlacklist"); if (blacklist != null) { for (int i = 0; i < blacklist.length(); i++) { if (lang.equals(blacklist.optString(i))) { return true; } } return false; } else { return Arrays.asList("en", "de", "it", "fr", "es", "ja", "nl", "pt", "tr", "zh-hant") .contains(lang); } } @NonNull private final WikiCachedService<Service> cachedService = new MwCachedService<>(Service.class); /** * Submit a new value for the Wikidata description associated with the given Wikipedia page. * * @param wiki the Wiki site to use this on. Should be "www.wikidata.org" * @param pageTitle specifies the Wikipedia page the Wikidata item is linked to * @param description the new value for the Wikidata description * @param editToken a token from Wikidata * @param cb called when this is done successfully or failed * @return Call object which can be used to cancel the request */ public Call<DescriptionEdit> request(@NonNull WikiSite wiki, @NonNull PageTitle pageTitle, @NonNull String description, @NonNull String editToken, @NonNull Callback cb) { return request(cachedService.service(wiki), pageTitle, description, editToken, User.isLoggedIn(), cb); } @SuppressWarnings("WeakerAccess") @VisibleForTesting Call<DescriptionEdit> request(@NonNull Service service, @NonNull PageTitle pageTitle, @NonNull String description, @NonNull String editToken, boolean loggedIn, @NonNull final Callback cb) { Call<DescriptionEdit> call = service.edit(pageTitle.getWikiSite().languageCode(), pageTitle.getWikiSite().languageCode(), pageTitle.getWikiSite().dbName(), pageTitle.getPrefixedText(), description, editToken, loggedIn ? "user" : null); call.enqueue(new retrofit2.Callback<DescriptionEdit>() { @Override public void onResponse(Call<DescriptionEdit> call, Response<DescriptionEdit> response) { final DescriptionEdit body = response.body(); if (body.editWasSuccessful()) { cb.success(call); } else if (body.hasError()) { handleError(call, body, cb); } else { cb.failure(call, RetrofitException.unexpectedError(new RuntimeException( "Received unrecognized description edit response"))); } } @Override public void onFailure(Call<DescriptionEdit> call, Throwable t) { cb.failure(call, t); } }); return call; } private void handleError(@NonNull Call<DescriptionEdit> call, @NonNull DescriptionEdit body, @NonNull Callback cb) { MwServiceError error = body.getError(); String info = body.info(); RuntimeException exception = new RuntimeException(info != null ? info : "An unknown error occurred"); if (body.badLoginState() || body.badToken()) { cb.invalidLogin(call, exception); } else if (error != null && error.hasMessageName(ABUSEFILTER_DISALLOWED)) { cb.abusefilter(call, ABUSEFILTER_DISALLOWED, error.getMessageHtml(ABUSEFILTER_DISALLOWED)); } else if (error != null && error.hasMessageName(ABUSEFILTER_WARNING)) { cb.abusefilter(call, ABUSEFILTER_WARNING, error.getMessageHtml(ABUSEFILTER_WARNING)); } else { // noinspection ConstantConditions cb.failure(call, new MwException(error)); } } @VisibleForTesting interface Service { @POST("w/api.php?action=wbsetdescription&format=json&formatversion=2") @FormUrlEncoded Call<DescriptionEdit> edit(@NonNull @Field("language") String language, @NonNull @Field("uselang") String useLang, @NonNull @Field("site") String site, @NonNull @Field("title") String title, @NonNull @Field("value") String newDescription, @NonNull @Field("token") String token, @Nullable @Field("assert") String user); } }