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