/* * HeadsUp Agile * Copyright 2009-2017 Heads Up Development Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.headsupdev.agile.web; import org.apache.wicket.*; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxFallbackLink; import org.apache.wicket.markup.html.CSSPackageResource; import org.apache.wicket.markup.html.PackageResource; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.protocol.http.WebRequest; import org.apache.wicket.protocol.http.WebResponse; import org.headsupdev.agile.HeadsUpResourceMarker; import org.headsupdev.agile.api.Application; import org.headsupdev.agile.api.*; import org.headsupdev.agile.api.Page; import org.headsupdev.agile.api.SecurityManager; import org.headsupdev.agile.api.logging.Logger; import org.headsupdev.agile.core.PrivateConfiguration; import org.headsupdev.agile.security.permission.AdminPermission; import org.headsupdev.agile.security.permission.ProjectListPermission; import org.headsupdev.agile.storage.StoredProject; import org.headsupdev.agile.web.auth.WebLoginManager; import org.headsupdev.agile.web.components.AccountSummaryPanel; import org.headsupdev.agile.web.components.HeadsUpResourceReference; import org.headsupdev.agile.web.components.MarkedUpTextModel; import org.headsupdev.agile.web.components.ProjectListPanel; import org.headsupdev.agile.web.components.UserDashboard; import org.headsupdev.agile.web.dialogs.LoginDialog; import org.headsupdev.agile.web.dialogs.LogoutDialog; import org.headsupdev.agile.web.model.UserDashboardModel; import org.wicketstuff.animator.Animator; import org.wicketstuff.animator.IAnimatorSubject; import org.wicketstuff.animator.MarkupIdModel; import java.io.Serializable; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * The parent to all HeadsUp styled pages * * @author Andrew Williams * @version $Id$ * @since 1.0 */ public abstract class HeadsUpPage extends Page implements Serializable { public static final int DEFAULT_AVATAR_EDGE_LENGTH = 32; public static final int SMALL_AVATAR_EDGE_LENGTH = 16; public static final String DIALOG_PANEL_ID = "agile-dialog"; protected static final String PAGE_TITLE_SEPARATOR = " :: "; private Application application; private Project project; private List<Link> links = new LinkedList<Link>(); private PageParameters parameters; private WebMarkupContainer dialog, submenu; private String searchQuery; private ArrayList<MenuLink> menuLinks = new ArrayList<MenuLink>(); private WebMarkupContainer filterButton; private boolean linksRendered = false; private Logger log = Manager.getLogger( getClass().getName() ); public HeadsUpPage() { parameters = new PageParameters(); } public PageParameters getPageParameters() { return parameters; } public void setPageParameters( PageParameters parameters ) { this.parameters = parameters; } protected void setHeaders( WebResponse response ) { response.setHeader( "Pragma", "no-cache" ); response.setHeader( "Cache-Control", "no-store" ); } public void layout() { WebManager.getInstance().checkPermissions( this ); User user = WebLoginManager.getInstance().getLoggedInUser( ( (WebRequest) getRequest() ).getHttpServletRequest() ); if ( user == null ) { user = HeadsUpSession.ANONYMOUS_USER; } /* security check */ if ( getProject().isDisabled() && !getClass().getName().endsWith( "ErrorDisabledPage" ) && ( getRequiredPermission() == null || !getRequiredPermission().equals( new AdminPermission() ) ) ) { throw new RestartResponseException( getPageClass( "projectdisabled" ), getProjectPageParameters() ); } if ( getRequiredPermission() != null ) { requirePermission( getRequiredPermission() ); } if ( !isErrorPage() ) { getSession().setProject( getProject() ); } for ( MenuLink link : application.getLinks( getProject() ) ) { addLink( link ); } add( CSSPackageResource.getHeaderContribution( HeadsUpPage.class, "agile.css" ) ); add( CSSPackageResource.getHeaderContribution( HeadsUpPage.class, "mobile.css", "handheld, only screen and (max-width: 767px)" ) ); add( CSSPackageResource.getHeaderContribution( HeadsUpPage.class, "style.css" ) ); add( new WebMarkupContainer( "headerlogo" ).add( new AttributeModifier( "src", true, new PropertyModel<String>( WebManager.getInstance(), "headerLogo" ) ) ) ); WebMarkupContainer mainmenu = new WebMarkupContainer( "mainmenu-container" ); mainmenu.setMarkupId( "mainmenu" ); mainmenu.add( new ListView<Application>( "mainmenu", ApplicationPageMapper.get().getApplications( getSession().getUser() ) ) { protected void populateItem( ListItem<Application> listItem ) { Project project = getProject(); final Application app = listItem.getModelObject(); if ( "home".equals( app.getApplicationId() ) ) { listItem.setVisible( false ); return; } String link = "/" + project.getId() + "/" + app.getApplicationId() + "/"; if ( ApplicationPageMapper.isHomeApp( app ) ) { if ( project.equals( StoredProject.getDefault() ) ) { link = "/"; } else { link = "/" + project.getId() + "/show/"; } } ExternalLink applink = new ExternalLink( "mainmenu-link", link ); String icon = "images/app/" + app.getApplicationId() + "_icon.png"; Image img = new Image( "mainmenu-icon", new HeadsUpResourceReference( icon ) ); img.setVisible( PackageResource.exists( HeadsUpResourceMarker.class, icon, null, null ) ); img.add( new AttributeModifier( "title", true, new Model<String>( app.getName() ) ) ); applink.add( img ); applink.add( new Label( "mainmenu-label", app.getName() ) ); listItem.add( applink ); listItem.add( new AttributeModifier( "class", new Model<String>() { public String getObject() { if ( app.equals( getHeadsUpApplication() ) ) { return "mainmenu-item-selected"; } else { return "mainmenu-item"; } } } ) ); } } ); add( mainmenu ); submenu = new WebMarkupContainer( "submenu-container" ); submenu.setMarkupId( "submenu" ); addAnimatedSelect( "sublink", "", submenu ); add( submenu ); submenu.add( new ListView<Link>( "submenu", links ) { protected void populateItem( ListItem<Link> listItem ) { Link link = listItem.getModelObject(); listItem.add( link ); listItem.add( new AttributeModifier( "class", new Model<String>() { public String getObject() { if ( getClass().equals( /* TODO find class of link */ null ) ) { return "submenu-item-selected"; } else { return "submenu-item"; } } } ) ); } } ); filterButton = new WebMarkupContainer( "filterbutton" ); filterButton.setMarkupId( "filterbutton" ); submenu.add( filterButton.setVisible( hasFilter() ) ); WebMarkupContainer projectmenu = createProjectMenu( user ); add( projectmenu ); if ( !getClass().getName().endsWith( "Login" ) && !getClass().getName().endsWith( "Logout") && !getClass().getName().contains( "Error" ) ) { getSession().setPreviousPageClass( getClass() ); getSession().setPreviousPageParameters( getPageParameters() ); } WebMarkupContainer userpanel = new WebMarkupContainer( "userpanel" ); userpanel.setOutputMarkupId( true ); userpanel.setMarkupId( "userpanel" ); if ( !PrivateConfiguration.isInstalled() || user.equals( HeadsUpSession.ANONYMOUS_USER ) ) { userpanel.add( new WebMarkupContainer( "account" ).setVisible( false ) ); Link login = new AjaxFallbackLink( "login-link" ) { @Override public void onClick( AjaxRequestTarget target ) { if ( target == null ) { setResponsePage( getPageClass( "login" ), getProjectPageParameters() ); } else { showDialog( new LoginDialog( DIALOG_PANEL_ID, true, HeadsUpPage.this ), target ); } } }; login.add( new Label( "login-label", "login" ) ); userpanel.add( login ); } else { Class<? extends Page> userLink = getPageClass( "account" ); if ( userLink != null ) { userpanel.add( new BookmarkablePageLink( "account", userLink, getProjectPageParameters() ) ); } else { userpanel.add( new WebMarkupContainer( "account" ).setVisible( false ) ); } Link login = new AjaxFallbackLink( "login-link" ) { @Override public void onClick( AjaxRequestTarget target ) { if ( target == null ) { setResponsePage( getPageClass( "logout" ), getProjectPageParameters() ); } else { showDialog( new LogoutDialog( DIALOG_PANEL_ID, true ), target ); } } }; login.add( new Label( "login-label", "logout" ) ); userpanel.add( login ); } if ( ApplicationPageMapper.get().getSupportApp() != null ) { userpanel.add( new BookmarkablePageLink( "support", ApplicationPageMapper.get().getSupportApp().getHomePage(), getProjectPageParameters() ) ); } else { userpanel.add( new WebMarkupContainer( "support" ).setVisible( false ) ); } boolean showUserTools = getSession().getUser() != null && !getSession().getUser().equals( HeadsUpSession.ANONYMOUS_USER ); if ( showUserTools ) { int userIssues = UserDashboardModel.getIssuesAssignedTo( getSession().getUser() ).size(); WebMarkupContainer userDashbutton = new WebMarkupContainer( "userdashbutton" ); userpanel.add( userDashbutton.setVisible( userIssues > 0 ) ); Label totals = new Label( "totals", String.valueOf( userIssues ) ); totals.add( new AttributeModifier( "class", true, new Model<String>() { @Override public String getObject() { if ( AccountSummaryPanel.userHasOverdueMilestones( getSession().getUser() ) ) { return "totals overdue"; } if ( AccountSummaryPanel.userHasDueSoonMilestones( getSession().getUser() ) ) { return "totals duesoon"; } return "totals"; } } ) ); userDashbutton.add( totals ); UserDashboard dash = new UserDashboard( "userdashboard", this ); dash.setOutputMarkupId( true ); dash.setMarkupId( "userdashboard" ); add( dash ); WebMarkupContainer dashBack = new WebMarkupContainer( "userdashboardbackground" ); dashBack.setOutputMarkupId( true ); dashBack.setMarkupId( "userdashboardbackground" ); add( dashBack ); Animator animator = new Animator(); animator.withEaseInOutTransition(); animator.addCssStyleSubject( new MarkupIdModel( dash ), "up", "down" ); animator.addCssStyleSubject( new MarkupIdModel( dashBack ), "up", "down" ); animator.addSubject( new IAnimatorSubject() { public String getJavaScript() { return "function showBackground() {" + " var background = Wicket.$('userdashboardbackground');" + " if (userdashbuttonAnimator.state > 0) {" + " background.style.display=\"block\";" + " document.body.style.overflow=\"hidden\";" + " } else {" + " background.style.display=\"none\";" + " document.body.style.overflow=\"auto\";" + " }" + "}"; } } ); animator.attachTo( userDashbutton, "onclick", Animator.Action.toggle() ); animator.attachTo( dash.getCloseButton(), "onclick", Animator.Action.toggle() ); } else { WebMarkupContainer userDashbutton = new WebMarkupContainer( "userdashbutton" ); userpanel.add( userDashbutton.setVisible( false ) ); add( new WebMarkupContainer( "userdashboard" ).setVisible( false ) ); add( new WebMarkupContainer( "userdashboardbackground" ).setVisible( false ) ); } add( userpanel ); Form form = new Form( "quicksearch" ) { protected void onSubmit() { if ( searchQuery == null ) { return; } super.onSubmit(); String quickLink = getQuickSearchResponse( searchQuery ); if ( quickLink != null ) { getRequestCycle().getResponse().redirect( quickLink ); throw new RedirectToUrlException( quickLink ); } PageParameters params = getProjectPageParameters(); params.add( "query", searchQuery ); setResponsePage( getPageClass( "search" ), params ); } }; add( form.setVisible( ApplicationPageMapper.get().getSearchApp() != null ) ); form.add( new TextField<String>( "query", new PropertyModel<String>( this, "searchQuery" ) ) ); addAnimatedSelect( "projectlink", getProject().getAlias() + " \u25bc", projectmenu ); add( new Label( "title", new PropertyModel( this, "title" ) ) { @Override protected void onBeforeRender() { super.onBeforeRender(); setVisible( getTitle() != null ); } } ); add( new Label( "pagetitle", new PropertyModel( this, "pageTitle" ) ) ); add( new WebMarkupContainer( "description" ).add( new AttributeModifier( "content", true, new Model<String>() { @Override public String getObject() { return getStorage().getGlobalConfiguration().getProductDescription(); } } ) ) ); add( new FeedbackPanel( "messages" ) ); add( new Label( "footer-description", WebManager.getInstance().getFooterDescriptionHTML( getSession().getTimeZone() ) ).setEscapeModelStrings( false ) ); add( new Label( "footer-copyright", WebManager.getInstance().getFooterCopyrightHTML() ).setEscapeModelStrings( false ) ); String noteString = WebManager.getInstance().getFooterNoteHTML(); add( new Label( "footer-note", noteString ).setVisible( noteString != null ) ); add( new BookmarkablePageLink( "footer-update", getPageClass( "updates" ) ) .setVisible( getManager().isUpdateAvailable() ) ); dialog = new WebMarkupContainer( DIALOG_PANEL_ID ); dialog.setVisible( false ); dialog.setOutputMarkupId( true ); dialog.setOutputMarkupPlaceholderTag( true ); add( dialog ); } public String getTitle() { return null; } public String getPageTitle() { String appProductTitle = getAppProductTitle(); if ( getTitle() == null || getTitle().trim().length() == 0 ) { return appProductTitle; } return getTitle() + PAGE_TITLE_SEPARATOR + appProductTitle; } public String getAppProductTitle() { return getHeadsUpApplication().getName() + PAGE_TITLE_SEPARATOR + getStorage().getGlobalConfiguration().getProductName(); } @Override protected void onBeforeRender() { renderMenuLinks(); super.onBeforeRender(); } public void addLink( MenuLink menuLink ) { menuLinks.add( menuLink ); } private void renderMenuLinks() { if ( linksRendered ) { return; } linksRendered = true; for ( MenuLink link : menuLinks ) { Link convertedLink = convertMenuLinkToLink( link ); links.add( convertedLink ); } } /** * * @param menuLink if successful returns true * @return */ public boolean removeLink( MenuLink menuLink ) { // if we have rendered, we can't remove if ( linksRendered ) { return false; } return menuLinks.remove( menuLink ); } private Link convertMenuLinkToLink( MenuLink menuLink ) { Link link; if ( menuLink instanceof SimpleMenuLink ) { String prefix = ""; if ( !getHeadsUpApplication().getApplicationId().equals( "home" ) ) { prefix = getHeadsUpApplication().getApplicationId() + "/"; } menuLink = new BookmarkableMenuLink( getPageClass( prefix + ( (SimpleMenuLink) menuLink ).getTarget() ), getProjectPageParameters(), menuLink.getLabel() ); } if ( menuLink instanceof BookmarkableMenuLink ) { link = ( (BookmarkableMenuLink) menuLink ).getLink(); } else if ( menuLink instanceof Link ) { link = (Link) menuLink; } else { final MenuLink oldLink = menuLink; link = new DynamicMenuLink( oldLink.getLabel() ) { public void onClick() { oldLink.onClick(); } }; } final MenuLink finalLink = menuLink; link.add( new Label( "submenu-label", new Model<String>() { @Override public String getObject() { return finalLink.getLabel(); } } ).setEscapeModelStrings( false ) ); return link; } public void addLinks( List<MenuLink> links ) { for ( MenuLink link : links ) { addLink( link ); } } protected String getPageHint() { if ( ApplicationPageMapper.isHomeApp( getHeadsUpApplication() ) ) { if ( ApplicationPageMapper.get().getApplication( "dashboard" ) != null ) { return "show"; } else { return ""; } } return getHeadsUpApplication().getApplicationId(); } protected WebMarkupContainer createProjectMenu( User user ) { String pageHint = getPageHint(); WebMarkupContainer projectmenu = new WebMarkupContainer( "projectmenu" ); projectmenu.setOutputMarkupId( true ); projectmenu.setMarkupId( "projectmenu" ); List<Project> recent = getStorage().getRecentRootProjects( user ); recent = getProjectsPermitted( recent ); projectmenu.add( new ProjectListPanel( "recent-project-tree", recent, getPageClass( pageHint ), getProject() ) .setVisible( userHasPermission( user, new ProjectListPermission(), null ) && recent.size() > 0 ) ); List<Project> active = new ArrayList<Project>( getStorage().getActiveRootProjects() ); active.removeAll( recent ); active = getProjectsPermitted( active ); projectmenu.add( new ProjectListPanel( "active-project-tree", active, getPageClass( pageHint ), getProject() ) .setVisible( userHasPermission( user, new ProjectListPermission(), null ) && active.size() > 0 ) ); List<Project> other = new ArrayList<Project>( getStorage().getRootProjects() ); other.removeAll( recent ); other.removeAll( active ); other = getProjectsPermitted( other ); projectmenu.add( new ProjectListPanel( "other-project-tree", other, getPageClass( pageHint ), getProject() ) .setVisible( userHasPermission( user, new ProjectListPermission(), null ) && other.size() > 0 ) ); WebMarkupContainer noProjects = new WebMarkupContainer( "noprojects" ); noProjects.setVisible( getStorage().getProjects().size() == 0 ); projectmenu.add( noProjects ); PageParameters params = new PageParameters(); params.add( "project", Project.ALL_PROJECT_ID ); BookmarkablePageLink allProjects = new BookmarkablePageLink( "allprojects-link", getPageClass( pageHint ), params ); allProjects.add( new AttributeModifier( "class", new Model<String>() { public String getObject() { if ( getProject().equals( StoredProject.getDefault() ) ) { return "selected"; } return ""; } } ) ); allProjects.setVisible( getStorage().getProjects().size() > 0 ); projectmenu.add( allProjects ); return projectmenu; } protected void addAnimatedSelect( String id, String title, WebMarkupContainer list ) { WebMarkupContainer select = new WebMarkupContainer( id ); select.add( new Label( "title", title ) ); add( select ); Animator animator = new Animator(); animator.addCssStyleSubject( new MarkupIdModel( list ), "up", "down" ); animator.attachTo( select, "onclick", Animator.Action.toggle() ); } public String getQuickSearchResponse( String search ) { if ( searchQuery == null ) { return null; } String returnURL = null; Map<String, LinkProvider> providers = Manager.getInstance().getLinkProviders(); for ( LinkProvider provider : providers.values() ) { if ( !provider.supportsQuickLink() ) { continue; } if ( provider.isLinkBroken( search, getProject() ) ) { continue; } if ( returnURL != null ) { log.debug( "QuickSearch also matched \"" + searchQuery + "\" in " + provider.getId() + ", running search" ); return null; } log.debug( "QuickSearch matched \"" + searchQuery + "\" in " + provider.getId() ); returnURL = provider.getLink( search, getProject() ); } if ( returnURL == null && searchQuery.contains( ":" ) ) { if ( !MarkedUpTextModel.isLinkBroken( searchQuery, getProject(), providers ) ) { returnURL = MarkedUpTextModel.getLink( searchQuery, getProject(), providers ); } } return returnURL; } public void userError( String reason ) { getSession().error( reason ); PageParameters userError = new PageParameters(); userError.add( "userError", "true" ); userError.add( "reason", reason ); setResponsePage( getPageClass( "error" ), userError ); } public void notFoundError() { PageParameters page = new PageParameters(); page.add( "uri", getRequest().getPath() ); setResponsePage( getPageClass( "filenotfound" ), page ); } public org.apache.wicket.Page getPage( String path ) { Class<? extends Page> page = ApplicationPageMapper.get().getPageClass( path ); return getApplication().getSessionSettings().getPageFactory().newPage( page ); } public org.apache.wicket.Page getPage( String path, PageParameters params ) { Class<? extends Page> page = ApplicationPageMapper.get().getPageClass( path ); return getApplication().getSessionSettings().getPageFactory().newPage( page, params ); } public Class<? extends Page> getPageClass( String path ) { return ApplicationPageMapper.get().getPageClass( path ); } public Manager getManager() { return Manager.getInstance(); } public SecurityManager getSecurityManager() { return Manager.getSecurityInstance(); } public Storage getStorage() { return Manager.getStorageInstance(); } public Application getHeadsUpApplication() { return application; } public void setApplication( Application application ) { this.application = application; } public Project getProject() { if ( project != null ) { return project; } String projectId = getPageParameters().getString( "project" ); if ( projectId != null && projectId.length() > 0 && !projectId.equals( StoredProject.ALL_PROJECT_ID ) ) { project = getStorage().getProject( projectId ); } if ( project == null ) { project = StoredProject.getDefault(); } return project; } public HeadsUpSession getSession() { return (HeadsUpSession) super.getSession(); } public PageParameters getProjectPageParameters() { PageParameters params = new PageParameters(); params.add( "project", getProject().getId() ); return params; } public abstract Permission getRequiredPermission(); public void requirePermission( Permission permission ) { User user = getSession().getUser(); if ( !userHasPermission( user, permission, getProject() ) ) { if ( user == null || user.equals( HeadsUpSession.ANONYMOUS_USER ) ) { throw new RestartResponseAtInterceptPageException( getPageClass( "login" ) ); } else { throw new RestartResponseException( getPageClass( "permissiondenied" ) ); } } } public boolean userHasPermission( User user, Permission permission, Project project ) { return PrivateConfiguration.isInstalled() && getSecurityManager().userHasPermission( user, permission, project ); } public void showDialog( Panel panel, AjaxRequestTarget target ) { if ( !panel.equals( dialog ) ) { panel.setOutputMarkupId( true ); panel.setOutputMarkupPlaceholderTag( true ); dialog.replaceWith( panel ); dialog = panel; } panel.setVisible( true ); if ( target != null ) { target.addComponent( panel ); } } protected List<Project> getProjectsPermitted( List<Project> projects ) { List<Project> permitted = new ArrayList<Project>( projects.size() ); for ( Project project : projects ) { if ( getSecurityManager().userCanListProject( getSession().getUser(), project ) ) { permitted.add( project ); } } return permitted; } public boolean hasFilter() { return false; } public WebMarkupContainer getFilterButton() { return filterButton; } }