2 Copyright (C) 2000-2008 SKYRIX Software AG
3 Copyright (C) 2008 Helge Hess
5 This file is part of SOPE.
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
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.
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
23 #include "WOHyperlink.h"
27 [WOHTMLDynamicElement]
33 _WODirectActionHyperlink
36 @interface _WOComplexHyperlink : WOHyperlink
38 /* superclass of most hyperlink classes */
40 WOAssociation *fragmentIdentifier;
41 WOAssociation *string;
42 WOAssociation *target;
43 WOAssociation *disabled;
47 WOAssociation *queryDictionary;
48 NSDictionary *queryParameters; /* associations beginning with ? */
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 */
57 - (NSString *)associationDescription;
61 @interface _WOHrefHyperlink : _WOComplexHyperlink
67 @interface _WOActionHyperlink : _WOComplexHyperlink
69 WOAssociation *action;
73 @interface _WOPageHyperlink : _WOComplexHyperlink
75 WOAssociation *pageName;
79 @interface _WODirectActionHyperlink : _WOComplexHyperlink
81 WOAssociation *actionClass;
82 WOAssociation *directActionName;
83 BOOL sidInUrl; /* include session-id in wa URL ? */
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>
95 static Class NSURLClass = Nil;
97 @implementation _WOComplexHyperlink
100 return [super version] /* v4 */;
103 NSAssert2([super version] == 4,
104 @"invalid superclass (%@) version %i !",
105 NSStringFromClass([self superclass]), [super version]);
106 if (NSURLClass == Nil)
107 NSURLClass = [NSURL class];
110 - (id)initWithName:(NSString *)_name
111 hyperlinkInfo:(WOHyperlinkInfo *)_info
112 template:(WOElement *)_t
114 if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
115 self->template = [_t retain];
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;
125 self->filename = _info->filename;
126 self->framework = _info->framework;
127 self->src = _info->src;
128 self->disabledFilename = _info->disabledFilename;
130 self->containsForm = self->queryParameters ? YES : NO;
136 [self->template release];
137 [self->queryDictionary release];
138 [self->queryParameters release];
139 [self->disabledFilename release];
140 [self->filename release];
141 [self->framework release];
143 [self->fragmentIdentifier release];
144 [self->string release];
145 [self->target release];
146 [self->disabled release];
153 return self->template;
156 /* handle requests */
158 - (void)takeValuesFromRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
159 /* links can take form values !!!! (for query-parameters) */
161 if (self->queryParameters != nil) {
162 /* apply values to ?style parameters */
163 WOComponent *sComponent = [_ctx component];
167 keys = [self->queryParameters keyEnumerator];
168 while ((key = [keys nextObject]) != nil) {
171 assoc = [self->queryParameters objectForKey:key];
173 if ([assoc isValueSettable]) {
174 value = [_rq formValueForKey:key];
175 [assoc setValue:value inComponent:sComponent];
180 [self->template takeValuesFromRequest:_rq inContext:_ctx];
183 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
184 if (self->disabled != nil) {
185 if ([self->disabled boolValueInComponent:[_ctx component]])
189 if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
190 /* link is not the active element */
191 return [self->template invokeActionForRequest:_rq inContext:_ctx];
194 [[_ctx session] logWithFormat:@"%@[0x%p]: no action/page set !",
195 NSStringFromClass([self class]), self];
199 - (BOOL)_appendHrefToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
200 [self subclassResponsibility:_cmd];
204 - (void)_addImageToResponse:(WOResponse *)_resp inContext:(WOContext *)_ctx {
205 WOComponent *sComponent = [_ctx component];
210 uUri = [[self->src valueInContext:_ctx] stringValue];
212 if ([self->disabled boolValueInComponent:sComponent]) {
213 uFi = [self->disabledFilename stringValueInComponent:sComponent];
215 uFi = [self->filename stringValueInComponent:sComponent];
218 uFi = [self->filename stringValueInComponent:sComponent];
220 if (!((uFi != nil) || (uUri != nil)))
223 languages = [_ctx resourceLookupLanguages];
225 WOResponse_AddCString(_resp, "<img src=\"");
228 WOResourceManager *rm;
230 if ((rm = [[_ctx component] resourceManager]) == nil)
231 rm = [[_ctx application] resourceManager];
233 uFi = [rm urlForResourceNamed:uFi
235 [self->framework stringValueInComponent:sComponent]
237 request:[_ctx request]];
239 NSLog(@"%@: did not find resource %@", sComponent,
240 [self->filename stringValueInComponent:sComponent]);
243 [_resp appendContentHTMLAttributeValue:uFi];
246 [_resp appendContentHTMLAttributeValue:uUri];
248 WOResponse_AddChar(_resp, '"');
250 [self appendExtraAttributesToResponse:_resp inContext:_ctx];
252 WOResponse_AddEmptyCloseParens(_resp, _ctx);
255 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
256 WOComponent *sComponent = [_ctx component];
260 if ([_ctx isRenderingDisabled] || [[_ctx request] isFromClientComponent]) {
261 [self->template appendToResponse:_response inContext:_ctx];
265 content = [self->string valueInContext:_ctx];
266 doNotDisplay = [self->disabled boolValueInComponent:sComponent];
269 NSString *targetView;
270 NSString *queryString = nil;
272 targetView = [self->target stringValueInComponent:sComponent];
274 WOResponse_AddCString(_response, "<a href=\"");
276 if ([self _appendHrefToResponse:_response inContext:_ctx]) {
277 queryString = [self queryStringForQueryDictionary:
278 [self->queryDictionary valueInComponent:sComponent]
279 andQueryParameters:self->queryParameters
283 if (self->fragmentIdentifier) {
284 [_response appendContentCharacter:'#'];
285 WOResponse_AddString(_response,
286 [self->fragmentIdentifier stringValueInComponent:sComponent]);
289 [_response appendContentCharacter:'?'];
290 WOResponse_AddString(_response, queryString);
292 [_response appendContentCharacter:'"'];
295 WOResponse_AddCString(_response, " target=\"");
296 WOResponse_AddString(_response, targetView);
297 [_response appendContentCharacter:'"'];
300 [self appendExtraAttributesToResponse:_response inContext:_ctx];
302 if (self->otherTagString) {
303 WOResponse_AddChar(_response, ' ');
304 WOResponse_AddString(_response,
305 [self->otherTagString stringValueInComponent:
308 [_response appendContentCharacter:'>'];
312 [self->template appendToResponse:_response inContext:_ctx];
313 if (content) [_response appendContentHTMLString:content];
316 if ((self->src != nil) || (self->filename != nil))
317 [self _addImageToResponse:_response inContext:_ctx];
321 WOResponse_AddCString(_response, "</a>");
327 - (NSString *)associationDescription {
328 NSMutableString *str = [NSMutableString stringWithCapacity:256];
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];
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];
344 @end /* _WOComplexHyperlink */
347 @implementation _WOHrefHyperlink
349 static BOOL debugStaticLinks = NO;
352 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
354 debugStaticLinks = [ud boolForKey:@"WODebugStaticLinkProcessing"];
357 - (id)initWithName:(NSString *)_name
358 hyperlinkInfo:(WOHyperlinkInfo *)_info
359 template:(WOElement *)_t
361 if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
362 self->href = _info->href;
368 [self->href release];
374 - (BOOL)shouldRewriteURLString:(NSString *)_s inContext:(WOContext *)_ctx {
375 // TODO: we need a binding to disable rewriting!
378 r.length = [_s length];
380 /* do not rewrite pure fragment URLs */
381 if (r.length > 0 && [_s characterAtIndex:0] == '#')
384 /* rewrite all URLs w/o a protocol */
385 r = [_s rangeOfString:@":"];
389 /* only rewrite HTTP URLs */
390 return [_s hasPrefix:@"http"];
393 - (BOOL)_appendHrefToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
398 base = [_ctx baseURL];
399 hrefValue = [self->href valueInContext:_ctx];
402 if (hrefValue == nil)
405 if ((*(Class *)hrefValue == NSURLClass) ||
406 [hrefValue isKindOfClass:NSURLClass]) {
407 s = [hrefValue stringValueRelativeToURL:base];
410 /* given HREF is a string */
411 s = [hrefValue stringValue];
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];
420 else if (base != nil) {
421 /* avoid creating a new URL for ".", just return the base */
422 url = [s isEqualToString:@"."]
424 : (NSURL *)[NSURLClass URLWithString:s relativeToURL:base];
427 [self warnWithFormat:@"missing base URL in context ..."];
428 WOResponse_AddString(_r, s);
434 @"could not construct URL from 'href' string '%@' (base=%@)",
439 s = [url stringValueRelativeToURL:base];
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];
451 [self logWithFormat:@" url-abs %@", [url absoluteString]];
453 [self logWithFormat:@" href %@", hrefValue];
454 [self logWithFormat:@" string %@", s];
457 WOResponse_AddString(_r, s);
463 - (NSString *)associationDescription {
464 NSMutableString *str = [NSMutableString stringWithCapacity:256];
466 [str appendFormat:@" href=%@", self->href];
467 [str appendString:[super associationDescription]];
472 @end /* _WOHrefHyperlink */
475 @implementation _WOActionHyperlink
477 - (id)initWithName:(NSString *)_name
478 hyperlinkInfo:(WOHyperlinkInfo *)_info
479 template:(WOElement *)_t
481 if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
482 self->action = _info->action;
488 [self->action release];
492 /* dynamic invocation */
494 - (id)invokeActionForRequest:(WORequest *)_rq
495 inContext:(WOContext *)_ctx
497 if (self->disabled) {
498 if ([self->disabled boolValueInComponent:[_ctx component]])
502 if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
503 /* link is not the active element */
504 return [self->template invokeActionForRequest:_rq inContext:_ctx];
507 return [self executeAction:self->action inContext:_ctx];
510 - (BOOL)_appendHrefToResponse:(WOResponse *)_response
511 inContext:(WOContext *)_ctx
513 WOResponse_AddString(_response, [_ctx componentActionURL]);
519 - (NSString *)associationDescription {
520 NSMutableString *str = [NSMutableString stringWithCapacity:256];
522 [str appendFormat:@" action=%@", self->action];
523 [str appendString:[super associationDescription]];
527 @end /* _WOActionHyperlink */
530 @implementation _WOPageHyperlink
532 - (id)initWithName:(NSString *)_name
533 hyperlinkInfo:(WOHyperlinkInfo *)_info
534 template:(WOElement *)_t
536 if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
537 self->pageName = _info->pageName;
543 [self->pageName release];
549 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
553 if (self->disabled) {
554 if ([self->disabled boolValueInComponent:[_ctx component]])
558 if (![[_ctx elementID] isEqualToString:[_ctx senderID]])
559 /* link is not the active element */
560 return [self->template invokeActionForRequest:_rq inContext:_ctx];
562 /* link is the active element */
564 name = [self->pageName stringValueInComponent:[_ctx component]];
565 page = [[_ctx application] pageWithName:name inContext:_ctx];
568 [[_ctx session] logWithFormat:
569 @"%@[0x%p]: did not find page with name %@ !",
570 NSStringFromClass([self class]), self, name];
575 /* generate response */
577 - (BOOL)_appendHrefToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
580 87% -componentActionURL
581 13% NSString dataUsingEncoding(appendContentString!)
582 TODO(prof): use addcstring
584 WOResponse_AddString(_r, [_ctx componentActionURL]);
590 - (NSString *)associationDescription {
591 NSMutableString *str = [NSMutableString stringWithCapacity:256];
593 [str appendFormat:@" pageName=%@", self->pageName];
594 [str appendString:[super associationDescription]];
598 @end /* _WOPageHyperlink */
601 @implementation _WODirectActionHyperlink
603 - (id)initWithName:(NSString *)_name
604 hyperlinkInfo:(WOHyperlinkInfo *)_info
605 template:(WOElement *)_t
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;
612 self->containsForm = NO; /* direct actions are never form stuff ... */
618 [self->actionClass release];
619 [self->directActionName release];
623 /* handle requests */
625 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
626 /* DA links can *never* take form values !!!! */
627 [self->template takeValuesFromRequest:_req inContext:_ctx];
630 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
631 /* DA links can *never* invoke an action !!!! */
632 return [self->template invokeActionForRequest:_rq inContext:_ctx];
635 /* generate response */
637 - (BOOL)_appendHrefToResponse:(WOResponse *)_r
638 inContext:(WOContext *)_ctx
640 WOComponent *sComponent;
643 NSMutableDictionary *qd;
646 sComponent = [_ctx component];
647 daClass = [self->actionClass stringValueInComponent:sComponent];
648 daName = [self->directActionName stringValueInComponent:sComponent];
652 if (![daClass isEqualToString:@"DirectAction"])
653 daName = [NSString stringWithFormat:@"%@/%@", daClass, daName];
659 qd = [NSMutableDictionary dictionaryWithCapacity:16];
661 /* add query dictionary */
663 if (self->queryDictionary) {
664 if ((tmp = [self->queryDictionary valueInComponent:sComponent]))
665 [qd addEntriesFromDictionary:tmp];
668 /* add ?style parameters */
670 if (self->queryParameters != nil) {
674 keys = [self->queryParameters keyEnumerator];
675 while ((key = [keys nextObject])) {
678 assoc = [self->queryParameters objectForKey:key];
679 value = [assoc stringValueInComponent:sComponent];
681 [qd setObject:(value != nil ? value : (id)@"") forKey:key];
687 if (self->sidInUrl) {
688 if ([_ctx hasSession]) {
692 [qd setObject:[sn sessionID] forKey:WORequestValueSessionID];
694 if (![sn isDistributionEnabled]) {
695 [qd setObject:[[WOApplication application] number]
696 forKey:WORequestValueInstance];
701 WOResponse_AddString(_r,
702 [_ctx directActionURLForActionNamed:daName
703 queryDictionary:qd]);
709 - (NSString *)associationDescription {
710 NSMutableString *str = [NSMutableString stringWithCapacity:256];
712 if (self->actionClass)
713 [str appendFormat:@" actionClass=%@", self->actionClass];
714 if (self->directActionName)
715 [str appendFormat:@" directAction=%@", self->directActionName];
717 [str appendString:[super associationDescription]];
721 @end /* _WODirectActionHyperlink */