/********************************************************************************** * $URL: $ * $Id: $ *********************************************************************************** * * Author: Charles Hedrick, hedrick@rutgers.edu * * Copyright (c) 2010 Rutgers, the State University of New Jersey * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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. * **********************************************************************************/ package org.sakaiproject.lessonbuildertool.tool.producers; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Map; import java.util.HashSet; import java.util.HashMap; import java.util.Date; import org.sakaiproject.lessonbuildertool.service.LessonEntity; import org.sakaiproject.lessonbuildertool.SimplePage; import org.sakaiproject.lessonbuildertool.SimplePageItem; import org.sakaiproject.lessonbuildertool.SimplePageLogEntry; import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean; import org.sakaiproject.lessonbuildertool.tool.view.GeneralViewParameters; import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.content.cover.ContentTypeImageService; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.lessonbuildertool.service.LessonsAccess; import uk.org.ponder.messageutil.MessageLocator; import uk.org.ponder.localeutil.LocaleGetter; import uk.org.ponder.rsf.components.UIBoundBoolean; import uk.org.ponder.rsf.components.UIBranchContainer; import uk.org.ponder.rsf.components.UICommand; import uk.org.ponder.rsf.components.UIContainer; import uk.org.ponder.rsf.components.UIForm; import uk.org.ponder.rsf.components.UILink; import uk.org.ponder.rsf.components.UIInternalLink; import uk.org.ponder.rsf.components.UIOutput; import uk.org.ponder.rsf.components.UIInput; import uk.org.ponder.rsf.components.UISelect; import uk.org.ponder.rsf.components.UISelectChoice; import uk.org.ponder.rsf.components.decorators.UIFreeAttributeDecorator; import uk.org.ponder.rsf.components.decorators.UITooltipDecorator; import uk.org.ponder.rsf.flow.jsfnav.NavigationCase; import uk.org.ponder.rsf.flow.jsfnav.NavigationCaseReporter; import uk.org.ponder.rsf.view.ComponentChecker; import uk.org.ponder.rsf.view.ViewComponentProducer; import uk.org.ponder.rsf.viewstate.SimpleViewParameters; import uk.org.ponder.rsf.viewstate.ViewParameters; import uk.org.ponder.rsf.viewstate.ViewParamsReporter; import uk.org.ponder.rsf.util.RSFUtil; /** * Creates a window for the user to choose which page to add * * @author Charles Hedrick <hedrick@rutgers.edu> * */ public class PagePickerProducer implements ViewComponentProducer, NavigationCaseReporter, ViewParamsReporter { public static final String VIEW_ID = "PagePicker"; private SimplePageBean simplePageBean; private SimplePageToolDao simplePageToolDao; private LessonsAccess lessonsAccess; private ToolManager toolManager; public MessageLocator messageLocator; public LocaleGetter localeGetter; private boolean somePagesHavePrerequisites = false; private long currentPageId = -1; private Map<String,String> imageToMimeMap; public void setImageToMimeMap(Map<String,String> map) { this.imageToMimeMap = map; } public boolean useSakaiIcons = ServerConfigurationService.getBoolean("lessonbuilder.use-sakai-icons", false); public boolean enableShowItems = ServerConfigurationService.getBoolean("lessonbuilder.enable-show-items", true); public class PageEntry { Long pageId; Long itemId; String title; int level; boolean toplevel; } public void setSimplePageBean(SimplePageBean simplePageBean) { this.simplePageBean = simplePageBean; } public void setSimplePageToolDao(Object dao) { simplePageToolDao = (SimplePageToolDao) dao; } public void setLessonsAccess(LessonsAccess l) { lessonsAccess = l; } public void setToolManager(ToolManager service) { toolManager = service; } public String getViewID() { return VIEW_ID; } // this is complicated. We're trying to produce a list of all pages in a reasonable order. // we start with a list of top level pages and traverse it recursively. But if there's a reference // Data: // entries - the list we're building to display // pageMap - a map that starts out having all pages in the site, we remove entries as we show them // that lets us find at the end anything that hasn't been shown public void findAllPages(SimplePageItem pageItem, List<PageEntry>entries, Map<Long,SimplePage> pageMap, Set<Long>topLevelPages, Set<Long>sharedPages, int level, boolean toplevel) { // System.out.println("in findallpages " + pageItem.getName() + " " + toplevel); Long pageId = Long.valueOf(pageItem.getSakaiId()); if (pageId == 0L) return; try { if (pageItem.isPrerequisite() || simplePageBean.getItemGroups(pageItem, null, false) != null) somePagesHavePrerequisites = true; } catch (IdUnusedException exe) { // underlying item missing. should be impossible for a page return; } // no need to check this if flag already set if (! somePagesHavePrerequisites) { SimplePage page = simplePageToolDao.getPage(pageId); if (page.isHidden()) somePagesHavePrerequisites = true; if (page.getReleaseDate() != null && page.getReleaseDate().after(new Date())) somePagesHavePrerequisites = true; } PageEntry entry = new PageEntry(); entry.pageId = pageId; entry.itemId = pageItem.getId(); entry.title = pageItem.getName(); entry.level = level; entry.toplevel = toplevel; // add entry entries.add(entry); // if page has already been done, don't do the subpages. Otherwise we can // get into infinite loops // already done if removed from map. // however for top level pages, expand them for their primary entry, // i.e. when toplevel is set. if (pageMap.get(pageId) == null || (topLevelPages.contains(pageId) && !toplevel)) { sharedPages.add(pageId); return; } // say done pageMap.remove(pageId); // now recursively do subpages List<SimplePageItem> items = simplePageToolDao.findItemsOnPage(pageId); List<SimplePageItem> nexts = new ArrayList<SimplePageItem>(); // subpages done in place for (SimplePageItem item: items) { if (item.getType() == SimplePageItem.PAGE) { Long pageNum = Long.valueOf(item.getSakaiId()); // ignore top-level pages. // show next pages (including top level pages) after all the subpages // so stick it on the delayed display list. if (item.getNextPage()) nexts.add(item); else { // System.out.println("call for subpage " + item.getName() + " " + false); findAllPages(item, entries, pageMap, topLevelPages, sharedPages, level +1, false); } } } // nexts done afterwards for (SimplePageItem item: nexts) { if (item.getType() == SimplePageItem.PAGE) { // System.out.println("calling findallpage " + item.getName() + " " + false); findAllPages(item, entries, pageMap, topLevelPages, sharedPages, level, false); } } } public boolean hasSubPages(Long pageId) { // now recursively do subpages List<SimplePageItem> items = simplePageToolDao.findItemsOnPage(pageId); // subpages done in place for (SimplePageItem item: items) { if (item.getType() == SimplePageItem.PAGE) { return true; } } return false; } public void fillComponents(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker) { if (((GeneralViewParameters) viewparams).getSendingPage() != -1) { // will fail if page not in this site // security then depends upon making sure that we only deal with this page try { simplePageBean.updatePageObject(((GeneralViewParameters) viewparams).getSendingPage()); } catch (Exception e) { System.out.println("PagePicker permission exception " + e); return; } } UIOutput.make(tofill, "html").decorate(new UIFreeAttributeDecorator("lang", localeGetter.get().getLanguage())) .decorate(new UIFreeAttributeDecorator("xml:lang", localeGetter.get().getLanguage())); boolean canEditPage = (simplePageBean.getEditPrivs() == 0); String source = ((GeneralViewParameters) viewparams).getSource(); // summaryPage is the "index of pages". It has status icons and links, but isn't a chooser // otherwise we have the chooser page for the "add subpage" command boolean summaryPage = "summary".equals(source); if (summaryPage) { GeneralViewParameters view = new GeneralViewParameters(ShowPageProducer.VIEW_ID); // path defaults to null, which is next UIOutput.make(tofill, "return-div"); UIInternalLink.make(tofill, "return", messageLocator.getMessage("simplepage.return"), view); UIOutput.make(tofill, "title", messageLocator.getMessage("simplepage.page.index")); } else { UIOutput.make(tofill, "title", messageLocator.getMessage("simplepage.page.chooser")); } // Explain which pages may be deleted if(summaryPage && canEditPage) { UIOutput.make(tofill, "deleteAlert"); } // this looks at pages in the site, which should be safe // but need to make sure the item we update is legal Long itemId = ((GeneralViewParameters) viewparams).getItemId(); simplePageBean.setItemId(itemId); SimplePage page = simplePageBean.getCurrentPage(); currentPageId = page.getPageId(); if (itemId != null && itemId != -1) { SimplePageItem currentItem = simplePageToolDao.findItem(itemId); if (currentItem == null) return; // trying to hack on item not on this page if (currentItem.getPageId() != page.getPageId()) return; } // list we're going to display List<PageEntry> entries = new ArrayList<PageEntry> (); // build map of all pages, so we can see if any are left over Map<Long,SimplePage> pageMap = new HashMap<Long,SimplePage>(); Set<Long> sharedPages = new HashSet<Long>(); // all pages List<SimplePage> pages = simplePageToolDao.getSitePages(simplePageBean.getCurrentSiteId()); for (SimplePage p: pages) pageMap.put(p.getPageId(), p); // set of all top level pages, actually the items pointing to them List<SimplePageItem> sitePages = simplePageToolDao.findItemsInSite(toolManager.getCurrentPlacement().getContext()); Set<Long> topLevelPages = new HashSet<Long>(); for (SimplePageItem i : sitePages) topLevelPages.add(Long.valueOf(i.getSakaiId())); // this adds everything you can find from top level pages to entries for (SimplePageItem sitePageItem : sitePages) { // System.out.println("findallpages " + sitePageItem.getName() + " " + true); findAllPages(sitePageItem, entries, pageMap, topLevelPages, sharedPages, 0, true); } // warn students if we aren't showing all the pages if (!canEditPage && somePagesHavePrerequisites) UIOutput.make(tofill, "onlyseen"); // now add everything we didn't find that way if (canEditPage && pageMap.size() > 0) { // marker PageEntry marker = new PageEntry(); marker.level = -1; entries.add(marker); for (SimplePage p: pageMap.values()) { if(p.getOwner() == null) { PageEntry entry = new PageEntry(); entry.pageId = p.getPageId(); entry.itemId = null; entry.title = p.getTitle(); entry.level = 0; // TopLevel determines if we can select the page. // Since this means that the page is detached, it isn't // a conflict to be able to select it. entry.toplevel = false; entries.add(entry); } } } if (canEditPage && sharedPages.size() > 0) UIOutput.make(tofill, "sharedpageexplanation"); UIForm form = UIForm.make(tofill, "page-picker"); ArrayList<String> values = new ArrayList<String>(); ArrayList<String> initValues = new ArrayList<String>(); for (PageEntry entry: entries) { if (entry.level >= 0) { values.add(entry.pageId.toString()); initValues.add(""); } } UISelect select = null; if (summaryPage) select = UISelect.makeMultiple(form, "page-span", values.toArray(new String[1]), "#{simplePageBean.selectedEntities}" , initValues.toArray(new String[1])); else select = UISelect.make(form, "page-span", values.toArray(new String[1]), "#{simplePageBean.selectedEntity}", null); int index = 0; boolean showDeleteButton = false; for (PageEntry entry: entries) { UIBranchContainer row = UIBranchContainer.make(form, "page:"); if (entry.toplevel) row.decorate(new UIFreeAttributeDecorator("style", "list-style-type:none; margin-top:0.5em")); if (entry.level < 0) UIOutput.make(row, "heading", messageLocator.getMessage("simplepage.chooser.unused")); // if no itemid, it's unused. Only canedit people will see it else if (summaryPage && entry.itemId != null) { int level = entry.level; if (level > 5) level = 5; String imagePath = "/lessonbuilder-tool/images/"; SimplePageItem item = simplePageBean.findItem(entry.itemId); SimplePageLogEntry logEntry = simplePageBean.getLogEntry(entry.itemId); String note = null; if (logEntry != null && logEntry.isComplete()) { imagePath += "checkmark.png"; note = messageLocator.getMessage("simplepage.status.completed"); } else if (logEntry != null && !logEntry.getDummy()) { imagePath += "hourglass.png"; note = messageLocator.getMessage("simplepage.status.inprogress"); } else if (!canEditPage && somePagesHavePrerequisites) { // it's too complex to compute prerequisites for all pages, and // I'm concerned that faculty may not be careful in setting them // for pages that would normally not be accessible. So if there are // any prerequisites in the site, only show pages that are // in progress or done. continue; } else { imagePath += "not-required.png"; } UIOutput.make(row, "status-image").decorate(new UIFreeAttributeDecorator("src", imagePath)); GeneralViewParameters p = new GeneralViewParameters(ShowPageProducer.VIEW_ID); p.setSendingPage(entry.pageId); p.setItemId(entry.itemId); // reset the path to the saved one p.setPath("log"); UIInternalLink.make(row, "link", p). decorate(new UIFreeAttributeDecorator("style", "padding-left: " + (2*level) + "em")); String levelstr = null; if (level > 0) levelstr = messageLocator.getMessage("simplepage.status.level").replace("{}", Integer.toString(level)); if (levelstr != null) { if (note != null) note = levelstr + " " + note; else note = levelstr; } if (note != null) UIOutput.make(row, "link-note", note + " "); UIOutput.make(row, "link-text", entry.title); if(enableShowItems) { UIOutput.make(row, "item-list-toggle"); UIOutput.make(row, "itemListContainer").decorate(new UIFreeAttributeDecorator("style", "margin-left: " + (3*level) + "em")); UIOutput.make(row, "itemList"); for(SimplePageItem pageItem : simplePageToolDao.findItemsOnPage(entry.pageId)) { UIBranchContainer itemListItem = UIBranchContainer.make(row, "item:"); if (pageItem.isRequired()) { UIOutput.make(itemListItem, "required-image"); } else { UIOutput.make(itemListItem, "not-required-image"); } UIOutput.make(itemListItem,"item-icon") .decorate(getImageSourceDecorator(pageItem)); if (pageItem.isPrerequisite()) { itemListItem.decorate(new UIFreeAttributeDecorator("class", "disabled-text-item")); } if(SimplePageItem.TEXT == pageItem.getType()) { UIOutput.make(itemListItem, "name", messageLocator.getMessage("simplepage.chooser.textitemplaceholder")) .decorate(new UIFreeAttributeDecorator("class", "text-item-placeholder")); } else { UIOutput.make(itemListItem, "name", pageItem.getName()); } } } index++; // for pagepicker or summary if canEdit and page doesn't have an item } else { int level = entry.level; if (level > 5) level = 5; if (!summaryPage /*&& !entry.toplevel*/) { // i.e. pagepicker; for the moment to edit something you need to attach it to something UISelectChoice.make(row, "select", select.getFullID(), index). decorate(new UIFreeAttributeDecorator("title", entry.title + " " + messageLocator.getMessage("simplepage.select"))); } else if(summaryPage) { // i.e. summary if canEdit and page doesn't have an item UISelectChoice.make(row, "select-for-deletion", select.getFullID(), index). decorate(new UIFreeAttributeDecorator("title", entry.title + " " + messageLocator.getMessage("simplepage.select-for-deletion"))); showDeleteButton = true; // at least one item to delete } GeneralViewParameters params = new GeneralViewParameters(); params.viewID = PreviewProducer.VIEW_ID; params.setSendingPage(entry.pageId); UIInternalLink.make(row, "link", params). decorate(new UIFreeAttributeDecorator("style", "padding-left: " + (2*level) + "em")). decorate(new UIFreeAttributeDecorator("target", "_blank")); String levelstr = messageLocator.getMessage("simplepage.status.level").replace("{}", Integer.toString(level)) + " "; if (level > 0) UIOutput.make(row, "link-note", levelstr); UIOutput.make(row, "link-text", entry.title); index++; } if (canEditPage && entry != null && entry.pageId != null && sharedPages.contains(entry.pageId)) { UIOutput.make(row, "shared"); } // debug code for development. this will be removed at some point if (ServerConfigurationService.getBoolean("lessonbuilder.accessibilitydebug", false)) { if (entry != null && entry.pageId != null && lessonsAccess.isPageAccessible(entry.pageId,simplePageBean.getCurrentSiteId(),"c08d3ac9-c717-472a-ad91-7ce0b434f42f", null)) { UIOutput.make(row, "page1"); } if (entry != null && entry.pageId != null && lessonsAccess.isPageAccessible(entry.pageId,simplePageBean.getCurrentSiteId(),"c08d3ac9-c717-472a-ad91-7ce0b434f42f", simplePageBean)) { UIOutput.make(row, "page2"); } if (entry != null && entry.pageId != null && lessonsAccess.isItemAccessible(entry.itemId,simplePageBean.getCurrentSiteId(),"c08d3ac9-c717-472a-ad91-7ce0b434f42f", null)) { UIOutput.make(row, "item1"); } if (entry != null && entry.pageId != null && lessonsAccess.isItemAccessible(entry.itemId,simplePageBean.getCurrentSiteId(),"c08d3ac9-c717-472a-ad91-7ce0b434f42f", simplePageBean)) { UIOutput.make(row, "item2"); } } } if (!summaryPage) { UIInput.make(form, "item-id", "#{simplePageBean.itemId}"); if (itemId == -1 && !((GeneralViewParameters) viewparams).newTopLevel) { UIOutput.make(form, "hr"); UIOutput.make(form, "options"); UIBoundBoolean.make(form, "subpage-next", "#{simplePageBean.subpageNext}", false); UIBoundBoolean.make(form, "subpage-button", "#{simplePageBean.subpageButton}", false); } String returnView = ((GeneralViewParameters) viewparams).getReturnView(); if(returnView != null && returnView.equals("reorder")) { // return to Reorder, to add items from this page UICommand.make(form, "submit", messageLocator.getMessage("simplepage.chooser.select"), "#{simplePageBean.selectPage}"); } else if(((GeneralViewParameters) viewparams).newTopLevel) { UICommand.make(form, "submit", messageLocator.getMessage("simplepage.chooser.select"), "#{simplePageBean.addOldPage}"); }else { UICommand.make(form, "submit", messageLocator.getMessage("simplepage.chooser.select"), "#{simplePageBean.createSubpage}"); } UICommand.make(form, "cancel", messageLocator.getMessage("simplepage.cancel"), "#{simplePageBean.cancel}"); } else if (showDeleteButton) { UICommand.make(form, "submit", messageLocator.getMessage("simplepage.delete-selected"), "#{simplePageBean.deletePages}"); } } private UIFreeAttributeDecorator getImageSourceDecorator(SimplePageItem pageItem) { switch (pageItem.getType()) { case SimplePageItem.FORUM: return new UIFreeAttributeDecorator("src", "/library/image/silk/comments.png"); case SimplePageItem.ASSIGNMENT: return new UIFreeAttributeDecorator("src", "/library/image/silk/page_edit.png"); case SimplePageItem.ASSESSMENT: return new UIFreeAttributeDecorator("src", "/library/image/silk/pencil.png"); case SimplePageItem.QUESTION: return new UIFreeAttributeDecorator("src", "/library/image/silk/pencil.png"); case SimplePageItem.COMMENTS: return new UIFreeAttributeDecorator("src", "/library/image/silk/comments.png"); case SimplePageItem.BLTI: return new UIFreeAttributeDecorator("src", "/library/image/silk/application_go.png"); case SimplePageItem.PAGE: return new UIFreeAttributeDecorator("src", "/library/image/silk/book_open.png"); case SimplePageItem.RESOURCE: return getImageSourceDecoratorFromMimeType(pageItem); case SimplePageItem.MULTIMEDIA: return getImageSourceDecoratorFromMimeType(pageItem); case SimplePageItem.TEXT: return getImageSourceDecoratorFromMimeType(pageItem); default: return new UIFreeAttributeDecorator("src", ""); } } private UIFreeAttributeDecorator getImageSourceDecoratorFromMimeType(SimplePageItem pageItem) { String mimeType = pageItem.getHtml(); if(SimplePageItem.TEXT == pageItem.getType()) { mimeType = "text/html"; } else if("application/octet-stream".equals(mimeType)) { // OS X reports octet stream for things like MS Excel documents. // Force a mimeType lookup so we get a decent icon. mimeType = null; } if (mimeType == null || mimeType.equals("")) { String s = pageItem.getSakaiId(); int j = s.lastIndexOf("."); if (j >= 0) s = s.substring(j+1); mimeType = ContentTypeImageService.getContentType(s); } String src = null; if (!useSakaiIcons) { src = imageToMimeMap.get(mimeType); } if (src == null) { String image = ContentTypeImageService.getContentTypeImage(mimeType); if (image != null) { src = "/library/image/" + image; } } if(src != null) { return new UIFreeAttributeDecorator("src", src); } else { return new UIFreeAttributeDecorator("src", ""); } } public ViewParameters getViewParameters() { return new GeneralViewParameters(); } public List reportNavigationCases() { List<NavigationCase> togo = new ArrayList<NavigationCase>(); togo.add(new NavigationCase("success", new SimpleViewParameters(ShowPageProducer.VIEW_ID))); togo.add(new NavigationCase("failure", new SimpleViewParameters(ForumPickerProducer.VIEW_ID))); togo.add(new NavigationCase("cancel", new SimpleViewParameters(ShowPageProducer.VIEW_ID))); togo.add(new NavigationCase("selectpage", new GeneralViewParameters(ReorderProducer.VIEW_ID))); return togo; } }