001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.util;
017
018import org.apache.commons.lang.StringEscapeUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Level;
021import org.apache.log4j.Logger;
022import org.apache.struts.Globals;
023import org.apache.struts.action.ActionForm;
024import org.apache.struts.action.ActionMapping;
025import org.apache.struts.action.ActionServletWrapper;
026import org.apache.struts.upload.CommonsMultipartRequestHandler;
027import org.apache.struts.upload.FormFile;
028import org.apache.struts.upload.MultipartRequestHandler;
029import org.apache.struts.upload.MultipartRequestWrapper;
030import org.kuali.rice.core.api.CoreApiServiceLocator;
031import org.kuali.rice.core.api.config.property.ConfigContext;
032import org.kuali.rice.core.api.config.property.ConfigurationService;
033import org.kuali.rice.core.api.util.RiceKeyConstants;
034import org.kuali.rice.kew.api.action.ActionRequest;
035import org.kuali.rice.kew.api.action.RecipientType;
036import org.kuali.rice.kim.api.role.Role;
037import org.kuali.rice.kim.api.services.KimApiServiceLocator;
038import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
039import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
040import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
041import org.kuali.rice.kns.service.KNSServiceLocator;
042import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
043import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
044import org.kuali.rice.kns.web.struts.form.KualiForm;
045import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
046import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
047import org.kuali.rice.kns.web.ui.Field;
048import org.kuali.rice.kns.web.ui.Row;
049import org.kuali.rice.kns.web.ui.Section;
050import org.kuali.rice.krad.datadictionary.AttributeDefinition;
051import org.kuali.rice.krad.datadictionary.AttributeSecurity;
052import org.kuali.rice.krad.datadictionary.DataDictionary;
053import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
054import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
055import org.kuali.rice.krad.document.Document;
056import org.kuali.rice.krad.exception.ValidationException;
057import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
058import org.kuali.rice.krad.util.GlobalVariables;
059import org.kuali.rice.krad.util.KRADConstants;
060import org.kuali.rice.krad.util.MessageMap;
061import org.kuali.rice.krad.util.ObjectUtils;
062
063import javax.servlet.ServletException;
064import javax.servlet.http.HttpServletRequest;
065import javax.servlet.http.HttpServletResponse;
066import javax.servlet.http.HttpSession;
067import javax.servlet.jsp.PageContext;
068import java.io.BufferedInputStream;
069import java.io.ByteArrayOutputStream;
070import java.io.IOException;
071import java.io.InputStream;
072import java.io.OutputStream;
073import java.net.URI;
074import java.net.URISyntaxException;
075import java.util.Arrays;
076import java.util.Collection;
077import java.util.Enumeration;
078import java.util.HashMap;
079import java.util.Hashtable;
080import java.util.Iterator;
081import java.util.LinkedHashMap;
082import java.util.List;
083import java.util.Map;
084import java.util.Set;
085import java.util.regex.Matcher;
086import java.util.regex.Pattern;
087
088/**
089 * General helper methods for handling requests.
090 *
091 * @deprecated Only used in KNS classes, use KRAD.
092 */
093@Deprecated
094public class WebUtils {
095        private static final Logger LOG = Logger.getLogger(WebUtils.class);
096
097        private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x";
098        private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y";
099
100        private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url";
101        private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url";
102
103    /**
104     * Prefixes indicating an absolute url
105     */
106    private static final String[] SCHEMES = { "http://", "https://" };
107
108        /**
109         * A request attribute name that indicates that a
110         * {@link org.kuali.rice.kns.exception.FileUploadLimitExceededException} has already been thrown for the
111         * request.
112         */
113        public static final String FILE_UPLOAD_LIMIT_EXCEEDED_EXCEPTION_ALREADY_THROWN = "fileUploadLimitExceededExceptionAlreadyThrown";
114
115        private static ConfigurationService configurationService;
116
117        /**
118         * Checks for methodToCall parameter, and picks off the value using set dot
119         * notation. Handles the problem of image submits.
120         * 
121         * @param request
122         * @return methodToCall String
123         */
124        public static String parseMethodToCall(ActionForm form, HttpServletRequest request) {
125                String methodToCall = null;
126
127                // check if is specified cleanly
128                if (StringUtils.isNotBlank(request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER))) {
129                        if (form instanceof KualiForm
130                                        && !((KualiForm) form).shouldMethodToCallParameterBeUsed(KRADConstants.DISPATCH_REQUEST_PARAMETER,
131                                                        request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER), request)) {
132                                throw new RuntimeException("Cannot verify that the methodToCall should be "
133                                                + request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER));
134                        }
135                        methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
136                        // include .x at the end of the parameter to make it consistent w/
137                        // other parameters
138                        request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, KRADConstants.DISPATCH_REQUEST_PARAMETER + "."
139                                        + methodToCall + IMAGE_COORDINATE_CLICKED_X_EXTENSION);
140                }
141
142                /**
143                 * The reason why we are checking for a ".x" at the end of the parameter
144                 * name: It is for the image names that in addition to sending the form
145                 * data, the web browser sends the x,y coordinate of where the user
146                 * clicked on the image. If the image input is not given a name then the
147                 * browser sends the x and y coordinates as the "x" and "y" input
148                 * fields. If the input image does have a name, the x and y coordinates
149                 * are sent using the format name.x and name.y.
150                 */
151                if (methodToCall == null) {
152                        // iterate through parameters looking for methodToCall
153                        for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
154                                String parameterName = (String) i.nextElement();
155
156                                // check if the parameter name is a specifying the methodToCall
157                                if (isMethodToCall(parameterName)) {
158                                        methodToCall = getMethodToCallSettingAttribute(form, request, parameterName);
159                                        break;
160                                }
161                                else {
162                                        // KULRICE-1218: Check if the parameter's values match (not
163                                        // just the name)
164                                        for (String value : request.getParameterValues(parameterName)) {
165                                                // adding period to startsWith check - don't want to get
166                                                // confused with methodToCallFoobar
167                                                if (isMethodToCall(value)) {
168                                                        methodToCall = getMethodToCallSettingAttribute(form, request, value);
169                                                        // why is there not a break outer loop here?
170                                                }
171                                        }
172                                }
173                        }
174                }
175
176                return methodToCall;
177        }
178
179    /**
180         * Checks if a string signifies a methodToCall string
181         * 
182         * @param string
183         *            the string to check
184         * @return true if is a methodToCall
185         */
186        private static boolean isMethodToCall(String string) {
187                // adding period to startsWith check - don't want to get confused with
188                // methodToCallFoobar
189                return string.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER + ".");
190        }
191
192        /**
193         * Parses out the methodToCall command and also sets the request attribute
194         * for the methodToCall.
195         * 
196         * @param form
197         *            the ActionForm
198         * @param request
199         *            the request to set the attribute on
200         * @param string
201         *            the methodToCall string
202         * @return the methodToCall command
203         */
204        private static String getMethodToCallSettingAttribute(ActionForm form, HttpServletRequest request, String string) {
205
206                if (form instanceof KualiForm
207                                && !((KualiForm) form).shouldMethodToCallParameterBeUsed(string, request.getParameter(string), request)) {
208                        throw new RuntimeException("Cannot verify that the methodToCall should be " + string);
209                }
210                // always adding a coordinate even if not an image
211                final String attributeValue = endsWithCoordinates(string) ? string : string
212                                + IMAGE_COORDINATE_CLICKED_X_EXTENSION;
213                final String methodToCall = StringUtils.substringBetween(attributeValue,
214                                KRADConstants.DISPATCH_REQUEST_PARAMETER + ".", ".");
215                request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, attributeValue);
216                return methodToCall;
217        }
218
219        /**
220         * Iterates through and logs (at the given level) all attributes and
221         * parameters of the given request onto the given Logger
222         * 
223         * @param request
224         * @param logger
225         */
226        public static void logRequestContents(Logger logger, Level level, HttpServletRequest request) {
227                if (logger.isEnabledFor(level)) {
228                        logger.log(level, "--------------------");
229                        logger.log(level, "HttpRequest attributes:");
230                        for (Enumeration e = request.getAttributeNames(); e.hasMoreElements();) {
231                                String attrName = (String) e.nextElement();
232                                Object attrValue = request.getAttribute(attrName);
233
234                                if (attrValue.getClass().isArray()) {
235                                        logCollection(logger, level, attrName, Arrays.asList((Object[]) attrValue));
236                                }
237                                else if (attrValue instanceof Collection) {
238                                        logCollection(logger, level, attrName, (Collection) attrValue);
239                                }
240                                else if (attrValue instanceof Map) {
241                                        logMap(logger, level, attrName, (Map) attrValue);
242                                }
243                                else {
244                                        logObject(logger, level, attrName, attrValue);
245                                }
246                        }
247
248                        logger.log(level, "--------------------");
249                        logger.log(level, "HttpRequest parameters:");
250                        for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
251                                String paramName = (String) i.nextElement();
252                                String[] paramValues = (String[]) request.getParameterValues(paramName);
253
254                                logArray(logger, level, paramName, paramValues);
255                        }
256
257                        logger.log(level, "--------------------");
258                }
259        }
260
261        private static void logArray(Logger logger, Level level, String arrayName, Object[] array) {
262                StringBuffer value = new StringBuffer("[");
263                for (int i = 0; i < array.length; ++i) {
264                        if (i > 0) {
265                                value.append(",");
266                        }
267                        value.append(array[i]);
268                }
269                value.append("]");
270
271                logThing(logger, level, arrayName, value);
272        }
273
274        private static void logCollection(Logger logger, Level level, String collectionName, Collection c) {
275                StringBuffer value = new StringBuffer("{");
276                for (Iterator i = c.iterator(); i.hasNext();) {
277                        value.append(i.next());
278                        if (i.hasNext()) {
279                                value.append(",");
280                        }
281                }
282                value.append("}");
283
284                logThing(logger, level, collectionName, value);
285        }
286
287        private static void logMap(Logger logger, Level level, String mapName, Map m) {
288                StringBuffer value = new StringBuffer("{");
289                for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
290                        Map.Entry e = (Map.Entry) i.next();
291                        value.append("('" + e.getKey() + "','" + e.getValue() + "')");
292                }
293                value.append("}");
294
295                logThing(logger, level, mapName, value);
296        }
297
298        private static void logObject(Logger logger, Level level, String objectName, Object o) {
299                logThing(logger, level, objectName, "'" + o + "'");
300        }
301
302        private static void logThing(Logger logger, Level level, String thingName, Object thing) {
303                logger.log(level, "    '" + thingName + "' => " + thing);
304        }
305
306        /**
307         * A file that is not of type text/plain or text/html can be output through
308         * the response using this method.
309         * 
310         * @param response
311         * @param contentType
312         * @param byteArrayOutputStream
313         * @param fileName
314         */
315        public static void saveMimeOutputStreamAsFile(HttpServletResponse response, String contentType,
316                        ByteArrayOutputStream byteArrayOutputStream, String fileName) throws IOException {
317
318        // If there are quotes in the name, we should replace them to avoid issues.
319        // The filename will be wrapped with quotes below when it is set in the header
320        String updateFileName;
321        if(fileName.contains("\"")) {
322            updateFileName = fileName.replaceAll("\"", "");
323        } else {
324            updateFileName =  fileName;
325        }
326
327                // set response
328                response.setContentType(contentType);
329        response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\"");
330        response.setHeader("Expires", "0");
331                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
332                response.setHeader("Pragma", "public");
333                response.setContentLength(byteArrayOutputStream.size());
334
335                // write to output
336                OutputStream outputStream = response.getOutputStream();
337                byteArrayOutputStream.writeTo(response.getOutputStream());
338                outputStream.flush();
339                outputStream.close();
340        }
341
342        /**
343         * A file that is not of type text/plain or text/html can be output through
344         * the response using this method.
345         * 
346         * @param response
347         * @param contentType
348         * @param inStream
349         * @param fileName
350         */
351        public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType,
352                        InputStream inStream, String fileName, int fileSize) throws IOException {
353
354        // If there are quotes in the name, we should replace them to avoid issues.
355        // The filename will be wrapped with quotes below when it is set in the header
356        String updateFileName;
357        if(fileName.contains("\"")) {
358            updateFileName = fileName.replaceAll("\"", "");
359        } else {
360            updateFileName =  fileName;
361        }
362
363                // set response
364                response.setContentType(contentType);
365        response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\"");
366        response.setHeader("Expires", "0");
367                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
368                response.setHeader("Pragma", "public");
369                response.setContentLength(fileSize);
370
371                // write to output
372                OutputStream out = response.getOutputStream();
373                BufferedInputStream is = new BufferedInputStream(inStream);
374                int data;
375                while ((data = is.read()) != -1) {
376                        out.write(data);
377                }
378                is.close();
379                out.flush();
380        }
381
382        /**
383         * JSTL function to return the tab state of the tab from the form.
384         * 
385         * @param form
386         * @param tabKey
387         * @return
388         */
389        public static String getTabState(KualiForm form, String tabKey) {
390                return form.getTabState(tabKey);
391        }
392
393        public static void incrementTabIndex(KualiForm form, String tabKey) {
394                form.incrementTabIndex();
395        }
396
397    /**
398     * Attempts to reopen sub tabs which would have been closed for inactive records
399     *
400     * @param sections the list of Sections whose rows and fields to set the open tab state on
401     * @param tabStates the map of tabKey->tabState.  This map will be modified to set entries to "OPEN"
402     * @param collectionName the name of the collection reopening
403     */
404    public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) {
405        for (Section section : sections) {
406            for (Row row: section.getRows()) {
407                for (Field field : row.getFields()) {
408                    if (field != null) {
409                        if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) {
410                            final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field));
411                            tabStates.put(tabKey, KualiForm.TabState.OPEN.name());
412                        }
413                    }
414                }
415            }
416        }
417    }
418
419        /**
420         * Generates a String from the title that can be used as a Map key.
421         * 
422         * @param tabTitle
423         * @return
424         */
425        public static String generateTabKey(String tabTitle) {
426                String key = "";
427                if (!StringUtils.isBlank(tabTitle)) {
428                        key = tabTitle.replaceAll("\\W", "");
429                        // if (key.length() > 25) {
430                        // key = key.substring(0, 24);
431                        // }
432                }
433
434                return key;
435        }
436
437        public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper,
438                        ActionForm form, ActionMapping mapping) {
439                Map params = new HashMap();
440
441                // Get the ActionServletWrapper from the form bean
442                // ActionServletWrapper servletWrapper = getServletWrapper();
443
444                try {
445                        CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler();
446                        if (multipartHandler != null) {
447                                // Set servlet and mapping info
448                                if (servletWrapper != null) {
449                                        // from pojoformbase
450                                        // servlet only affects tempdir on local disk
451                                        servletWrapper.setServletFor(multipartHandler);
452                                }
453                                multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
454                                // Initialize multipart request class handler
455                                multipartHandler.handleRequest(request);
456
457                                Collection<FormFile> files = multipartHandler.getFileElements().values();
458                                Enumeration keys = multipartHandler.getFileElements().keys();
459
460                                while (keys.hasMoreElements()) {
461                                        Object key = keys.nextElement();
462                                        FormFile file = (FormFile) multipartHandler.getFileElements().get(key);
463                                        long maxSize = WebUtils.getMaxUploadSize(form);
464                                        if (LOG.isDebugEnabled()) {
465                                                LOG.debug(file.getFileSize());
466                                        }
467                                        if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) {
468
469                                                GlobalVariables.getMessageMap().putError(key.toString(),
470                                                                RiceKeyConstants.ERROR_UPLOADFILE_SIZE,
471                                                                new String[] { file.getFileName(), Long.toString(maxSize) });
472
473                                        }
474                                }
475
476                                // get file elements for kualirequestprocessor
477                                if (servletWrapper == null) {
478                                        request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY,
479                                                        getFileParametersForMultipartRequest(request, multipartHandler));
480                                }
481                        }
482                }
483                catch (ServletException e) {
484                        throw new ValidationException("unable to handle multipart request " + e.getMessage(), e);
485                }
486        }
487
488        public static long getMaxUploadSize(ActionForm form) {
489                long max = 0L;
490                KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler();
491                if (form instanceof PojoFormBase) {
492                        max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes());
493                }
494                if (LOG.isDebugEnabled()) {
495                        LOG.debug("Max File Upload Size: " + max);
496                }
497                return max;
498        }
499
500        private static Map getFileParametersForMultipartRequest(HttpServletRequest request,
501                        MultipartRequestHandler multipartHandler) {
502                Map parameters = new HashMap();
503                Hashtable elements = multipartHandler.getFileElements();
504                Enumeration e = elements.keys();
505                while (e.hasMoreElements()) {
506                        String key = (String) e.nextElement();
507                        parameters.put(key, elements.get(key));
508                }
509
510                if (request instanceof MultipartRequestWrapper) {
511                        request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
512                        e = request.getParameterNames();
513                        while (e.hasMoreElements()) {
514                                String key = (String) e.nextElement();
515                                parameters.put(key, request.getParameterValues(key));
516                        }
517                }
518                else {
519                        LOG.debug("Gathering multipart parameters for unwrapped request");
520                }
521                return parameters;
522        }
523
524        // end multipart
525
526        public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) {
527                form.registerEditableProperty(editablePropertyName);
528        }
529
530        public static boolean isDocumentSession(Document document, PojoFormBase docForm) {
531                boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument;
532                boolean dataDictionarySessionDoc = false;
533                if (!sessionDoc) {
534                        DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary();
535                        if (docForm instanceof KualiMaintenanceForm) {
536                                KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm;
537                                if (dataDictionary != null) {
538                                        if (maintenanceForm.getDocTypeName() != null) {
539                        MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName());
540                                                dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument();
541                                        }
542                                }
543                        }
544                        else {
545                                if (document != null && dataDictionary != null) {
546                                        KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName());
547                                        dataDictionarySessionDoc = documentEntry.isSessionDocument();
548                                }
549                        }
550                }
551                return sessionDoc || dataDictionarySessionDoc;
552        }
553
554        public static boolean isFormSessionDocument(PojoFormBase form) {
555                Document document = null;
556                if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) {
557                        KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
558                        document = docForm.getDocument();
559                }
560                return isDocumentSession(document, form);
561        }
562
563        public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm";
564
565        public static ActionForm getKualiForm(PageContext pageContext) {
566                return getKualiForm((HttpServletRequest) pageContext.getRequest());
567        }
568
569        public static ActionForm getKualiForm(HttpServletRequest request) {
570                if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) {
571                        return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION);
572                }
573                else {
574                        final HttpSession session = request.getSession(false);
575                        return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null;
576                }
577        }
578
579        public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) {
580                if (LOG.isDebugEnabled()) {
581                        LOG.debug("isPropertyEditable(" + propertyName + ")");
582                }
583
584                boolean returnVal = editableProperties == null
585                                || editableProperties.contains(propertyName)
586                                || (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties
587                                                .contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName))));
588                if (!returnVal) {
589                        if (LOG.isDebugEnabled()) {
590                                LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: "
591                                                + editableProperties);
592                        }
593                }
594                return returnVal;
595        }
596
597        public static boolean endsWithCoordinates(String parameter) {
598                return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION)
599                                || parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
600        }
601
602        public static int getIndexOfCoordinateExtension(String parameter) {
603                int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION);
604        if (indexOfCoordinateExtension == -1) {
605            indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
606        }
607                return indexOfCoordinateExtension;
608        }
609
610    public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) {
611        boolean isHidden = false;
612        String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden");
613        if (StringUtils.isEmpty(hiddenInquiryFields)) {
614                return isHidden;
615        }
616        List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(","));
617        if (hiddenFields.contains(fieldName.trim())) {
618                isHidden = true;
619        }
620        return isHidden;
621    }
622
623    public static boolean isHiddenKimObjectType(String type, String configParameter) {
624        boolean hideType = false;
625        String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter);
626        if (StringUtils.isEmpty(hiddenTypes)) {
627                return hideType;
628        }
629        List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(","));
630        if (hiddenTypeValues.contains(type.trim())) {
631                hideType = true;
632        }
633        return hideType;
634    }
635
636        public static String getFullyMaskedValue(String className, String fieldName, Object formObject, String propertyName) {
637                String displayMaskValue = null;
638                Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
639
640                DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
641                                .getDataDictionary().getDictionaryObjectEntry(className);
642                AttributeDefinition a = entry.getAttributeDefinition(fieldName);
643
644                AttributeSecurity attributeSecurity = a.getAttributeSecurity();
645                if (attributeSecurity != null && attributeSecurity.isMask()) {
646                        MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
647                        displayMaskValue = maskFormatter.maskValue(propertyValue);
648
649                }
650                return displayMaskValue;
651        }
652
653        public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject,
654                        String propertyName) {
655                String displayMaskValue = null;
656                Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
657
658                DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
659                                .getDataDictionary().getDictionaryObjectEntry(className);
660                AttributeDefinition a = entry.getAttributeDefinition(fieldName);
661
662                AttributeSecurity attributeSecurity = a.getAttributeSecurity();
663                if (attributeSecurity != null && attributeSecurity.isPartialMask()) {
664                        MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter();
665                        displayMaskValue = partialMaskFormatter.maskValue(propertyValue);
666
667                }
668                return displayMaskValue;
669        }
670
671        public static boolean canFullyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
672                Class businessObjClass = null;
673                try {
674                        businessObjClass = Class.forName(businessObjectClassName);
675                }
676                catch (Exception e) {
677                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
678                }
679                if (form instanceof KualiDocumentFormBase) {
680                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
681                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
682                                        ((KualiDocumentFormBase) form).getDocument());
683                }
684                else {
685                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
686                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
687                }
688        }
689
690        public static boolean canPartiallyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
691                Class businessObjClass = null;
692                try {
693                        businessObjClass = Class.forName(businessObjectClassName);
694                }
695                catch (Exception e) {
696                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
697                }
698                if (form instanceof KualiDocumentFormBase) {
699                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
700                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
701                                        ((KualiDocumentFormBase) form).getDocument());
702                }
703                else {
704                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
705                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
706                }
707        }
708
709        public static boolean canAddNoteAttachment(Document document) {
710                boolean canViewNoteAttachment = false;
711                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
712                                document);
713                canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables
714                                .getUserSession().getPerson());
715                return canViewNoteAttachment;
716        }
717
718        public static boolean canViewNoteAttachment(Document document, String attachmentTypeCode) {
719                boolean canViewNoteAttachment = false;
720                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
721                                document);
722                canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables
723                                .getUserSession().getPerson());
724                return canViewNoteAttachment;
725        }
726
727        public static boolean canDeleteNoteAttachment(Document document, String attachmentTypeCode,
728                        String authorUniversalIdentifier) {
729                boolean canDeleteNoteAttachment = false;
730                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
731                                document);
732                canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false",
733                                GlobalVariables.getUserSession().getPerson());
734                if (canDeleteNoteAttachment) {
735                        return canDeleteNoteAttachment;
736                }
737                else {
738                        canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true",
739                                        GlobalVariables.getUserSession().getPerson());
740                        if (canDeleteNoteAttachment
741                                        && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
742                                canDeleteNoteAttachment = false;
743                        }
744                }
745                return canDeleteNoteAttachment;
746        }
747
748        public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) {
749                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) {
750                        LOG.error("Error map from previous request is null!");
751                        return;
752                }
753                MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap();
754                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) {
755                        // if we've switched them already, then return early and do nothing
756                        return;
757                }
758                if (!errorMapFromGlobalVariables.hasNoErrors()) {
759                        throw new RuntimeException("Cannot replace error map because it is not empty");
760                }
761                GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest());
762                GlobalVariables.getMessageMap().clearErrorPath();
763        }
764
765        /**
766         * Excapes out HTML to prevent XSS attacks, and replaces the following
767         * strings to allow for a limited set of HTML tags
768         * 
769         * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used
770         * to specify the equivalent tag in HTML (i.e. &lt;X&gt; and &lt;/X&gt;) <li>
771         * [font COLOR], where COLOR represents any valid html color (i.e. color
772         * name or hexcode preceeded by #) will be filtered into &lt;font
773         * color="COLOR"/&gt; <li>[/font] will be filtered into &lt;/font&gt; <li>
774         * [table CLASS], where CLASS gives the style class to use, will be filter
775         * into &lt;table class="CLASS"/&gt; <li>[/table] will be filtered into
776         * &lt;/table&gt; <li>[td CLASS], where CLASS gives the style class to use,
777         * will be filter into &lt;td class="CLASS"/&gt;
778         * 
779         * @param inputString
780         * @return
781         */
782        public static String filterHtmlAndReplaceRiceMarkup(String inputString) {
783                String outputString = StringEscapeUtils.escapeHtml(inputString);
784                // string has been escaped of all <, >, and & (and other characters)
785
786        Map<String, String> findAndReplacePatterns = new LinkedHashMap<String, String>();
787
788        // now replace our rice custom markup into html
789
790        // DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH
791
792        //strip out instances where javascript precedes a URL
793        findAndReplacePatterns.put("\\[a ((javascript|JAVASCRIPT|JavaScript).+)\\]", "");
794        //turn passed a href value into appropriate tag
795        findAndReplacePatterns.put("\\[a (.+)\\]", "<a href=\"$1\">");
796        findAndReplacePatterns.put("\\[/a\\]", "</a>");
797        
798                // filter any one character tags
799                findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>");
800                findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>");
801                // filter any two character tags
802                findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>");
803                findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>");
804                // filter the font tag
805                findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">");
806                findAndReplacePatterns.put("\\[/font\\]", "</font>");
807                // filter the table tag
808                findAndReplacePatterns.put("\\[table\\]", "<table>");
809                findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">");
810                findAndReplacePatterns.put("\\[/table\\]", "</table>");
811                // fiter td with class
812                findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">");
813
814                for (String findPattern : findAndReplacePatterns.keySet()) {
815                        Pattern p = Pattern.compile(findPattern);
816                        Matcher m = p.matcher(outputString);
817                        if (m.find()) {
818                                String replacePattern = findAndReplacePatterns.get(findPattern);
819                                outputString = m.replaceAll(replacePattern);
820                        }
821                }
822
823                return outputString;
824        }
825
826    /**
827         * Determines and returns the URL for question button images; looks first
828         * for a property "application.custom.image.url", and if that is missing,
829         * uses the image url returned by getDefaultButtonImageUrl()
830         * 
831         * @param imageName
832         *            the name of the image to find a button for
833         * @return the URL where question button images are located
834         */
835        public static String getButtonImageUrl(String imageName) {
836                String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString(
837                WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName);
838                if (StringUtils.isBlank(buttonImageUrl)) {
839                        buttonImageUrl = getDefaultButtonImageUrl(imageName);
840                }
841                return buttonImageUrl;
842        }
843
844    public static String getAttachmentImageForUrl(String contentType) {
845        String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType);
846        if (StringUtils.isEmpty(image)) {
847            return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT);
848        }
849        return image;
850    }
851
852        /**
853         * Generates a default button image URL, in the form of:
854         * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif
855         * 
856         * @param imageName
857         *            the image name to generate a default button name for
858         * @return the default button image url
859         */
860        public static String getDefaultButtonImageUrl(String imageName) {
861                return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME)
862                                + "buttonsmall_" + imageName + ".gif";
863        }
864
865    /**
866         * @return an implementation of the KualiConfigurationService
867         */
868        public static ConfigurationService getKualiConfigurationService() {
869                if (configurationService == null) {
870                        configurationService = CoreApiServiceLocator.getKualiConfigurationService();
871                }
872                return configurationService;
873        }
874        
875    /**
876     * Takes a string an converts the whitespace which would be ignored in an
877     * HTML document into HTML elements so the whitespace is preserved
878     * 
879     * @param startingString The string to preserve whitespace in
880     * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document
881     */
882    public static String preserveWhitespace(String startingString) {
883        String convertedString = startingString.replaceAll("\n", "<br />");
884        convertedString = convertedString.replaceAll("  ", "&nbsp;&nbsp;").replaceAll("(&nbsp; | &nbsp;)", "&nbsp;&nbsp;");
885        return convertedString;
886    }
887    
888    public static String getKimGroupDisplayName(String groupId) {
889        if(StringUtils.isBlank(groupId)) {
890                throw new IllegalArgumentException("Group ID must have a value");
891        }
892        return KimApiServiceLocator.getGroupService().getGroup(groupId).getName();
893    }
894    
895    public static String getPrincipalDisplayName(String principalId) {
896        if(StringUtils.isBlank(principalId)) {
897                throw new IllegalArgumentException("Principal ID must have a value");
898        }
899        if (KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId) == null){
900            return "";
901        }
902        else {
903            return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName();
904        }
905    }
906
907    /**
908     * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of
909     * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role.
910     *
911     * @param actionRequest the action request
912     * @return the display name for the role
913     * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE
914     */
915    public static String getRoleDisplayName(ActionRequest actionRequest) {
916        String result;
917
918        if(actionRequest == null) {
919            throw new IllegalArgumentException("actionRequest must be non-null");
920        }
921        if (RecipientType.ROLE != actionRequest.getRecipientType()) {
922            throw new IllegalArgumentException("actionRequest recipient must be a Role");
923        }
924
925        Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName());
926
927        if (role != null) {
928            result = role.getName();
929        } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) {
930            result = actionRequest.getQualifiedRoleNameLabel();
931        } else {
932            result = actionRequest.getRoleName();
933        }
934
935        return result;
936    }
937
938    /**
939     * Returns an absolute URL which is a combination of a base part plus path,
940     * or in the case that the path is already an absolute URL, the path alone
941     * @param base the url base path
942     * @param path the path to append to base
943     * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute
944     */
945    public static String toAbsoluteURL(String base, String path) {
946        boolean abs = false;
947        if (StringUtils.isBlank(path)) {
948            path = "";
949        } else {
950            for (String scheme: SCHEMES) {
951                if (path.startsWith(scheme)) {
952                    abs = true;
953                    break;
954                }
955            }
956        }
957        if (abs) {
958            return path;
959        }
960        return base + path;
961    }
962
963        public static String sanitizeBackLocation(String backLocation) {
964                if(StringUtils.isBlank(backLocation)) {
965                        return backLocation;
966                }
967                // if it's a relative URL then we should be good regardless
968                try {
969                        URI uri = new URI(backLocation);
970                        if (!uri.isAbsolute()) {
971                                return backLocation;
972                        }
973                } catch (URISyntaxException e) {
974                        // move on to the other checks if it doesn't parse up as a URI for some reason
975                }
976                Pattern pattern = Pattern.compile(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_ALLOWED_REGEX));
977                if(pattern.matcher(backLocation).matches()) {
978                        return backLocation;
979                } else {
980                        return ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_DEFAULT_URL);
981                }
982        }
983
984}