]> err.no Git - sope/blob - sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m
hotfix
[sope] / sope-appserver / NGObjWeb / DynamicElements / _WOComplexHyperlink.m
1 /*
2   Copyright (C) 2000-2005 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 #include "WOHyperlink.h"
23
24 /*
25   Class Hierachy
26     [WOHTMLDynamicElement]
27       [WOHyperlink]
28         _WOComplexHyperlink
29           _WOHrefHyperlink
30           _WOActionHyperlink
31           _WOPageHyperlink
32           _WODirectActionHyperlink
33 */
34
35 @interface _WOComplexHyperlink : WOHyperlink
36 {
37   /* superclass of most hyperlink classes */
38 @protected
39   WOAssociation *fragmentIdentifier;
40   WOAssociation *string;
41   WOAssociation *target;
42   WOAssociation *disabled;
43   WOElement     *template;
44   
45   /* new in WO4: */
46   WOAssociation *queryDictionary;
47   NSDictionary  *queryParameters;  /* associations beginning with ? */
48
49   /* non WO, image stuff */
50   WOAssociation *filename;         /* path relative to WebServerResources */
51   WOAssociation *framework;
52   WOAssociation *src;              /* absolute URL */
53   WOAssociation *disabledFilename; /* icon for 'disabled' state */
54 }
55
56 - (NSString *)associationDescription;
57
58 @end
59
60 @interface _WOHrefHyperlink : _WOComplexHyperlink
61 {
62   WOAssociation *href;
63 }
64 @end
65
66 @interface _WOActionHyperlink : _WOComplexHyperlink
67 {
68   WOAssociation *action;
69 }
70 @end
71
72 @interface _WOPageHyperlink : _WOComplexHyperlink
73 {
74   WOAssociation *pageName;
75 }
76 @end
77
78 @interface _WODirectActionHyperlink : _WOComplexHyperlink
79 {
80   WOAssociation *actionClass;
81   WOAssociation *directActionName;
82   BOOL          sidInUrl;          /* include session-id in wa URL ? */
83 }
84 @end
85
86 #include "WOElement+private.h"
87 #include "WOHyperlinkInfo.h"
88 #include "WOCompoundElement.h"
89 #include <NGObjWeb/WOApplication.h>
90 #include <NGObjWeb/WOResourceManager.h>
91 #include <NGExtensions/NSString+Ext.h>
92 #include "decommon.h"
93
94 static Class NSURLClass = Nil;
95
96 @implementation _WOComplexHyperlink
97
98 + (int)version {
99   return [super version] /* v4 */;
100 }
101 + (void)initialize {
102   NSAssert2([super version] == 4,
103             @"invalid superclass (%@) version %i !",
104             NSStringFromClass([self superclass]), [super version]);
105   if (NSURLClass == Nil)
106     NSURLClass = [NSURL class];
107 }
108
109 - (id)initWithName:(NSString *)_name
110   hyperlinkInfo:(WOHyperlinkInfo *)_info
111   template:(WOElement *)_t
112 {
113   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
114     self->template = [_t retain];
115     
116     self->fragmentIdentifier = _info->fragmentIdentifier;
117     self->string             = _info->string;
118     self->target             = _info->target;
119     self->disabled           = _info->disabled;
120     self->queryDictionary    = _info->queryDictionary;
121     self->queryParameters    = _info->queryParameters;
122     
123     /* image */
124     self->filename         = _info->filename;
125     self->framework        = _info->framework;
126     self->src              = _info->src;
127     self->disabledFilename = _info->disabledFilename;
128     
129     self->containsForm = self->queryParameters ? YES : NO;
130   }
131   return self;
132 }
133
134 - (void)dealloc {
135   [self->template           release];
136   [self->queryDictionary    release];
137   [self->queryParameters    release];
138   [self->disabledFilename   release];
139   [self->filename           release];
140   [self->framework          release];
141   [self->src                release];
142   [self->fragmentIdentifier release];
143   [self->string             release];
144   [self->target             release];
145   [self->disabled           release];
146   [super dealloc];
147 }
148
149 /* accessors */
150
151 - (id)template {
152   return self->template;
153 }
154
155 /* handle requests */
156
157 - (void)takeValuesFromRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
158   /* links can take form values !!!! (for query-parameters) */
159   
160   if (self->queryParameters != nil) {
161     /* apply values to ?style parameters */
162     WOComponent  *sComponent = [_ctx component];
163     NSEnumerator *keys;
164     NSString     *key;
165     
166     keys = [self->queryParameters keyEnumerator];
167     while ((key = [keys nextObject]) != nil) {
168       id assoc, value;
169       
170       assoc = [self->queryParameters objectForKey:key];
171       
172       if ([assoc isValueSettable]) {
173         value = [_rq formValueForKey:key];
174         [assoc setValue:value inComponent:sComponent];
175       }
176     }
177   }
178   
179   [self->template takeValuesFromRequest:_rq inContext:_ctx];
180 }
181
182 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
183   if (self->disabled != nil) {
184     if ([self->disabled boolValueInComponent:[_ctx component]])
185       return nil;
186   }
187   
188   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
189     /* link is not the active element */
190     return [self->template invokeActionForRequest:_rq inContext:_ctx];
191   
192   /* link is active */
193   [[_ctx session] logWithFormat:@"%@[0x%p]: no action/page set !",
194                   NSStringFromClass([self class]), self];
195   return nil;
196 }
197
198 - (BOOL)_appendHrefToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
199   [self subclassResponsibility:_cmd];
200   return NO;
201 }
202
203 - (void)_addImageToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
204   WOComponent *sComponent = [_ctx component];
205   NSString *uUri;
206   NSString *uFi  = nil;
207   NSArray *languages;
208   
209   uUri = [[self->src valueInContext:_ctx] stringValue];
210       
211   if ([self->disabled boolValueInComponent:sComponent]) {
212     uFi =  [self->disabledFilename stringValueInComponent:sComponent];
213     if (uFi == nil)
214       uFi = [self->filename stringValueInComponent:sComponent];
215   }
216   else
217     uFi = [self->filename stringValueInComponent:sComponent];
218   
219   if (!((uFi != nil) || (uUri != nil))) 
220     return;
221
222   languages = [_ctx resourceLookupLanguages];
223         
224   WOResponse_AddCString(_resp, "<img src=\"");
225   
226   if (uFi) {
227     WOResourceManager *rm;
228           
229     if ((rm = [[_ctx component] resourceManager]) == nil)
230       rm = [[_ctx application] resourceManager];
231           
232     uFi = [rm urlForResourceNamed:uFi
233               inFramework:
234                 [self->framework stringValueInComponent:sComponent]
235               languages:languages
236               request:[_ctx request]];
237     if (uFi == nil) {
238       NSLog(@"%@: did not find resource %@", sComponent,
239             [self->filename stringValueInComponent:sComponent]);
240       uFi = uUri;
241     }
242     [_resp appendContentHTMLAttributeValue:uFi];
243   }
244   else {
245     [_resp appendContentHTMLAttributeValue:uUri];
246   }
247   WOResponse_AddChar(_resp, '"');
248   
249   [self appendExtraAttributesToResponse:_resp inContext:_ctx];
250   
251   WOResponse_AddEmptyCloseParens(_resp, _ctx);
252 }
253
254 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
255   WOComponent *sComponent = [_ctx component];
256   NSString    *content;
257   BOOL        doNotDisplay;
258
259   if ([[_ctx request] isFromClientComponent])
260     return;
261   
262   content      = [self->string valueInContext:_ctx];
263   doNotDisplay = [self->disabled boolValueInComponent:sComponent];
264
265   if (!doNotDisplay) {
266     NSString *targetView;
267     NSString *queryString = nil;
268
269     targetView = [self->target stringValueInComponent:sComponent];
270     
271     WOResponse_AddCString(_response, "<a href=\"");
272       
273     if ([self _appendHrefToResponse:_response inContext:_ctx]) {
274       queryString = [self queryStringForQueryDictionary:
275           [self->queryDictionary valueInComponent:sComponent]
276           andQueryParameters:self->queryParameters
277           inContext:_ctx];
278     }
279
280     if (self->fragmentIdentifier) {
281         [_response appendContentCharacter:'#'];
282         WOResponse_AddString(_response,
283            [self->fragmentIdentifier stringValueInComponent:sComponent]);
284     }
285     if (queryString) {
286       [_response appendContentCharacter:'?'];
287       WOResponse_AddString(_response, queryString);
288     }
289     [_response appendContentCharacter:'"'];
290       
291     if (targetView) {
292       WOResponse_AddCString(_response, " target=\"");
293       WOResponse_AddString(_response, targetView);
294       [_response appendContentCharacter:'"'];
295     }
296       
297     [self appendExtraAttributesToResponse:_response inContext:_ctx];
298       
299     if (self->otherTagString) {
300       WOResponse_AddChar(_response, ' ');
301       WOResponse_AddString(_response,
302                            [self->otherTagString stringValueInComponent:
303                              [_ctx component]]);
304     }
305     [_response appendContentCharacter:'>'];
306   }
307
308   /* content */
309   [self->template appendToResponse:_response inContext:_ctx];
310   if (content) [_response appendContentHTMLString:content];
311   
312   /* image content */
313   if ((self->src != nil) || (self->filename != nil))
314     [self _addImageToResponse:_response inContext:_ctx];
315   
316   if (!doNotDisplay) {
317     /* closing tag */
318     WOResponse_AddCString(_response, "</a>");
319   }
320 }
321
322 /* description */
323
324 - (NSString *)associationDescription {
325   NSMutableString *str = [NSMutableString stringWithCapacity:256];
326
327   if (self->fragmentIdentifier)
328     [str appendFormat:@" fragment=%@", self->fragmentIdentifier];
329   if (self->string)   [str appendFormat:@" string=%@",   self->string];
330   if (self->target)   [str appendFormat:@" target=%@",   self->target];
331   if (self->disabled) [str appendFormat:@" disabled=%@", self->disabled];
332
333   /* image .. */
334   if (self->filename)  [str appendFormat:@" filename=%@",  self->filename];
335   if (self->framework) [str appendFormat:@" framework=%@", self->framework];
336   if (self->src)       [str appendFormat:@" src=%@",       self->src];
337
338   return str;
339 }
340
341 @end /* _WOComplexHyperlink */
342
343
344 @implementation _WOHrefHyperlink
345
346 static BOOL debugStaticLinks = NO;
347
348 + (void)initialize {
349   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
350   
351   debugStaticLinks = [ud boolForKey:@"WODebugStaticLinkProcessing"];
352 }
353
354 - (id)initWithName:(NSString *)_name
355   hyperlinkInfo:(WOHyperlinkInfo *)_info
356   template:(WOElement *)_t
357 {
358   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
359     self->href = _info->href;
360   }
361   return self;
362 }
363
364 - (void)dealloc {
365   [self->href release];
366   [super dealloc];
367 }
368
369 /* URI generation */
370
371 - (BOOL)shouldRewriteURLString:(NSString *)_s inContext:(WOContext *)_ctx {
372   // TODO: we need a binding to disable rewriting!
373   NSRange  r;
374   
375   r = [_s rangeOfString:@":"];
376   if (r.length == 0) 
377     return YES;
378   
379   /* only rewrite HTTP URLs */
380   return [_s hasPrefix:@"http"];
381 }
382
383 - (BOOL)_appendHrefToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
384   NSString *s;
385   id    hrefValue;
386   NSURL *url, *base;
387   
388   base      = [_ctx baseURL];
389   hrefValue = [self->href valueInContext:_ctx];
390   url       = nil;
391   
392   if (hrefValue == nil)
393     return NO;
394   
395   if ((*(Class *)hrefValue == NSURLClass) ||
396       [hrefValue isKindOfClass:NSURLClass]) {
397     s = [hrefValue stringValueRelativeToURL:base];
398   }
399   else {
400     /* given HREF is a string */
401     s = [hrefValue stringValue];
402     
403     /* we do not want to rewrite stuff like mailto: or javascript: URLs */
404     if ([self shouldRewriteURLString:s inContext:_ctx]) {
405       if ([s isAbsoluteURL]) {
406         // TODO: why are we doing this? we could just pass through the string?
407         //    => probably to generate relative links
408         url = [NSURLClass URLWithString:s];
409       }
410       else if (base != nil) {
411         /* avoid creating a new URL for ".", just return the base */
412         url = [s isEqualToString:@"."]
413           ? base
414           : (NSURL *)[NSURLClass URLWithString:s relativeToURL:base];
415       }
416       else {
417         [self warnWithFormat:@"missing base URL in context ..."];
418         WOResponse_AddString(_r, s);
419         return YES;
420       }
421       
422       if (url == nil) {
423         [self logWithFormat:
424                 @"could not construct URL from 'href' string '%@' (base=%@)",
425                 s, base];
426         return NO;
427       }
428       
429       s = [url stringValueRelativeToURL:base];
430     }
431   }
432   
433   /* generate URL */
434   
435   if (debugStaticLinks) {
436     [self logWithFormat:@"static links based on 'href': '%@'", hrefValue];
437     [self logWithFormat:@"  base     %@", base];
438     [self logWithFormat:@"  base-abs %@", [base absoluteString]];
439     [self logWithFormat:@"  url      %@", url];
440     if (url != nil)
441       [self logWithFormat:@"  url-abs  %@", [url absoluteString]];
442     else
443       [self logWithFormat:@"  href     %@", hrefValue];
444     [self logWithFormat:@"  string   %@", s];
445   }
446   
447   WOResponse_AddString(_r, s);
448   return YES;
449 }
450
451 /* description */
452
453 - (NSString *)associationDescription {
454   NSMutableString *str = [NSMutableString stringWithCapacity:256];
455
456   [str appendFormat:@" href=%@", self->href];
457   [str appendString:[super associationDescription]];
458   
459   return str;
460 }
461
462 @end /* _WOHrefHyperlink */
463
464
465 @implementation _WOActionHyperlink
466
467 - (id)initWithName:(NSString *)_name
468   hyperlinkInfo:(WOHyperlinkInfo *)_info
469   template:(WOElement *)_t
470 {
471   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
472     self->action = _info->action;
473   }
474   return self;
475 }
476
477 - (void)dealloc {
478   [self->action release];
479   [super dealloc];
480 }
481
482 /* dynamic invocation */
483
484 - (id)invokeActionForRequest:(WORequest *)_rq
485   inContext:(WOContext *)_ctx
486 {
487   if (self->disabled) {
488     if ([self->disabled boolValueInComponent:[_ctx component]])
489       return nil;
490   }
491
492   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
493     /* link is not the active element */
494     return [self->template invokeActionForRequest:_rq inContext:_ctx];
495   
496   /* link is active */
497   return [self executeAction:self->action inContext:_ctx];
498 }
499
500 - (BOOL)_appendHrefToResponse:(WOResponse *)_response
501   inContext:(WOContext *)_ctx
502 {
503   WOResponse_AddString(_response, [_ctx componentActionURL]);
504   return YES;
505 }
506
507 /* description */
508
509 - (NSString *)associationDescription {
510   NSMutableString *str = [NSMutableString stringWithCapacity:256];
511
512   [str appendFormat:@" action=%@", self->action];
513   [str appendString:[super associationDescription]];
514   return str;
515 }
516
517 @end /* _WOActionHyperlink */
518
519
520 @implementation _WOPageHyperlink
521
522 - (id)initWithName:(NSString *)_name
523   hyperlinkInfo:(WOHyperlinkInfo *)_info
524   template:(WOElement *)_t
525 {
526   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
527     self->pageName = _info->pageName;
528   }
529   return self;
530 }
531
532 - (void)dealloc {
533   [self->pageName release];
534   [super dealloc];
535 }
536
537 /* handle request */
538
539 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
540   WOComponent *page;
541   NSString    *name;
542
543   if (self->disabled) {
544     if ([self->disabled boolValueInComponent:[_ctx component]])
545       return nil;
546   }
547   
548   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
549     /* link is not the active element */
550     return [self->template invokeActionForRequest:_rq inContext:_ctx];
551   
552   /* link is the active element */
553   
554   name = [self->pageName stringValueInComponent:[_ctx component]];
555   page = [[_ctx application] pageWithName:name inContext:_ctx];
556
557   if (page == nil) {
558     [[_ctx session] logWithFormat:
559                       @"%@[0x%p]: did not find page with name %@ !",
560                       NSStringFromClass([self class]), self, name];
561   }
562   return page;
563 }
564
565 /* generate response */
566
567 - (BOOL)_appendHrefToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
568   /*
569     Profiling:
570       87% -componentActionURL
571       13% NSString dataUsingEncoding(appendContentString!)
572     TODO(prof): use addcstring
573   */
574   WOResponse_AddString(_r, [_ctx componentActionURL]);
575   return YES;
576 }
577
578 /* description */
579
580 - (NSString *)associationDescription {
581   NSMutableString *str = [NSMutableString stringWithCapacity:256];
582
583   [str appendFormat:@" pageName=%@", self->pageName];
584   [str appendString:[super associationDescription]];
585   return str;
586 }
587
588 @end /* _WOPageHyperlink */
589
590
591 @implementation _WODirectActionHyperlink
592
593 - (id)initWithName:(NSString *)_name
594   hyperlinkInfo:(WOHyperlinkInfo *)_info
595   template:(WOElement *)_t
596 {
597   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
598     self->actionClass      = _info->actionClass;
599     self->directActionName = _info->directActionName;
600     self->sidInUrl         = _info->sidInUrl;
601
602     self->containsForm = NO; /* direct actions are never form stuff ... */
603   }
604   return self;
605 }
606
607 - (void)dealloc {
608   [self->actionClass      release];
609   [self->directActionName release];
610   [super dealloc];
611 }
612
613 /* handle requests */
614
615 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
616   /* DA links can *never* take form values !!!! */
617   [self->template takeValuesFromRequest:_req inContext:_ctx];
618 }
619
620 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
621   /* DA links can *never* invoke an action !!!! */
622   return [self->template invokeActionForRequest:_rq inContext:_ctx];
623 }
624
625 /* generate response */
626
627 - (BOOL)_appendHrefToResponse:(WOResponse *)_r
628   inContext:(WOContext *)_ctx
629 {
630   WOComponent         *sComponent;
631   NSString            *daClass;
632   NSString            *daName;
633   NSMutableDictionary *qd;
634   NSDictionary        *tmp;
635
636   sComponent = [_ctx component];
637   daClass = [self->actionClass stringValueInComponent:sComponent];
638   daName  = [self->directActionName stringValueInComponent:sComponent];
639
640   if (daClass) {
641     if (daName) {
642       if (![daClass isEqualToString:@"DirectAction"])
643         daName = [NSString stringWithFormat:@"%@/%@", daClass, daName];
644     }
645     else
646       daName = daClass;
647   }
648   
649   qd = [NSMutableDictionary dictionaryWithCapacity:16];
650   
651   /* add query dictionary */
652   
653   if (self->queryDictionary) {
654     if ((tmp = [self->queryDictionary valueInComponent:sComponent]))
655       [qd addEntriesFromDictionary:tmp];
656   }
657   
658   /* add ?style parameters */
659
660   if (self->queryParameters != nil) {
661     NSEnumerator *keys;
662     NSString     *key;
663
664     keys = [self->queryParameters keyEnumerator];
665     while ((key = [keys nextObject])) {
666       id assoc, value;
667
668       assoc = [self->queryParameters objectForKey:key];
669       value = [assoc stringValueInComponent:sComponent];
670           
671       [qd setObject:(value != nil ? value : (id)@"") forKey:key];
672     }
673   }
674       
675   /* add session ID */
676   
677   if (self->sidInUrl) {
678     if ([_ctx hasSession]) {
679       WOSession *sn;
680       
681       sn = [_ctx session];
682       [qd setObject:[sn sessionID] forKey:WORequestValueSessionID];
683       
684       if (![sn isDistributionEnabled]) {
685         [qd setObject:[[WOApplication application] number]
686             forKey:WORequestValueInstance];
687       }
688     }
689   }
690
691   WOResponse_AddString(_r,
692                        [_ctx directActionURLForActionNamed:daName
693                              queryDictionary:qd]);
694   return NO;
695 }
696
697 /* description */
698
699 - (NSString *)associationDescription {
700   NSMutableString *str = [NSMutableString stringWithCapacity:256];
701
702   if (self->actionClass)
703     [str appendFormat:@" actionClass=%@", self->actionClass];
704   if (self->directActionName)
705     [str appendFormat:@" directAction=%@", self->directActionName];
706   
707   [str appendString:[super associationDescription]];
708   return str;
709 }
710
711 @end /* _WODirectActionHyperlink */