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