From 2aae5c47eb7ff3de805dde0cda724d994c2fd0ea Mon Sep 17 00:00:00 2001 From: "Eric J. Bowersox" Date: Mon, 16 Jun 2003 23:59:30 +0000 Subject: [PATCH] added community logo support and the logo on top of the community left menu; fixed some bugs with menu handling --- conf-sso/sp/dynamo.xml | 6 +- conf/dynamo-venice.xml | 6 +- conf/venice-db-init-mysql.sql | 9 +- .../venice/content/CommunityLogoRenderer.java | 318 ++++++++++++++++++ .../venice/content/UserPhotoRenderer.java | 2 +- .../venice/dialog/CommunityLogoField.java | 163 +++++++++ .../dialog/VeniceDialogItemFactory.java | 2 + .../venice/dialog/VeniceDialogManager.java | 1 + .../dialog/VeniceDialogMessages.properties | 1 + .../venice/frame/FrameAssembler.java | 159 ++++++++- .../venice/frame/FrameMessages.properties | 1 + .../venice/frame/FrameWrapper.java | 138 ++++++++ .../venice/script/LibraryVenice.java | 1 - .../dialogs/comm/community_profile.dlg.xml | 3 +- venice-data/scripts/comm/admin/profile.js | 32 +- .../scripts/comm/admin/profile_logo.js | 103 ++++++ venice-data/velocity/comm/community_logo.vm | 43 +++ venice-data/velocity/user/user_photo.vm | 4 + venice-web/images/community_other.jpg | Bin 0 -> 2813 bytes 19 files changed, 981 insertions(+), 11 deletions(-) create mode 100644 src/venice-base/com/silverwrist/venice/content/CommunityLogoRenderer.java create mode 100644 src/venice-base/com/silverwrist/venice/dialog/CommunityLogoField.java create mode 100644 src/venice-base/com/silverwrist/venice/frame/FrameWrapper.java create mode 100644 venice-data/scripts/comm/admin/profile_logo.js create mode 100644 venice-data/velocity/comm/community_logo.vm create mode 100644 venice-web/images/community_other.jpg diff --git a/conf-sso/sp/dynamo.xml b/conf-sso/sp/dynamo.xml index 0bc5761..58f22ec 100644 --- a/conf-sso/sp/dynamo.xml +++ b/conf-sso/sp/dynamo.xml @@ -167,7 +167,7 @@ - + @@ -182,6 +182,10 @@ + + + + diff --git a/conf/dynamo-venice.xml b/conf/dynamo-venice.xml index 3f08abe..b8a365e 100644 --- a/conf/dynamo-venice.xml +++ b/conf/dynamo-venice.xml @@ -167,7 +167,7 @@ - + @@ -182,6 +182,10 @@ + + + + diff --git a/conf/venice-db-init-mysql.sql b/conf/venice-db-init-mysql.sql index 4000e0c..2105988 100644 --- a/conf/venice-db-init-mysql.sql +++ b/conf/venice-db-init-mysql.sql @@ -618,7 +618,11 @@ INSERT INTO globalprop (nsid, prop_name, prop_value) VALUES (15, 'rules', '!Please treat one another with courtesy and respect.'), (15, 'language', '_LANG:en-US' ), (15, 'country', '_CTRY:US' ), - (16, 'options', '_OS:A' ); + (16, 'options', '_OS:A' ), + (16, 'community.logo.width', 'I110' ), + (16, 'community.logo.height', 'I65' ), + (16, 'community.nologo.url', '!community_other.jpg' ), + (16, 'community.nologo.url.type', '!IMAGE' ); # Initial global blocks setup INSERT INTO globalblock (nsid, block_name, block) VALUES @@ -842,7 +846,8 @@ INSERT INTO auditevent (eventid, event_nsid, event_name, descr) VALUES # Insert some image types. INSERT INTO imagetype (typecode, nsid, name) VALUES - (1, 11, 'user.photo'); + (1, 11, 'user.photo' ), + (2, 15, 'community.logo'); # Create the "members" group for the initial community. # (GID 4) diff --git a/src/venice-base/com/silverwrist/venice/content/CommunityLogoRenderer.java b/src/venice-base/com/silverwrist/venice/content/CommunityLogoRenderer.java new file mode 100644 index 0000000..40e715f --- /dev/null +++ b/src/venice-base/com/silverwrist/venice/content/CommunityLogoRenderer.java @@ -0,0 +1,318 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.content; + +import java.io.*; +import org.apache.log4j.Logger; +import org.w3c.dom.*; +import com.silverwrist.util.xml.*; +import com.silverwrist.dynamo.event.*; +import com.silverwrist.dynamo.except.*; +import com.silverwrist.dynamo.iface.*; +import com.silverwrist.dynamo.util.*; +import com.silverwrist.venice.VeniceNamespaces; +import com.silverwrist.venice.iface.*; + +public class CommunityLogoRenderer + implements NamedObject, ComponentInitialize, ComponentShutdown, DynamicUpdateListener, ServiceProvider, + RenderImage +{ + /*-------------------------------------------------------------------------------- + * Internal rendering object + *-------------------------------------------------------------------------------- + */ + + private class RenderObject implements SelfRenderable + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + private String m_url; + + /*==================================================================== + * Constructor + *==================================================================== + */ + + RenderObject(String url) + { + m_url = url; + + } // end constructor + + /*==================================================================== + * Implementations from interface SelfRenderable + *==================================================================== + */ + + public void render(SelfRenderControl control) throws IOException, RenderingException + { + renderImageTag(control.getTextRender(),m_url); + + } // end render + + } // end class RenderObject + + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Logger logger = Logger.getLogger(CommunityLogoRenderer.class); + + private static final String NAMESPACE = VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE; + private static final String PROP_WIDTH = "community.logo.width"; + private static final String PROP_HEIGHT = "community.logo.height"; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private String m_name = null; // object name + private ObjectProvider m_props; // pointer to global property provider + private ComponentShutdown m_shut_event; // event handler shutdown + private int m_width; // width for community logos + private int m_height; // height for community logos + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public CommunityLogoRenderer() + { // do nothing + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + private final int getInitInteger(String name) throws ConfigException + { + try + { // call through to the property provider + return ((Integer)(m_props.getObject(NAMESPACE,name))).intValue(); + + } // end try + catch (NoSuchObjectException e) + { // the property value isn't present? yIkes! + ConfigException ce = new ConfigException(CommunityLogoRenderer.class,"ContentMessages","prop.missing",e); + ce.setParameter(0,name); + throw ce; + + } // end catch + catch (ClassCastException e) + { // not an Integer? for shame! + ConfigException ce = new ConfigException(CommunityLogoRenderer.class,"ContentMessages","prop.wrongType",e); + ce.setParameter(0,name); + throw ce; + + } // end catch + + } // end getInitInteger + + /*-------------------------------------------------------------------------------- + * Implementations from interface NamedObject + *-------------------------------------------------------------------------------- + */ + + public String getName() + { + return m_name; + + } // end getName + + /*-------------------------------------------------------------------------------- + * Implementations from interface ComponentInitialize + *-------------------------------------------------------------------------------- + */ + + /** + * Initialize the component. + * + * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this + * particular component. This is to be considered "read-only" by the component. + * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} + * which provides initialization services to the component. This will include an implementation + * of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to + * get information about other objects previously initialized by the application. + * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component + * configuration. + */ + public void initialize(Element config_root, ServiceProvider services) throws ConfigException + { + logger.info("CommunityLogoRenderer initializing"); + + XMLLoader loader = XMLLoader.get(); + String gprops = null; + try + { // verify the right node name + loader.verifyNodeName(config_root,"object"); + + // get the object's name + m_name = loader.getAttribute(config_root,"name"); + + // get the name of the global properties object + DOMElementHelper config_root_h = new DOMElementHelper(config_root); + Element foo = loader.getSubElement(config_root_h,"global-properties"); + gprops = loader.getAttribute(foo,"object"); + + } // end try + catch (XMLLoadException e) + { // error loading XML config data + logger.fatal("XML loader exception in CommunityLogoRenderer",e); + throw new ConfigException(e); + + } // end catch + + // Get the standard properties provider. + ServiceProvider gdm_sp = + (ServiceProvider)(GetObjectUtils.getDynamoComponent(services,ServiceProvider.class,gprops)); + try + { // get the "properties" service from that object + m_props = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"properties")); + + } // end try + catch (NoSuchServiceException e) + { // this shouldn't happen, but... + logger.fatal("Unable to find global properties object \"" + gprops + "\"",e); + throw new ConfigException(CommunityLogoRenderer.class,"ContentMessages","init.noProp",e); + + } // end catch + + // Get the initial property values for our properties. + m_width = getInitInteger(PROP_WIDTH); + m_height = getInitInteger(PROP_HEIGHT); + + // Register with the event listener to monitor those property values. + EventListenerRegistration reg = + (EventListenerRegistration)(services.queryService(EventListenerRegistration.class)); + m_shut_event = reg.registerDynamicUpdateListener(GlobalPropertyUpdateEvent.class,this); + + } // end initialize + + /*-------------------------------------------------------------------------------- + * Implementations from interface ComponentShutdown + *-------------------------------------------------------------------------------- + */ + + public void shutdown() + { + m_shut_event.shutdown(); + m_shut_event = null; + m_props = null; + + } // end shutdown + + public void updateReceived(DynamicUpdateEvent evt) + { + GlobalPropertyUpdateEvent event = (GlobalPropertyUpdateEvent)evt; + if (NAMESPACE.equals(event.getPropertyNamespace())) + { // see if this is one of the properties we monitor + try + { // reuse the "initialization" getters here + if (PROP_WIDTH.equals(event.getPropertyName())) + { // process new width + int x = getInitInteger(PROP_WIDTH); + m_width = x; + + } // end else if + else if (PROP_HEIGHT.equals(event.getPropertyName())) + { // process new height + int x = getInitInteger(PROP_HEIGHT); + m_height = x; + + } // end else if + + } // end try + catch (ConfigException ce) + { // property get failed - don't change the value + } // end catch + + } // end if + + } // end updateReceived + + /*-------------------------------------------------------------------------------- + * Implementations from interface ServiceProvider + *-------------------------------------------------------------------------------- + */ + + /** + * Queries this object for a specified service. + * + * @param klass The class of the object that should be returned as a service. + * @return A service object. The service object is guaranteed to be of the class + * specified by klass; that is, if queryService(klass) + * yields some object x, then the expression klass.isInstance(x) + * is true. + * @exception com.silverwrist.dynamo.except.NoSuchServiceException If no service is available in + * the specified class. + */ + public Object queryService(Class klass) + { + if (klass==RenderImage.class) + return (RenderImage)this; + throw new NoSuchServiceException(getName(),klass); + + } // end queryService + + /** + * Queries this object for a specified service. + * + * @param klass The class of the object that should be returned as a service. + * @param serviceid ID for the service to be requested, to further discriminate between requests. + * @return A service object. The service object is guaranteed to be of the class + * specified by klass; that is, if queryService(klass) + * yields some object x, then the expression klass.isInstance(x) + * is true. + * @exception com.silverwrist.dynamo.except.NoSuchServiceException If no service is available in + * the specified class. + */ + public Object queryService(Class klass, String serviceid) + { + if (klass==RenderImage.class) + return (RenderImage)this; + throw new NoSuchServiceException(getName(),klass,serviceid); + + } // end queryService + + /*-------------------------------------------------------------------------------- + * Implementations from interface RenderImage + *-------------------------------------------------------------------------------- + */ + + public void renderImageTag(TextRenderControl control, String url) throws IOException, RenderingException + { + PrintWriter wr = control.getWriter(); + wr.write("\"\""); + + } // end renderImageTag + + public Object getRenderingObject(String url) + { + return new RenderObject(url); + + } // end getRenderingObject + +} // end class CommunityLogoRenderer diff --git a/src/venice-base/com/silverwrist/venice/content/UserPhotoRenderer.java b/src/venice-base/com/silverwrist/venice/content/UserPhotoRenderer.java index 06f7653..1940bdb 100644 --- a/src/venice-base/com/silverwrist/venice/content/UserPhotoRenderer.java +++ b/src/venice-base/com/silverwrist/venice/content/UserPhotoRenderer.java @@ -37,7 +37,7 @@ public class UserPhotoRenderer *-------------------------------------------------------------------------------- */ - class RenderObject implements SelfRenderable + private class RenderObject implements SelfRenderable { /*==================================================================== * Attributes diff --git a/src/venice-base/com/silverwrist/venice/dialog/CommunityLogoField.java b/src/venice-base/com/silverwrist/venice/dialog/CommunityLogoField.java new file mode 100644 index 0000000..1f3a342 --- /dev/null +++ b/src/venice-base/com/silverwrist/venice/dialog/CommunityLogoField.java @@ -0,0 +1,163 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.dialog; + +import java.io.*; +import java.util.*; +import org.w3c.dom.*; +import com.silverwrist.util.*; +import com.silverwrist.util.xml.*; +import com.silverwrist.dynamo.Namespaces; +import com.silverwrist.dynamo.dialog.BaseDialogField; +import com.silverwrist.dynamo.except.*; +import com.silverwrist.dynamo.iface.*; +import com.silverwrist.venice.iface.*; + +class CommunityLogoField extends BaseDialogField +{ + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private String m_logo_url = null; + private String m_link_url; + private String m_link_type; + + /*-------------------------------------------------------------------------------- + * Constructors + *-------------------------------------------------------------------------------- + */ + + CommunityLogoField(Element elt) throws DialogException + { + super(false,elt); + XMLLoader loader = XMLLoader.get(); + try + { // get link URL and link type + m_link_url = loader.getAttribute(elt,"link"); + m_link_type = loader.getAttribute(elt,"type"); + + } // end try + catch (XMLLoadException e) + { // translate to DialogException + throw new DialogException(e); + + } // end catch + + // Load caption 2 from messages + ResourceBundle b = ResourceBundle.getBundle("com.silverwrist.venice.dialog.VeniceDialogMessages", + Locale.getDefault()); + setCaption2(b.getString("communitylogo.caption2")); + + } // end constructor + + protected CommunityLogoField(CommunityLogoField other) + { + super(other); + m_logo_url = null; + m_link_url = other.m_link_url; + m_link_type = other.m_link_type; + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Abstract implementations from class BaseDialogField + *-------------------------------------------------------------------------------- + */ + + protected void renderField(TextRenderControl control, Map render_params) + throws IOException, RenderingException + { + PrintWriter wr = control.getWriter(); + if (isEnabled()) + { // write the opening tag + URLRewriter rewriter = (URLRewriter)(control.queryService(URLRewriter.class)); + wr.write(""); + + } // end if + + // write the tag by running it through the standard communtiy logo renderer + ObjectProvider oprov = (ObjectProvider)(control.queryService(ObjectProvider.class)); + ServiceProvider sp = (ServiceProvider)(oprov.getObject(Namespaces.DYNAMO_OBJECT_NAMESPACE, + "venice-communitylogo")); + RenderImage rimg = (RenderImage)(sp.queryService(RenderImage.class)); + rimg.renderImageTag(control,m_logo_url); + + // write the tag + if (isEnabled()) + wr.write(""); + + } // end renderField + + protected void validateContents(Request r, Object data) throws ValidationException + { // do nothing + } // end validateContents + + public Object clone() + { + return new CommunityLogoField(this); + + } // end clone + + public Object getValue() + { + return m_logo_url; + + } // end getValue + + public boolean containsValue() + { + return (m_logo_url!=null); + + } // end containsValue + + public void setValue(Object obj) + { + m_logo_url = ((obj==null) ? null : obj.toString()); + + } // end setValue + + public void setValueFrom(Request r) + { // do nothing - this doesn't get handled in the usual way + } // end setValueFrom + + public void reset() + { // do nothing + } // end reset + + /*-------------------------------------------------------------------------------- + * Overrides from class BaseDialogField + *-------------------------------------------------------------------------------- + */ + + protected boolean isNull(Object value) + { + return false; + + } // end isNull + + public int getFlags() + { + return 0; + + } // end getFlags + +} // end class CommunityLogoField diff --git a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogItemFactory.java b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogItemFactory.java index 33c7584..535c497 100644 --- a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogItemFactory.java +++ b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogItemFactory.java @@ -42,6 +42,8 @@ public class VeniceDialogItemFactory implements DialogItemFactory String tagname = elt.getTagName(); if (tagname.equals("userphoto")) return new UserPhotoField(elt); + if (tagname.equals("communitylogo")) + return new CommunityLogoField(elt); throw new UnknownDialogFieldException(tagname,elt); diff --git a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogManager.java b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogManager.java index 3eaf815..71b262a 100644 --- a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogManager.java +++ b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogManager.java @@ -122,6 +122,7 @@ public class VeniceDialogManager implements NamedObject, ComponentInitialize, Co // Register our own field types. VeniceDialogItemFactory itemfact = new VeniceDialogItemFactory(); m_shutdown_list.addFirst(dlg_conf.registerDialogItem("userphoto",itemfact)); + m_shutdown_list.addFirst(dlg_conf.registerDialogItem("communitylogo",itemfact)); } // end initialize diff --git a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogMessages.properties b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogMessages.properties index 5968759..e009b50 100644 --- a/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogMessages.properties +++ b/src/venice-base/com/silverwrist/venice/dialog/VeniceDialogMessages.properties @@ -17,3 +17,4 @@ # This file has been localized for the en_US locale userphoto.caption2=(click to change) cheader.fail=Unable to get content header object: {0} +communitylogo.caption2=(click to change) diff --git a/src/venice-base/com/silverwrist/venice/frame/FrameAssembler.java b/src/venice-base/com/silverwrist/venice/frame/FrameAssembler.java index 41fa3f3..4bdf19f 100644 --- a/src/venice-base/com/silverwrist/venice/frame/FrameAssembler.java +++ b/src/venice-base/com/silverwrist/venice/frame/FrameAssembler.java @@ -17,6 +17,7 @@ */ package com.silverwrist.venice.frame; +import java.io.*; import java.security.acl.AclNotFoundException; import java.text.MessageFormat; import java.util.*; @@ -26,6 +27,7 @@ import org.w3c.dom.*; import com.silverwrist.util.*; import com.silverwrist.util.xml.*; import com.silverwrist.dynamo.DynamoVersion; +import com.silverwrist.dynamo.Namespaces; import com.silverwrist.dynamo.RequestType; import com.silverwrist.dynamo.event.*; import com.silverwrist.dynamo.except.*; @@ -36,6 +38,7 @@ import com.silverwrist.dynamo.velocity.VelocityParamSupplier; import com.silverwrist.dynamo.velocity.VelocityRendererConfig; import com.silverwrist.venice.VeniceNamespaces; import com.silverwrist.venice.VeniceVersion; +import com.silverwrist.venice.community.CommunityService; import com.silverwrist.venice.iface.*; import com.silverwrist.venice.session.SessionInfoParams; @@ -107,6 +110,110 @@ public class FrameAssembler } // end class VelocityOps + /*-------------------------------------------------------------------------------- + * Internal class that "wraps" the community menu to insert the logo + *-------------------------------------------------------------------------------- + */ + + private class MyMenuObject implements MenuRenderObject, SelfRenderable + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + private MenuRenderObject m_menu; // inner menu object + private String m_url; // URL of community logo + private String m_urltype = null; // URL type for communtiy logo + + /*==================================================================== + * Constructor + *==================================================================== + */ + + MyMenuObject(MenuRenderObject menu, VeniceCommunity comm) + { + m_menu = menu; + m_url = StringUtils.stringize(PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE, + "url.logo")); + if (m_url==null) + { // load "no logo" URL from the global properties store + m_url = StringUtils.stringize(m_props.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE, + "community.nologo.url")); + m_urltype = StringUtils.stringize(m_props.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE, + "community.nologo.url.type")); + + } // end if + + } // end constructor + + /*==================================================================== + * Implementations from interface MenuRenderObject + *==================================================================== + */ + + public int getItemCount() + { + return m_menu.getItemCount(); + + } // end getItemCount + + public int getItemContainingLinkText(String text) + { + return m_menu.getItemContainingLinkText(text); + + } // end getItemContainingLinkText + + public void setVariable(String name, String value) + { + m_menu.setVariable(name,value); + + } // end setVariable + + public void setSelectedIndex(int index) + { + m_menu.setSelectedIndex(index); + + } // end setSelectedIndex + + /*==================================================================== + * Implementations from interface SelfRenderable + *==================================================================== + */ + + /** + * Renders this object to the output. + * + * @param control A rendering control object that provides the means to render this object to the output, as + * well as providing "output services" (through the + * {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} interface) for the + * rendering process to use. + * @exception java.io.IOException If an I/O error occurs while writing the output. + * @exception com.silverwrist.dynamo.except.RenderingException If the rendering process fails. + */ + public void render(SelfRenderControl control) throws IOException, RenderingException + { + // figure out the final image URL + String image_url = null; + if (m_urltype!=null) + { // need to use the URL rewriter on this + URLRewriter rewriter = (URLRewriter)(control.queryService(URLRewriter.class)); + image_url = rewriter.rewriteURL(m_urltype,m_url); + + } // end if + else // just use this URL + image_url = m_url; + + TextRenderControl tctl = control.getTextRender(); + m_commlogo_render.renderImageTag(tctl,image_url); + PrintWriter wr = tctl.getWriter(); + wr.write("
"); + tctl.renderSubObject(m_menu); + + } // end render + + } // end class MyMenuObject + /*-------------------------------------------------------------------------------- * Static data members *-------------------------------------------------------------------------------- @@ -129,6 +236,7 @@ public class FrameAssembler private ObjectProvider m_blocks; // global blocks private SecurityReferenceMonitor m_srm; // security reference monitor private MenuProvider m_menu_prov; // menu provider + private RenderImage m_commlogo_render; // community logo renderer private String m_frame_template; // frame template parameter private HashMap m_globals = new HashMap(); // globals fed to Velocity private BaseParams m_base_params; // base parameters object @@ -237,7 +345,7 @@ public class FrameAssembler logger.info("FrameAssembler initializing"); XMLLoader loader = XMLLoader.get(); - String gprops = null, name_srm = null, name_menu = null; + String gprops = null, name_srm = null, name_menu = null, name_commlogo = null; try { // verify the right node name loader.verifyNodeName(config_root,"object"); @@ -254,6 +362,7 @@ public class FrameAssembler foo = loader.getSubElement(config_root_h,"providers"); name_srm = loader.getAttribute(foo,"security"); name_menu = loader.getAttribute(foo,"menu"); + name_commlogo = loader.getAttribute(foo,"commlogo"); } // end try catch (XMLLoadException e) @@ -278,6 +387,20 @@ public class FrameAssembler } // end catch + // Get the community logo provider. + ServiceProvider commlogo_sp = + (ServiceProvider)(GetObjectUtils.getDynamoComponent(services,ServiceProvider.class,name_commlogo)); + try + { // get the RenderImage provider + m_commlogo_render = (RenderImage)(commlogo_sp.queryService(RenderImage.class)); + + } // end try + catch (NoSuchServiceException e) + { // this shouldn't happen, but... + throw new ConfigException(FrameAssembler.class,"FrameMessages","init.nocommlogo",e); + + } // end catch + // Set up the initial values of the global properties. Iterator it = PROP_TO_VELOCITY.entrySet().iterator(); while (it.hasNext()) @@ -334,6 +457,7 @@ public class FrameAssembler m_shut_event.shutdown(); m_shut_event = null; m_base_params = null; + m_commlogo_render = null; m_menu_prov = null; m_props = null; m_blocks = null; @@ -387,10 +511,40 @@ public class FrameAssembler { // get the current menu selector Object curr_menusel = PropertyUtils.getPropertyNoErr(session,SessionInfoParams.NAMESPACE, SessionInfoParams.ATTR_MENU_SELECTOR); - if ((curr_menusel==null) || !(menusel_full.equals(curr_menusel.toString()))) + if ( (curr_menusel==null) || !(menusel_full.equals(curr_menusel.toString())) + || !(PropertyUtils.hasProperty(session,SessionInfoParams.NAMESPACE,SessionInfoParams.ATTR_MENU))) reload = true; // need to reload the menu } // end if + else + { // no menu selector specified...but do we have to reload the menu anyway? + if (!(PropertyUtils.hasProperty(session,SessionInfoParams.NAMESPACE,SessionInfoParams.ATTR_MENU))) + { // look to see if there's an actual menu selector present + String curr_menusel = + StringUtils.stringize(PropertyUtils.getPropertyNoErr(session,SessionInfoParams.NAMESPACE, + SessionInfoParams.ATTR_MENU_SELECTOR)); + if (curr_menusel!=null) + { // OK, synthesize menu selector elements from the current one + reload = true; + menusel_full = curr_menusel; + if (curr_menusel.startsWith("community:")) + { // strip off the community ID, get the community + int cid = Integer.parseInt(curr_menusel.substring(10)); + ObjectProvider op = (ObjectProvider)(r.queryService(ObjectProvider.class)); + CommunityService csvc = (CommunityService)(op.getObject(Namespaces.DYNAMO_OBJECT_NAMESPACE, + "communities")); + comm = csvc.getCommunity(cid); + menusel = "community"; + + } // end if + else // normal "top" + menusel = menusel_full; + + } // end if + + } // end if + + } // end else MenuRenderObject floating_menu = null; String floating_selector = null; @@ -412,6 +566,7 @@ public class FrameAssembler floating_menu.setVariable("alias",comm.getAlias()); floating_menu.setVariable("cid",String.valueOf(comm.getCID())); floating_menu.setVariable("name",comm.getName()); + floating_menu = new MyMenuObject(floating_menu,comm); } // end else if diff --git a/src/venice-base/com/silverwrist/venice/frame/FrameMessages.properties b/src/venice-base/com/silverwrist/venice/frame/FrameMessages.properties index 47f269b..6ea5756 100644 --- a/src/venice-base/com/silverwrist/venice/frame/FrameMessages.properties +++ b/src/venice-base/com/silverwrist/venice/frame/FrameMessages.properties @@ -23,3 +23,4 @@ ss.noTemplate=No stylesheet template name found for property "{0}". ss.renderFail=Unable to render stylesheet template {0}: {1} generator=Venice Web Communities System {0}; Dynamo Application Framework {1} frame.noACL=Unable to locate ACL for menu generation. +init.nocommlogo=Unable to find the community logo renderer. diff --git a/src/venice-base/com/silverwrist/venice/frame/FrameWrapper.java b/src/venice-base/com/silverwrist/venice/frame/FrameWrapper.java new file mode 100644 index 0000000..909d8a0 --- /dev/null +++ b/src/venice-base/com/silverwrist/venice/frame/FrameWrapper.java @@ -0,0 +1,138 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2002-03 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.frame; + +import java.io.IOException; +import com.silverwrist.dynamo.except.*; +import com.silverwrist.dynamo.iface.*; + +public class FrameWrapper implements FramedContent, SelfRenderable +{ + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private FramedContent m_content; // the actual content + private String m_menu_sel; // menu selector + private String m_page_title; // page title + private String m_page_qid; // page QID + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public FrameWrapper(FramedContent content) + { + m_content = content; + m_menu_sel = content.getMenuSelector(); + m_page_title = content.getPageTitle(); + m_page_qid = content.getPageQID(); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Implementations from interface FramedContent + *-------------------------------------------------------------------------------- + */ + + /** + * Returns the desired menu selector for this page. The menu selector is a string that specifies + * which menu is to be displayed on the left menu bar, in addition to the "global menu." This value may + * be null, in which case the current menu selector is not changed. + * + * @return The desired menu selector. + */ + public String getMenuSelector() + { + return m_menu_sel; + + } // end getMenuSelector + + /** + * Returns the desired title for the page. This title is concatenated with the site title (set in + * global properties) to form the actual page title that gets sent to the browser. + * + * @return The desired page title. + */ + public String getPageTitle() + { + return m_page_title; + + } // end getPageTitle + + /** + * Returns the desired quick ID for the page. The page quick ID is a small text string which can be used + * to "tag" the page with a unique identifier, for use with external tools such as offsite hit counters. + * If this value is null, the quick ID is not used for this page. + * + * @return The desired page quick ID. + */ + public String getPageQID() + { + return m_page_qid; + + } // end getPageQID + + /*-------------------------------------------------------------------------------- + * Implementations from interface SelfRenderable + *-------------------------------------------------------------------------------- + */ + + /** + * Renders this object to the output. + * + * @param control A rendering control object that provides the means to render this object to the output, as + * well as providing "output services" (through the + * {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider} interface) for the + * rendering process to use. + * @exception java.io.IOException If an I/O error occurs while writing the output. + * @exception com.silverwrist.dynamo.except.RenderingException If the rendering process fails. + */ + public void render(SelfRenderControl control) throws IOException, RenderingException + { + TextRenderControl ctl = control.getTextRender(); + ctl.renderSubObject(m_content); + + } // end render + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public void setMenuSelector(String s) + { + m_menu_sel = s; + + } // end setMenuSelector + + public void setPageTitle(String s) + { + m_page_title = s; + + } // end setPageTitle + + public void setPageQID(String s) + { + m_page_qid = s; + + } // end setPageQID + +} // end class FrameWrapper diff --git a/src/venice-base/com/silverwrist/venice/script/LibraryVenice.java b/src/venice-base/com/silverwrist/venice/script/LibraryVenice.java index 5baf6e6..ab6c1ad 100644 --- a/src/venice-base/com/silverwrist/venice/script/LibraryVenice.java +++ b/src/venice-base/com/silverwrist/venice/script/LibraryVenice.java @@ -136,7 +136,6 @@ public class LibraryVenice public void forceReloadMenu(SessionInfo session) { - session.removeObject(SessionInfoParams.NAMESPACE,SessionInfoParams.ATTR_MENU_SELECTOR); session.removeObject(SessionInfoParams.NAMESPACE,SessionInfoParams.ATTR_MENU); } // end forceReloadMenu diff --git a/venice-data/dialogs/comm/community_profile.dlg.xml b/venice-data/dialogs/comm/community_profile.dlg.xml index 50d9c5e..1de43f9 100644 --- a/venice-data/dialogs/comm/community_profile.dlg.xml +++ b/venice-data/dialogs/comm/community_profile.dlg.xml @@ -19,6 +19,7 @@ Edit Community Profile: comm/admin/profile.js.vs +
@@ -28,7 +29,7 @@ - +
diff --git a/venice-data/scripts/comm/admin/profile.js b/venice-data/scripts/comm/admin/profile.js index bc3fd3e..85c36ae 100644 --- a/venice-data/scripts/comm/admin/profile.js +++ b/venice-data/scripts/comm/admin/profile.js @@ -57,7 +57,16 @@ if (req_help.isVerb("GET")) dlg.setValue("rules",PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"rules")); dlg.setValue("language",comm.getObject(VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"language")); dlg.setValue("url",PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.homepage")); - // TODO: set community logo here + dlg.setValue("logo",PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.logo")); + if (dlg.getValue("logo")==null) + { // fill in the "no logo" URL + globals = vcast.getGlobalPropertiesStore(req); + url = globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.nologo.url"); + urltype = globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.nologo.url.type"); + rewriter = cast.queryURLRewriter(req); + dlg.setValue("logo",rewriter.rewriteURL(urltype,url)); + + } // end if dlg.setValue("company",PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE, "company.name")); @@ -94,7 +103,7 @@ else comm.setObject(user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"language",dlg.getValue("language")); PropertyUtils.setOrRemove(comm,user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.homepage", dlg.getValue("url")); - // TODO: deal with community logo + // N.B.: community logo is handled elsewhere PropertyUtils.setOrRemove(comm,user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"company.name", dlg.getValue("company")); @@ -123,7 +132,23 @@ else etype = dynamo.exceptionType(e) + ""; logger.error("Caught exception of type " + etype); if (etype.match(/ValidationException/)) + { // validation error... dlg.setErrorMessage(dynamo.exceptionMessage(e) + " Please try again."); + + // have to re-prep the community logo + dlg.setValue("logo",PropertyUtils.getPropertyNoErr(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE, + "url.logo")); + if (dlg.getValue("logo")==null) + { // fill in the "no logo" URL + globals = vcast.getGlobalPropertiesStore(req); + url = globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.nologo.url"); + urltype = globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.nologo.url.type"); + rewriter = cast.queryURLRewriter(req); + dlg.setValue("logo",rewriter.rewriteURL(urltype,url)); + + } // end if + + } // end if else if (etype.match(/DatabaseException/)) rc = new ErrorBox("Database Error",e,"SERVLET",return_URL); else if (etype.match(/DynamoSecurityException/)) @@ -140,7 +165,10 @@ else if (rc==null) { // output dialog if we don't have another value dlg.setSubtitle(comm.getName()); + dlg.setRenderParam("cid",comm.getCID() + ""); rc = new FrameDialog(dlg); + rc.menuSelector = "community"; } // end if + dynamo.scriptOutput(rc); diff --git a/venice-data/scripts/comm/admin/profile_logo.js b/venice-data/scripts/comm/admin/profile_logo.js new file mode 100644 index 0000000..f285426 --- /dev/null +++ b/venice-data/scripts/comm/admin/profile_logo.js @@ -0,0 +1,103 @@ +// The contents of this file are subject to the Mozilla Public License Version 1.1 +// (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at . +// +// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT +// WARRANTY OF ANY KIND, either express or implied. See the License for the specific +// language governing rights and limitations under the License. +// +// The Original Code is the Venice Web Communities System. +// +// The Initial Developer of the Original Code is Eric J. Bowersox , +// for Silverwrist Design Studios. Portions created by Eric J. Bowersox are +// Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. +// +// Contributor(s): + +importPackage(Packages.com.silverwrist.util); +importClass(Packages.com.silverwrist.dynamo.Namespaces); +importClass(Packages.com.silverwrist.dynamo.db.ImageStore); +importPackage(Packages.com.silverwrist.dynamo.iface); +importPackage(Packages.com.silverwrist.dynamo.util); +importClass(Packages.com.silverwrist.venice.VeniceNamespaces); +importPackage(Packages.com.silverwrist.venice.content); +importPackage(Packages.com.silverwrist.venice.iface); + +req = bsf.lookupBean("request"); +req_help = bsf.lookupBean("request_help"); +user = vlib.getUser(req); +comm = vlib.getCommunity(req); + +return_URL = "comm/admin/profile.js.vs?cc=" + comm.getCID(); + +// Make sure we can actually edit the profile logo. +acl = comm.getAcl(); +if (!(acl.testPermission(user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"set.property"))) + dynamo.scriptReturn(vlib.stdErrorBox(req,"Security Error", + "You are not permitted to modify this community's logo.")); + +if (req_help.isVerb("GET")) +{ // on GET, display the form + view = new VelocityView("Set Community Logo","comm/community_logo.vm"); + view.setParameter("community",comm); + dynamo.scriptReturn(view); + +} // end if + +// everything from this point on is a POST operation +if (req_help.isImageButtonClicked("cancel")) + dynamo.scriptReturn(new Redirect("SERVLET",return_URL)); + +// get the image provider object +imgprov = cast.queryImageStore(req_help.getRequestObject(Namespaces.DYNAMO_OBJECT_NAMESPACE,"images")); + +// figure out which image ID we have +imgid = imgprov.findImageID(VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"community.logo",comm.getHostGroup()); + +selector = req_help.getParameterString("selector") + ""; +if (selector=="none") +{ // take out the image property and the image + if (PropertyUtils.hasProperty(comm,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.logo")) + comm.removeObject(user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.logo"); + if (imgid!=-1) + imgprov.deleteImage(user,imgid); + +} // end if +else if (selector=="set") +{ // set the image URL directly, delete the image + s = req_help.getParameterString("url"); + if (stringutils.isEmpty(s)) + dynamo.scriptReturn(new ErrorBox(null,"No logo URL specified.",return_URL)); + comm.setObject(user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.logo",s); + if (imgid!=-1) + imgprov.deleteImage(user,imgid); + +} // end else if +else if (selector=="upload") +{ // take the uploaded image, reformat it, and save it off, then set the image URL + input_di = req_help.getDataItem("image"); + if (input_di==null) + dynamo.scriptReturn(new ErrorBox(null,"No uploaded logo specified.",return_URL)); + mtype = input_di.getMimeType(); + if (!(mtype.startsWith("image/"))) + dynamo.scriptReturn(new ErrorBox(null,"Uploaded data item is not an image.",return_URL)); + globals = vcast.getGlobalPropertiesStore(req); + width = cast.toInteger(globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.logo.width")); + height = cast.toInteger(globals.getObject(VeniceNamespaces.COMMUNITY_GLOBALS_NAMESPACE,"community.logo.height")); + di = imgprov.normalizeImage(input_di,width,height,"image/jpeg"); + if (imgid!=-1) + imgprov.replaceImage(user,imgid,di); + else + imgid = imgprov.saveNewImage(VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"community.logo",comm.getHostGroup(),di); + rewriter = cast.queryURLRewriter(req); + comm.setObject(user,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"url.logo", + rewriter.rewriteURL("IMAGEDATA",imgid + "")); + +} // end else if +else // return a "No Content" and just leave the dialog box up + dynamo.scriptReturn(new NoContent()); + +vlib.forceReloadMenu(req); // force the menu to be reloaded + +// All done - bounce back to the Profile dialog +dynamo.scriptOutput(new Redirect("SERVLET",return_URL)); diff --git a/venice-data/velocity/comm/community_logo.vm b/venice-data/velocity/comm/community_logo.vm new file mode 100644 index 0000000..58a4a57 --- /dev/null +++ b/venice-data/velocity/comm/community_logo.vm @@ -0,0 +1,43 @@ +#* + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +*# +#* + Parameters: + community = Community we're setting the logo for. +*# +#header2( "Set Community Logo:" "${community.Name}" ) +
+ + + + + + + + + + + + + + + + + +
No logo 
Set logo URL:
Upload logo:
+ #button( "INPUT" "set" )  #button( "INPUT" "cancel" ) +
diff --git a/venice-data/velocity/user/user_photo.vm b/venice-data/velocity/user/user_photo.vm index 618442d..57942dc 100644 --- a/venice-data/velocity/user/user_photo.vm +++ b/venice-data/velocity/user/user_photo.vm @@ -15,6 +15,10 @@ Contributor(s): *# +#* + Parameters: + target = Target URL. +*# #header1( "Set User Photo" )
diff --git a/venice-web/images/community_other.jpg b/venice-web/images/community_other.jpg new file mode 100644 index 0000000000000000000000000000000000000000..132630f147e38657f98ec555c8f99791b0c8c537 GIT binary patch literal 2813 zcmb7^c{r4N8^@nnjAhU>c16rEg|Y8DL&#P}cG+TV$G%jQEFrtfE@X*h-wio0gOV*v zL!3~SbV6AQN%9Ww={@iH^SyunJ@<8eulxDlpZj|_b@&bd4RrN&0SE*FI8q;Q_y*7h z7-(tfXb}u_baY4t1|%~V$jrpV%*)P!;u7G)3S#&$=rcG4i8I2o!f1>nUP@L;MO|GT zD}GL2TlKtxn!3u72!sKNWM*RK0YM%WA&ij9|6PaO0E!O413oZ*3;;zzU?|98FK`k7 zpfKvNe-{i6KxrTdYL5#7{IdlB5GV|O_y@oU;|HK145WV8h6>|tS^@MZ*O`FJG`a}r zd^?*O0?J^SSS6R?Q3KZ({*s?88oBsFRKhU+_uOlatvUrmk)*{On?3G7uLan&TEIs$ zm5tg~r%vA!y`7g+pY~+Xe)>Bv<6v`|>BdA$Dq-!_`_$7T#Ik+@%04i8*qD-muRPQ=*ihKvql;l1M?LPW~Ahg|lK<@?8Y=M}pCH*H?TfJ^#B zB_rb6oSt=XZg_T7JQ~BAgEMHn>iR$uoYpH{?U}^xU_AOpEQB&1$oT;Vs+G^~G6z=EkJO za8q^_#(o_6bg3Ofxnx9ZQJZtXS_%9q2V=BVEY@v&R>#fn1|pnfc8#@liuK<9A@Hzk zlaGA;?P|&b`&3NF@SAT{jC_ieK{BKx$6|vR}<3V})a+^e2BaO^lx4 z+DD9AAo{;{3bs?KNit4dgP|HdR55-O6&(OT!2ae6f`Wo9toD!bT2R+hTpDY?0 z)+mn8M4G*A!2C>=_8Unj8lP$!m{9h=gvx~}wnwHUh%<0^-fc5edc1cr#Gezlv5hsIRlK^@_QY>1#)@oXd{9>g zRp(+`{`iXRNpOQ!iIGFFZT80iZ&#kB%j1F}BJseEDG#tisG2(SjwPiapFM~m9Q!rZ z#m0XY5NluxY<#fRA)gHsQh~=Az2fdZU^QU+P0cQ|MEj(GkgR-;@r@k$_$|}PNSp^g z!fra_>WF!Hx6W+4fE;GBNjSl~ow9r_?wmp)KBj2+bDp-K5|gncjyr!eBunIq>(9?9uUJ3z zSVyJnZa3Aj`|ZRV4N9l0HPRI$49NR;PM1U-IK_TUdaA@{?WE_f!z4or=0Bxu=vx@R z#@NzkUKMb1(K_8-I$<%21!vQs`^;}>+PWp+R-u*VEz9#hOu^mQN6V|F&kO6nce0Gy z$oNp`4tpN%#zdeNb7pu6bIR*8qT&(@zl^9yP1v zE5!2QJA4StU4w6$Y|ZAptURa6P!-LiiZM*QB`(|R(qS+_Aotv-+R9p8had)DNpY>g zjPmOP$yOE5q#Iv!HIn4XgJENwIwrcl%2=^7zi9alAC7*qOR%HjI$egJcXJ4q&P`B} z3mccl9uRteB1+AKWhPTp)T2*8IrQa4*7*8)4S^=~!H;Wv+9+I9M&({YqUN4hN0*~3@J#L}YAh-@FWiht?!jgCV()Vc#qG)FgkjAj z`f_K&WlCEEFR`h)T-ZrJx9Udph(d(MZ`+Zh8~ovQJ9;m}6<{Qyy(*t{>Wg;wE# zG=!7nq6Z{!>>s!Ws)bA z>}I?8?sL*LZ#M*`skmMsCv*oJnSYa!8Th&$q3THK7erX>tw4Lq8;xzl-9sJI-_kdF zwq2AeiZON&Ps1dz+3^tdA zi>657sND~Tz;&T|w~5~7m{Ntc9bFUT*~I6!TqlYiN0J`9rbe0Zm6R7{Mi=r)M=+HJ zT|kniB%hgNW^90O15c5bgzV9G5qcwbJ+u4bS-si-zykq4FhmWHrPZK=*tX+M?@tgG z7%J^tzN`Mw)cC$Br4iU9QPP734pmjgfTX@Aym*gUZRu+!1E@^F}P d2