package org.wordpress.android.ui.posts; import android.app.Activity; import android.app.FragmentManager; import android.text.TextUtils; import android.text.format.DateUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.fluxc.model.PostModel; import org.wordpress.android.fluxc.model.SiteModel; import org.wordpress.android.fluxc.model.post.PostLocation; import org.wordpress.android.fluxc.model.post.PostStatus; import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.DateTimeUtils; import org.wordpress.android.util.HtmlUtils; import org.wordpress.android.widgets.WPAlertDialogFragment; import java.text.BreakIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PostUtils { private static final int MAX_EXCERPT_LEN = 150; private static final HashSet<String> mShortcodeTable = new HashSet<>(); /* * collapses shortcodes in the passed post content, stripping anything between the * shortcode name and the closing brace * ex: collapseShortcodes("[gallery ids="1206,1205,1191"]") -> "[gallery]" */ public static String collapseShortcodes(final String postContent) { // speed things up by skipping regex if content doesn't contain a brace if (postContent == null || !postContent.contains("[")) { return postContent; } String shortCode; Pattern p = Pattern.compile("(\\[ *([^ ]+) [^\\[\\]]*\\])"); Matcher m = p.matcher(postContent); StringBuffer sb = new StringBuffer(); while (m.find()) { shortCode = m.group(2); if (isKnownShortcode(shortCode)) { m.appendReplacement(sb, "[" + shortCode + "]"); } else { AppLog.d(AppLog.T.POSTS, "unknown shortcode - " + shortCode); } } m.appendTail(sb); return sb.toString(); } private static boolean isKnownShortcode(String shortCode) { if (shortCode == null) return false; // populate on first use if (mShortcodeTable.size() == 0) { // default shortcodes mShortcodeTable.add("audio"); mShortcodeTable.add("caption"); mShortcodeTable.add("embed"); mShortcodeTable.add("gallery"); mShortcodeTable.add("playlist"); mShortcodeTable.add("video"); mShortcodeTable.add("wp_caption"); // audio/video mShortcodeTable.add("dailymotion"); mShortcodeTable.add("flickr"); mShortcodeTable.add("hulu"); mShortcodeTable.add("kickstarter"); mShortcodeTable.add("soundcloud"); mShortcodeTable.add("vimeo"); mShortcodeTable.add("vine"); mShortcodeTable.add("wpvideo"); mShortcodeTable.add("youtube"); // images and documents mShortcodeTable.add("instagram"); mShortcodeTable.add("scribd"); mShortcodeTable.add("slideshare"); mShortcodeTable.add("slideshow"); mShortcodeTable.add("presentation"); mShortcodeTable.add("googleapps"); mShortcodeTable.add("office"); // other mShortcodeTable.add("googlemaps"); mShortcodeTable.add("polldaddy"); mShortcodeTable.add("recipe"); mShortcodeTable.add("sitemap"); mShortcodeTable.add("twitter-timeline"); mShortcodeTable.add("upcomingevents"); } return mShortcodeTable.contains(shortCode); } public static void trackSavePostAnalytics(PostModel post, SiteModel site) { PostStatus status = PostStatus.fromPost(post); Map<String, Object> properties = new HashMap<>(); switch (status) { case PUBLISHED: if (!post.isLocalDraft()) { properties.put("post_id", post.getRemotePostId()); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.EDITOR_UPDATED_POST, site, properties); } else { // Analytics for the event EDITOR_PUBLISHED_POST are tracked in PostUploadService } break; case SCHEDULED: if (!post.isLocalDraft()) { properties.put("post_id", post.getRemotePostId()); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.EDITOR_UPDATED_POST, site, properties); } else { properties.put("word_count", AnalyticsUtils.getWordCount(post.getContent())); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.EDITOR_SCHEDULED_POST, site, properties); } break; case DRAFT: properties.put("post_id", post.getRemotePostId()); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.EDITOR_SAVED_DRAFT, site); break; default: // No-op } } public static void showCustomDialog(Activity activity, String title, String message, String positiveButton, String negativeButton, String tag) { FragmentManager fm = activity.getFragmentManager(); WPAlertDialogFragment saveDialog = (WPAlertDialogFragment) fm.findFragmentByTag(tag); if (saveDialog == null) { saveDialog = WPAlertDialogFragment.newCustomDialog(title, message, positiveButton, negativeButton); } if (!saveDialog.isAdded()) { saveDialog.show(fm, tag); } } public static boolean isPublishable(PostModel post) { return !(post.getContent().isEmpty() && post.getExcerpt().isEmpty() && post.getTitle().isEmpty()); } public static boolean hasEmptyContentFields(PostModel post) { return TextUtils.isEmpty(post.getTitle()) && TextUtils.isEmpty(post.getContent()); } /** * Checks if two posts have differing data */ public static boolean postHasEdits(PostModel oldPost, PostModel newPost) { if (oldPost == null) { return newPost != null; } return newPost == null || !(StringUtils.equals(oldPost.getTitle(), newPost.getTitle()) && StringUtils.equals(oldPost.getContent(), newPost.getContent()) && StringUtils.equals(oldPost.getExcerpt(), newPost.getExcerpt()) && StringUtils.equals(oldPost.getStatus(), newPost.getStatus()) && StringUtils.equals(oldPost.getPassword(), newPost.getPassword()) && StringUtils.equals(oldPost.getPostFormat(), newPost.getPostFormat()) && StringUtils.equals(oldPost.getDateCreated(), newPost.getDateCreated()) && oldPost.getFeaturedImageId() == newPost.getFeaturedImageId() && oldPost.getTagNameList().containsAll(newPost.getTagNameList()) && newPost.getTagNameList().containsAll(oldPost.getTagNameList()) && oldPost.getCategoryIdList().containsAll(newPost.getCategoryIdList()) && newPost.getCategoryIdList().containsAll(oldPost.getCategoryIdList()) && PostLocation.equals(oldPost.getLocation(), newPost.getLocation()) ); } public static String getPostListExcerptFromPost(PostModel post) { if (StringUtils.isEmpty(post.getExcerpt())) { return makeExcerpt(post.getContent()); } else { return makeExcerpt(post.getExcerpt()); } } /* * Java's string.trim() doesn't handle non-breaking space chars (#160), which may appear at the * end of post content - work around this by converting them to standard spaces before trimming */ private static final String NBSP = String.valueOf((char) 160); private static String trimEx(final String s) { return s.replace(NBSP, " ").trim(); } private static String makeExcerpt(String description) { if (TextUtils.isEmpty(description)) { return null; } String s = HtmlUtils.fastStripHtml(description); if (s.length() < MAX_EXCERPT_LEN) { return trimEx(s); } StringBuilder result = new StringBuilder(); BreakIterator wordIterator = BreakIterator.getWordInstance(); wordIterator.setText(s); int start = wordIterator.first(); int end = wordIterator.next(); int totalLen = 0; while (end != BreakIterator.DONE) { String word = s.substring(start, end); result.append(word); totalLen += word.length(); if (totalLen >= MAX_EXCERPT_LEN) { break; } start = end; end = wordIterator.next(); } if (totalLen == 0) { return null; } return trimEx(result.toString()) + "..."; } public static String getFormattedDate(PostModel post) { if (PostStatus.fromPost(post) == PostStatus.SCHEDULED) { return DateUtils.formatDateTime(WordPress.getContext(), DateTimeUtils.timestampFromIso8601Millis(post.getDateCreated()), DateUtils.FORMAT_ABBREV_ALL); } else { return DateTimeUtils.javaDateToTimeSpan(DateTimeUtils.dateUTCFromIso8601(post.getDateCreated()), WordPress.getContext()); } } public static boolean postListsAreEqual(List<PostModel> lhs, List<PostModel> rhs) { if (lhs == null || rhs == null || lhs.size() != rhs.size()) { return false; } for (int i = 0; i < rhs.size(); i++) { PostModel newPost = rhs.get(i); PostModel currentPost = lhs.get(i); if (!newPost.equals(currentPost)) { return false; } } return true; } public static int indexOfPostInList(final PostModel post, final List<PostModel> posts) { if (post == null) { return -1; } for (int i = 0; i < posts.size(); i++) { if (posts.get(i).getId() == post.getId() && posts.get(i).getLocalSiteId() == post.getLocalSiteId()) { return i; } } return -1; } public static @NotNull List<Integer> indexesOfFeaturedMediaIdInList(final long mediaId, List<PostModel> posts) { List<Integer> list = new ArrayList<>(); if (mediaId == 0) { return list; } for (int i = 0; i < posts.size(); i++) { if (posts.get(i).getFeaturedImageId() == mediaId) { list.add(i); } } return list; } }