View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.web.stylesheet;
35  
36  import java.io.ByteArrayInputStream;
37  import java.io.File;
38  import java.io.FileOutputStream;
39  import java.io.IOException;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  
46  import javax.servlet.http.HttpServletRequest;
47  import javax.xml.parsers.SAXParser;
48  import javax.xml.parsers.SAXParserFactory;
49  
50  import org.apache.commons.fileupload.FileItem;
51  import org.xml.sax.InputSource;
52  
53  import fr.paris.lutece.portal.business.portalcomponent.PortalComponentHome;
54  import fr.paris.lutece.portal.business.portlet.PortletType;
55  import fr.paris.lutece.portal.business.portlet.PortletTypeHome;
56  import fr.paris.lutece.portal.business.style.Mode;
57  import fr.paris.lutece.portal.business.style.ModeHome;
58  import fr.paris.lutece.portal.business.style.Style;
59  import fr.paris.lutece.portal.business.style.StyleHome;
60  import fr.paris.lutece.portal.business.stylesheet.StyleSheet;
61  import fr.paris.lutece.portal.business.stylesheet.StyleSheetHome;
62  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
63  import fr.paris.lutece.portal.service.fileupload.FileUploadService;
64  import fr.paris.lutece.portal.service.i18n.I18nService;
65  import fr.paris.lutece.portal.service.message.AdminMessage;
66  import fr.paris.lutece.portal.service.message.AdminMessageService;
67  import fr.paris.lutece.portal.service.security.SecurityTokenService;
68  import fr.paris.lutece.portal.service.template.AppTemplateService;
69  import fr.paris.lutece.portal.service.util.AppLogService;
70  import fr.paris.lutece.portal.service.util.AppPathService;
71  import fr.paris.lutece.portal.service.util.AppPropertiesService;
72  import fr.paris.lutece.portal.web.admin.AdminFeaturesPageJspBean;
73  import fr.paris.lutece.portal.web.constants.Messages;
74  import fr.paris.lutece.portal.web.constants.Parameters;
75  import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
76  import fr.paris.lutece.portal.web.util.LocalizedPaginator;
77  import fr.paris.lutece.util.ReferenceList;
78  import fr.paris.lutece.util.file.FileUtil;
79  import fr.paris.lutece.util.html.AbstractPaginator;
80  import fr.paris.lutece.util.html.HtmlTemplate;
81  import fr.paris.lutece.util.sort.AttributeComparator;
82  
83  /**
84   * This class provides the user interface to manage StyleSheet features
85   */
86  public class StyleSheetJspBean extends AdminFeaturesPageJspBean
87  {
88      // //////////////////////////////////////////////////////////////////////////
89      // Constants
90  
91      // Right
92      /**
93       * Right to manage stylesheets
94       */
95      public static final String RIGHT_MANAGE_STYLESHEET = "CORE_STYLESHEET_MANAGEMENT";
96  
97      /**
98       * Serial version UID
99       */
100     private static final long serialVersionUID = 8176263113722225633L;
101 
102     // Markers
103     private static final String MARK_MODE_ID = "mode_id";
104     private static final String MARK_MODE_LIST = "mode_list";
105     private static final String MARK_STYLESHEET_LIST = "stylesheet_list";
106     private static final String MARK_STYLE_LIST = "style_list";
107     private static final String MARK_STYLESHEET = "stylesheet";
108     private static final String MARK_PAGINATOR = "paginator";
109     private static final String MARK_NB_ITEMS_PER_PAGE = "nb_items_per_page";
110     private static final String MARK_PORTAL_COMPONENT_NAME = "portal_component_name";
111     private static final String MARK_PORTLET_TYPE_NAME = "portlet_type_name";
112     private static final String MARK_STYLE_DESCRIPTION = "style_description";
113 
114     // Templates files path
115     private static final String TEMPLATE_MANAGE_STYLESHEETS = "admin/stylesheet/manage_stylesheets.html";
116     private static final String TEMPLATE_CREATE_STYLESHEET = "admin/stylesheet/create_stylesheet.html";
117     private static final String TEMPLATE_MODIFY_STYLESHEET = "admin/stylesheet/modify_stylesheet.html";
118     private static final String TEMPLATE_STYLE_SELECT_OPTION = "admin/stylesheet/style_select_option.html";
119 
120     // Properties
121     private static final String PROPERTY_PATH_XSL = "path.stylesheet";
122     private static final String PROPERTY_STYLESHEETS_PER_PAGE = "paginator.stylesheet.itemsPerPage";
123     private static final String MESSAGE_STYLESHEET_ALREADY_EXISTS = "portal.style.message.stylesheetAlreadyExists";
124     private static final String MESSAGE_STYLESHEET_NOT_VALID = "portal.style.message.stylesheetNotValid";
125     private static final String MESSAGE_CONFIRM_DELETE_STYLESHEET = "portal.style.message.stylesheetConfirmDelete";
126     private static final String LABEL_ALL = "portal.util.labelAll";
127     private static final String JSP_DO_REMOVE_STYLESHEET = "jsp/admin/style/DoRemoveStyleSheet.jsp";
128     private static final String JSP_REMOVE_STYLE = "RemoveStyle.jsp";
129     private int _nItemsPerPage;
130     private String _strCurrentPageIndex;
131 
132     /**
133      * Displays the stylesheets list
134      * 
135      * @return the html code for displaying the stylesheets list
136      * @param request
137      *            The request
138      */
139     public String getManageStyleSheet( HttpServletRequest request )
140     {
141         // Parameters processing
142         String strModeId = request.getParameter( Parameters.MODE_ID );
143         strModeId = ( strModeId != null ) ? strModeId : "-1";
144 
145         int nModeId = Integer.parseInt( strModeId );
146 
147         ReferenceList listModes = ModeHome.getModes( );
148         String strComboItem = I18nService.getLocalizedString( LABEL_ALL, getLocale( ) );
149         listModes.addItem( -1, strComboItem );
150 
151         List<StyleSheet> listStyleSheets = (List<StyleSheet>) StyleSheetHome.getStyleSheetList( nModeId );
152 
153         String strSortedAttributeName = request.getParameter( Parameters.SORTED_ATTRIBUTE_NAME );
154         String strAscSort = null;
155 
156         if ( strSortedAttributeName != null )
157         {
158             strAscSort = request.getParameter( Parameters.SORTED_ASC );
159 
160             boolean bIsAscSort = Boolean.parseBoolean( strAscSort );
161 
162             Collections.sort( listStyleSheets, new AttributeComparator( strSortedAttributeName, bIsAscSort ) );
163         }
164 
165         int defaultItemsPerPage = AppPropertiesService.getPropertyInt( PROPERTY_STYLESHEETS_PER_PAGE, 50 );
166         _strCurrentPageIndex = AbstractPaginator.getPageIndex( request, AbstractPaginator.PARAMETER_PAGE_INDEX, _strCurrentPageIndex );
167         _nItemsPerPage = AbstractPaginator.getItemsPerPage( request, AbstractPaginator.PARAMETER_ITEMS_PER_PAGE, _nItemsPerPage, defaultItemsPerPage );
168 
169         String strURL = getHomeUrl( request );
170 
171         if ( strSortedAttributeName != null )
172         {
173             strURL += ( "?" + Parameters.SORTED_ATTRIBUTE_NAME + "=" + strSortedAttributeName );
174         }
175 
176         if ( strAscSort != null )
177         {
178             strURL += ( "&" + Parameters.SORTED_ASC + "=" + strAscSort );
179         }
180 
181         LocalizedPaginator<StyleSheet> paginator = new LocalizedPaginator<>( listStyleSheets, _nItemsPerPage, strURL, AbstractPaginator.PARAMETER_PAGE_INDEX,
182                 _strCurrentPageIndex, getLocale( ) );
183 
184         Map<String, Object> model = new HashMap<>( );
185         model.put( MARK_MODE_ID, strModeId );
186         model.put( MARK_NB_ITEMS_PER_PAGE, "" + _nItemsPerPage );
187         model.put( MARK_PAGINATOR, paginator );
188         model.put( MARK_STYLESHEET_LIST, paginator.getPageItems( ) );
189         model.put( MARK_MODE_LIST, listModes );
190 
191         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MANAGE_STYLESHEETS, getLocale( ), model );
192 
193         return getAdminPage( template.getHtml( ) );
194     }
195 
196     /**
197      * Returns the create form of a new stylesheet with the upload field
198      * 
199      * @param request
200      *            the http request
201      * @return the html code for the create form of a new stylesheet
202      */
203     public String getCreateStyleSheet( HttpServletRequest request )
204     {
205         String strModeId = request.getParameter( Parameters.MODE_ID );
206 
207         Map<String, Object> model = new HashMap<>( );
208         model.put( MARK_STYLE_LIST, getStyleList( ) );
209         model.put( MARK_MODE_LIST, ModeHome.getModes( ) );
210         model.put( MARK_MODE_ID, strModeId );
211         model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, TEMPLATE_CREATE_STYLESHEET ) );
212 
213         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_CREATE_STYLESHEET, getLocale( ), model );
214 
215         return getAdminPage( template.getHtml( ) );
216     }
217 
218     /**
219      * Processes the creation form of a new stylesheet by recovering the parameters in the http request
220      * 
221      * @param request
222      *            the http request
223      * @return The Jsp URL of the process result
224      * @throws AccessDeniedException
225      *             if the security token is invalid
226      */
227     public String doCreateStyleSheet( HttpServletRequest request ) throws AccessDeniedException
228     {
229         StyleSheets/stylesheet/StyleSheet.html#StyleSheet">StyleSheet stylesheet = new StyleSheet( );
230         MultipartHttpServletRequestce/portal/web/upload/MultipartHttpServletRequest.html#MultipartHttpServletRequest">MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
231         String strErrorUrl = getData( multipartRequest, stylesheet );
232 
233         if ( strErrorUrl != null )
234         {
235             return strErrorUrl;
236         }
237         if ( !SecurityTokenService.getInstance( ).validate( multipartRequest, TEMPLATE_CREATE_STYLESHEET ) )
238         {
239             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
240         }
241 
242         // insert in the table stylesheet of the database
243         StyleSheetHome.create( stylesheet );
244 
245         // create a local file
246         localStyleSheetFile( stylesheet );
247 
248         // Displays the list of the stylesheet files
249         return getHomeUrl( request );
250     }
251 
252     /**
253      * Reads stylesheet's data
254      * 
255      * @param multipartRequest
256      *            The request
257      * @param stylesheet
258      *            The style sheet
259      * @return An error message URL or null if no error
260      */
261     private String getData( MultipartHttpServletRequest multipartRequest, StyleSheet stylesheet )
262     {
263         String strErrorUrl = null;
264         String strDescription = multipartRequest.getParameter( Parameters.STYLESHEET_NAME );
265         String strStyleId = multipartRequest.getParameter( Parameters.STYLES );
266         String strModeId = multipartRequest.getParameter( Parameters.MODE_STYLESHEET );
267 
268         FileItem fileSource = multipartRequest.getFile( Parameters.STYLESHEET_SOURCE );
269         byte [ ] baXslSource = fileSource.get( );
270         String strFilename = FileUploadService.getFileNameOnly( fileSource );
271 
272         // Mandatory fields
273         if ( strDescription.equals( "" ) || ( strFilename == null ) || strFilename.equals( "" ) )
274         {
275             return AdminMessageService.getMessageUrl( multipartRequest, Messages.MANDATORY_FIELDS, AdminMessage.TYPE_STOP );
276         }
277 
278         // test the existence of style or mode already associate with this stylesheet
279         int nStyleId = Integer.parseInt( strStyleId );
280         int nModeId = Integer.parseInt( strModeId );
281         int nCount = StyleSheetHome.getStyleSheetNbPerStyleMode( nStyleId, nModeId );
282 
283         // Do not create a stylesheet of there is already one
284         if ( ( nCount >= 1 ) && ( stylesheet.getId( ) == 0 /* creation */ ) )
285         {
286             return AdminMessageService.getMessageUrl( multipartRequest, MESSAGE_STYLESHEET_ALREADY_EXISTS, AdminMessage.TYPE_STOP );
287         }
288 
289         // Check the XML validity of the XSL stylesheet
290         if ( isValid( baXslSource ) != null )
291         {
292             Object [ ] args = {
293                     isValid( baXslSource )
294             };
295 
296             return AdminMessageService.getMessageUrl( multipartRequest, MESSAGE_STYLESHEET_NOT_VALID, args, AdminMessage.TYPE_STOP );
297         }
298 
299         stylesheet.setDescription( strDescription );
300         stylesheet.setStyleId( Integer.parseInt( strStyleId ) );
301         stylesheet.setModeId( Integer.parseInt( strModeId ) );
302         stylesheet.setSource( baXslSource );
303         stylesheet.setFile( strFilename );
304 
305         return strErrorUrl;
306     }
307 
308     /**
309      * Returns the form to update a stylesheet whose identifer is stored in the http request
310      * 
311      * @param request
312      *            The http request
313      * @return The html code
314      */
315     public String getModifyStyleSheet( HttpServletRequest request )
316     {
317         String strStyleSheetId = request.getParameter( Parameters.STYLESHEET_ID );
318         int nId = Integer.parseInt( strStyleSheetId );
319 
320         Map<String, Object> model = new HashMap<>( );
321         model.put( MARK_STYLE_LIST, getStyleList( ) );
322         model.put( MARK_MODE_LIST, ModeHome.getModes( ) );
323         model.put( MARK_STYLESHEET, StyleSheetHome.findByPrimaryKey( nId ) );
324         model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, TEMPLATE_MODIFY_STYLESHEET ) );
325 
326         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MODIFY_STYLESHEET, getLocale( ), model );
327 
328         return getAdminPage( template.getHtml( ) );
329     }
330 
331     /**
332      * Return a ReferenceList with id style for code and a concatenation of portal name + portlet type name + style description for name.
333      * 
334      * @return The {@link ReferenceList}
335      */
336     public ReferenceList getStyleList( )
337     {
338         Collection<Style> stylesList = StyleHome.getStylesList( );
339         ReferenceListerenceList">ReferenceList stylesListWithLabels = new ReferenceList( );
340 
341         for ( Style style : stylesList )
342         {
343             HashMap<String, Object> model = new HashMap<>( );
344             model.put( MARK_PORTAL_COMPONENT_NAME, PortalComponentHome.findByPrimaryKey( style.getPortalComponentId( ) ).getName( ) );
345 
346             PortletType portletType = PortletTypeHome.findByPrimaryKey( style.getPortletTypeId( ) );
347 
348             model.put( MARK_PORTLET_TYPE_NAME,
349                     ( ( portletType != null ) ? ( I18nService.getLocalizedString( portletType.getNameKey( ), getLocale( ) ) ) : "" ) );
350             model.put( MARK_STYLE_DESCRIPTION, style.getDescription( ) );
351 
352             HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_STYLE_SELECT_OPTION, getLocale( ), model );
353             stylesListWithLabels.addItem( style.getId( ), template.getHtml( ) );
354         }
355 
356         return stylesListWithLabels;
357     }
358 
359     /**
360      * Processes the updating form of a stylesheet whose new parameters are stored in the http request
361      * 
362      * @param request
363      *            The http request
364      * @return The Jsp URL of the process result
365      * @throws AccessDeniedException
366      *             if the security token is invalid
367      */
368     public String doModifyStyleSheet( HttpServletRequest request ) throws AccessDeniedException
369     {
370         MultipartHttpServletRequestce/portal/web/upload/MultipartHttpServletRequest.html#MultipartHttpServletRequest">MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
371         int nId = Integer.parseInt( multipartRequest.getParameter( Parameters.STYLESHEET_ID ) );
372         StyleSheet stylesheet = StyleSheetHome.findByPrimaryKey( nId );
373         String strErrorUrl = getData( multipartRequest, stylesheet );
374 
375         if ( strErrorUrl != null )
376         {
377             return strErrorUrl;
378         }
379         if ( !SecurityTokenService.getInstance( ).validate( multipartRequest, TEMPLATE_MODIFY_STYLESHEET ) )
380         {
381             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
382         }
383 
384         // Remove the old local file
385         removeOldLocalStyleSheet( nId );
386 
387         // Update the stylesheet in database
388         StyleSheetHome.update( stylesheet );
389 
390         // Recreate the local file
391         localStyleSheetFile( stylesheet );
392 
393         // Displays the management stylesheet page
394         return getHomeUrl( request );
395     }
396 
397     /**
398      * Returns the confirm of removing the style whose identifier is in the http request
399      *
400      * @param request
401      *            The Http request
402      * @return the html code for the remove confirmation page
403      */
404     public String getRemoveStyleSheet( HttpServletRequest request )
405     {
406         String strId = request.getParameter( Parameters.STYLESHEET_ID );
407 
408         StyleSheet stylesheet = StyleSheetHome.findByPrimaryKey( Integer.parseInt( strId ) );
409         Object [ ] args = {
410                 stylesheet.getDescription( )
411         };
412 
413         Map<String, Object> parameters = new HashMap<>( );
414         parameters.put( Parameters.STYLESHEET_ID, strId );
415         parameters.put( Parameters.STYLE_ID, stylesheet.getStyleId( ) );
416         parameters.put( SecurityTokenService.PARAMETER_TOKEN, SecurityTokenService.getInstance( ).getToken( request, JSP_DO_REMOVE_STYLESHEET ) );
417         return AdminMessageService.getMessageUrl( request, MESSAGE_CONFIRM_DELETE_STYLESHEET, args, null, JSP_DO_REMOVE_STYLESHEET, null,
418                 AdminMessage.TYPE_CONFIRMATION, parameters );
419     }
420 
421     /**
422      * Processes the deletion of a stylesheet
423      * 
424      * @param request
425      *            the http request
426      * @return The Jsp URL of the process result
427      * @throws AccessDeniedException
428      *             if the security token is invalid
429      */
430     public String doRemoveStyleSheet( HttpServletRequest request ) throws AccessDeniedException
431     {
432         if ( !SecurityTokenService.getInstance( ).validate( request, JSP_DO_REMOVE_STYLESHEET ) )
433         {
434             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
435         }
436         int nId = Integer.parseInt( request.getParameter( Parameters.STYLESHEET_ID ) );
437         int nIdStyle = Integer.parseInt( request.getParameter( Parameters.STYLE_ID ) );
438         StyleSheet stylesheet = StyleSheetHome.findByPrimaryKey( nId );
439         String strFile = stylesheet.getFile( );
440         StyleSheetHome.remove( nId );
441 
442         // removal of the XSL file
443         int nModeId = stylesheet.getModeId( );
444         Mode mode = ModeHome.findByPrimaryKey( nModeId );
445         String strPathStyleSheet = AppPathService.getPath( PROPERTY_PATH_XSL ) + mode.getPath( );
446         File fileToDelete = new File( strPathStyleSheet, strFile );
447         FileUtil.deleteFile( fileToDelete );
448 
449         return JSP_REMOVE_STYLE + "?" + Parameters.STYLE_ID + "=" + nIdStyle;
450     }
451 
452     // ////////////////////////////////////////////////////////////////////////////////
453     // Private implementation
454 
455     /**
456      * Use parsing for validate the modify xsl file
457      *
458      * @param baXslSource
459      *            The XSL source
460      * @return the message exception when the validation is false
461      */
462     private String isValid( byte [ ] baXslSource )
463     {
464         String strError = null;
465 
466         try
467         {
468             SAXParserFactory factory = SAXParserFactory.newInstance( );
469             factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
470             factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
471             SAXParser analyzer = factory.newSAXParser( );
472             InputSource is = new InputSource( new ByteArrayInputStream( baXslSource ) );
473             analyzer.getXMLReader( ).parse( is );
474         }
475         catch( Exception e )
476         {
477             strError = e.getMessage( );
478         }
479 
480         return strError;
481     }
482 
483     /**
484      * Create and Update the local download file
485      *
486      * @param stylesheet
487      *            The style sheet
488      */
489     private void localStyleSheetFile( StyleSheet stylesheet )
490     {
491         int nModeId = stylesheet.getModeId( );
492         Mode mode = ModeHome.findByPrimaryKey( nModeId );
493         String strPathStyleSheet = AppPathService.getPath( PROPERTY_PATH_XSL ) + mode.getPath( );
494         String strFileName = stylesheet.getFile( );
495         String strFilePath = strPathStyleSheet + strFileName;
496 
497         File file = new File( strFilePath );
498         FileUtil.deleteFile( file );
499         try ( FileOutputStream fos = new FileOutputStream( file ) )
500         {
501             fos.write( stylesheet.getSource( ) );
502         }
503         catch( IOException e )
504         {
505             AppLogService.error( e.getMessage( ), e );
506         }
507     }
508 
509     /**
510      * remove the xsl file from the tmp directory
511      * 
512      * @param nId
513      *            the identifier of the file
514      */
515     private void removeOldLocalStyleSheet( int nId )
516     {
517         // Remove the file which been modify
518         StyleSheet stylesheet = StyleSheetHome.findByPrimaryKey( nId );
519         int nMode = stylesheet.getModeId( );
520         Mode mode = ModeHome.findByPrimaryKey( nMode );
521         String strPathStyleSheet = AppPathService.getPath( PROPERTY_PATH_XSL ) + mode.getPath( );
522         String strOldFileName = stylesheet.getFile( );
523         String strOldFilePath = strPathStyleSheet + strOldFileName;
524         File oldFile = new File( strOldFilePath );
525         FileUtil.deleteFile( oldFile );
526     }
527 }