001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.tag;
020
021import java.io.IOException;
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.Map;
025import java.util.Map.Entry;
026
027import lucee.commons.lang.IDGenerator;
028import lucee.commons.lang.StringUtil;
029import lucee.runtime.exp.ApplicationException;
030import lucee.runtime.exp.PageException;
031import lucee.runtime.ext.tag.BodyTagImpl;
032import lucee.runtime.functions.dynamicEvaluation.DE;
033import lucee.runtime.functions.string.JSStringFormat;
034import lucee.runtime.net.http.ReqRspUtil;
035import lucee.runtime.op.Caster;
036import lucee.runtime.op.Decision;
037import lucee.runtime.tag.util.DeprecatedUtil;
038import lucee.runtime.type.Collection.Key;
039import lucee.runtime.type.Struct;
040import lucee.runtime.type.StructImpl;
041import lucee.runtime.type.util.KeyConstants;
042
043/**
044 * implementation of the form tag 
045 */
046public final class Form extends BodyTagImpl {
047
048        public static final int FORMAT_HTML = 0;
049    public static final int FORMAT_FLASH = 1;
050    public static final int FORMAT_XML = 2;
051        private static final String DEFAULT_ARCHIVE = "/lucee/lucee-applet.cfm";
052        
053        private static final int WMODE_WINDOW = 0;
054        private static final int WMODE_TRANSPARENT = 1;
055        private static final int WMODE_OPAQUE = 2;
056
057
058        //private static int _count=0;
059        private int count=0;
060    private String name;
061    private String action;
062    private boolean preserveData;
063    private String onsubmit;
064    private String onreset;
065    private String onload;
066    private String passthrough;
067    private String method="post";
068    private String scriptSrc;
069    
070    private int format=FORMAT_HTML;
071    
072    
073    
074    private Struct attributes=new StructImpl();
075    private Map inputs=new LinkedHashMap();
076        private String strSkin;
077        private String archive=null;
078        private String codebase=null; // TODO muss einen wert haben -> /lucee - context/classes/cf-j2re-win.cab.cfm
079        private String height="100%";
080        private String width="100%";
081        private boolean preloader=true;
082        private int timeout=0;
083        private int wMode=WMODE_WINDOW;
084        private boolean accessible=false;
085        private String onError;
086      
087
088    @Override
089    public void release() {
090        super.release();
091        name=null;
092        action=null;
093        preserveData=false;
094        attributes.clear();
095        onsubmit=null;
096        onreset=null;
097        onload=null;
098        passthrough=null;
099        method="post";
100        scriptSrc=null;
101        strSkin=null;
102        archive=null;
103        codebase=null;
104        height="100%";
105        width="100%";
106        preloader=true;
107        timeout=0;
108        wMode=WMODE_WINDOW;
109        accessible=false;
110        onError=null;
111        inputs.clear();
112    }
113    
114
115    /**
116     * @param enablecab The enablecab to set.
117     * @throws ApplicationException
118     */
119    public void setEnablecab(boolean enablecab) {
120                DeprecatedUtil.tagAttribute(pageContext,"Form", "enablecab");
121        
122    }
123    /**
124     * @param method The method to set.
125     * @throws ApplicationException 
126     */
127    public void setMethod(String method) throws ApplicationException {
128        method=method.trim().toLowerCase();
129        if(method.equals("get") || method.equals("post"))
130            this.method = method;
131        else
132            throw new ApplicationException("invalid value for attribute method from tag form, attribute can have value [get,post] but now is ["+method+"]");
133    }    
134    
135
136        /**
137         * @param format the format to set
138         * @throws ApplicationException 
139         */
140        public void setFormat(String strFormat) throws ApplicationException {
141                strFormat=strFormat.trim().toLowerCase();
142                        
143                if("html".equals(strFormat))                    format=FORMAT_HTML;
144                else if("xml".equals(strFormat))                format=FORMAT_XML;
145                else if("flash".equals(strFormat))              format=FORMAT_FLASH;
146                
147                else throw new ApplicationException("invalid value ["+strFormat+"] for attribute format, for this attribute only the following values are supported " +
148                                "[xml, html, flash]");
149        
150                if(format!=FORMAT_HTML)
151                        throw new ApplicationException("format ["+strFormat+"] is not supported, only the following formats are supported [html]");
152                // TODO support other formats
153        }
154        
155        /**
156     * @param skin The skin to set.
157     */
158    public void setSkin(String strSkin) {
159                this.strSkin=strSkin;
160        }
161
162        /**
163     * @param action The action to set.
164     */
165    public void setAction(String action) {
166        this.action = action;
167    }   
168    
169    /**
170     * @param scriptSrc The scriptSrc to set.
171     */
172    public void setScriptsrc(String scriptSrc) {
173        this.scriptSrc = scriptSrc;
174    }
175    
176    /**
177     * @param archive The archive to set.
178     * @throws ApplicationException
179     */
180    public void setArchive(String archive) {
181        archive=archive.trim().toLowerCase().replace('\\', '/'); 
182        if(!StringUtil.startsWith(archive, '/')) archive="/"+archive;
183        this.archive = archive;
184    }
185    /**
186     * @param codebase The codebase to set.
187     * @throws ApplicationException
188     */
189    public void setCodebase(String codebase) {
190        this.codebase = codebase;
191    }
192    /**
193     * @param cssclass The cssclass to set.
194     */
195    public void setClass(String cssclass) {
196        attributes.setEL("class",cssclass);
197    }
198    /**
199     * @param cssstyle The cssstyle to set.
200     */
201    public void setStyle(String cssstyle) {
202        attributes.setEL("style",cssstyle);
203    }
204    /**
205     * @param enctype The enctype to set.
206     */
207    public void setEnctype(String enctype) {
208        attributes.setEL("enctype",enctype);
209    }
210    /**
211     * @param id The id to set.
212     */
213    public void setId(String id) {
214        attributes.setEL("id",id);
215    }
216    
217    public void setAccept(String accept) {
218        attributes.setEL("accept",accept);
219    }
220    
221    public void setAcceptcharset(String accept_charset) {
222        attributes.setEL("accept-charset",accept_charset);
223    }
224    
225    public void setAccept_charset(String accept_charset) {
226        attributes.setEL("accept-charset",accept_charset);
227    }
228    /**
229     * @param name The name to set.
230     * @throws ApplicationException 
231     */
232    public void setName(String name) throws ApplicationException {
233        this.name=name;
234        checkName(name);
235    }
236    
237    private static void checkName(String name) throws ApplicationException {
238                if(name.length()==0)return;
239                int len=name.length();
240                
241                for(int pos=0;pos<len;pos++) {
242                        char c=name.charAt(pos);
243                        if((c>='a' && c<='z')||(c>='A' && c<='Z')||(c>='0' && c<='9')||(c=='_')||(c=='-')||(c==':')||(c=='.'))
244                                continue;
245                        throw new ApplicationException("value of attribute name ["+name+"] is invalid, only the following characters are allowed [a-z,A-Z,0-9,-,_,:,.]");
246                }
247        }
248    
249    /**
250     * @param onreset The onreset to set.
251     */
252    public void setOnreset(String onreset) {
253        this.onreset=onreset;
254    }
255
256    /**
257     * @param onreset The onreset to set.
258     */
259    public void setOnload(String onload) {
260        this.onload=onload;
261    }
262    
263    /**
264     * @param onsubmit The onsubmit to set.
265     */
266    public void setOnsubmit(String onsubmit) {
267        this.onsubmit = onsubmit;
268    }
269
270    public void setOnerror(String onError) {
271        this.onError=onError;
272    }
273
274    public void setOnclick(String onclick) {
275        attributes.setEL("onclick",onclick);
276    }
277
278    public void setOndblclick(String ondblclick) {
279        attributes.setEL("ondblclick",ondblclick);
280    }
281
282    public void setOnmousedown(String onmousedown) {
283        attributes.setEL("onmousedown",onmousedown);
284    }
285
286    public void setOnmouseup(String onmouseup) {
287        attributes.setEL("onmouseup",onmouseup);
288    }
289
290    public void setOnmouseover(String onmouseover) {
291        attributes.setEL("onmouseover",onmouseover);
292    }
293
294    public void setOnmousemove(String onmousemove) {
295        attributes.setEL("onmousemove",onmousemove);
296    }
297
298    public void setOnmouseout(String onmouseout) {
299        attributes.setEL("onmouseout",onmouseout);
300    }
301
302    public void setOnkeypress(String onkeypress) {
303        attributes.setEL("onkeypress",onkeypress);
304    }
305
306    public void setOnkeydown(String onkeydown) {
307        attributes.setEL("onkeydown",onkeydown);
308    }
309
310    public void setOnkeyup(String onkeyup) {
311        attributes.setEL("onkeyup",onkeyup);
312    }
313
314    /**
315     * @param passthrough The passthrough to set.
316     * @throws PageException
317     */
318    public void setPassthrough(Object passthrough) throws PageException {
319        if(passthrough instanceof Struct) {
320            Struct sct = (Struct) passthrough;
321            //lucee.runtime.type.Collection.Key[] keys=sct.keys();
322            //lucee.runtime.type.Collection.Key key;
323            Iterator<Entry<Key, Object>> it = sct.entryIterator();
324            Entry<Key, Object> e;
325            while(it.hasNext()) {
326                e = it.next();
327                attributes.setEL(e.getKey(),e.getValue());
328            }
329        }
330        else this.passthrough = Caster.toString(passthrough);
331    }
332    /**
333     * @param preserveData The preserveData to set.
334     * @throws ApplicationException
335     */
336    public void setPreservedata(boolean preserveData) throws ApplicationException {
337        //this.preserveData = preserveData;
338        if(preserveData)throw new ApplicationException("attribute preserveData for tag form is not supported at the moment");
339    }
340    /**
341     * @param target The target to set.
342     */
343    public void setTarget(String target) {
344        attributes.setEL("target",target);
345    }
346
347    public void setTitle(String title) {
348        attributes.setEL("title",title);
349    }
350
351    public void setDir(String dir) {
352        attributes.setEL("dir",dir);
353    }
354
355    public void setLang(String lang) {
356        attributes.setEL("lang",lang);
357    }
358
359        /**
360         * @param height the height to set
361         */
362        public void setHeight(String height) {
363                this.height=height;
364        }
365
366        /**
367         * @param width the width to set
368         */
369        public void setWidth(String width) {
370                this.width=width;
371        }
372
373        /**
374         * @param preloader the preloader to set
375         */
376        public void setPreloader(boolean preloader) {
377                this.preloader=preloader;
378        }
379
380        /**
381         * @param timeout the timeout to set
382         */
383        public void setTimeout(double timeout) {
384                this.timeout=(int)timeout;
385        }
386
387        /**
388         * @param strWMode the wmode to set
389         * @throws ApplicationException 
390         */
391        public void setWmode(String strWMode) throws ApplicationException {
392                strWMode=strWMode.toLowerCase().trim();
393                if("window".equals(strWMode))                   wMode=WMODE_WINDOW;
394                else if("transparent".equals(strWMode)) wMode=WMODE_TRANSPARENT;
395                else if("opaque".equals(strWMode))              wMode=WMODE_OPAQUE;
396                
397                else throw new ApplicationException("invalid value ["+strWMode+"] for attribute wmode, for this attribute only the following values are supported " +
398                                "[window, transparent, opaque]");
399        }
400
401        /**
402         * @param strWMode the wmode to set
403         */
404        public void setAccessible(boolean accessible) {
405                this.accessible=accessible;
406        }
407    
408    @Override
409    public int doStartTag() throws PageException {
410        
411        try {
412            return _doStartTag();
413        } 
414        catch (IOException e) {
415            throw Caster.toPageException(e);
416        }
417    }
418    private int _doStartTag() throws PageException, IOException {
419        String contextPath = pageContext. getHttpServletRequest().getContextPath();
420        if(contextPath==null) contextPath="";
421        if(archive==null) {
422                archive=contextPath+DEFAULT_ARCHIVE;
423        }
424        count=IDGenerator.intId();
425        
426        if(name==null) {
427            name="CFForm_"+count;
428        }
429        attributes.setEL(KeyConstants._name,name);
430        
431        if(action==null)        action=ReqRspUtil.self(pageContext. getHttpServletRequest());
432        attributes.setEL(KeyConstants._action,action);
433        
434        String suffix=StringUtil.isEmpty(name)?""+count:StringUtil.toVariableName(name);
435        String funcName="lucee_form_"+count;
436        
437        String checkName="_CF_check"+suffix;
438        String resetName="_CF_reset"+suffix;
439        String loadName="_CF_load"+suffix;
440        
441        
442        
443        //boolean hasListener=false;
444        if(onsubmit==null) attributes.setEL("onsubmit","return "+funcName+".check();");
445        else {
446            attributes.setEL("onsubmit","return "+checkName+"();");
447            //hasListener=true;
448        }
449        if(onreset!=null) {
450            attributes.setEL("onreset",resetName+"();");
451            //hasListener=true;
452        }
453        if(onload!=null) {
454            attributes.setEL("onload",loadName+"();");
455            //hasListener=true;
456        }
457        
458        if(scriptSrc==null)scriptSrc=contextPath+"/lucee/form.cfm";
459        attributes.setEL("method",method);
460        
461        pageContext.forceWrite("<script language = \"JavaScript\" type=\"text/javascript\" src=\""+scriptSrc+"\"></script>");
462        //if(hasListener) {
463            pageContext.forceWrite("<script language = \"JavaScript\" type=\"text/javascript\">\n");
464            if(onsubmit!=null)pageContext.forceWrite("function "+checkName+"() { if("+funcName+".check()){"+onsubmit+"\nreturn true;}else {return false;}}\n");
465            else pageContext.forceWrite("function "+checkName+"() { return "+funcName+".check();}\n");
466             
467            
468            if(onreset!=null)pageContext.forceWrite("function "+resetName+"() {"+onreset+"\n}\n");
469            if(onload!=null)pageContext.forceWrite("function "+loadName+"() {"+onload+"\n}\n");
470            pageContext.forceWrite("\n</script>");
471            
472        //}
473        pageContext.forceWrite("<form");
474        
475        Iterator<Entry<Key, Object>> it = attributes.entryIterator();
476        Entry<Key, Object> e;
477        while(it.hasNext()) {
478                e = it.next();
479            pageContext.forceWrite(" ");
480            pageContext.forceWrite(e.getKey().getString());
481            pageContext.forceWrite("=");
482            pageContext.forceWrite(de(Caster.toString(e.getValue())));
483            
484        }
485        
486        if(passthrough!=null) {
487            pageContext.forceWrite(" ");
488            pageContext.forceWrite(passthrough);
489        }
490        pageContext.forceWrite(">");
491        
492        return EVAL_BODY_INCLUDE;
493    }
494
495    @Override
496    public int doEndTag() throws PageException {
497        String funcName="lucee_form_"+count;
498        try {
499            pageContext.forceWrite("</form><!-- name:"+name+" --><script>\n");
500            pageContext.forceWrite(funcName+"=new LuceeForms("+js(name)+","+js(onError)+");\n");
501            Iterator it = inputs.keySet().iterator();
502            while(it.hasNext()) {
503                InputBean input=(InputBean) inputs.get(it.next());
504                
505                pageContext.forceWrite(funcName+".addInput("+js(input.getName())+","+input.isRequired()+
506                        ","+input.getType()+","+input.getValidate()+
507                        ","+(input.getPattern())+","+js(input.getMessage())+
508                        ","+js(input.getOnError())+","+js(input.getOnValidate())+
509                        ","+range(input.getRangeMin())+","+range(input.getRangeMax())+
510                        ","+(input.getMaxLength())+
511                ");\n");
512            }
513            pageContext.forceWrite("</script>");    
514        } 
515        catch (IOException e) {
516            throw Caster.toPageException(e);
517        }
518        return EVAL_PAGE;
519    }
520    
521    private String range(double range) {
522        if(!Decision.isValid(range)) return "null";
523        return Caster.toString(range);
524    }
525
526
527    private String de(String str) {
528        return DE.call(pageContext,str);
529    }
530    private String js(String str) {
531        if(str==null) return "null";
532        return "'"+JSStringFormat.call(pageContext,str)+"'";
533    }
534
535
536    /**
537     * @param input
538     * @throws ApplicationException
539     */
540    public void setInput(InputBean input) throws ApplicationException {
541        if(input.getType()==Input.TYPE_TEXT || input.getType()==Input.TYPE_PASSWORD) {
542            InputBean i=(InputBean)inputs.get(input.getName().toLowerCase());
543            if(i!=null && (i.getType()==Input.TYPE_TEXT || i.getType()==Input.TYPE_PASSWORD)) {
544                throw new ApplicationException("duplicate input field ["+i.getName()+"] for form","a text or password field must be unique");
545            }
546        }
547        
548        //if(StringUtil.isEmpty(input.getOnError(),true) && !StringUtil.isEmpty(onError,true))
549        //      input.setOnError(onError);
550        
551        inputs.put(input.getName().toLowerCase(),input);
552    }
553
554    /**
555     * @return Returns the name.
556     */
557    public String getName() {
558        return name;
559    }
560    /**
561     * @return Returns the onsubmit.
562     */
563    public String getOnsubmit() {
564        return onsubmit;
565    }
566
567        public int getFormat() {
568                return format;
569        }
570
571
572        public String getArchive() {
573                return archive;
574        }
575
576
577        public String getCodebase() {
578                return codebase;
579        }
580}