diff --git a/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java b/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java index f8a361132..326b46feb 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/MediaFileManager.java @@ -103,6 +103,13 @@ void removeMediaFile(Weblog weblog, MediaFile mediaFile) List searchMediaFiles(Weblog weblog, MediaFileFilter filter) throws WebloggerException; + /** + * Count the media files searchMediaFiles() would return for the same filter, + * ignoring start index, length and order. Used to size pagers. + */ + long getMediaFileCount(Weblog weblog, + MediaFileFilter filter) throws WebloggerException; + /** * Create default folder for media files in a weblog. */ diff --git a/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java b/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java index 3ed56d16b..f841193e7 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/WeblogEntryManager.java @@ -79,6 +79,16 @@ WeblogEntry getWeblogEntryByAnchor(Weblog website, String anchor) List getWeblogEntries(WeblogEntrySearchCriteria wesc) throws WebloggerException; + /** + * Count the rows getWeblogEntries() would return for the same criteria, + * ignoring offset, max results and sort order. Used to size pagers. + * @param wesc WeblogEntrySearchCriteria object listing desired search parameters + * @return number of matching rows + * @throws WebloggerException + */ + long getWeblogEntryCount(WeblogEntrySearchCriteria wesc) + throws WebloggerException; + /** * Get Weblog Entries grouped by day. * @param wesc WeblogEntrySearchCriteria object listing desired search parameters @@ -227,6 +237,14 @@ List getWeblogCategories(Weblog website) */ List getComments(CommentSearchCriteria csc) throws WebloggerException; + /** + * Count the comments getComments() would return for the same criteria, + * ignoring offset, max results and sort order. Used to size pagers. + * @param csc CommentSearchCriteria object with fields indicating search criteria + * @return number of matching comments + */ + long getCommentCount(CommentSearchCriteria csc) throws WebloggerException; + /** * Deletes comments that match paramters. * @param website Website or null for all comments on site diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java index e3c0ddcaa..a3eec1b38 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAMediaFileManagerImpl.java @@ -583,11 +583,65 @@ public List searchMediaFiles(Weblog weblog, MediaFileFilter filter) throws WebloggerException { List params = new ArrayList<>(); - int size = 0; - String queryString = "SELECT m FROM MediaFile m WHERE "; - StringBuilder whereClause = new StringBuilder(); + String queryString = "SELECT m " + mediaFilesFromWhere(weblog, filter, params); StringBuilder orderBy = new StringBuilder(); + if (filter.getOrder() != null) { + switch (filter.getOrder()) { + case NAME: + orderBy.append(" order by m.name"); + break; + case DATE_UPLOADED: + orderBy.append(" order by m.dateUploaded"); + break; + case TYPE: + orderBy.append(" order by m.contentType"); + break; + default: + } + } else { + orderBy.append(" order by m.name"); + } + + TypedQuery query = strategy.getDynamicQuery(queryString + + orderBy.toString(), MediaFile.class); + for (int i = 0; i < params.size(); i++) { + query.setParameter(i + 1, params.get(i)); + } + + if (filter.getStartIndex() >= 0) { + query.setFirstResult(filter.getStartIndex()); + query.setMaxResults(filter.getLength()); + } + return query.getResultList(); + } + + /** + * {@inheritDoc} + */ + @Override + public long getMediaFileCount(Weblog weblog, + MediaFileFilter filter) throws WebloggerException { + + List params = new ArrayList<>(); + TypedQuery query = strategy.getDynamicQuery( + "SELECT COUNT(m) " + mediaFilesFromWhere(weblog, filter, params), Long.class); + for (int i = 0; i < params.size(); i++) { + query.setParameter(i + 1, params.get(i)); + } + return query.getSingleResult(); + } + + /** + * Build the FROM and WHERE clauses shared by searchMediaFiles() and + * getMediaFileCount(), adding positional parameters to params. + */ + private String mediaFilesFromWhere(Weblog weblog, MediaFileFilter filter, + List params) { + + int size = params.size(); + StringBuilder whereClause = new StringBuilder(); + params.add(size++, weblog); whereClause.append("m.directory.weblog = ?").append(size); @@ -659,34 +713,7 @@ public List searchMediaFiles(Weblog weblog, } } - if (filter.getOrder() != null) { - switch (filter.getOrder()) { - case NAME: - orderBy.append(" order by m.name"); - break; - case DATE_UPLOADED: - orderBy.append(" order by m.dateUploaded"); - break; - case TYPE: - orderBy.append(" order by m.contentType"); - break; - default: - } - } else { - orderBy.append(" order by m.name"); - } - - TypedQuery query = strategy.getDynamicQuery(queryString - + whereClause.toString() + orderBy.toString(), MediaFile.class); - for (int i = 0; i < params.size(); i++) { - query.setParameter(i + 1, params.get(i)); - } - - if (filter.getStartIndex() >= 0) { - query.setFirstResult(filter.getStartIndex()); - query.setMaxResults(filter.getLength()); - } - return query.getResultList(); + return "FROM MediaFile m WHERE " + whereClause; } /** diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java index 477179a3f..93a478a9e 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java @@ -381,19 +381,67 @@ public List getWeblogCategories(Weblog website) @Override public List getWeblogEntries(WeblogEntrySearchCriteria wesc) throws WebloggerException { + List params = new ArrayList<>(); + StringBuilder queryString = new StringBuilder("SELECT e "); + queryString.append(weblogEntriesFromWhere(wesc, params)); + + if (wesc.getSortBy() != null && wesc.getSortBy().equals(WeblogEntrySearchCriteria.SortBy.UPDATE_TIME)) { + queryString.append(" ORDER BY e.updateTime "); + } else { + queryString.append(" ORDER BY e.pubTime "); + } + + if (wesc.getSortOrder() != null && wesc.getSortOrder().equals(WeblogEntrySearchCriteria.SortOrder.ASCENDING)) { + queryString.append("ASC "); + } else { + queryString.append("DESC "); + } + + + TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntry.class); + for (int i=0; i params = new ArrayList<>(); + // COUNT(e), not COUNT(DISTINCT e): the tag join can repeat an entry, and the + // count must match the rows getWeblogEntries() pages through. + TypedQuery query = strategy.getDynamicQuery( + "SELECT COUNT(e) " + weblogEntriesFromWhere(wesc, params), Long.class); + for (int i=0; i params) + throws WebloggerException { + WeblogCategory cat = null; if (StringUtils.isNotEmpty(wesc.getCatName()) && wesc.getWeblog() != null) { cat = getWeblogCategoryByName(wesc.getWeblog(), wesc.getCatName()); } - List params = new ArrayList<>(); - int size = 0; + int size = params.size(); StringBuilder queryString = new StringBuilder(); if (wesc.getTags() == null || wesc.getTags().isEmpty()) { - queryString.append("SELECT e FROM WeblogEntry e WHERE "); + queryString.append("FROM WeblogEntry e WHERE "); } else { - queryString.append("SELECT e FROM WeblogEntry e JOIN e.tags t WHERE "); + queryString.append("FROM WeblogEntry e JOIN e.tags t WHERE "); queryString.append("("); for (int i = 0; i < wesc.getTags().size(); i++) { if (i != 0) { @@ -453,26 +501,7 @@ public List getWeblogEntries(WeblogEntrySearchCriteria wesc) throws queryString.append(") "); } - if (wesc.getSortBy() != null && wesc.getSortBy().equals(WeblogEntrySearchCriteria.SortBy.UPDATE_TIME)) { - queryString.append(" ORDER BY e.updateTime "); - } else { - queryString.append(" ORDER BY e.pubTime "); - } - - if (wesc.getSortOrder() != null && wesc.getSortOrder().equals(WeblogEntrySearchCriteria.SortOrder.ASCENDING)) { - queryString.append("ASC "); - } else { - queryString.append("DESC "); - } - - - TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntry.class); - for (int i=0; i getComments(CommentSearchCriteria csc) throws WebloggerException { List params = new ArrayList<>(); - int size = 0; - StringBuilder queryString = new StringBuilder(); - queryString.append("SELECT c FROM WeblogEntryComment c "); + StringBuilder queryString = new StringBuilder("SELECT c "); + queryString.append(commentsFromWhere(csc, params)); + if (csc.isReverseChrono()) { + queryString.append(" ORDER BY c.postTime DESC"); + } else { + queryString.append(" ORDER BY c.postTime ASC"); + } + TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntryComment.class); + setFirstMax( query, csc.getOffset(), csc.getMaxResults()); + for (int i=0; i params = new ArrayList<>(); + TypedQuery query = strategy.getDynamicQuery( + "SELECT COUNT(c) " + commentsFromWhere(csc, params), Long.class); + for (int i=0; i params) { + + int size = params.size(); + StringBuilder queryString = new StringBuilder("FROM WeblogEntryComment c "); + StringBuilder whereClause = new StringBuilder(); if (csc.getEntry() != null) { params.add(size++, csc.getEntry()); @@ -669,19 +734,8 @@ public List getComments(CommentSearchCriteria csc) throws We if(whereClause.length() != 0) { queryString.append(" WHERE ").append(whereClause); } - if (csc.isReverseChrono()) { - queryString.append(" ORDER BY c.postTime DESC"); - } else { - queryString.append(" ORDER BY c.postTime ASC"); - } - - TypedQuery query = strategy.getDynamicQuery(queryString.toString(), WeblogEntryComment.class); - setFirstMax( query, csc.getOffset(), csc.getMaxResults()); - for (int i=0; i comments = Collections.emptyList(); - boolean hasMore = false; + long totalCount = 0; try { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); @@ -109,19 +109,13 @@ public void loadComments() { csc.setEndDate(getBean().getEndDate()); csc.setStatus(getBean().getStatus()); csc.setReverseChrono(true); + totalCount = wmgr.getCommentCount(csc); csc.setOffset(getBean().getPage() * COUNT); - csc.setMaxResults(COUNT+1); + csc.setMaxResults(COUNT); - List rawComments = wmgr.getComments(csc); - comments = new ArrayList<>(); - comments.addAll(rawComments); + comments = new ArrayList<>(wmgr.getComments(csc)); if(!comments.isEmpty()) { - if(comments.size() > COUNT) { - comments.remove(comments.size()-1); - hasMore = true; - } - setFirstComment(comments.get(0)); setLastComment(comments.get(comments.size()-1)); } @@ -132,7 +126,7 @@ public void loadComments() { // build comments pager String baseUrl = buildBaseUrl(); - setPager(new CommentsPager(baseUrl, getBean().getPage(), comments, hasMore)); + setPager(new CommentsPager(baseUrl, getBean().getPage(), comments, totalCount, COUNT)); } @@ -184,25 +178,9 @@ public String query() { // load bean data using comments list getBean().loadCheckboxes(getPager().getItems()); - try { - WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); - - CommentSearchCriteria csc = new CommentSearchCriteria(); - csc.setSearchText(getBean().getSearchString()); - csc.setStartDate(getBean().getStartDate()); - csc.setEndDate(getBean().getEndDate()); - csc.setStatus(getBean().getStatus()); - csc.setReverseChrono(true); - - List allMatchingComments = wmgr.getComments(csc); - - if(allMatchingComments.size() > COUNT) { - setBulkDeleteCount(allMatchingComments.size()); - } - - } catch (WebloggerException ex) { - log.error("Error looking up comments", ex); - addError("commentManagement.lookupError"); + // offer bulk delete when the matches span more than one page + if (getPager().getTotalCount() > COUNT) { + setBulkDeleteCount((int) getPager().getTotalCount()); } return LIST; diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java index 889fc9cae..a19f7f980 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Comments.java @@ -99,30 +99,24 @@ public List requiredWeblogPermissionActions() { public void loadComments() { List comments = Collections.emptyList(); - boolean hasMore = false; + long totalCount = 0; try { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger() .getWeblogEntryManager(); if (!resolveQueryEntry(wmgr)) { setPager(new CommentsPager(buildBaseUrl(), getBean().getPage(), - comments, false)); + comments, 0, COUNT)); return; } CommentSearchCriteria csc = getCommentSearchCriteria(); + totalCount = wmgr.getCommentCount(csc); csc.setOffset(getBean().getPage() * COUNT); - csc.setMaxResults(COUNT + 1); + csc.setMaxResults(COUNT); - List rawComments = wmgr.getComments(csc); - comments = new ArrayList<>(); - comments.addAll(rawComments); + comments = new ArrayList<>(wmgr.getComments(csc)); if (!comments.isEmpty()) { - if (comments.size() > COUNT) { - comments.remove(comments.size() - 1); - hasMore = true; - } - setFirstComment(comments.get(0)); setLastComment(comments.get(comments .size() - 1)); @@ -135,7 +129,7 @@ public void loadComments() { // build comments pager String baseUrl = buildBaseUrl(); setPager(new CommentsPager(baseUrl, getBean().getPage(), comments, - hasMore)); + totalCount, COUNT)); } // use the action data to build a url representing this action, including @@ -191,19 +185,9 @@ public String query() { return LIST; } - try { - WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); - - CommentSearchCriteria csc = getCommentSearchCriteria(); - - List allMatchingComments = wmgr.getComments(csc); - if (allMatchingComments.size() > COUNT) { - setBulkDeleteCount(allMatchingComments.size()); - } - - } catch (WebloggerException ex) { - log.error("Error looking up comments", ex); - addError("Error looking up comments"); + // offer bulk delete when the matches span more than one page + if (getPager().getTotalCount() > COUNT) { + setBulkDeleteCount((int) getPager().getTotalCount()); } return LIST; diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java index e34aa4778..942a12648 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Entries.java @@ -85,7 +85,7 @@ public String execute() { } List entries = null; - boolean hasMore = false; + long totalCount = 0; try { String status = getBean().getStatus(); @@ -99,18 +99,12 @@ public String execute() { wesc.setStatus("ALL".equals(status) ? null : WeblogEntry.PubStatus.valueOf(status)); wesc.setText(getBean().getText()); wesc.setSortBy(getBean().getSortBy()); + totalCount = wmgr.getWeblogEntryCount(wesc); wesc.setOffset(getBean().getPage() * COUNT); - wesc.setMaxResults(COUNT + 1); - List rawEntries = wmgr.getWeblogEntries(wesc); - entries = new ArrayList<>(); - entries.addAll(rawEntries); + wesc.setMaxResults(COUNT); + entries = new ArrayList<>(wmgr.getWeblogEntries(wesc)); if (!entries.isEmpty()) { - log.debug("query found "+rawEntries.size()+" results"); - - if(rawEntries.size() > COUNT) { - entries.remove(entries.size()-1); - hasMore = true; - } + log.debug("query found "+entries.size()+" of "+totalCount+" results"); setFirstEntry(entries.get(0)); setLastEntry(entries.get(entries.size()-1)); @@ -122,7 +116,7 @@ public String execute() { // build entries pager String baseUrl = buildBaseUrl(); - setPager(new EntriesPager(baseUrl, getBean().getPage(), entries, hasMore)); + setPager(new EntriesPager(baseUrl, getBean().getPage(), entries, totalCount, COUNT)); return LIST; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java index 3b96c875c..d015ae1ad 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileSearchBean.java @@ -1,211 +1,209 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. The ASF licenses this file to You - * under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. For additional information regarding - * copyright in this work, please see the NOTICE file in the top level - * directory of this distribution. - */ -package org.apache.roller.weblogger.ui.struts2.editor; - -import java.util.Arrays; - -import java.util.ResourceBundle; -import org.apache.commons.lang3.StringUtils; -import org.apache.roller.util.RollerConstants; -import org.apache.roller.weblogger.pojos.MediaFileFilter; -import org.apache.roller.weblogger.pojos.MediaFileType; -import org.apache.roller.weblogger.pojos.MediaFileFilter.MediaFileOrder; -import org.apache.roller.weblogger.pojos.MediaFileFilter.SizeFilterType; - -/** - * Bean for holding media file search criteria. - */ -public class MediaFileSearchBean { - private transient ResourceBundle bundle = - ResourceBundle.getBundle("ApplicationResources"); - - public static final int PAGE_SIZE = 10; - - // Media file name as search criteria - private String name; - - // Media file type as search criteria - private String type; - - // Type of size filter as search criteria - private String sizeFilterType; - - // Size of file as search criteria - private long size; - - // Size unit - private String sizeUnit; - - // Tags as search criteria - private String tags; - - // Page number of results - private int pageNum = 0; - - // Sort option for search results - private int sortOption; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public String getTypeLabel() { - return this.bundle.getString(type); - } - - public void setType(String type) { - this.type = type; - } - - public String getSizeFilterType() { - return sizeFilterType; - } - - public void setSizeFilterType(String sizeFilterType) { - this.sizeFilterType = sizeFilterType; - } - - public String getSizeFilterTypeLabel() { - return this.bundle.getString(sizeFilterType); - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public String getSizeUnit() { - return sizeUnit; - } - - public String getSizeUnitLabel() { - return this.bundle.getString(sizeUnit); - } - - public void setSizeUnit(String sizeUnit) { - this.sizeUnit = sizeUnit; - } - - public String getTags() { - return tags; - } - - public void setTags(String tags) { - this.tags = tags; - } - - public int getPageNum() { - return pageNum; - } - - public void setPageNum(int pageNum) { - this.pageNum = pageNum; - } - - public int getSortOption() { - return sortOption; - } - - public void setSortOption(int sortOption) { - this.sortOption = sortOption; - } - - /** - * Copies data from this bean to media file filter object. - * - */ - public void copyTo(MediaFileFilter dataHolder) { - dataHolder.setName(this.name); - - if (!StringUtils.isEmpty(this.type)) { - MediaFileType filterType = null; - if ("mediaFileView.audio".equals(this.type)) { - filterType = MediaFileType.AUDIO; - } else if ("mediaFileView.video".equals(this.type)) { - filterType = MediaFileType.VIDEO; - } else if ("mediaFileView.image".equals(this.type)) { - filterType = MediaFileType.IMAGE; - } else if ("mediaFileView.others".equals(this.type)) { - filterType = MediaFileType.OTHERS; - } - - dataHolder.setType(filterType); - } - - if (this.size > 0) { - SizeFilterType sftype = SizeFilterType.EQ; - if ("mediaFileView.gt".equals(this.sizeFilterType)) { - sftype = SizeFilterType.GT; - } else if ("mediaFileView.ge".equals(this.sizeFilterType)) { - sftype = SizeFilterType.GTE; - } else if ("mediaFileView.eq".equals(this.sizeFilterType)) { - sftype = SizeFilterType.EQ; - } else if ("mediaFileView.le".equals(this.sizeFilterType)) { - sftype = SizeFilterType.LTE; - } else if ("mediaFileView.lt".equals(this.sizeFilterType)) { - sftype = SizeFilterType.LT; - } - dataHolder.setSizeFilterType(sftype); - - long filterSize = this.size ; - if ("mediaFileView.kb".equals(this.sizeUnit)) { - filterSize = this.size * RollerConstants.ONE_KB_IN_BYTES; - } else if ("mediaFileView.mb".equals(this.sizeUnit)) { - filterSize = this.size * RollerConstants.ONE_MB_IN_BYTES; - } - dataHolder.setSize(filterSize); - } - - if (!StringUtils.isEmpty(this.tags)) { - dataHolder.setTags(Arrays.asList(this.tags.split(" "))); - } - - dataHolder.setStartIndex(pageNum * PAGE_SIZE); - - // set length to fetch to one more than what is required. - // this would help us determine whether there are more pages - dataHolder.setLength(PAGE_SIZE + 1); - - MediaFileOrder order; - switch (this.sortOption) { - case 0: - order = MediaFileOrder.NAME; - break; - case 1: - order = MediaFileOrder.DATE_UPLOADED; - break; - case 2: - order = MediaFileOrder.TYPE; - break; - default: - order = null; - } - dataHolder.setOrder(order); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package org.apache.roller.weblogger.ui.struts2.editor; + +import java.util.Arrays; + +import java.util.ResourceBundle; +import org.apache.commons.lang3.StringUtils; +import org.apache.roller.util.RollerConstants; +import org.apache.roller.weblogger.pojos.MediaFileFilter; +import org.apache.roller.weblogger.pojos.MediaFileType; +import org.apache.roller.weblogger.pojos.MediaFileFilter.MediaFileOrder; +import org.apache.roller.weblogger.pojos.MediaFileFilter.SizeFilterType; + +/** + * Bean for holding media file search criteria. + */ +public class MediaFileSearchBean { + private transient ResourceBundle bundle = + ResourceBundle.getBundle("ApplicationResources"); + + public static final int PAGE_SIZE = 10; + + // Media file name as search criteria + private String name; + + // Media file type as search criteria + private String type; + + // Type of size filter as search criteria + private String sizeFilterType; + + // Size of file as search criteria + private long size; + + // Size unit + private String sizeUnit; + + // Tags as search criteria + private String tags; + + // Page number of results + private int pageNum = 0; + + // Sort option for search results + private int sortOption; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public String getTypeLabel() { + return this.bundle.getString(type); + } + + public void setType(String type) { + this.type = type; + } + + public String getSizeFilterType() { + return sizeFilterType; + } + + public void setSizeFilterType(String sizeFilterType) { + this.sizeFilterType = sizeFilterType; + } + + public String getSizeFilterTypeLabel() { + return this.bundle.getString(sizeFilterType); + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public String getSizeUnit() { + return sizeUnit; + } + + public String getSizeUnitLabel() { + return this.bundle.getString(sizeUnit); + } + + public void setSizeUnit(String sizeUnit) { + this.sizeUnit = sizeUnit; + } + + public String getTags() { + return tags; + } + + public void setTags(String tags) { + this.tags = tags; + } + + public int getPageNum() { + return pageNum; + } + + public void setPageNum(int pageNum) { + this.pageNum = pageNum; + } + + public int getSortOption() { + return sortOption; + } + + public void setSortOption(int sortOption) { + this.sortOption = sortOption; + } + + /** + * Copies data from this bean to media file filter object. + * + */ + public void copyTo(MediaFileFilter dataHolder) { + dataHolder.setName(this.name); + + if (!StringUtils.isEmpty(this.type)) { + MediaFileType filterType = null; + if ("mediaFileView.audio".equals(this.type)) { + filterType = MediaFileType.AUDIO; + } else if ("mediaFileView.video".equals(this.type)) { + filterType = MediaFileType.VIDEO; + } else if ("mediaFileView.image".equals(this.type)) { + filterType = MediaFileType.IMAGE; + } else if ("mediaFileView.others".equals(this.type)) { + filterType = MediaFileType.OTHERS; + } + + dataHolder.setType(filterType); + } + + if (this.size > 0) { + SizeFilterType sftype = SizeFilterType.EQ; + if ("mediaFileView.gt".equals(this.sizeFilterType)) { + sftype = SizeFilterType.GT; + } else if ("mediaFileView.ge".equals(this.sizeFilterType)) { + sftype = SizeFilterType.GTE; + } else if ("mediaFileView.eq".equals(this.sizeFilterType)) { + sftype = SizeFilterType.EQ; + } else if ("mediaFileView.le".equals(this.sizeFilterType)) { + sftype = SizeFilterType.LTE; + } else if ("mediaFileView.lt".equals(this.sizeFilterType)) { + sftype = SizeFilterType.LT; + } + dataHolder.setSizeFilterType(sftype); + + long filterSize = this.size ; + if ("mediaFileView.kb".equals(this.sizeUnit)) { + filterSize = this.size * RollerConstants.ONE_KB_IN_BYTES; + } else if ("mediaFileView.mb".equals(this.sizeUnit)) { + filterSize = this.size * RollerConstants.ONE_MB_IN_BYTES; + } + dataHolder.setSize(filterSize); + } + + if (!StringUtils.isEmpty(this.tags)) { + dataHolder.setTags(Arrays.asList(this.tags.split(" "))); + } + + dataHolder.setStartIndex(pageNum * PAGE_SIZE); + + dataHolder.setLength(PAGE_SIZE); + + MediaFileOrder order; + switch (this.sortOption) { + case 0: + order = MediaFileOrder.NAME; + break; + case 1: + order = MediaFileOrder.DATE_UPLOADED; + break; + case 2: + order = MediaFileOrder.TYPE; + break; + default: + order = null; + } + dataHolder.setOrder(order); + } +} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java index e52a7b243..fdf8ba498 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java @@ -1,491 +1,487 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. The ASF licenses this file to You - * under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. For additional information regarding - * copyright in this work, please see the NOTICE file in the top level - * directory of this distribution. - */ -package org.apache.roller.weblogger.ui.struts2.editor; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.FileIOException; -import org.apache.roller.weblogger.business.MediaFileManager; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.pojos.MediaFile; -import org.apache.roller.weblogger.pojos.MediaFileComparator; -import org.apache.roller.weblogger.pojos.MediaFileComparator.MediaFileComparatorType; -import org.apache.roller.weblogger.pojos.MediaFileDirectory; -import org.apache.roller.weblogger.pojos.MediaFileFilter; -import org.apache.roller.weblogger.ui.struts2.pagers.MediaFilePager; -import org.apache.roller.weblogger.ui.struts2.util.KeyValueObject; -import org.apache.roller.weblogger.util.cache.CacheManager; -import org.apache.struts2.convention.annotation.AllowedMethods; -import org.apache.struts2.interceptor.parameter.StrutsParameter; -import org.apache.struts2.interceptor.validation.SkipValidation; - -/** - * View media files. - */ -@SuppressWarnings("serial") -// TODO: make this work @AllowedMethods({"execute","view","search","delete","deleteSelected","deleteFolder","includeInGallery","moveSelected"}) -public class MediaFileView extends MediaFileBase { - - private static Log log = LogFactory.getLog(MediaFileView.class); - - private String directoryId; - private String directoryName; - private String sortBy; - private String newDirectoryName; - - private List childFiles; - private MediaFileDirectory currentDirectory; - - // Search criteria - drop-down for file type - private static List FILE_TYPES = null; - - // Search criteria - drop-down for size filter - private static List SIZE_FILTER_TYPES = null; - - // Search criteria - drop-down for size unit - private static List SIZE_UNITS = null; - - // Sort options for search results. - private static List SORT_OPTIONS = null; - - // Pager for displaying search results. - private MediaFilePager pager; - - // Path of new directory to be created. - private String newDirectoryPath; - - // a new directory the user wishes to view - private String viewDirectoryId = null; - - private MediaFileSearchBean bean = new MediaFileSearchBean(); - - public MediaFileView() { - this.actionName = "mediaFileView"; - this.desiredMenu = "editor"; - this.pageTitle = "mediaFileView.title"; - } - - /** - * Prepares view action - */ - @Override - public void myPrepare() { - - if (SIZE_FILTER_TYPES == null) { - - SIZE_FILTER_TYPES = Arrays.asList(new KeyValueObject( - "mediaFileView.gt", getText("mediaFileView.gt")), - new KeyValueObject("mediaFileView.ge", - getText("mediaFileView.ge")), new KeyValueObject( - "mediaFileView.eq", getText("mediaFileView.eq")), - new KeyValueObject("mediaFileView.le", - getText("mediaFileView.le")), new KeyValueObject( - "mediaFileView.lt", getText("mediaFileView.lt"))); - - FILE_TYPES = Arrays.asList(new KeyValueObject("mediaFileView.any", - getText("mediaFileView.any")), new KeyValueObject( - "mediaFileView.others", getText("mediaFileView.others")), - new KeyValueObject("mediaFileView.image", - getText("mediaFileView.image")), - new KeyValueObject("mediaFileView.video", - getText("mediaFileView.video")), - new KeyValueObject("mediaFileView.audio", - getText("mediaFileView.audio"))); - - SIZE_UNITS = Arrays.asList(new KeyValueObject( - "mediaFileView.bytes", getText("mediaFileView.bytes")), - new KeyValueObject("mediaFileView.kb", - getText("mediaFileView.kb")), new KeyValueObject( - "mediaFileView.mb", getText("mediaFileView.mb"))); - - SORT_OPTIONS = Arrays.asList(new KeyValueObject("name", - getText("generic.name")), new KeyValueObject( - "date_uploaded", getText("mediaFileView.date")), - new KeyValueObject("type", getText("mediaFileView.type"))); - } - - refreshAllDirectories(); - } - - /** - * Create a new directory by name. All folders placed at the root. - */ - public String createNewDirectory() { - boolean dirCreated = false; - if (StringUtils.isEmpty(this.newDirectoryName)) { - addError("mediaFile.error.view.dirNameEmpty"); - } else if (this.newDirectoryName.contains("/")) { - addError("mediaFile.error.view.dirNameInvalid"); - } else { - try { - - log.debug("Creating new directory - " + this.newDirectoryName); - MediaFileManager manager = WebloggerFactory.getWeblogger() - .getMediaFileManager(); - - if (!getActionWeblog().hasMediaFileDirectory( - this.newDirectoryName)) { - // Create - MediaFileDirectory dir = manager.createMediaFileDirectory( - getActionWeblog(), this.newDirectoryName); - // flush changes - WebloggerFactory.getWeblogger().flush(); - addMessage("mediaFile.directoryCreate.success", - this.newDirectoryName); - - // Switch to folder - setDirectoryId(dir.getId()); - - dirCreated = true; - } else { - // already exists - addMessage("mediaFile.directoryCreate.error.exists", - this.newDirectoryName); - } - - } catch (WebloggerException e) { - log.error("Error creating new directory", e); - addError("Error creating new directory"); - } - } - - if (dirCreated) { - // Refresh list of directories so the newly created directory is - // included. - refreshAllDirectories(); - } - return execute(); - - } - - /** - * Returns directory content in JSON format. - */ - public String fetchDirectoryContentLight() { - execute(); - return "success.json"; - } - - /** - * Fetches and displays list of media file for the given directory. The - * directory could be chosen by ID or path. - * - * @return String The result of the action. - */ - @SkipValidation - @Override - public String execute() { - MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); - try { - MediaFileDirectory directory; - if (StringUtils.isNotEmpty(this.directoryId)) { - directory = manager.getMediaFileDirectory(getActionWeblog(), this.directoryId); - - } else if (StringUtils.isNotEmpty(this.directoryName)) { - directory = manager.getMediaFileDirectoryByName( - getActionWeblog(), this.directoryName); - - } else { - directory = manager.getDefaultMediaFileDirectory(getActionWeblog()); - } - this.directoryId = directory.getId(); - this.directoryName = directory.getName(); - - this.childFiles = new ArrayList<>(); - this.childFiles.addAll(directory.getMediaFiles()); - - if ("type".equals(sortBy)) { - this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.TYPE)); - - } else if ("date_uploaded".equals(sortBy)) { - this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.DATE_UPLOADED)); - - } else { - // default to sort by name - sortBy = "name"; - this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.NAME)); - } - - this.currentDirectory = directory; - - // set current directory if valid - if (directory != null) { - setViewDirectoryId(directory.getId()); - } - - return SUCCESS; - - } catch (FileIOException ex) { - log.error("Error viewing media file directory ", ex); - addError("MediaFile.error.view"); - } catch (Exception e) { - log.error("Error viewing media file directory ", e); - addError("MediaFile.error.view"); - } - return SUCCESS; - } - - /** - * View the contents of another Media folder. - */ - public String view() { - try { - MediaFileManager manager = WebloggerFactory.getWeblogger() - .getMediaFileManager(); - if (!StringUtils.isEmpty(viewDirectoryId)) { - setDirectoryId(viewDirectoryId); - setCurrentDirectory(manager.getMediaFileDirectory(getActionWeblog(), viewDirectoryId)); - } - } catch (WebloggerException ex) { - log.error("Error looking up directory", ex); - } - return execute(); - } - - /** - * Save a media file. - * - * @return String The result of the action. - */ - public String search() { - - boolean valSuccess = myValidate(); - - if (valSuccess) { - MediaFileFilter filter = new MediaFileFilter(); - bean.copyTo(filter); - MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); - try { - List rawResults = manager.searchMediaFiles(getActionWeblog(), filter); - boolean hasMore = false; - List results = new ArrayList<>(rawResults); - if (results.size() > MediaFileSearchBean.PAGE_SIZE) { - results.remove(results.size() - 1); - hasMore = true; - } - - this.pager = new MediaFilePager(bean.getPageNum(), results, - hasMore); - - } catch (Exception e) { - log.error("Error applying search criteria", e); - addError("Error applying search criteria - check Roller logs"); - } - - } - - return SUCCESS; - } - - /** - * Delete selected media files. - * - */ - public String deleteSelected() { - doDeleteSelected(); - return execute(); - } - - /** - * Delete selected media file - * - */ - public String delete() { - doDeleteMediaFile(); - return execute(); - } - - /** - * Delete folder - */ - public String deleteFolder() { - - try { - MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); - if (directoryId != null) { - log.debug("Deleting media file folder - " + directoryId + " (" - + directoryName + ")"); - MediaFileDirectory mediaFileDir = manager - .getMediaFileDirectory(getActionWeblog(), directoryId); - manager.removeMediaFileDirectory(mediaFileDir); - refreshAllDirectories(); - WebloggerFactory.getWeblogger().getWeblogManager() - .saveWeblog(this.getActionWeblog()); - - // flush changes - WebloggerFactory.getWeblogger().flush(); - WebloggerFactory.getWeblogger().release(); - addMessage("mediaFile.deleteFolder.success"); - - // notify caches - CacheManager.invalidate(getActionWeblog()); - - // re-route to default folder - mediaFileDir = manager - .getDefaultMediaFileDirectory(getActionWeblog()); - setDirectoryId(mediaFileDir.getId()); - setDirectoryName(mediaFileDir.getName()); - } else { - log.error("(System error) No directory ID provided for media file folder delete."); - } - } catch (WebloggerException ex) { - log.error("Error deleting folder", ex); - } - return execute(); - } - - /** - * Include selected media file in gallery - * - */ - public String includeInGallery() { - doIncludeMediaFileInGallery(); - return execute(); - } - - /** - * Move selected media files to a different directory - * - */ - public String moveSelected() { - doMoveSelected(); - return execute(); - } - - public String getDirectoryId() { - return directoryId; - } - - @StrutsParameter - public void setDirectoryId(String id) { - this.directoryId = id; - } - - public List getChildFiles() { - return childFiles; - } - - public void setChildFiles(List files) { - this.childFiles = files; - } - - public String getNewDirectoryName() { - return newDirectoryName; - } - - @StrutsParameter - public void setNewDirectoryName(String newDirectoryName) { - this.newDirectoryName = newDirectoryName; - } - - public MediaFileDirectory getCurrentDirectory() { - return currentDirectory; - } - - public void setCurrentDirectory(MediaFileDirectory currentDirectory) { - this.currentDirectory = currentDirectory; - } - - public String getDirectoryName() { - return directoryName; - } - - @StrutsParameter - public void setDirectoryName(String path) { - this.directoryName = path; - } - - public String getSortBy() { - return sortBy; - } - - @StrutsParameter - public void setSortBy(String sortBy) { - this.sortBy = sortBy; - } - - /** - * Validates search input - */ - public boolean myValidate() { - if (StringUtils.isEmpty(bean.getName()) - && StringUtils.isEmpty(bean.getTags()) - && StringUtils.isEmpty(bean.getType()) && bean.getSize() == 0) { - addError("MediaFile.error.search.empty"); - return false; - } - return true; - } - - @StrutsParameter(depth = 1) - public MediaFileSearchBean getBean() { - return bean; - } - - public void setBean(MediaFileSearchBean b) { - this.bean = b; - } - - public List getFileTypes() { - return FILE_TYPES; - } - - public List getSizeFilterTypes() { - return SIZE_FILTER_TYPES; - } - - public List getSizeUnits() { - return SIZE_UNITS; - } - - public List getSortOptions() { - return SORT_OPTIONS; - } - - public MediaFilePager getPager() { - return pager; - } - - public void setPager(MediaFilePager pager) { - this.pager = pager; - } - - public String getNewDirectoryPath() { - return newDirectoryPath; - } - - public void setNewDirectoryPath(String newDirectoryPath) { - this.newDirectoryPath = newDirectoryPath; - } - - public String getViewDirectoryId() { - return viewDirectoryId; - } - - @StrutsParameter - public void setViewDirectoryId(String viewDirectoryId) { - this.viewDirectoryId = viewDirectoryId; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package org.apache.roller.weblogger.ui.struts2.editor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.roller.weblogger.WebloggerException; +import org.apache.roller.weblogger.business.FileIOException; +import org.apache.roller.weblogger.business.MediaFileManager; +import org.apache.roller.weblogger.business.WebloggerFactory; +import org.apache.roller.weblogger.pojos.MediaFile; +import org.apache.roller.weblogger.pojos.MediaFileComparator; +import org.apache.roller.weblogger.pojos.MediaFileComparator.MediaFileComparatorType; +import org.apache.roller.weblogger.pojos.MediaFileDirectory; +import org.apache.roller.weblogger.pojos.MediaFileFilter; +import org.apache.roller.weblogger.ui.struts2.pagers.MediaFilePager; +import org.apache.roller.weblogger.ui.struts2.util.KeyValueObject; +import org.apache.roller.weblogger.util.cache.CacheManager; +import org.apache.struts2.convention.annotation.AllowedMethods; +import org.apache.struts2.interceptor.parameter.StrutsParameter; +import org.apache.struts2.interceptor.validation.SkipValidation; + +/** + * View media files. + */ +@SuppressWarnings("serial") +// TODO: make this work @AllowedMethods({"execute","view","search","delete","deleteSelected","deleteFolder","includeInGallery","moveSelected"}) +public class MediaFileView extends MediaFileBase { + + private static Log log = LogFactory.getLog(MediaFileView.class); + + private String directoryId; + private String directoryName; + private String sortBy; + private String newDirectoryName; + + private List childFiles; + private MediaFileDirectory currentDirectory; + + // Search criteria - drop-down for file type + private static List FILE_TYPES = null; + + // Search criteria - drop-down for size filter + private static List SIZE_FILTER_TYPES = null; + + // Search criteria - drop-down for size unit + private static List SIZE_UNITS = null; + + // Sort options for search results. + private static List SORT_OPTIONS = null; + + // Pager for displaying search results. + private MediaFilePager pager; + + // Path of new directory to be created. + private String newDirectoryPath; + + // a new directory the user wishes to view + private String viewDirectoryId = null; + + private MediaFileSearchBean bean = new MediaFileSearchBean(); + + public MediaFileView() { + this.actionName = "mediaFileView"; + this.desiredMenu = "editor"; + this.pageTitle = "mediaFileView.title"; + } + + /** + * Prepares view action + */ + @Override + public void myPrepare() { + + if (SIZE_FILTER_TYPES == null) { + + SIZE_FILTER_TYPES = Arrays.asList(new KeyValueObject( + "mediaFileView.gt", getText("mediaFileView.gt")), + new KeyValueObject("mediaFileView.ge", + getText("mediaFileView.ge")), new KeyValueObject( + "mediaFileView.eq", getText("mediaFileView.eq")), + new KeyValueObject("mediaFileView.le", + getText("mediaFileView.le")), new KeyValueObject( + "mediaFileView.lt", getText("mediaFileView.lt"))); + + FILE_TYPES = Arrays.asList(new KeyValueObject("mediaFileView.any", + getText("mediaFileView.any")), new KeyValueObject( + "mediaFileView.others", getText("mediaFileView.others")), + new KeyValueObject("mediaFileView.image", + getText("mediaFileView.image")), + new KeyValueObject("mediaFileView.video", + getText("mediaFileView.video")), + new KeyValueObject("mediaFileView.audio", + getText("mediaFileView.audio"))); + + SIZE_UNITS = Arrays.asList(new KeyValueObject( + "mediaFileView.bytes", getText("mediaFileView.bytes")), + new KeyValueObject("mediaFileView.kb", + getText("mediaFileView.kb")), new KeyValueObject( + "mediaFileView.mb", getText("mediaFileView.mb"))); + + SORT_OPTIONS = Arrays.asList(new KeyValueObject("name", + getText("generic.name")), new KeyValueObject( + "date_uploaded", getText("mediaFileView.date")), + new KeyValueObject("type", getText("mediaFileView.type"))); + } + + refreshAllDirectories(); + } + + /** + * Create a new directory by name. All folders placed at the root. + */ + public String createNewDirectory() { + boolean dirCreated = false; + if (StringUtils.isEmpty(this.newDirectoryName)) { + addError("mediaFile.error.view.dirNameEmpty"); + } else if (this.newDirectoryName.contains("/")) { + addError("mediaFile.error.view.dirNameInvalid"); + } else { + try { + + log.debug("Creating new directory - " + this.newDirectoryName); + MediaFileManager manager = WebloggerFactory.getWeblogger() + .getMediaFileManager(); + + if (!getActionWeblog().hasMediaFileDirectory( + this.newDirectoryName)) { + // Create + MediaFileDirectory dir = manager.createMediaFileDirectory( + getActionWeblog(), this.newDirectoryName); + // flush changes + WebloggerFactory.getWeblogger().flush(); + addMessage("mediaFile.directoryCreate.success", + this.newDirectoryName); + + // Switch to folder + setDirectoryId(dir.getId()); + + dirCreated = true; + } else { + // already exists + addMessage("mediaFile.directoryCreate.error.exists", + this.newDirectoryName); + } + + } catch (WebloggerException e) { + log.error("Error creating new directory", e); + addError("Error creating new directory"); + } + } + + if (dirCreated) { + // Refresh list of directories so the newly created directory is + // included. + refreshAllDirectories(); + } + return execute(); + + } + + /** + * Returns directory content in JSON format. + */ + public String fetchDirectoryContentLight() { + execute(); + return "success.json"; + } + + /** + * Fetches and displays list of media file for the given directory. The + * directory could be chosen by ID or path. + * + * @return String The result of the action. + */ + @SkipValidation + @Override + public String execute() { + MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); + try { + MediaFileDirectory directory; + if (StringUtils.isNotEmpty(this.directoryId)) { + directory = manager.getMediaFileDirectory(getActionWeblog(), this.directoryId); + + } else if (StringUtils.isNotEmpty(this.directoryName)) { + directory = manager.getMediaFileDirectoryByName( + getActionWeblog(), this.directoryName); + + } else { + directory = manager.getDefaultMediaFileDirectory(getActionWeblog()); + } + this.directoryId = directory.getId(); + this.directoryName = directory.getName(); + + this.childFiles = new ArrayList<>(); + this.childFiles.addAll(directory.getMediaFiles()); + + if ("type".equals(sortBy)) { + this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.TYPE)); + + } else if ("date_uploaded".equals(sortBy)) { + this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.DATE_UPLOADED)); + + } else { + // default to sort by name + sortBy = "name"; + this.childFiles.sort(new MediaFileComparator(MediaFileComparatorType.NAME)); + } + + this.currentDirectory = directory; + + // set current directory if valid + if (directory != null) { + setViewDirectoryId(directory.getId()); + } + + return SUCCESS; + + } catch (FileIOException ex) { + log.error("Error viewing media file directory ", ex); + addError("MediaFile.error.view"); + } catch (Exception e) { + log.error("Error viewing media file directory ", e); + addError("MediaFile.error.view"); + } + return SUCCESS; + } + + /** + * View the contents of another Media folder. + */ + public String view() { + try { + MediaFileManager manager = WebloggerFactory.getWeblogger() + .getMediaFileManager(); + if (!StringUtils.isEmpty(viewDirectoryId)) { + setDirectoryId(viewDirectoryId); + setCurrentDirectory(manager.getMediaFileDirectory(getActionWeblog(), viewDirectoryId)); + } + } catch (WebloggerException ex) { + log.error("Error looking up directory", ex); + } + return execute(); + } + + /** + * Save a media file. + * + * @return String The result of the action. + */ + public String search() { + + boolean valSuccess = myValidate(); + + if (valSuccess) { + MediaFileFilter filter = new MediaFileFilter(); + bean.copyTo(filter); + MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); + try { + List results = new ArrayList<>( + manager.searchMediaFiles(getActionWeblog(), filter)); + long totalCount = manager.getMediaFileCount(getActionWeblog(), filter); + + this.pager = new MediaFilePager(bean.getPageNum(), results, + totalCount, MediaFileSearchBean.PAGE_SIZE); + + } catch (Exception e) { + log.error("Error applying search criteria", e); + addError("Error applying search criteria - check Roller logs"); + } + + } + + return SUCCESS; + } + + /** + * Delete selected media files. + * + */ + public String deleteSelected() { + doDeleteSelected(); + return execute(); + } + + /** + * Delete selected media file + * + */ + public String delete() { + doDeleteMediaFile(); + return execute(); + } + + /** + * Delete folder + */ + public String deleteFolder() { + + try { + MediaFileManager manager = WebloggerFactory.getWeblogger().getMediaFileManager(); + if (directoryId != null) { + log.debug("Deleting media file folder - " + directoryId + " (" + + directoryName + ")"); + MediaFileDirectory mediaFileDir = manager + .getMediaFileDirectory(getActionWeblog(), directoryId); + manager.removeMediaFileDirectory(mediaFileDir); + refreshAllDirectories(); + WebloggerFactory.getWeblogger().getWeblogManager() + .saveWeblog(this.getActionWeblog()); + + // flush changes + WebloggerFactory.getWeblogger().flush(); + WebloggerFactory.getWeblogger().release(); + addMessage("mediaFile.deleteFolder.success"); + + // notify caches + CacheManager.invalidate(getActionWeblog()); + + // re-route to default folder + mediaFileDir = manager + .getDefaultMediaFileDirectory(getActionWeblog()); + setDirectoryId(mediaFileDir.getId()); + setDirectoryName(mediaFileDir.getName()); + } else { + log.error("(System error) No directory ID provided for media file folder delete."); + } + } catch (WebloggerException ex) { + log.error("Error deleting folder", ex); + } + return execute(); + } + + /** + * Include selected media file in gallery + * + */ + public String includeInGallery() { + doIncludeMediaFileInGallery(); + return execute(); + } + + /** + * Move selected media files to a different directory + * + */ + public String moveSelected() { + doMoveSelected(); + return execute(); + } + + public String getDirectoryId() { + return directoryId; + } + + @StrutsParameter + public void setDirectoryId(String id) { + this.directoryId = id; + } + + public List getChildFiles() { + return childFiles; + } + + public void setChildFiles(List files) { + this.childFiles = files; + } + + public String getNewDirectoryName() { + return newDirectoryName; + } + + @StrutsParameter + public void setNewDirectoryName(String newDirectoryName) { + this.newDirectoryName = newDirectoryName; + } + + public MediaFileDirectory getCurrentDirectory() { + return currentDirectory; + } + + public void setCurrentDirectory(MediaFileDirectory currentDirectory) { + this.currentDirectory = currentDirectory; + } + + public String getDirectoryName() { + return directoryName; + } + + @StrutsParameter + public void setDirectoryName(String path) { + this.directoryName = path; + } + + public String getSortBy() { + return sortBy; + } + + @StrutsParameter + public void setSortBy(String sortBy) { + this.sortBy = sortBy; + } + + /** + * Validates search input + */ + public boolean myValidate() { + if (StringUtils.isEmpty(bean.getName()) + && StringUtils.isEmpty(bean.getTags()) + && StringUtils.isEmpty(bean.getType()) && bean.getSize() == 0) { + addError("MediaFile.error.search.empty"); + return false; + } + return true; + } + + @StrutsParameter(depth = 1) + public MediaFileSearchBean getBean() { + return bean; + } + + public void setBean(MediaFileSearchBean b) { + this.bean = b; + } + + public List getFileTypes() { + return FILE_TYPES; + } + + public List getSizeFilterTypes() { + return SIZE_FILTER_TYPES; + } + + public List getSizeUnits() { + return SIZE_UNITS; + } + + public List getSortOptions() { + return SORT_OPTIONS; + } + + public MediaFilePager getPager() { + return pager; + } + + public void setPager(MediaFilePager pager) { + this.pager = pager; + } + + public String getNewDirectoryPath() { + return newDirectoryPath; + } + + public void setNewDirectoryPath(String newDirectoryPath) { + this.newDirectoryPath = newDirectoryPath; + } + + public String getViewDirectoryId() { + return viewDirectoryId; + } + + @StrutsParameter + public void setViewDirectoryId(String viewDirectoryId) { + this.viewDirectoryId = viewDirectoryId; + } + +} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java index 7f1b0de42..629c3cba4 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/CommentsPager.java @@ -18,79 +18,17 @@ package org.apache.roller.weblogger.ui.struts2.pagers; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.roller.weblogger.pojos.WeblogEntryComment; -import org.apache.roller.weblogger.util.URLUtilities; /** * Paging through a collection of comments. */ -public class CommentsPager { - - // the collection for the pager - private final List items; - - // base url for the pager - private final String baseUrl; - - // what page we are on - private final int pageNum; - - // are there more items? - private final boolean moreItems; - - - public CommentsPager(String url, int page, List comments, boolean hasMore) { - this.baseUrl = url; - this.pageNum = page; - this.items = comments; - this.moreItems = hasMore; - } - - - public String getNextLink() { - if(isMoreItems()) { - int nextPage = pageNum + 1; - Map params = new HashMap<>(); - params.put("bean.page", ""+nextPage); - return createURL(baseUrl, params); - } - return null; - } - - - public String getPrevLink() { - if (pageNum > 0) { - int prevPage = pageNum - 1; - Map params = new HashMap<>(); - params.put("bean.page", ""+prevPage); - return createURL(baseUrl, params); - } - return null; - } - - - private String createURL(String base, Map params) { - String qString = URLUtilities.getQueryString(params); - - if(base.indexOf('?') != -1) { - // if base url already has params them just append our query string - return base + "&" + qString.substring(1); - } else { - return base + qString; - } - } - - - public List getItems() { - return items; - } +public class CommentsPager extends LinkPager { - public boolean isMoreItems() { - return moreItems; + public CommentsPager(String url, int page, List comments, long totalCount, int pageSize) { + super(url, page, comments, totalCount, pageSize); } - + } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java index 58e1768c1..943473a9d 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/EntriesPager.java @@ -18,79 +18,17 @@ package org.apache.roller.weblogger.ui.struts2.pagers; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.roller.weblogger.pojos.WeblogEntry; -import org.apache.roller.weblogger.util.URLUtilities; /** * Paging through a collection of entries. */ -public class EntriesPager { - - // the collection for the pager - private final List items; - - // base url for the pager - private final String baseUrl; - - // what page we are on - private final int pageNum; - - // are there more items? - private final boolean moreItems; - - - public EntriesPager(String url, int page, List entries, boolean hasMore) { - this.baseUrl = url; - this.pageNum = page; - this.items = entries; - this.moreItems = hasMore; - } - - - public String getNextLink() { - if(isMoreItems()) { - int nextPage = pageNum + 1; - Map params = new HashMap<>(); - params.put("bean.page", ""+nextPage); - return createURL(baseUrl, params); - } - return null; - } - - - public String getPrevLink() { - if (pageNum > 0) { - int prevPage = pageNum - 1; - Map params = new HashMap<>(); - params.put("bean.page", ""+prevPage); - return createURL(baseUrl, params); - } - return null; - } - - - private String createURL(String base, Map params) { - String qString = URLUtilities.getQueryString(params); - - if(base.indexOf('?') != -1) { - // if base url already has params them just append our query string - return base + "&" + qString.substring(1); - } else { - return base + qString; - } - } - - - public List getItems() { - return items; - } +public class EntriesPager extends LinkPager { - public boolean isMoreItems() { - return moreItems; + public EntriesPager(String url, int page, List entries, long totalCount, int pageSize) { + super(url, page, entries, totalCount, pageSize); } - + } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java new file mode 100644 index 000000000..d064130e5 --- /dev/null +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPager.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ + +package org.apache.roller.weblogger.ui.struts2.pagers; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.roller.weblogger.util.URLUtilities; + + +/** + * Paging through query results with links: first, previous (newer), next (older) + * and last page. Page numbers are zero-based in links and one-based for display. + */ +public class LinkPager { + + // the collection for the pager + private final List items; + + // base url for the pager + private final String baseUrl; + + // what page we are on + private final int pageNum; + + // rows matching the query across all pages + private final long totalCount; + + private final int pageSize; + + + public LinkPager(String url, int page, List items, long totalCount, int pageSize) { + this.baseUrl = url; + this.pageNum = page; + this.items = items; + this.totalCount = totalCount; + this.pageSize = pageSize; + } + + + public String getFirstLink() { + return pageNum > 0 ? pageLink(0) : null; + } + + public String getPrevLink() { + return pageNum > 0 ? pageLink(pageNum - 1) : null; + } + + public String getNextLink() { + return isMoreItems() ? pageLink(pageNum + 1) : null; + } + + public String getLastLink() { + return isMoreItems() ? pageLink(getPageCount() - 1) : null; + } + + + private String pageLink(int page) { + Map params = new HashMap<>(); + params.put("bean.page", ""+page); + String qString = URLUtilities.getQueryString(params); + + if(baseUrl.indexOf('?') != -1) { + // if base url already has params them just append our query string + return baseUrl + "&" + qString.substring(1); + } else { + return baseUrl + qString; + } + } + + + public List getItems() { + return items; + } + + public boolean isMoreItems() { + return (long) (pageNum + 1) * pageSize < totalCount; + } + + public long getTotalCount() { + return totalCount; + } + + /** One-based page number, for display. */ + public int getPageNumber() { + return pageNum + 1; + } + + /** Number of pages; at least 1, so an empty result reads "Page 1 of 1". */ + public int getPageCount() { + return (int) Math.max(1, (totalCount + pageSize - 1) / pageSize); + } + +} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java index de328eb54..ecd3aeee7 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/pagers/MediaFilePager.java @@ -1,80 +1,77 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. The ASF licenses this file to You - * under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. For additional information regarding - * copyright in this work, please see the NOTICE file in the top level - * directory of this distribution. - */ - -package org.apache.roller.weblogger.ui.struts2.pagers; - -import java.util.List; - -import org.apache.roller.weblogger.pojos.MediaFile; - -/** - * A pager implementation for a collection of media files. - * - */ -public class MediaFilePager { - - // the collection for the pager - private final List items; - - // What page we are on - private final int pageNum; - - // Are there more items? - private final boolean moreItems; - - public MediaFilePager(int page, List mediaFiles, boolean hasMore) { - this.pageNum = page; - this.items = mediaFiles; - this.moreItems = hasMore; - } - - public List getItems() { - return items; - } - - public boolean isMoreItems() { - return moreItems; - } - - /** - * Indicates whether there are more than one page of item. - * - */ - public boolean isJustOnePage() { - return (pageNum == 0 && !moreItems); - } - - /** - * Has previous page? - * - */ - public boolean hasPrevious() { - return (pageNum > 0); - } - - /** - * Has next page? - * - */ - public boolean hasNext() { - return this.moreItems; - } - - - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ + +package org.apache.roller.weblogger.ui.struts2.pagers; + +import java.util.List; + +import org.apache.roller.weblogger.pojos.MediaFile; + +/** + * A pager implementation for a collection of media files. Media file search + * is a POSTed form, so instead of links this exposes page numbers that the + * page resubmits the search form with. + */ +public class MediaFilePager { + + // the collection for the pager + private final List items; + + // What page we are on + private final int pageNum; + + // media files matching the search across all pages + private final long totalCount; + + private final int pageSize; + + public MediaFilePager(int page, List mediaFiles, long totalCount, int pageSize) { + this.pageNum = page; + this.items = mediaFiles; + this.totalCount = totalCount; + this.pageSize = pageSize; + } + + public List getItems() { + return items; + } + + public boolean isMoreItems() { + return (long) (pageNum + 1) * pageSize < totalCount; + } + + public long getTotalCount() { + return totalCount; + } + + /** Zero-based page number, as the search form submits it. */ + public int getPageNum() { + return pageNum; + } + + /** One-based page number, for display. */ + public int getPageNumber() { + return pageNum + 1; + } + + /** Number of pages; at least 1, so an empty result reads "Page 1 of 1". */ + public int getPageCount() { + return (int) Math.max(1, (totalCount + pageSize - 1) / pageSize); + } + +} diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties index 598bd4c61..38ca7a7d7 100644 --- a/app/src/main/resources/ApplicationResources.properties +++ b/app/src/main/resources/ApplicationResources.properties @@ -43,6 +43,14 @@ generic.toggle=Toggle generic.success=Success generic.looksGood=Looks good! generic.poweredBy=Powered by Apache Roller +generic.first=First +generic.newer=Newer +generic.older=Older +generic.last=Last +generic.pageNumber=Page {0} of {1} +generic.pagination=Pagination +generic.chooseDate=Choose date +generic.clearDate=Clear date # ------------------------------------------------------------- BookmarkForm.jsp diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp index ad2240cc0..718c63ffb 100644 --- a/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp +++ b/app/src/main/webapp/WEB-INF/jsps/editor/Comments.jsp @@ -84,46 +84,32 @@
-
- - - -
-
- - - - - - --- - - - +
+
+ + - +
+
+ + + + + + --- + + + + + +
-
<%-- ============================================================= --%> <%-- Next / previous links --%> - +
<%-- class="tablenav" --%> @@ -143,231 +129,218 @@

- +
+
- <%-- ======================================================== --%> - <%-- Comment table header --%> + <%-- ======================================================== --%> + <%-- Comment table header --%> - - + + + + - - - - - - - <%-- ======================================================== --%> - <%-- Select ALL and NONE buttons --%> - - - - + + -
- - - - - - - - - - <%-- ========================================================= --%> - <%-- Loop through comments --%> - - <%-- it is a bit funky to use checkbox list here, but using checkbox didn't work for me :( - we are effectively just creating a checkbox list of 1 item for each iteration of our collection. --%> - - - + <%-- ======================================================== --%> + <%-- Select ALL and NONE buttons --%> + + - <%-- only blog admins (not the global admin) can approve blog comments --%> - - - + + - <%-- ======================================================== --%> - <%-- Display comment details and text --%> - <%-- + + <%-- only blog admins (not the global admin) can approve blog comments --%> + - - +
+ + - + - - - - - -
-
+
+ + + +
-
- -
- -
-
- -
- -
-
-      -    -      -    -
- + +
+ +
+
- + +
+ +
+
- + +
+ +
+
+
+      +    +      +    +
with style if comment is spam or pending --%> - - + <%-- ========================================================= --%> + <%-- Loop through comments --%> + + <%-- it is a bit funky to use checkbox list here, but using checkbox didn't work for me :( + we are effectively just creating a checkbox list of 1 item for each iteration of our collection. --%> + + +
+ + - - - - - - <%-- comment details table in table --%> - - - + -
-  :  - - - - - - - - - - - - - - - - - - - -
+ <%-- ======================================================== --%> + <%-- Display comment details and text --%> + + <%-- - - -
- -
- -
-  :  - "> - -
+
+ + + + with style if comment is spam or pending --%> + + + + + + + + + + + <%-- comment details table in table --%> + + + -
+ +
- -
-  :  - "> - +  :  + "> +
-
- +
-  :  - +  :  + + + + + + + + + + + + + + + + + + +
-
- -
-  :  - -
- -
-
- -
- -
"> - - - -
- + +
+  :  + +
+
- -
- " - style="display: none"> -  |  - - " - style="display: none"> - - + +
+
+ +
+ + +
"> + + + +
+ +
+ + "> +
- -
-
- <%-- end comment details table in table --%> -
+ + +
+ " + style="display: none"> +  |  + + " + style="display: none"> + + +
+
+ + +
+ <%-- end comment details table in table --%> + + + + +
<%-- ============================================================= --%> <%-- Next / previous links --%> - + <%-- ========================================================= --%> <%-- Save changes and cancel buttons --%> diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp index 7e8cf2f74..50a10c1a8 100644 --- a/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp +++ b/app/src/main/webapp/WEB-INF/jsps/editor/CommentsSidebar.jsp @@ -29,7 +29,7 @@

- + @@ -39,24 +39,11 @@ - - <%-- ========================================================= --%> <%-- filter by date --%> - -
-
- -
-
-
- <%-- ========================================================= --%> <%-- filter by status--%> diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Entries.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Entries.jsp index 3e2fc8d26..42935802a 100644 --- a/app/src/main/webapp/WEB-INF/jsps/editor/Entries.jsp +++ b/app/src/main/webapp/WEB-INF/jsps/editor/Entries.jsp @@ -30,22 +30,7 @@ <%-- ============================================================= --%> <%-- Next / previous links --%> - + <%-- ============================================================= --%> @@ -60,120 +45,107 @@   

- - - - - - - - - - - - - <%-- - - - - - - - - - - - - - -
- - - - - - - -
with style if comment is spam or pending --%> - -
- - - - - - - - - - - - - +
+ + + + + + + + + + + + + <%-- - + + + + + + + + + + + + + - + - + + - - - + + + - - + + -
+ + + + + + + +
with style if comment is spam or pending --%> + +
+ + + + + + + + + + + + + + + - - - - - - + + + + + + - - + + + + + + + - - - - - - - - - - - " - data-post-title=""> - - - - + + + + + " + data-post-title=""> + + + +
+
+ <%-- ============================================================= --%> <%-- Next / previous links --%> - + diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp index e58527d92..ae3b9bab6 100644 --- a/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp +++ b/app/src/main/webapp/WEB-INF/jsps/editor/EntriesSidebar.jsp @@ -22,7 +22,7 @@

- + @@ -49,7 +49,7 @@ <%-- filter by date --%>
-
-
-
- <%-- ========================================================= --%> <%-- filter by status --%> @@ -106,12 +114,4 @@
- diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp index 59b6cd858..e93b76ded 100644 --- a/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp +++ b/app/src/main/webapp/WEB-INF/jsps/editor/EntryEdit.jsp @@ -150,7 +150,7 @@ -
+
<%-- Weblog editor --%> @@ -160,17 +160,16 @@ -
-
- -

- -

- -
-
-
+
+

+ +

+
+
@@ -182,18 +181,16 @@ <%-- Advanced settings --%> -
-
- -

- -

- -
-
-
+
+

+ +

+
+
@@ -213,9 +210,10 @@
- +
@@ -257,43 +255,45 @@ <%-- ================================================================== --%> <%-- The button box --%> - <%-- save draft --%> - +
- + <%-- save draft --%> + - <%-- preview mode --%> - - - + - <%-- publish --%> - - - + <%-- preview mode --%> + + + - <%-- submit for review --%> - - + <%-- publish --%> + + + - + <%-- submit for review --%> + + + + - <%-- delete --%> - - + " data-post-title=""> - - + + +
@@ -363,10 +363,6 @@ + diff --git a/app/src/main/webapp/roller-ui/scripts/date-fields.js b/app/src/main/webapp/roller-ui/scripts/date-fields.js new file mode 100644 index 000000000..fd4e01a5e --- /dev/null +++ b/app/src/main/webapp/roller-ui/scripts/date-fields.js @@ -0,0 +1,35 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. The ASF licenses this file to You +* under the Apache License, Version 2.0 (the "License"); you may not +* use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. For additional information regarding +* copyright in this work, please see the NOTICE file in the top level +* directory of this distribution. +*/ + + +/* + * Date fields in the admin UI: a readonly input.date-picker inside an .input-group, + * optionally followed by a .date-picker-show button (opens the picker) and a + * .date-picker-clear button (empties the field, so a filter date can be removed). + */ +$(function () { + $(".date-picker").datepicker(); + + $(document).on("click", ".date-picker-show", function () { + $(this).closest(".input-group").find(".date-picker").datepicker("show"); + }); + + $(document).on("click", ".date-picker-clear", function () { + $(this).closest(".input-group").find(".date-picker").val("").trigger("change"); + }); +}); diff --git a/app/src/main/webapp/roller-ui/styles/roller.css b/app/src/main/webapp/roller-ui/styles/roller.css index b8dea7b99..8e4bde61d 100644 --- a/app/src/main/webapp/roller-ui/styles/roller.css +++ b/app/src/main/webapp/roller-ui/styles/roller.css @@ -30,9 +30,31 @@ p { margin: 1em; } +/* Bootstrap 5 sizes everything for a 16px root, which reads large in this + dense admin UI; 15px scales text, controls and spacing down together. */ +html { + font-size: 15px; +} + .roller-page-title { margin-top: 0px; margin-bottom: 5px; + font-size: 1.75rem; +} + +/* Section headings in sidebar cards */ +.roller-column-left h3 { + font-size: 1.25rem; +} + +.roller-column-left h4 { + font-size: 1.1rem; +} + +/* Cards stacked in a column (sidebar cards, setup steps, upload results) + would otherwise touch */ +.card + .card { + margin-top: 1rem; } .roller-column-right { @@ -82,20 +104,34 @@ table.mm_table_actions td { /* edit entry page */ -/* Only the panel collapse toggles, not links in other toolbars that use - .panel-heading, such as the rich text editor's menus */ -.panel-heading a[data-toggle="collapse"]:after { - font-family:'Glyphicons Halflings'; - content:"\e114"; - float: right; - color: grey; +/* Panel titles at the size of a section label, not a page heading */ +.accordion-button { + font-size: 1.1rem; + font-weight: 500; } -.panel-heading a[data-toggle="collapse"].collapsed:after { - content:"\e080"; + +/* entries and comments pages */ + +/* Sidebar filter forms: the bootstrap theme adds no spacing to form-vertical groups */ +.roller-filter-form > div { + margin-bottom: 1rem; } -#panel-plugins { /* Kludge for EntryEditor.jsp */ - margin-top: 6px; /* Without this there no space between Summary and Plugins to Apply panels */ +.roller-filter-form input[type="submit"] { + margin-top: 0.5rem; +} + +.roller-pager { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + margin: 1rem 0; +} + +.roller-pager-page { + color: var(--bs-secondary-color); + font-size: 0.875rem; } /* keep jQuery UI datepicker in front of other elements */ @@ -165,6 +201,8 @@ table.innertable tbody td { } table.innertable td.viewbody { padding: 5px; + /* spam often holds long unbroken URLs; let them wrap so the table fits the page */ + overflow-wrap: anywhere; } table.innertable tr .bot { margin-bottom: 5px; @@ -357,6 +395,12 @@ tr.rollertable_pending { opacity: 1; } +/* Date fields are readonly only so the picker sets them; they are not locked */ +.form-control.date-picker[readonly] { + background-color: var(--bs-body-bg); + cursor: pointer; +} + /* Field hint icons sit next to the label rather than in the text flow. */ .s2b_tooltip { margin-left: .35rem; diff --git a/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java b/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java index 075c1f141..df55fd99b 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/CommentTest.java @@ -30,6 +30,7 @@ import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -85,6 +86,7 @@ public void tearDown() throws Exception { /** * Test basic persistence operations ... Create, Update, Delete */ + @Test public void testCommentCRUD() throws Exception { WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); @@ -132,6 +134,7 @@ public void testCommentCRUD() throws Exception { /** * Test lookup mechanisms ... */ + @Test public void testCommentLookups() throws Exception { WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); @@ -149,6 +152,7 @@ public void testCommentLookups() throws Exception { comments = mgr.getComments(csc); assertNotNull(comments); assertEquals(3, comments.size()); + assertEquals(3, mgr.getCommentCount(csc)); // get all comments for entry testEntry = TestUtils.getManagedWeblogEntry(testEntry); @@ -169,6 +173,7 @@ public void testCommentLookups() throws Exception { comments = mgr.getComments(csc); assertNotNull(comments); assertEquals(1, comments.size()); + assertEquals(1, mgr.getCommentCount(csc)); // get approved comments csc.setStatus(ApprovalStatus.APPROVED); @@ -182,6 +187,8 @@ public void testCommentLookups() throws Exception { comments = mgr.getComments(csc); assertNotNull(comments); assertEquals(2, comments.size()); + // the count ignores offset: it sizes the pager across all pages + assertEquals(3, mgr.getCommentCount(csc)); // remove test comments TestUtils.teardownComment(comment1.getId()); @@ -197,6 +204,7 @@ public void testCommentLookups() throws Exception { * should delete all comments on that entry, and deleting a weblog should * delete all comments, etc. */ + @Test public void testCommentParentDeletes() throws Exception { log.info("BEGIN"); diff --git a/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java b/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java index 7e8929574..32515d5e2 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/MediaFileTest.java @@ -505,6 +505,7 @@ public void testSearchMediaFile() throws Exception { filter1.setName("mytest.jpg"); searchResults = mfMgr.searchMediaFiles(testWeblog, filter1); assertTrue(searchResults.isEmpty()); + assertEquals(0, mfMgr.getMediaFileCount(testWeblog, filter1)); MediaFileFilter filter2 = new MediaFileFilter(); filter2.setName("test_home.jpg"); @@ -542,6 +543,7 @@ public void testSearchMediaFile() throws Exception { searchResults = mfMgr.searchMediaFiles(testWeblog, filter4); assertFalse(searchResults.isEmpty()); assertEquals(1, searchResults.size()); + assertEquals(1, mfMgr.getMediaFileCount(testWeblog, filter4)); assertEquals("test_work.jpg", searchResults.get(0).getName()); // search by size @@ -576,6 +578,7 @@ public void testSearchMediaFile() throws Exception { searchResults = mfMgr.searchMediaFiles(testWeblog, filter9); assertFalse(searchResults.isEmpty()); assertEquals(2, searchResults.size()); + assertEquals(2, mfMgr.getMediaFileCount(testWeblog, filter9)); MediaFileFilter filter10 = new MediaFileFilter(); filter10.setSize(3000); @@ -598,6 +601,7 @@ public void testSearchMediaFile() throws Exception { searchResults = mfMgr.searchMediaFiles(testWeblog, filter12); assertFalse(searchResults.isEmpty()); assertEquals(2, searchResults.size()); + assertEquals(2, mfMgr.getMediaFileCount(testWeblog, filter12)); } finally { TestUtils.endSession(true); @@ -668,6 +672,8 @@ public void testSearchMediaFilePaging() throws Exception { assertFalse(searchResults2.isEmpty()); assertEquals(3, searchResults2.size()); assertEquals("test_file13.jpg", searchResults2.get(0).getName()); + // the count ignores start index and length: it sizes the pager across all pages + assertEquals(15, mfMgr.getMediaFileCount(testWeblog, filter2)); MediaFileFilter filter3 = new MediaFileFilter(); filter3.setSize(1000); diff --git a/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java b/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java index b9398d926..04837304a 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/WeblogEntryTest.java @@ -209,6 +209,7 @@ public void testWeblogEntryLookups() throws Exception { assertNotNull(entries); assertEquals(5, entries.size()); assertEquals(entry3, entries.get(0)); + assertEquals(5, mgr.getWeblogEntryCount(wesc)); // get all (non-future) PUBLISHED entries in category WeblogEntrySearchCriteria wesc9 = new WeblogEntrySearchCriteria(); @@ -226,6 +227,7 @@ public void testWeblogEntryLookups() throws Exception { entries = mgr.getWeblogEntries(wesc2); assertNotNull(entries); assertEquals(3, entries.size()); + assertEquals(3, mgr.getWeblogEntryCount(wesc2)); // get all entries in date range WeblogEntrySearchCriteria wesc3 = new WeblogEntrySearchCriteria(); @@ -244,6 +246,8 @@ public void testWeblogEntryLookups() throws Exception { entries = mgr.getWeblogEntries(wesc4); assertNotNull(entries); assertEquals(2, entries.size()); + // the count ignores maxResults: it sizes the pager across all pages + assertEquals(5, mgr.getWeblogEntryCount(wesc4)); // get all entries in category WeblogEntrySearchCriteria wesc5 = new WeblogEntrySearchCriteria(); @@ -262,6 +266,7 @@ public void testWeblogEntryLookups() throws Exception { assertNotNull(entries); assertEquals(1, entries.size()); assertEquals(entry2, entries.get(0)); + assertEquals(5, mgr.getWeblogEntryCount(wesc6)); // get all entries, limited by locale WeblogEntrySearchCriteria wesc7 = new WeblogEntrySearchCriteria(); @@ -271,6 +276,7 @@ public void testWeblogEntryLookups() throws Exception { assertNotNull(entries); assertEquals(4, entries.size()); assertEquals(entry3, entries.get(0)); + assertEquals(4, mgr.getWeblogEntryCount(wesc7)); // get pinned entries only entries = mgr.getWeblogEntriesPinnedToMain(5); @@ -575,6 +581,39 @@ public void testGetEntriesByTag() throws Exception { } + /** + * The entry count must match the rows getWeblogEntries() returns, even when a + * multi-tag search joins an entry once per matching tag, or the pager's last page is wrong. + */ + @Test + public void testWeblogEntryCountWithMultipleTags() throws Exception { + + WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); + + testWeblog = TestUtils.getManagedWebsite(testWeblog); + testUser = TestUtils.getManagedUser(testUser); + WeblogEntry entry1 = TestUtils.setupWeblogEntry("entry1", testWeblog, testUser); + entry1.addTag("counttagone"); + entry1.addTag("counttagtwo"); + mgr.saveWeblogEntry(entry1); + WeblogEntry entry2 = TestUtils.setupWeblogEntry("entry2", testWeblog, testUser); + entry2.addTag("counttagone"); + mgr.saveWeblogEntry(entry2); + TestUtils.endSession(true); + + testWeblog = TestUtils.getManagedWebsite(testWeblog); + WeblogEntrySearchCriteria wesc = new WeblogEntrySearchCriteria(); + wesc.setWeblog(testWeblog); + wesc.setTags(Arrays.asList("counttagone", "counttagtwo")); + List results = mgr.getWeblogEntries(wesc); + assertEquals(results.size(), mgr.getWeblogEntryCount(wesc)); + + TestUtils.teardownWeblogEntry(entry1.getId()); + TestUtils.teardownWeblogEntry(entry2.getId()); + TestUtils.endSession(true); + } + + @Test public void testRemoveEntryTagCascading() throws Exception { diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java index 02d9fed20..5528aa903 100644 --- a/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/ui/EntryEditorAssetsTest.java @@ -63,15 +63,24 @@ public void summernoteClosesItsIconElements() throws IOException { @Test public void panelChevronIsOnlyOnCollapseToggles() throws IOException { + // The entry editor's panels are Bootstrap 5 accordions, which draw their own + // chevron on .accordion-button. A roller.css chevron on ".panel-heading a" + // also lands on the rich text editor's menu items, so none may come back. String css = read(WEBAPP.resolve("roller-ui/styles/roller.css")); - Matcher rule = Pattern.compile("([^{}]*\\.panel-heading\\s+a[^{}]*:after)\\s*\\{").matcher(css); - boolean found = false; - while (rule.find()) { - found = true; - assertTrue(rule.group(1).contains("[data-toggle=\"collapse\"]"), - "rule must be limited to collapse toggles: " + rule.group(1).trim()); + assertFalse(Pattern.compile("\\.panel-heading[^{}]*:after").matcher(css).find(), + "roller.css must not add a chevron to .panel-heading links"); + + for (String jsp : new String[] {"EntryEdit.jsp", "EntryEditor.jsp"}) { + String page = read(WEBAPP.resolve("WEB-INF/jsps/editor/" + jsp)); + Matcher toggle = Pattern.compile("<[^>]*data-bs-toggle=\"collapse\"[^>]*>").matcher(page); + boolean found = false; + while (toggle.find()) { + found = true; + assertTrue(toggle.group().contains("accordion-button"), + jsp + " collapse toggle must be an accordion button: " + toggle.group()); + } + assertTrue(found, jsp + " has collapsible panels"); } - assertTrue(found, "roller.css still styles the panel collapse toggles"); } private static String read(Path file) throws IOException { diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java new file mode 100644 index 000000000..01a0d2c49 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/pagers/LinkPagerTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ + +package org.apache.roller.weblogger.ui.struts2.pagers; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class LinkPagerTest { + + private static final String URL = "/roller-ui/authoring/entries.rol?weblog=roller"; + + private static LinkPager pager(int page, long total) { + return new LinkPager<>(URL, page, Collections.emptyList(), total, 30); + } + + @Test + public void testEmptyResultIsOnePage() { + LinkPager p = pager(0, 0); + assertEquals(1, p.getPageNumber()); + assertEquals(1, p.getPageCount()); + assertFalse(p.isMoreItems()); + assertNull(p.getFirstLink()); + assertNull(p.getPrevLink()); + assertNull(p.getNextLink()); + assertNull(p.getLastLink()); + } + + @Test + public void testFirstPage() { + LinkPager p = pager(0, 65); + assertEquals(1, p.getPageNumber()); + assertEquals(3, p.getPageCount()); + assertTrue(p.isMoreItems()); + assertNull(p.getFirstLink()); + assertNull(p.getPrevLink()); + assertEquals(URL + "&bean.page=1", p.getNextLink()); + assertEquals(URL + "&bean.page=2", p.getLastLink()); + } + + @Test + public void testMiddlePage() { + LinkPager p = pager(1, 65); + assertEquals(2, p.getPageNumber()); + assertEquals(URL + "&bean.page=0", p.getFirstLink()); + assertEquals(URL + "&bean.page=0", p.getPrevLink()); + assertEquals(URL + "&bean.page=2", p.getNextLink()); + assertEquals(URL + "&bean.page=2", p.getLastLink()); + } + + @Test + public void testLastPage() { + LinkPager p = pager(2, 65); + assertEquals(3, p.getPageNumber()); + assertFalse(p.isMoreItems()); + assertEquals(URL + "&bean.page=0", p.getFirstLink()); + assertEquals(URL + "&bean.page=1", p.getPrevLink()); + assertNull(p.getNextLink()); + assertNull(p.getLastLink()); + } + + @Test + public void testExactMultipleOfPageSize() { + // 60 rows is exactly two pages, not three + assertEquals(2, pager(0, 60).getPageCount()); + assertFalse(pager(1, 60).isMoreItems()); + assertEquals(URL + "&bean.page=1", pager(0, 60).getLastLink()); + } + + @Test + public void testBaseUrlWithoutQueryString() { + LinkPager p = new LinkPager<>("/entries.rol", 0, Collections.emptyList(), 31, 30); + assertEquals("/entries.rol?bean.page=1", p.getNextLink()); + } + +} diff --git a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java index 158f70f20..05859f275 100644 --- a/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java +++ b/it-playwright/src/test/java/org/apache/roller/playwright/BaseIT.java @@ -349,11 +349,16 @@ protected void insertMediaFileThroughDialog(String weblogHandle) { * stacked form rows. */ protected void assertPublishingTimeOnOneLine() { - page.locator("a[href='#collapseAdvanced']").click(); - assertThat(page.locator("#collapseAdvanced")).isVisible(); - double hours = page.locator("select[name='bean.hours']").boundingBox().y; - double minutes = page.locator("select[name='bean.minutes']").boundingBox().y; - double seconds = page.locator("select[name='bean.seconds']").boundingBox().y; + page.locator("[data-bs-target='#collapseAdvanced']").click(); + // Bootstrap adds "show" only when the collapse transition ends; read + // all three positions at once so the layout cannot move between reads + assertThat(page.locator("#collapseAdvanced.show:not(.collapsing)")).isVisible(); + @SuppressWarnings("unchecked") + var ys = (java.util.List) page.evaluate("() => ['hours', 'minutes', 'seconds'].map(f =>" + + " document.querySelector(`select[name='bean.${f}']`).getBoundingClientRect().y)"); + double hours = ys.get(0).doubleValue(); + double minutes = ys.get(1).doubleValue(); + double seconds = ys.get(2).doubleValue(); org.junit.jupiter.api.Assertions.assertTrue( Math.abs(hours - minutes) < 2 && Math.abs(minutes - seconds) < 2, "publishing time selects should share a line, but sit at y=" + hours + ", " + minutes + ", " + seconds);