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