diff --git a/zkwebui/WEB-INF/src/metainfo/zk/lang-addon.xml b/zkwebui/WEB-INF/src/metainfo/zk/lang-addon.xml index a5e48881f3..4619a69ce6 100644 --- a/zkwebui/WEB-INF/src/metainfo/zk/lang-addon.xml +++ b/zkwebui/WEB-INF/src/metainfo/zk/lang-addon.xml @@ -24,5 +24,7 @@ Copyright (C) 2007 Ashley G Ramdass (ADempiere WebUI). - + + + diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java b/zkwebui/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java index ffe8a71d43..fa4b81eae9 100644 --- a/zkwebui/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/AdempiereWebUI.java @@ -25,15 +25,19 @@ import javax.servlet.http.HttpSession; import org.adempiere.webui.apps.AEnv; import org.adempiere.webui.component.DrillCommand; +import org.adempiere.webui.component.TokenCommand; import org.adempiere.webui.component.ZoomCommand; import org.adempiere.webui.desktop.DefaultDesktop; import org.adempiere.webui.desktop.IDesktop; +import org.adempiere.webui.event.TokenEvent; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; +import org.adempiere.webui.util.BrowserToken; import org.adempiere.webui.util.UserPreference; import org.compiere.model.MRole; import org.compiere.model.MSession; import org.compiere.model.MSysConfig; +import org.compiere.model.MUser; import org.compiere.util.CLogger; import org.compiere.util.Env; import org.compiere.util.Language; @@ -160,7 +164,7 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient Session currSess = Executions.getCurrent().getDesktop().getSession(); HttpSession httpSess = (HttpSession) currSess.getNativeSession(); - MSession.get (ctx, currSess.getRemoteAddr(), + MSession mSession = MSession.get (ctx, currSess.getRemoteAddr(), currSess.getRemoteHost(), httpSess.getId() ); //enable full interface, relook into this when doing preference @@ -239,6 +243,16 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient ExecutionCarryOver eco = new ExecutionCarryOver(this.getPage().getDesktop()); currSess.setAttribute("execution.carryover", eco); } + + if ("Y".equalsIgnoreCase(Env.getContext(ctx, BrowserToken.REMEMBER_ME))) + { + MUser user = MUser.get(ctx); + BrowserToken.save(mSession, user); + } + else + { + BrowserToken.remove(); + } } private void createDesktop() @@ -326,5 +340,6 @@ public class AdempiereWebUI extends Window implements EventListener, IWebClient new ZoomCommand("onZoom", Command.IGNORE_OLD_EQUIV); new DrillCommand("onDrillAcross", Command.IGNORE_OLD_EQUIV); new DrillCommand("onDrillDown", Command.IGNORE_OLD_EQUIV); + new TokenCommand(TokenEvent.ON_USER_TOKEN, Command.IGNORE_OLD_EQUIV); } } diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/component/TokenCommand.java b/zkwebui/WEB-INF/src/org/adempiere/webui/component/TokenCommand.java new file mode 100644 index 0000000000..a5531e33cd --- /dev/null +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/component/TokenCommand.java @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (C) 2009 Low Heng Sin * + * Copyright (C) 2009 Idalica Corporation * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.component; + +import org.adempiere.webui.event.TokenEvent; +import org.zkoss.lang.Objects; +import org.zkoss.zk.au.AuRequest; +import org.zkoss.zk.au.Command; +import org.zkoss.zk.mesg.MZk; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.UiException; +import org.zkoss.zk.ui.event.Events; + +/** + * Command class to handle user authentication token event + * @author hengsin + * + */ +public class TokenCommand extends Command { + + public TokenCommand(String id, int flags) { + super(id, flags); + } + + @Override + protected void process(AuRequest request) { + final String[] data = request.getData(); + + final Component comp = request.getComponent(); + if (comp == null) + throw new UiException(MZk.ILLEGAL_REQUEST_COMPONENT_REQUIRED, this); + + if (data == null || data.length < 2) + throw new UiException(MZk.ILLEGAL_REQUEST_WRONG_DATA, new Object[] { + Objects.toString(data), this }); + + Events.postEvent(new TokenEvent(getId(), comp, data)); + } +} diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/event/TokenEvent.java b/zkwebui/WEB-INF/src/org/adempiere/webui/event/TokenEvent.java new file mode 100644 index 0000000000..de046cc48c --- /dev/null +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/event/TokenEvent.java @@ -0,0 +1,38 @@ +/****************************************************************************** + * Copyright (C) 2009 Low Heng Sin * + * Copyright (C) 2009 Idalica Corporation * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.event; + +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.event.Event; + +/** + * Event for user authentication token + * @author hengsin + * + */ +public class TokenEvent extends Event { + + /** on loading of user token **/ + public final static String ON_USER_TOKEN = "onUserToken"; + + /** + * @param name + * @param target + * @param data + */ + public TokenEvent(String name, Component target, Object data) { + super(name, target, data); + } + +} diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/AbstractADWindowPanel.java b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/AbstractADWindowPanel.java index 54c9943481..bb6bf476ac 100644 --- a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/AbstractADWindowPanel.java +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/AbstractADWindowPanel.java @@ -1347,8 +1347,7 @@ public abstract class AbstractADWindowPanel extends AbstractUIPart implements To String msg = CLogger.retrieveErrorString(null); if (msg != null) { - FDialog.error(curWindowNo, parent, null, msg); - statusBar.setStatusLine(Msg.getMsg(Env.getCtx(), "SaveIgnored"), true, false); + statusBar.setStatusLine(Msg.getMsg(Env.getCtx(), msg), true, true); } //other error will be catch in the dataStatusChanged event } diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/LoginPanel.java b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/LoginPanel.java index 254c35dddb..23bf8efb94 100644 --- a/zkwebui/WEB-INF/src/org/adempiere/webui/panel/LoginPanel.java +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/panel/LoginPanel.java @@ -34,13 +34,17 @@ import org.adempiere.webui.component.ConfirmPanel; import org.adempiere.webui.component.Label; import org.adempiere.webui.component.Textbox; import org.adempiere.webui.component.Window; +import org.adempiere.webui.event.TokenEvent; import org.adempiere.webui.exception.ApplicationException; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ITheme; import org.adempiere.webui.theme.ThemeManager; +import org.adempiere.webui.util.BrowserToken; import org.adempiere.webui.util.UserPreference; import org.adempiere.webui.window.LoginWindow; import org.compiere.Adempiere; +import org.compiere.model.MSession; +import org.compiere.model.MUser; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.KeyNamePair; @@ -63,6 +67,7 @@ import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.util.Clients; +import org.zkoss.zul.Checkbox; import org.zkoss.zul.Comboitem; import org.zkoss.zul.Image; @@ -75,11 +80,11 @@ import org.zkoss.zul.Image; * @date July 18, 2007 */ public class LoginPanel extends Window implements EventListener -{ +{ /** - * + * */ - private static final long serialVersionUID = -2243984359460922023L; + private static final long serialVersionUID = 3992171368813030624L; private static final String RESOURCE = "org.compiere.apps.ALoginRes"; private ResourceBundle res = ResourceBundle.getBundle(RESOURCE); @@ -91,6 +96,7 @@ public class LoginPanel extends Window implements EventListener private Textbox txtPassword; private Combobox lstLanguage; private LoginWindow wndLogin; + private Checkbox chkRememberMe; public LoginPanel(Properties ctx, LoginWindow loginWindow) { @@ -102,6 +108,8 @@ public class LoginPanel extends Window implements EventListener AuFocus auf = new AuFocus(txtUserId); Clients.response(auf); + + BrowserToken.load(this.getUuid()); } private void init() @@ -167,6 +175,18 @@ public class LoginPanel extends Window implements EventListener tr.appendChild(td); td.appendChild(lstLanguage); + tr = new Tr(); + tr.setId("rowRememberMe"); + table.appendChild(tr); + td = new Td(); + tr.appendChild(td); + td.setSclass(ITheme.LOGIN_LABEL_CLASS); + td.appendChild(new Label("")); + td = new Td(); + td.setSclass(ITheme.LOGIN_FIELD_CLASS); + tr.appendChild(td); + td.appendChild(chkRememberMe); + div = new Div(); div.setSclass(ITheme.LOGIN_BOX_FOOTER_CLASS); ConfirmPanel pnlButtons = new ConfirmPanel(false); @@ -176,6 +196,34 @@ public class LoginPanel extends Window implements EventListener pnlButtons.getButton(ConfirmPanel.A_OK).setSclass(ITheme.LOGIN_BUTTON_CLASS); div.appendChild(pnlButtons); this.appendChild(div); + + this.addEventListener(TokenEvent.ON_USER_TOKEN, new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + String[] data = (String[]) event.getData(); + int AD_Session_ID = Integer.parseInt(data[0]); + MSession session = new MSession(Env.getCtx(), AD_Session_ID, null); + if (session.get_ID() == AD_Session_ID) + { + int AD_User_ID = session.getCreatedBy(); + MUser user = MUser.get(Env.getCtx(), AD_User_ID); + if (user != null && user.get_ID() == AD_User_ID) + { + String token = data[1]; + if (BrowserToken.validateToken(session, user, token)) + { + txtUserId.setValue(user.getName()); + onUserIdChange(); + txtPassword.setValue(token); + txtPassword.setAttribute("user.token.hash", token); + txtPassword.setAttribute("user.token.sid", AD_Session_ID); + chkRememberMe.setChecked(true); + } + } + } + } + }); } private void initComponents() @@ -203,7 +251,7 @@ public class LoginPanel extends Window implements EventListener txtPassword.setId("txtPassword"); txtPassword.setType("password"); txtPassword.setCols(25); - txtPassword.setMaxlength(40); +// txtPassword.setMaxlength(40); txtPassword.setWidth("220px"); lstLanguage = new Combobox(); @@ -233,6 +281,9 @@ public class LoginPanel extends Window implements EventListener break; } } + + //TODO: localization + chkRememberMe = new Checkbox("Remember Me"); } public void onEvent(Event event) @@ -255,32 +306,36 @@ public class LoginPanel extends Window implements EventListener { if(eventComp.getId().equals(txtUserId.getId())) { - String userId = txtUserId.getValue(); - if(userId != null && userId.length() > 0) - { - int AD_User_ID = DB.getSQLValue(null, "SELECT AD_User_ID FROM AD_User WHERE Name = ?", userId); - if(AD_User_ID > 0) - { - // Elaine 2009/02/06 Load preference from AD_Preference - UserPreference userPreference = SessionManager.getSessionApplication().loadUserPreference(AD_User_ID); - String initDefault = userPreference.getProperty(UserPreference.P_LANGUAGE); - for(int i = 0; i < lstLanguage.getItemCount(); i++) - { - Comboitem li = lstLanguage.getItemAtIndex(i); - if(li.getLabel().equals(initDefault)) - { - lstLanguage.setSelectedIndex(i); - languageChanged(li.getLabel()); // Elaine 2009/04/17 language changed - break; - } - } - } - } + onUserIdChange(); } } // } + private void onUserIdChange() { + String userId = txtUserId.getValue(); + if(userId != null && userId.length() > 0) + { + int AD_User_ID = DB.getSQLValue(null, "SELECT AD_User_ID FROM AD_User WHERE Name = ?", userId); + if(AD_User_ID > 0) + { + // Elaine 2009/02/06 Load preference from AD_Preference + UserPreference userPreference = SessionManager.getSessionApplication().loadUserPreference(AD_User_ID); + String initDefault = userPreference.getProperty(UserPreference.P_LANGUAGE); + for(int i = 0; i < lstLanguage.getItemCount(); i++) + { + Comboitem li = lstLanguage.getItemAtIndex(i); + if(li.getLabel().equals(initDefault)) + { + lstLanguage.setSelectedIndex(i); + languageChanged(li.getLabel()); // Elaine 2009/04/17 language changed + break; + } + } + } + } + } + private void languageChanged(String langName) { Language language = findLanguage(langName); @@ -309,6 +364,24 @@ public class LoginPanel extends Window implements EventListener Login login = new Login(ctx); String userId = txtUserId.getValue(); String userPassword = txtPassword.getValue(); + + //check is token + String token = (String) txtPassword.getAttribute("user.token.hash"); + if (token != null && token.equals(userPassword)) + { + userPassword = ""; + int AD_Session_ID = (Integer)txtPassword.getAttribute("user.token.sid"); + MSession session = new MSession(Env.getCtx(), AD_Session_ID, null); + if (session.get_ID() == AD_Session_ID) + { + MUser user = MUser.get(Env.getCtx(), session.getCreatedBy()); + if (BrowserToken.validateToken(session, user, token)) + { + userPassword = user.getPassword(); + } + } + } + KeyNamePair rolesKNPairs[] = login.getRoles(userId, userPassword); if(rolesKNPairs == null || rolesKNPairs.length == 0) throw new WrongValueException("User Id or Password invalid!!!"); @@ -348,6 +421,8 @@ public class LoginPanel extends Window implements EventListener Session currSess = Executions.getCurrent().getDesktop().getSession(); currSess.setAttribute("Check_AD_User_ID", Env.getAD_User_ID(ctx)); // End of temporary code for [ adempiere-ZK Web Client-2832968 ] User context lost? + + Env.setContext(ctx, BrowserToken.REMEMBER_ME, chkRememberMe.isChecked()); } } diff --git a/zkwebui/WEB-INF/src/org/adempiere/webui/util/BrowserToken.java b/zkwebui/WEB-INF/src/org/adempiere/webui/util/BrowserToken.java new file mode 100644 index 0000000000..4205a83129 --- /dev/null +++ b/zkwebui/WEB-INF/src/org/adempiere/webui/util/BrowserToken.java @@ -0,0 +1,136 @@ +/****************************************************************************** + * Copyright (C) 2009 Low Heng Sin * + * Copyright (C) 2009 Idalica Corporation * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.logging.Level; + +import org.compiere.Adempiere; +import org.compiere.model.MSession; +import org.compiere.model.MUser; +import org.compiere.util.CLogger; +import org.zkoss.zk.au.out.AuScript; +import org.zkoss.zk.ui.util.Clients; + +import sun.misc.BASE64Encoder; + +/** + * class to manage browser token for auto authentication + * @author hengsin + * + */ +public final class BrowserToken { + + private final static CLogger log = CLogger.getCLogger(BrowserToken.class); + + private BrowserToken() {} + + public final static String REMEMBER_ME = "Login.RememberMe"; + /** + * save session and user as client side token for future auto login + * @param session + * @param user + */ + public static void save(MSession session, MUser user) { + try + { + String home = getHomeToken(); + String hash = getPasswordHash(session, user); + String script = "adempiere.saveUserToken('" + home + "', '" + hash + "', '" + session.getAD_Session_ID() + "');"; + AuScript aus = new AuScript(null, script); + Clients.response("saveUserToken", aus); + } + catch (Exception e) + { + log.log(Level.WARNING, e.getLocalizedMessage(), e); + } + } + + /** + * remove client side token for auto login + */ + public static void remove() { + try { + String home = getHomeToken(); + String script = "adempiere.removeUserToken('" + home + "');"; + AuScript aus = new AuScript(null, script); + Clients.response("removeUserToken", aus); + } catch (Exception e) { + log.log(Level.WARNING, e.getLocalizedMessage(), e); + } + } + + /** + * load stored client side token for auto login + * @param cmpid + */ + public static void load(String cmpid) { + //remember me + try + { + String home = getHomeToken(); + String script = "adempiere.findUserToken('" + cmpid + "', '" + home + "');"; + AuScript aus = new AuScript(null, script); + Clients.response("findUserToken", aus); + } + catch (Exception e) + { + log.log(Level.WARNING, e.getLocalizedMessage(), e); + } + } + + /** + * validate a stored client side token is valid + * @param session + * @param user + * @param token + * @return true if token is valid + */ + public static boolean validateToken(MSession session, MUser user, String token) { + try + { + String hash = getPasswordHash(session, user); + return hash.equals(token); + } + catch (Exception e) + { + log.log(Level.WARNING, e.getLocalizedMessage(), e); + } + return false; + } + + private static String getHomeToken() throws UnsupportedEncodingException { + String home = Adempiere.getAdempiereHome(); + BASE64Encoder encoder = new BASE64Encoder(); + home = encoder.encode(home.getBytes("UTF-8")); + home = URLEncoder.encode(home, "UTF-8"); + return home; + } + + private static String getPasswordHash(MSession session, MUser user) throws UnsupportedEncodingException, NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + BASE64Encoder encoder = new BASE64Encoder(); + digest.reset(); + digest.update(session.getWebSession().getBytes("UTF-8")); + String password = user.getPassword(); + byte[] input = digest.digest(password.getBytes("UTF-8")); + String hash = encoder.encode(input); + hash = URLEncoder.encode(hash, "UTF-8"); + + return hash; + } +} diff --git a/zkwebui/js/persist-min.js b/zkwebui/js/persist-min.js new file mode 100644 index 0000000000..5a46b42e3f --- /dev/null +++ b/zkwebui/js/persist-min.js @@ -0,0 +1,68 @@ + +(function(){if(window.google&&google.gears) +return;var F=null;if(typeof GearsFactory!='undefined'){F=new GearsFactory();}else{try{F=new ActiveXObject('Gears.Factory');if(F.getBuildInfo().indexOf('ie_mobile')!=-1) +F.privateSetGlobalObject(this);}catch(e){if((typeof navigator.mimeTypes!='undefined')&&navigator.mimeTypes["application/x-googlegears"]){F=document.createElement("object");F.style.display="none";F.width=0;F.height=0;F.type="application/x-googlegears";document.documentElement.appendChild(F);}}} +if(!F) +return;if(!window.google) +google={};if(!google.gears) +google.gears={factory:F};})();Persist=(function(){var VERSION='0.2.0',P,B,esc,init,empty,ec;ec=(function(){var EPOCH='Thu, 01-Jan-1970 00:00:01 GMT',RATIO=1000*60*60*24,KEYS=['expires','path','domain'],esc=escape,un=unescape,doc=document,me;var get_now=function(){var r=new Date();r.setTime(r.getTime());return r;} +var cookify=function(c_key,c_val){var i,key,val,r=[],opt=(arguments.length>2)?arguments[2]:{};r.push(esc(c_key)+'='+esc(c_val));for(i=0;i2)?arguments[2]:{},now=get_now(),expire_at,cfg={};if(opt.expires){opt.expires*=RATIO;cfg.expires=new Date(now.getTime()+opt.expires);cfg.expires=cfg.expires.toGMTString();} +var keys=['path','domain','secure'];for(i=0;i0) +fn.call(scope,true,r.rows.item(0)['v']);else +fn.call(scope,false,null);});});},set:function(key,val,fn,scope){var rm_sql=C.sql.remove,sql=C.sql.set;this.transaction(function(t){t.executeSql(rm_sql,[key],function(){t.executeSql(sql,[key,val],function(t,r){if(fn) +fn.call(scope||this,true,val);});});});return val;},remove:function(key,fn,scope){var get_sql=C.sql.get;sql=C.sql.remove;this.transaction(function(t){if(fn){t.executeSql(get_sql,[key],function(t,r){if(r.rows.length>0){var val=r.rows.item(0)['v'];t.executeSql(sql,[key],function(t,r){fn.call(scope||this,true,val);});}else{fn.call(scope||this,false,null);}});}else{t.executeSql(sql,[key]);}});}}},globalstorage:{size:5*1024*1024,test:function(){return window.globalStorage?true:false;},methods:{key:function(key){return esc(this.name)+esc(key);},init:function(){alert('domain = '+this.o.domain);this.store=globalStorage[this.o.domain];},get:function(key,fn,scope){key=this.key(key);if(fn) +fn.call(scope||this,true,this.store.getItem(key));},set:function(key,val,fn,scope){key=this.key(key);this.store.setItem(key,val);if(fn) +fn.call(scope||this,true,val);},remove:function(key,fn,scope){var val;key=this.key(key);val=this.store[key];this.store.removeItem(key);if(fn) +fn.call(scope||this,(val!==null),val);}}},localstorage:{size:-1,test:function(){return window.localStorage?true:false;},methods:{key:function(key){return esc(this.name)+esc(key);},init:function(){this.store=localStorage;},get:function(key,fn,scope){key=this.key(key);if(fn) +fn.call(scope||this,true,this.store.getItem(key));},set:function(key,val,fn,scope){key=this.key(key);this.store.setItem(key,val);if(fn) +fn.call(scope||this,true,val);},remove:function(key,fn,scope){var val;key=this.key(key);val=this.getItem(key);this.store.removeItem(key);if(fn) +fn.call(scope||this,(val!==null),val);}}},ie:{prefix:'_persist_data-',size:64*1024,test:function(){return window.ActiveXObject?true:false;},make_userdata:function(id){var el=document.createElement('div');el.id=id;el.style.display='none';el.addBehavior('#default#userdata');document.body.appendChild(el);return el;},methods:{init:function(){var id=B.ie.prefix+esc(this.name);this.el=B.ie.make_userdata(id);if(this.o.defer) +this.load();},get:function(key,fn,scope){var val;key=esc(key);if(!this.o.defer) +this.load();val=this.el.getAttribute(key);if(fn) +fn.call(scope||this,val?true:false,val);},set:function(key,val,fn,scope){key=esc(key);this.el.setAttribute(key,val);if(!this.o.defer) +this.save();if(fn) +fn.call(scope||this,true,val);},remove:function(key,fn,scope){var val;key=esc(key);if(!this.o.defer) +this.load();val=this.el.getAttribute(key);this.el.removeAttribute(key);if(!this.o.defer) +this.save();if(fn) +fn.call(scope||this,val?true:false,val);},load:function(){this.el.load(esc(this.name));},save:function(){this.el.save(esc(this.name));}}},cookie:{delim:':',size:4000,test:function(){return P.Cookie.enabled?true:false;},methods:{key:function(key){return this.name+B.cookie.delim+key;},get:function(key,fn,scope){var val;key=this.key(key);val=ec.get(key);if(fn) +fn.call(scope||this,val!=null,val);},set:function(key,val,fn,scope){key=this.key(key);ec.set(key,val,this.o);if(fn) +fn.call(scope||this,true,val);},remove:function(key,fn,scope){var val;key=this.key(key);val=ec.remove(key) +if(fn) +fn.call(scope||this,val!=null,val);}}},flash:{test:function(){if(typeof deconcept=="undefined"||!deconcept||!deconcept.SWFObjectUtil) +return false;var major=deconcept.SWFObjectUtil.getPlayerVersion().major;return(major>=8)?true:false;},methods:{init:function(){if(!B.flash.el){var o,key,el,cfg=C.flash;el=document.createElement('div');el.id=cfg.div_id;document.body.appendChild(el);o=new deconcept.SWFObject(this.o.swf_path||cfg.path,cfg.id,cfg.size.w,cfg.size.h,'8');for(key in cfg.args) +o.addVariable(key,cfg.args[key]);o.write(el);B.flash.el=document.getElementById(cfg.id);} +this.el=B.flash.el;},get:function(key,fn,scope){var val;key=esc(key);val=this.el.get(this.name,key);if(fn) +fn.call(scope||this,val!==null,val);},set:function(key,val,fn,scope){var old_val;key=esc(key);old_val=this.el.set(this.name,key,val);if(fn) +fn.call(scope||this,true,val);},remove:function(key,fn,scope){var val;key=esc(key);val=this.el.remove(this.name,key);if(fn) +fn.call(scope||this,true,val);}}}};var init=function(){var i,l,b,key,fns=C.methods,keys=C.search_order;for(i=0,l=fns.length;i