]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEEpozEditor.m
fix for SoProductResourceManager.m
[sope] / sope-appserver / WEExtensions / WEEpozEditor.m
1 /*
2   Copyright (C) 2003-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21 // $Id$
22
23 /*
24   Note: Its very important that the URLs generated properly match the
25         appname! Otherwise you will get security exceptions in JavaScript
26         on the client side (probably depends on the browser).
27         This also implies, that WOResourcePrefix cannot be used in conjunction
28         with Epoz.
29 */
30
31 #include <NGObjWeb/WODynamicElement.h>
32
33 @interface WEEpozEditor : WODynamicElement
34 {
35   // WODynamicElement: extraAttributes
36   // WODynamicElement: otherTagString
37 @protected
38   WOAssociation *name;
39   WOAssociation *value;
40   WOAssociation *disabled;
41   WOAssociation *rows;
42   WOAssociation *cols;
43   WOAssociation *epozCharset; /* def: iso-8859-1 */
44   WOAssociation *epozButtonStyle;
45   WOAssociation *epozStyle;
46 }
47
48 @end
49
50 #include <NGObjWeb/WEClientCapabilities.h>
51 #include <NGExtensions/NSString+Ext.h>
52 #include "common.h"
53
54 @implementation WEEpozEditor
55
56 + (int)version {
57   return [super version] + 0 /* v2 */;
58 }
59 + (void)initialize {
60   NSAssert2([super version] == 2,
61             @"invalid superclass (%@) version %i !",
62             NSStringFromClass([self superclass]), [super version]);
63 }
64
65 - (id)initWithName:(NSString *)_name
66   associations:(NSDictionary *)_config
67   template:(WOElement *)_root {
68
69   if ((self = [super initWithName:_name associations:_config template:_root])) {
70     self->containsForm = YES;
71     self->name     = OWGetProperty(_config, @"name");
72     self->value    = OWGetProperty(_config, @"value");
73     self->disabled = OWGetProperty(_config, @"disabled");
74     self->rows     = OWGetProperty(_config, @"rows");
75     self->cols     = OWGetProperty(_config, @"cols");
76     
77     self->epozCharset     = OWGetProperty(_config, @"charset");
78     self->epozStyle       = OWGetProperty(_config, @"style");
79     self->epozButtonStyle = OWGetProperty(_config, @"buttonstyle");
80   }
81   return self;
82 }
83
84 - (void)dealloc {
85   [self->epozCharset     release];
86   [self->epozButtonStyle release];
87   [self->epozStyle       release];
88   [self->rows     release];
89   [self->cols     release];
90   [self->name     release];
91   [self->value    release];
92   [self->disabled release];
93   [super dealloc];
94 }
95
96 /* form support */
97
98 static NSString *OWFormElementName(WEEpozEditor *self, WOContext *_ctx) {
99   NSString *name;
100   
101   if (self->name == nil) 
102     return [_ctx elementID];
103   
104   if ((name = [self->name stringValueInComponent:[_ctx component]]))
105     return name;
106
107   [[_ctx component]
108              logWithFormat:
109                @"WARNING: in element %@, 'name' attribute configured (%@),"
110                @"but no name assigned (using elementID as name) !",
111                self, self->name];
112   return [_ctx elementID];
113 }
114
115 // ******************** responder ********************
116
117 - (id)parseFormValue:(id)_value inContext:(WOContext *)_ctx {
118   return _value;
119 }
120
121 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
122   NSString *formName;
123   id formValue = nil;
124   
125   if ([self->disabled boolValueInComponent:[_ctx component]])
126     return;
127   
128   formName = OWFormElementName(self, _ctx);
129   
130   if ((formValue = [_req formValueForKey:formName])) {
131 #if DEBUG && 0
132     NSLog(@"%s(%@): form=%@ ctx=%@ value=%@ ..", __PRETTY_FUNCTION__,
133           [_ctx elementID], formName, [_ctx contextID], formValue);
134 #endif
135     
136     if ([self->value isValueSettable]) {
137       formValue = [self parseFormValue:formValue inContext:_ctx];
138       [self->value setStringValue:formValue inComponent:[_ctx component]];
139     }
140 #if DEBUG
141     else {
142       NSLog(@"%s: form value is not settable: %@", __PRETTY_FUNCTION__,
143             self->value);
144     }
145 #endif
146   }
147 }
148
149 - (BOOL)isDebuggingEnabled {
150   return NO;
151 }
152
153 - (BOOL)isEpozBrowserInContext:(WOContext *)_ctx {
154   WEClientCapabilities *cc;
155   
156   if ((cc = [[_ctx request] clientCapabilities]) == nil) {
157     [self debugWithFormat:@"WARNING: missing client capabilities object!"];
158     return YES;
159   }
160   
161   if ([cc isInternetExplorer]) {
162     if ([cc majorVersion] <= 4) {
163       [self debugWithFormat:@"disable Epoz with IE <5"];
164       return NO;
165     }
166     if ([cc majorVersion] == 5 && [cc minorVersion] <= 5) {
167       [self debugWithFormat:@"disable Epoz with IE <5.5"];
168       return NO;
169     }
170     [self debugWithFormat:@"enable Epoz with IE >=5.5"];
171     return YES;
172   }
173   
174   if ([cc isMozilla] || [cc isNetscape]) {
175     [self debugWithFormat:@"enable Epoz with Mozilla: %@", cc];
176     return YES;
177   }
178   
179   [self debugWithFormat:@"does not use Epoz with this browser: %@", cc];
180   return NO;
181 }
182
183 - (NSString *)stringValueInContext:(WOContext *)_ctx {
184   BOOL     removeCR = NO;
185   NSString *ua;
186   NSString *v;
187   
188   v = [[self->value valueInComponent:[_ctx component]] stringValue];
189   if ([v length] == 0)
190     return v;
191     
192   ua = [[_ctx request] headerForKey:@"user-agent"];
193   if ([ua rangeOfString:@"Opera"].length > 0)
194     removeCR = YES;
195     
196   if (removeCR)
197     v = [v stringByReplacingString:@"\r" withString:@""];
198   
199   return v;
200 }
201
202 - (void)appendTextAreaToResponse:(WOResponse *)_response 
203   inContext:(WOContext *)_ctx 
204 {
205   WOComponent *sComponent;
206   NSString *v;
207   NSString *r, *c;
208   
209   sComponent = [_ctx component];
210   v = [self stringValueInContext:_ctx];
211   r = [self->rows  stringValueInComponent:sComponent];
212   c = [self->cols  stringValueInComponent:sComponent];
213   
214   [_response appendContentString:@"<textarea name=\""];
215   [_response appendContentHTMLAttributeValue:OWFormElementName(self, _ctx)];
216   [_response appendContentString:@"\""];
217   if (r > 0) {
218     [_response appendContentString:@" rows=\""];
219     [_response appendContentString:r];
220     [_response appendContentString:@"\""];
221   }
222   if (c > 0) {
223     [_response appendContentString:@" cols=\""];
224     [_response appendContentString:c];
225     [_response appendContentString:@"\""];
226   }
227   
228   [self appendExtraAttributesToResponse:_response inContext:_ctx];
229   if (self->otherTagString) {
230     NSString *str;
231     
232     str = [self->otherTagString stringValueInComponent:sComponent];
233     [_response appendContentString:str];
234   }
235   [_response appendContentString:@">"];
236   
237   if ([v length] > 0)
238     [_response appendContentHTMLString:v];
239   
240   [_response appendContentString:@"</textarea>"];
241 }
242
243 - (WOResourceManager *)resourceManagerInContext:(WOContext *)_ctx {
244   WOResourceManager *rm;
245   
246   if ((rm = [[_ctx component] resourceManager]) == nil)
247     rm = [[_ctx application] resourceManager];
248   return rm;
249 }
250 - (NSString *)urlForEpozResourceNamed:(NSString *)_name 
251   inContext:(WOContext *)_ctx
252 {
253   WOResourceManager *rm;
254   NSArray  *languages;
255   
256   rm = [self resourceManagerInContext:_ctx];
257   languages = [_ctx hasSession] ? [[_ctx session] languages] : nil;
258   
259   return [rm urlForResourceNamed:_name inFramework:nil
260              languages:languages request:[_ctx request]];
261 }
262
263 - (void)appendEpozScript:(NSString *)_scriptName
264   toResponse:(WOResponse *)_response 
265   inContext:(WOContext *)_ctx 
266 {
267   NSString *src;
268   
269   src = [self urlForEpozResourceNamed:_scriptName inContext:_ctx];
270   [_response appendContentString:@"<script language=\"JavaScript\""];
271   [_response appendContentString:@" type=\"text/javascript\""];
272   [_response appendContentString:@" src=\""];
273   [_response appendContentHTMLAttributeValue:src];
274   [_response appendContentString:@"\"></script>\n"];
275 }
276
277 - (NSString *)epozImageURLInContext:(WOContext *)_ctx {
278   /*
279     Note: Epoz takes the directory where the resources are located, not
280           individual pointers. This also means that you need to have all
281           Epoz resources in the directory which contains the 
282           "epoz_button_bold.gif" (which is used as the "reference" point
283           to the active buttons).
284    */
285   NSString *src;
286   NSRange  r;
287   
288   src = [self urlForEpozResourceNamed:@"epoz_button_bold.gif" inContext:_ctx];
289   r = [src rangeOfString:@"/" options:(NSBackwardsSearch | NSLiteralSearch)];
290   if (r.length > 0)
291     src = [src substringToIndex:(r.location + r.length)];
292   
293   return src;
294 }
295 - (NSString *)epozToolboxURLInContext:(WOContext *)_ctx {
296   /* TODO: replace */
297   // return @"/epoz/WebServerResources/toolbox";
298   return [self urlForEpozResourceNamed:@"epoz_toolbox.html" inContext:_ctx];
299 }
300 - (NSString *)epozCSSURLInContext:(WOContext *)_ctx {
301   return [self urlForEpozResourceNamed:@"epoz.css" inContext:_ctx];
302 }
303
304 - (void)appendEpozToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
305   WOComponent *sComponent;
306   NSString *v, *tmp;
307   
308   sComponent = [_ctx component];
309   v = [self stringValueInContext:_ctx];
310   
311   /* first, the background iframe */
312   [_r appendContentString:@"<iframe id=\"EpozIFrame\""];
313   [_r appendContentString:@" style=\""];
314   [_r appendContentString:@"position: absolute;"];
315   [_r appendContentString:@" visibility: hidden;"];
316   [_r appendContentString:@" width: 0px; height: 0px;"];
317   [_r appendContentString:@"\"></iframe>\n"];
318   
319   /* the language mappings */
320   [self appendEpozScript:@"epoz_lang_en.js" 
321         toResponse:_r inContext:_ctx];
322   
323   /* the processing JavaScripts */
324   [self appendEpozScript:@"epoz_script_widget.js"
325         toResponse:_r inContext:_ctx];
326   [self appendEpozScript:@"epoz_script_detect.js" 
327         toResponse:_r inContext:_ctx];
328   [self appendEpozScript:@"epoz_script_main.js" 
329         toResponse:_r inContext:_ctx];
330   
331   /* 
332      Start Epoz
333     """ Create an Epoz-Wysiwyg-Editor.
334     
335         name : the name of the form-element which submits the data
336         data : the data to edit
337         toolbox : a link to a HTML-Page which delivers additional tools
338         lang: a code for the language-file (en,de,...)
339         path : path to Epoz-Javascript. Needed mainly for Plone (portal_url).
340         widget: You can specify a path to an alternative JavaScript for
341                 epoz_script_widget.js
342         style : style-definition for the editor-area
343         button : style-definiton for buttons
344         
345         If Epoz can't create a Rich-Text-Editor, a simple textarea is created.
346     """
347   */
348   
349   [_r appendContentString:
350         @"<script language=\"JavaScript\" type=\"text/javascript\"><!--\n"];
351   [_r appendContentString:@"InitEpoz("];
352
353   /* name */
354   [_r appendContentString:@"'"];
355   [_r appendContentString:OWFormElementName(self, _ctx)];
356   [_r appendContentString:@"',"];
357   
358   /* value */
359   [_r appendContentString:@"'"];
360   {
361     /* TODO: escape value, is that enough? */
362     NSString *jsv;
363     
364     jsv = v;
365     jsv = [jsv stringByReplacingString:@"'"  withString:@"\\'"];
366     jsv = [jsv stringByReplacingString:@"\n" withString:@"\\n"];
367     jsv = [jsv stringByReplacingString:@"\r" withString:@""];
368     [_r appendContentString:jsv];
369   }
370   [_r appendContentString:@"',"];
371   
372   /* image path */
373   [_r appendContentString:@"'"];
374   [_r appendContentString:[self epozImageURLInContext:_ctx]];
375   [_r appendContentString:@"',"];
376   
377   /* toolbox path */
378   [_r appendContentString:@"'"];
379   [_r appendContentString:[self epozToolboxURLInContext:_ctx]];
380   [_r appendContentString:@"',"];
381
382   if ((tmp = [self->epozStyle stringValueInComponent:sComponent]) == nil)
383     tmp = @"width: 590px; height: 250px; border: 1px solid #000000;";
384   [_r appendContentString:@"'"];
385   [_r appendContentString:tmp];
386   [_r appendContentString:@"',"];
387
388   if ((tmp=[self->epozButtonStyle stringValueInComponent:sComponent])==nil) {
389     tmp = 
390       @"background-color: #EFEFEF; border: 1px solid #A0A0A0; "
391       @"cursor: pointer; margin-right: 1px; margin-bottom: 1px;";
392   }
393   [_r appendContentString:@"'"];
394   [_r appendContentString:tmp];
395   [_r appendContentString:@"',"];
396   
397   /* CSS path */
398   [_r appendContentString:@"'"];
399   [_r appendContentString:[self epozCSSURLInContext:_ctx]];
400   [_r appendContentString:@"',"];
401   
402   /* charset */
403   if ((tmp = [self->epozCharset stringValueInComponent:sComponent]) == nil)
404     tmp = @"iso-8859-1";
405   [_r appendContentString:@"'"];
406   [_r appendContentString:tmp];
407   [_r appendContentString:@"'"];
408   
409   [_r appendContentString:@");\n"];
410   [_r appendContentString:@"//-->\n</script>"];
411   
412   /* fallback if scripting is disabled */
413   [_r appendContentString:@"<noscript>"];
414   [self appendTextAreaToResponse:_r inContext:_ctx];
415   [_r appendContentString:@"</noscript>"];
416 }
417
418 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
419   if ([self isEpozBrowserInContext:_ctx])
420     [self appendEpozToResponse:_response inContext:_ctx];
421   else
422     [self appendTextAreaToResponse:_response inContext:_ctx];
423 }
424
425 /* description */
426
427 - (NSString *)associationDescription {
428   NSMutableString *str = nil;
429   
430   str = [NSMutableString stringWithCapacity:64];
431   
432   if (self->value)    [str appendFormat:@" value=%@",    self->value];
433   if (self->name)     [str appendFormat:@" name=%@",     self->name];
434   if (self->disabled) [str appendFormat:@" disabled=%@", self->disabled];
435   
436   if (self->rows) [str appendFormat:@" rows=%@", self->rows];
437   if (self->cols) [str appendFormat:@" cols=%@", self->cols];
438   
439   return str;
440 }
441
442 @end /* WEEpozEditor */