]> err.no Git - sope/blob - sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m
fixed copyrights for 2005
[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 "common.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 - (WOElement *)template {
152   return self->template;
153 }
154
155 // ******************** responder ********************
156
157 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
158   /* links can take form values !!!! (for query-parameters) */
159   
160   if (self->queryParameters) {
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])) {
168       id assoc, value;
169
170       assoc = [self->queryParameters objectForKey:key];
171       
172       if ([assoc isValueSettable]) {
173         value = [_req formValueForKey:key];
174         [assoc setValue:value inComponent:sComponent];
175       }
176     }
177   }
178   
179   [self->template takeValuesFromRequest:_req inContext:_ctx];
180 }
181
182 - (id)invokeActionForRequest:(WORequest *)_request
183   inContext:(WOContext *)_ctx
184 {
185   if (self->disabled) {
186     if ([self->disabled boolValueInComponent:[_ctx component]])
187       return nil;
188   }
189   
190   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
191     /* link is not the active element */
192     return [self->template invokeActionForRequest:_request inContext:_ctx];
193   
194   /* link is active */
195   [[_ctx session] logWithFormat:@"%@[0x%08X]: no action/page set !",
196                   NSStringFromClass([self class]), self];
197   return nil;
198 }
199
200 - (BOOL)_appendHrefToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
201   [self subclassResponsibility:_cmd];
202   return NO;
203 }
204
205 - (void)_addImageToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
206   WOComponent *sComponent = [_ctx component];
207   NSString *uUri;
208   NSString *uFi  = nil;
209   NSArray *languages;
210   
211   uUri = [[self->src valueInContext:_ctx] stringValue];
212       
213   if ([self->disabled boolValueInComponent:sComponent]) {
214     uFi =  [self->disabledFilename stringValueInComponent:sComponent];
215     if (uFi == nil)
216       uFi = [self->filename stringValueInComponent:sComponent];
217   }
218   else
219     uFi = [self->filename stringValueInComponent:sComponent];
220   
221   if (!((uFi != nil) || (uUri != nil))) 
222     return;
223
224   languages = [_ctx hasSession]
225     ? [[_ctx session] languages]
226     : [[_ctx request] browserLanguages];
227         
228   WOResponse_AddCString(_resp, "<img src=\"");
229   
230   if (uFi) {
231     WOResourceManager *rm;
232           
233     if ((rm = [[_ctx component] resourceManager]) == nil)
234       rm = [[_ctx application] resourceManager];
235           
236     uFi = [rm urlForResourceNamed:uFi
237               inFramework:
238                 [self->framework stringValueInComponent:sComponent]
239               languages:languages
240               request:[_ctx request]];
241     if (uFi == nil) {
242       NSLog(@"%@: did not find resource %@", sComponent,
243             [self->filename stringValueInComponent:sComponent]);
244       uFi = uUri;
245     }
246     [_resp appendContentHTMLAttributeValue:uFi];
247   }
248   else {
249     [_resp appendContentHTMLAttributeValue:uUri];
250   }
251   WOResponse_AddChar(_resp, '"');
252   
253   [self appendExtraAttributesToResponse:_resp inContext:_ctx];
254   WOResponse_AddCString(_resp, " />");
255 }
256
257 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
258   WOComponent *sComponent = [_ctx component];
259   NSString *content;
260   NSString *targetView;
261   NSString *queryString = nil;
262
263   if ([[_ctx request] isFromClientComponent])
264     return;
265   
266   content    = [self->string valueInContext:_ctx];
267   targetView = [self->target stringValueInComponent:sComponent];
268     
269   WOResponse_AddCString(_response, "<a href=\"");
270     
271   if ([self _appendHrefToResponse:_response inContext:_ctx]) {
272       queryString = [self queryStringForQueryDictionary:
273                             [self->queryDictionary valueInComponent:sComponent]
274                           andQueryParameters:self->queryParameters
275                           inContext:_ctx];
276   }
277   
278   if (self->fragmentIdentifier) {
279       [_response appendContentCharacter:'#'];
280       WOResponse_AddString(_response,
281          [self->fragmentIdentifier stringValueInComponent:sComponent]);
282   }
283   if (queryString) {
284     [_response appendContentCharacter:'?'];
285     WOResponse_AddString(_response, queryString);
286   }
287   [_response appendContentCharacter:'"'];
288     
289   if (targetView) {
290     WOResponse_AddCString(_response, " target=\"");
291     WOResponse_AddString(_response, targetView);
292     [_response appendContentCharacter:'"'];
293   }
294     
295   [self appendExtraAttributesToResponse:_response inContext:_ctx];
296     
297   if (self->otherTagString) {
298     WOResponse_AddChar(_response, ' ');
299     WOResponse_AddString(_response,
300                          [self->otherTagString stringValueInComponent:
301                            [_ctx component]]);
302   }
303   [_response appendContentCharacter:'>'];
304     
305   /* content */
306   [self->template appendToResponse:_response inContext:_ctx];
307   if (content) [_response appendContentHTMLString:content];
308   
309   /* image content */
310   if ((self->src != nil) || (self->filename != nil))
311     [self _addImageToResponse:_response inContext:_ctx];
312   
313   /* closing tag */
314   WOResponse_AddCString(_response, "</a>");
315 }
316
317 /* description */
318
319 - (NSString *)associationDescription {
320   NSMutableString *str = [NSMutableString stringWithCapacity:256];
321
322   if (self->fragmentIdentifier)
323     [str appendFormat:@" fragment=%@", self->fragmentIdentifier];
324   if (self->string)   [str appendFormat:@" string=%@",   self->string];
325   if (self->target)   [str appendFormat:@" target=%@",   self->target];
326   if (self->disabled) [str appendFormat:@" disabled=%@", self->disabled];
327
328   /* image .. */
329   if (self->filename)  [str appendFormat:@" filename=%@",  self->filename];
330   if (self->framework) [str appendFormat:@" framework=%@", self->framework];
331   if (self->src)       [str appendFormat:@" src=%@",       self->src];
332
333   return str;
334 }
335
336 @end /* _WOComplexHyperlink */
337
338 @implementation _WOHrefHyperlink
339
340 static BOOL debugStaticLinks = NO;
341
342 + (void)initialize {
343   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
344   
345   debugStaticLinks = [ud boolForKey:@"WODebugStaticLinkProcessing"];
346 }
347
348 - (id)initWithName:(NSString *)_name
349   hyperlinkInfo:(WOHyperlinkInfo *)_info
350   template:(WOElement *)_t
351 {
352   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
353     self->href = _info->href;
354   }
355   return self;
356 }
357
358 - (void)dealloc {
359   [self->href release];
360   [super dealloc];
361 }
362
363 /* URI generation */
364
365 - (BOOL)shouldRewriteURLString:(NSString *)_s inContext:(WOContext *)_ctx {
366   // TODO: we need a binding to disable rewriting!
367   if ([_s hasPrefix:@"mailto:"])
368     return NO;
369   if ([_s hasPrefix:@"javascript:"])
370     return NO;
371   return YES;
372 }
373
374 - (BOOL)_appendHrefToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
375   NSString *s;
376   id    hrefValue;
377   NSURL *url, *base;
378   
379   base      = [_ctx baseURL];
380   hrefValue = [self->href valueInContext:_ctx];
381   url       = hrefValue;
382   
383   if (hrefValue == nil)
384     return NO;
385   
386   if ([hrefValue isKindOfClass:NSURLClass]) {
387     s = [hrefValue stringValueRelativeToURL:base];
388   }
389   else {
390     s = [hrefValue stringValue];
391     
392     if ([self shouldRewriteURLString:s inContext:_ctx]) {
393       if ([s isAbsoluteURL]) {
394         // TODO: why are we doing this? we could just pass through the string?
395         //    => probably to generate relative links
396         url = [NSURLClass URLWithString:s];
397       }
398       else if (base != nil) {
399         /* avoid creating a new URL for ".", just return the base */
400         url = [s isEqualToString:@"."]
401           ? base
402           : [NSURLClass URLWithString:s relativeToURL:base];
403       }
404       else {
405         [self logWithFormat:@"WARNING: missing base URL in context ..."];
406         WOResponse_AddString(_r, s);
407         return YES;
408       }
409       
410       if (url == nil) {
411         [self logWithFormat:
412                 @"couldn't construct URL from 'href' string '%@' (base=%@)",
413                 s, base];
414         return NO;
415       }
416       
417       s = [url stringValueRelativeToURL:base];
418     }
419   }
420   
421   /* generate URL */
422   
423   if (debugStaticLinks) {
424     [self logWithFormat:@"static links based on 'href': '%@'", hrefValue];
425     [self logWithFormat:@"  base     %@", base];
426     [self logWithFormat:@"  base-abs %@", [base absoluteString]];
427     [self logWithFormat:@"  url      %@", url];
428     [self logWithFormat:@"  url-abs  %@", [url absoluteString]];
429     [self logWithFormat:@"  string   %@", s];
430   }
431   WOResponse_AddString(_r, s);
432   return YES;
433 }
434
435 /* description */
436
437 - (NSString *)associationDescription {
438   NSMutableString *str = [NSMutableString stringWithCapacity:256];
439
440   [str appendFormat:@" href=%@", self->href];
441   [str appendString:[super associationDescription]];
442   
443   return str;
444 }
445
446 @end /* _WOHrefHyperlink */
447
448 @implementation _WOActionHyperlink
449
450 - (id)initWithName:(NSString *)_name
451   hyperlinkInfo:(WOHyperlinkInfo *)_info
452   template:(WOElement *)_t
453 {
454   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
455     self->action = _info->action;
456   }
457   return self;
458 }
459
460 - (void)dealloc {
461   [self->action release];
462   [super dealloc];
463 }
464
465 /* dynamic invocation */
466
467 - (id)invokeActionForRequest:(WORequest *)_request
468   inContext:(WOContext *)_ctx
469 {
470   if (self->disabled) {
471     if ([self->disabled boolValueInComponent:[_ctx component]])
472       return nil;
473   }
474
475   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
476     /* link is not the active element */
477     return [self->template invokeActionForRequest:_request inContext:_ctx];
478   
479   /* link is active */
480   return [self executeAction:self->action inContext:_ctx];
481 }
482
483 - (BOOL)_appendHrefToResponse:(WOResponse *)_response
484   inContext:(WOContext *)_ctx
485 {
486   WOResponse_AddString(_response, [_ctx componentActionURL]);
487   return YES;
488 }
489
490 /* description */
491
492 - (NSString *)associationDescription {
493   NSMutableString *str = [NSMutableString stringWithCapacity:256];
494
495   [str appendFormat:@" action=%@", self->action];
496   [str appendString:[super associationDescription]];
497   return str;
498 }
499
500 @end /* _WOActionHyperlink */
501
502 @implementation _WOPageHyperlink
503
504 - (id)initWithName:(NSString *)_name
505   hyperlinkInfo:(WOHyperlinkInfo *)_info
506   template:(WOElement *)_t
507 {
508   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
509     self->pageName = _info->pageName;
510   }
511   return self;
512 }
513
514 - (void)dealloc {
515   [self->pageName release];
516   [super dealloc];
517 }
518
519 /* actions */
520
521 - (id)invokeActionForRequest:(WORequest *)_request
522   inContext:(WOContext *)_ctx
523 {
524   WOComponent *page;
525   NSString    *name;
526
527   if (self->disabled) {
528     if ([self->disabled boolValueInComponent:[_ctx component]])
529       return nil;
530   }
531   
532   if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
533     /* link is not the active element */
534     return [self->template invokeActionForRequest:_request inContext:_ctx];
535   
536   /* link is the active element */
537   
538   name = [self->pageName stringValueInComponent:[_ctx component]];
539   page = [[_ctx application] pageWithName:name inContext:_ctx];
540
541   if (page == nil) {
542     [[_ctx session] logWithFormat:
543                       @"%@[0x%08X]: did not find page with name %@ !",
544                       NSStringFromClass([self class]), self, name];
545   }
546   return page;
547 }
548
549 - (BOOL)_appendHrefToResponse:(WOResponse *)_response
550   inContext:(WOContext *)_ctx
551 {
552   /*
553     Profiling:
554       87% -componentActionURL
555       13% NSString dataUsingEncoding(appendContentString!)
556     TODO(prof): use addcstring
557   */
558   WOResponse_AddString(_response, [_ctx componentActionURL]);
559   return YES;
560 }
561
562 /* description */
563
564 - (NSString *)associationDescription {
565   NSMutableString *str = [NSMutableString stringWithCapacity:256];
566
567   [str appendFormat:@" pageName=%@", self->pageName];
568   [str appendString:[super associationDescription]];
569   return str;
570 }
571
572 @end /* _WOPageHyperlink */
573
574 @implementation _WODirectActionHyperlink
575
576 - (id)initWithName:(NSString *)_name
577   hyperlinkInfo:(WOHyperlinkInfo *)_info
578   template:(WOElement *)_t
579 {
580   if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
581     self->actionClass      = _info->actionClass;
582     self->directActionName = _info->directActionName;
583     self->sidInUrl         = _info->sidInUrl;
584
585     self->containsForm = NO; /* direct actions are never form stuff ... */
586   }
587   return self;
588 }
589
590 - (void)dealloc {
591   [self->actionClass      release];
592   [self->directActionName release];
593   [super dealloc];
594 }
595
596 /* href */
597
598 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
599   /* DA links can *never* take form values !!!! */
600   [self->template takeValuesFromRequest:_req inContext:_ctx];
601 }
602
603 - (id)invokeActionForRequest:(WORequest *)_request
604   inContext:(WOContext *)_ctx
605 {
606   /* DA links can *never* invoke an action !!!! */
607   return [self->template invokeActionForRequest:_request inContext:_ctx];
608 }
609
610 - (BOOL)_appendHrefToResponse:(WOResponse *)_response
611   inContext:(WOContext *)_ctx
612 {
613   WOComponent         *sComponent;
614   NSString            *daClass;
615   NSString            *daName;
616   NSMutableDictionary *qd;
617   NSDictionary        *tmp;
618
619   sComponent = [_ctx component];
620   daClass = [self->actionClass stringValueInComponent:sComponent];
621   daName  = [self->directActionName stringValueInComponent:sComponent];
622
623   if (daClass) {
624     if (daName) {
625       if (![daClass isEqualToString:@"DirectAction"])
626         daName = [NSString stringWithFormat:@"%@/%@", daClass, daName];
627     }
628     else
629       daName = daClass;
630   }
631   
632   qd = [NSMutableDictionary dictionaryWithCapacity:16];
633   
634   /* add query dictionary */
635   
636   if (self->queryDictionary) {
637     if ((tmp = [self->queryDictionary valueInComponent:sComponent]))
638       [qd addEntriesFromDictionary:tmp];
639   }
640   
641   /* add ?style parameters */
642
643   if (self->queryParameters) {
644     NSEnumerator *keys;
645     NSString     *key;
646
647     keys = [self->queryParameters keyEnumerator];
648     while ((key = [keys nextObject])) {
649       id assoc, value;
650
651       assoc = [self->queryParameters objectForKey:key];
652       value = [assoc stringValueInComponent:sComponent];
653           
654       [qd setObject:(value ? value : @"") forKey:key];
655     }
656   }
657       
658   /* add session ID */
659   
660   if (self->sidInUrl) {
661     if ([_ctx hasSession]) {
662       WOSession *sn;
663       
664       sn = [_ctx session];
665       [qd setObject:[sn sessionID] forKey:WORequestValueSessionID];
666       
667       if (![sn isDistributionEnabled]) {
668         [qd setObject:[[WOApplication application] number]
669             forKey:WORequestValueInstance];
670       }
671     }
672   }
673
674   WOResponse_AddString(_response,
675                        [_ctx directActionURLForActionNamed:daName
676                              queryDictionary:qd]);
677   return NO;
678 }
679
680 /* description */
681
682 - (NSString *)associationDescription {
683   NSMutableString *str = [NSMutableString stringWithCapacity:256];
684
685   if (self->actionClass)
686     [str appendFormat:@" actionClass=%@", self->actionClass];
687   if (self->directActionName)
688     [str appendFormat:@" directAction=%@", self->directActionName];
689   
690   [str appendString:[super associationDescription]];
691   return str;
692 }
693
694 @end /* _WODirectActionHyperlink */