]>
git.p6c8.net - selfforum.git/blob - selfforum-cgi/user/fo_posting.pl
3 ################################################################################
5 # File: user/fo_posting.pl #
7 # Authors: André Malo <nd@o3media.de>, 2001-04-08 #
9 # Description: Accept new postings, display "Neue Nachricht" page #
11 ################################################################################
24 my $null = $0; $null =~ s/\\/\//g
; # for win :-(
25 $Bin = ($null =~ /^(.*)\/.*$/)?
$1 : '.';
26 $Shared = "$Bin/../shared";
27 $Config = "$Bin/config";
28 $Script = ($null =~ /^.*\/(.*)$/)?
$1 : $null;
30 # my $null = $0; #$null =~ s/\\/\//g; # for win :-(
31 # $Bin = ($null =~ /^(.*)\/.*$/)? $1 : '.';
32 # $Config = "$Bin/../../../cgi-config/devforum";
33 # $Shared = "$Bin/../../../cgi-shared";
34 # $Script = ($null =~ /^.*\/(.*)$/)? $1 : $null;
38 use CGI
::Carp
qw(fatalsToBrowser);
44 # load script configuration and admin default conf.
46 my $conf = read_script_conf
($Config, $Shared, $Script);
47 my $adminDefault = read_admin_conf
($conf -> {files
} -> {adminDefault
});
49 # Initialize the request
51 my $request = new Posting
::Request
($conf, $adminDefault);
53 # fetch and parse the cgi-params
55 $request -> parse_cgi
;
57 # handle errors or save the posting
59 $request -> handle_error
or $request -> save
;
67 ### main end ###################################################################
69 ################################################################################
70 ### Posting::Request ###########################################################
71 package Posting
::Request
;
74 use Encode
::Plain
; $Encode::Plain
::utf8
= 1; # generally convert from UTF-8
88 use Template
::Posting
;
92 ### sub new ####################################################################
94 # initialising the Posting::Request object
95 # check parameters and fill in object properties
98 my ($class, $conf, $adminDefault) = @_;
100 my $sp = $conf -> {show
} -> {Posting
};
105 admin
=> $adminDefault,
107 message_path
=> $conf -> {files
} -> {messagePath
},
108 forum_file_name
=> $conf -> {files
} -> {forum
},
111 assign
=> $sp -> {assign
},
112 template
=> $conf -> {template
},
113 form_must
=> $sp -> {form
} -> {must
},
114 form_data
=> $sp -> {form
} -> {data
},
115 form_action
=> $sp -> {form
} -> {action
},
118 template
=> new Template
$sp -> {templateFile
},
127 ### sub response ###############################################################
129 # print the response to STDOUT
135 my $formdata = $self -> {conf
} -> {form_data
};
136 my $formact = $self -> {conf
} -> {form_action
};
137 my $template = $self -> {template
};
138 my $assign = $self -> {conf
} -> {assign
};
139 my $q = $self -> {cgi_object
};
141 # fill out the form field names
144 for (keys %$formdata) {
145 $pars -> {$formdata -> {$_} -> {assign
} -> {name
}} = plain
($formdata -> {$_} -> {name
}) if (
146 exists($formdata -> {$_} -> {name
})
147 and exists ($formdata -> {$_} -> {assign
})
148 and exists ($formdata -> {$_} -> {assign
} -> {name
})
152 # response the 'new message' page
154 if ($self -> {response
} -> {new_thread
}) {
156 # fill in the default form data
160 for (keys %$formdata) {
161 unless (exists ($formdata -> {$_} -> {type
}) and $formdata -> {$_} -> {type
} eq 'internal') {
162 if (exists ($formdata -> {$_} -> {default}) and exists ($formdata -> {$_} -> {assign
} -> {value
})) {
163 $default -> {$formdata -> {$_} -> {assign
} -> {value
}}
164 = $formdata -> {$_} -> {default};
166 elsif (exists($formdata -> {$_} -> {values})) {
167 my ($_name, $val) = $_;
168 $val = exists ($formdata -> {$_} -> {default})
169 ?
$formdata -> {$_} -> {default}
171 $default -> {$formdata -> {$_} -> {assign
} -> {value
}}
172 = $self -> {template
} -> list
(
175 { $assign -> {optval
} => plain
($_),
176 ((defined $val and $_ eq $val)
177 ?
($assign -> {optsel
} => 1)
181 } @
{$formdata -> {$_name} -> {values}}
188 print $q -> header
(-type
=> 'text/html');
189 print ${$template -> scrap
(
191 { $formdata->{uniqueID
} ->{assign
}->{value
} => plain
(unique_id
),
192 $formdata->{quoteChar
} ->{assign
}->{value
} => 'ÿ'.plain
($self -> {conf
} -> {admin
} -> {View
} -> {quoteChars
}),
193 $formact->{post
}->{assign
} => $formact->{post
}->{url
},
201 # check the response -> doc
203 unless ($self -> {response
} -> {doc
}) {
205 spec
=> 'unknown_error',
209 $self -> handle_error
;
211 unless ($self -> {response
} -> {doc
}) {
212 $self -> jerk
('While producing the HTML response an unknown error has occurred.');
217 # ok, print the response document to STDOUT
219 print $q -> header
(-type
=> 'text/html');
220 print ${$template -> scrap
(
221 $self -> {response
} -> {doc
},
223 $self -> {response
} -> {pars
}
230 ### sub handle_error ###########################################################
232 # analyze error data and create content for the response method
234 # Return: true if error detected
240 my $spec = $self -> {error
} -> {spec
};
242 return unless ($spec);
244 my $assign = $self -> {conf
} -> {assign
};
245 my $formdata = $self -> {conf
} -> {form_data
};
247 my $desc = $self -> {error
} -> {desc
} || '';
248 my $type = $self -> {error
} -> {type
};
251 if (exists ($formdata -> {$desc})
252 and exists ($formdata -> {$desc} -> {assign
} -> {$spec})) {
253 $emsg = $formdata -> {$desc} -> {assign
} -> {$spec};
256 $emsg = $assign -> {$spec} || '';
261 if ($type eq 'fatal') {
262 $self -> {response
} -> {doc
} = $assign -> {docFatal
};
263 $self -> {response
} -> {pars
} = {
264 $assign -> {errorMessage
} => $self -> {template
} -> insert
($emsg)
269 # user is able to repair his request
271 elsif ($type eq 'repeat' or $type eq 'fetch') {
272 $self -> {response
} -> {doc
} = $assign -> {docError
};
273 $self -> fillout_form
;
274 $self -> {response
} -> {pars
} -> {$assign -> {errorMessage
}} = $self -> {template
} -> insert
($emsg);
275 my $num = $spec eq 'too_long'
276 ?
$formdata -> {$desc} -> {maxlength
}
277 : ($spec eq 'too_short'
278 ?
$formdata -> {$desc} -> {minlength
}
282 $self -> {response
} -> {pars
} -> {$assign -> {charNum
}} = $num
289 ### sub fillout_form ###########################################################
291 # fill out the form using available form data
298 my $assign = $self -> {conf
} -> {assign
};
299 my $formdata = $self -> {conf
} -> {form_data
};
300 my $formact = $self -> {conf
} -> {form_action
};
301 my $q = $self -> {cgi_object
};
306 $pars -> {$formact -> {post
} -> {assign
}} = $formact -> {post
} -> {url
};
308 for (keys %$formdata) {
309 if ($_ eq 'quoteChar') {
310 $pars -> {$formdata->{$_}->{assign
}->{value
}}
311 = 'ÿ'.plain
($q -> param
($formdata -> {quoteChar
} -> {name
}) or '');
313 elsif (exists ($formdata -> {$_} -> {name
})) {
314 unless (exists ($formdata -> {$_} -> {values})) {
315 $pars -> {$formdata -> {$_} -> {assign
} -> {value
}}
316 = plain
($q -> param
($formdata -> {$_} -> {name
}));
320 $pars -> {$formdata -> {$_} -> {assign
} -> {value
}}
321 = $self -> {template
} -> list
(
324 { $assign -> {optval
} => plain
($_),
325 (( $_ eq $q -> param
($formdata -> {$_name} -> {name
}))
326 ?
($assign -> {optsel
} => 1)
330 } @
{$formdata -> {$_name} -> {values}}
337 $self -> {response
} -> {pars
} = $pars;
341 ### sub save ###################################################################
344 # check on legal reply or dupe is released here
351 # if an empty 'new message' document, there's nothing to save
353 return if ($self -> {response
} -> {new_thread
});
355 $self -> {check_success
} = 0;
357 # lock and load the forum main file
359 if ($self -> load_main_file
) {
361 # if a reply - is it legal?
364 if ($self -> check_reply_dupe
) {
366 unless ($self -> {response
} -> {reply
} or $self -> {response
} -> {new
}) {
367 # don't know, if we any time come to this branch
368 # the script is probably broken
371 spec
=> 'unknown_error',
377 my $formdata = $self -> {conf
} -> {form_data
};
378 my $q = $self -> {cgi_object
};
379 my $f = $self -> {forum
};
381 quoteChars
=> $q -> param
($formdata -> {quoteChar
} -> {name
}),
382 uniqueID
=> $q -> param
($formdata -> {uniqueID
} -> {name
}),
384 ip
=> $q -> remote_addr
,
385 forumFile
=> $self -> {conf
} -> {forum_file_name
},
386 messagePath
=> $self -> {conf
} -> {message_path
},
387 lastThread
=> $f -> {last_thread
},
388 lastMessage
=> $f -> {last_message
},
389 parsedThreads
=> $f -> {threads
},
391 messages
=> $self -> {conf
} -> {template
} -> {messages
} || {},
392 base_uri
=> $self -> {conf
} -> {original
} -> {files
} -> {forum_base
}
395 # set the variables if defined..
398 author
=> 'posterName',
399 email
=> 'posterEmail',
400 category
=> 'posterCategory',
401 subject
=> 'posterSubject',
402 body
=> 'posterBody',
403 homepage
=> 'posterURL',
404 image
=> 'posterImage'
408 $pars -> {$_} = $q -> param
($formdata -> {$may{$_}} -> {name
})
409 if (defined $q -> param
($formdata -> {$may{$_}} -> {name
}));
412 my ($stat, $xml, $mid, $tid);
414 # we've got a fup if it's a reply
416 if ($self -> {response
} -> {reply
}) {
417 $pars -> {parentMessage
} = $self -> {fup_mid
};
418 $pars -> {thread
} = $self -> {fup_tid
};
419 ($stat, $xml, $mid, $tid) = write_reply_posting
($pars);
422 ($stat, $xml, $mid, $tid) = write_new_thread
($pars);
433 my $cache = new Posting
::Cache
($self->{conf
}->{original
}->{files
}->{cacheFile
});
434 $cache -> add_posting
(
435 { thread
=> ($tid =~ /(\d+)/)[0],
436 posting
=> ($mid =~ /(\d+)/)[0]
440 $self -> {check_success
} = 1;
441 my $thx = $self -> {conf
} -> {show_posting
} -> {thanx
};
443 # define special response data
445 $self -> {response
} -> {doc
} = $self -> {conf
} -> {assign
} -> {docThx
};
446 $self -> {response
} -> {pars
} = {
447 $thx -> {time} => plain
(hr_time
($time)),
448 $thx -> {body
} => message_as_HTML
(
452 assign
=> $self -> {conf
} -> {assign
},
453 quoteChars
=> $q -> param
($formdata -> {quoteChar
} -> {name
}),
454 quoting
=> $self -> {conf
} -> {admin
} -> {View
} -> {quoting
}
458 # set the variables if defined..
461 author
=> 'posterName',
462 email
=> 'posterEmail',
463 category
=> 'posterCategory',
464 subject
=> 'posterSubject',
465 homepage
=> 'posterURL',
466 image
=> 'posterImage'
470 my $x = $q -> param
($formdata -> {$may{$_}} -> {name
});
471 $x = '' unless (defined $x);
472 $self -> {response
} -> {pars
} -> {$thx -> {$_}} = plain
($x)
473 if (defined $thx -> {$_});
480 # unlock forum main file
482 if ($self -> {forum
} -> {flocked
}) {
483 violent_unlock_file
($self -> {conf
} -> {forum_file_name
}) unless write_unlock_file
($self -> {conf
} -> {forum_file_name
});
484 $self -> {forum
} -> {flocked
} = 0;
487 $self -> handle_error
unless $self -> {check_success
};
492 ### sub parse_cgi ##############################################################
494 # fetch and decode cgi-parameters,
495 # find out the kind of response requested by the user (new message, reply)
502 # create the CGI object
504 $self -> {cgi_object
} = new CGI
;
508 $self -> {check_success
} = $self -> check_cgi
;
513 ### sub load_main_file #########################################################
515 # load and parse the forum main file
517 # Return: Success (true/false)
523 unless ($lock_stat = write_lock_file
($self -> {conf
} -> {forum_file_name
})) {
524 if (defined $lock_stat) {
525 # occupied or no w-bit set for the directory..., hmmm
527 violent_unlock_file
($self -> {conf
} -> {forum_file_name
});
538 spec
=> 'master_lock',
545 $self -> {forum
} -> {flocked
} = 1;
546 ( $self -> {forum
} -> {threads
},
547 $self -> {forum
} -> {last_thread
},
548 $self -> {forum
} -> {last_message
},
549 $self -> {forum
} -> {dtd
},
550 $self -> {forum
} -> {unids
}
551 ) = get_all_threads
($self -> {conf
} -> {forum_file_name
}, KEEP_DELETED
);
558 ### sub check_reply_dupe #######################################################
560 # check whether a reply is legal
561 # (followup posting must exists)
563 # check whether this form request is a dupe
564 # (unique id already exists)
566 # Return: Status Code (Bool)
568 sub check_reply_dupe
{
572 # return true unless it's not a reply
576 $self -> {response
} -> {reply
}
577 or $self -> {response
} -> {new
}
580 if ($self -> {response
} -> {reply
}) {
582 my ($threads, $ftid, $fmid, $i, %msg) = (
583 $self -> {forum
} -> {threads
},
588 # thread doesn't exist
590 unless (exists($threads -> {$ftid})) {
598 # build a reverse lookup hash (mid => number in array)
599 # and ignore invisible messages
600 # (users can't reply to "deleted" msg)
602 for ($i=0; $i < @
{$threads -> {$ftid}}; $i++) {
604 if ($threads -> {$ftid} -> [$i] -> {deleted
}) {
605 $i+=$threads -> {$ftid} -> [$i] -> {answers
};
608 $msg{$threads -> {$ftid} -> [$i] -> {mid
}}=$i;
612 # message doesn't exist
614 unless (exists($msg{$fmid})) {
622 # build a unique id lookup hash
623 # use the unids of parent message's kids
625 %unids = map {$_ => 1} @
{$threads -> {$ftid} -> [$msg{$fmid}] -> {unids
}};
628 # build a unique id lookup hash, too
629 # but use only the level-zero-messages
631 %unids = map {$_ => 1} @
{$self -> {forum
} -> {unids
}};
637 $self -> {cgi_object
} -> param
(
638 $self -> {conf
} -> {form_data
} -> {uniqueID
} -> {name
})})) {
650 ### sub check_cgi ##############################################################
652 # cgi params are like raw eggs...
654 # Return: Status Code (Bool)
655 # creates content for the handle_error method if anything fails
660 # count the submitted keys and get the keys themselves
662 my %got_keys = map {($_ => 1)} $self -> {cgi_object
} -> param
;
663 my $cnt_got_keys = keys %got_keys;
664 my $formdata = $self -> {conf
} -> {form_data
};
665 my $formmust = $self -> {conf
} -> {form_must
};
667 # user requested the 'new thread' page
668 # (no params but perhaps the user-ID have been submitted)
670 if ($cnt_got_keys == 0 or (
671 exists ($formdata -> {userID
})
672 and $cnt_got_keys == 1
673 and $got_keys{$formdata -> {userID
} -> {name
}}
675 $self -> {response
} -> {new_thread
} = 1;
676 $self -> {check_success
} = 1;
680 # now we know, we've got a filled out form
681 # we do the following steps to check it:
683 # 1st: create a reverse Hash (CGI-key - identifier)
684 # 2nd: did we get _all_ must-keys?
685 # check whether reply or new message request
686 # 3rd: did we get too many keys?
687 # 4th: do _all_ submitted values accord to
689 # fetch the "missing" keys
695 exists($formdata -> {$_} -> {name
})
696 ?
($formdata -> {$_} -> {name
} => $_)
702 $self -> {response
} -> {reply
} = $got_keys {$formdata -> {followUp
} -> {name
}}?
1 : 0;
703 $self -> {response
} -> {new
} = not $self -> {response
} -> {reply
};
705 # define the fetch array (values to fetch from parent message)
707 $self -> {fetch
} = [];
709 for ( @
{$formmust -> {$self -> {response
} -> {reply
}?
'reply':'new'}} ) {
711 unless ($got_keys {$formdata -> {$_} -> {name
}}) {
713 # only miss the key unless we're able to fetch it from parent posting
716 not $self -> {response
} -> {reply
}
717 or $formdata -> {$_} -> {errorType
} eq 'fetch') {
720 spec
=> 'missing_key',
727 # keep in mind to fetch the value later
729 push @
{$self -> {fetch
}} => $_;
734 # I'm lazy - I know...
735 my $q = $self -> {cgi_object
};
740 unless (exists ($name {$_})) {
742 spec
=> 'unexpected_key',
752 unless ($self -> decode_param
) {
754 spec
=> 'unknown_encoding',
760 if ($self -> {response
} -> {reply
}) {
762 # get the parent-identifiers if we got a reply request
764 my ($ftid, $fmid) = split /;/ => $q -> param
($formdata -> {followUp
} -> {name
}) => 2;
766 unless ($ftid =~ /\d+/ and $fmid =~ /\d+/) {
768 spec
=> 'unknown_followup',
773 $self -> {fup_tid
} = $ftid;
774 $self -> {fup_mid
} = $fmid;
776 # fetch the missing keys
777 # if it fails, they're too short, too... ;)
780 $got_keys{$formdata -> {$_} -> {name
}} = 1 for (@
{$self -> {fetch
}});
783 # now we can check on length, type etc.
785 for (keys %got_keys) {
787 # we are sure, we've got only one value for one key
789 my $val = $q -> param
($_);
791 $val =~ s/\302\240/ /g; # convert nbsp (UTF-8 encoded) into normal spaces
792 $val =~ s/\015\012|\015|\012/ /g # convert \n into spaces unless it's a multiline field
794 exists ($formdata -> {$name {$_}} -> {type
})
795 and $formdata -> {$name {$_}} -> {type
} eq 'multiline-text'
798 $q -> param
($_ => $val); # write it back
802 if (length $val > $formdata -> {$name {$_}} -> {maxlength
}) {
806 type
=> $formdata -> {$name {$_}} -> {errorType
}
808 $self -> kill_param
or return;
812 # (only check if there's defined a minimum length)
814 if (exists ($formdata -> {$name {$_}} -> {minlength
})) {
816 # kill the whitespaces to get only the visible characters...
818 (my $val_ww = $val) =~ s/\s+//g;
820 $val_ww =~ y/a-zA-Z//cd
821 if (exists ($formdata -> {$name {$_}} -> {type
}) and $formdata -> {$name {$_}} -> {type
} eq 'name');
823 if (length $val_ww < $formdata -> {$name {$_}} -> {minlength
}) {
827 type
=> $formdata -> {$name {$_}} -> {errorType
}
829 $self -> kill_param
or return;
833 # check the values on expected kinds of content
834 # (email, http-url, url, option)
836 if (exists ($formdata -> {$name {$_}} -> {type
}) and length $val) {
837 if ($formdata -> {$name {$_}} -> {type
} eq 'email' and not is_email
$val) {
839 spec
=> 'wrong_mail',
841 type
=> $formdata -> {$name {$_}} -> {errorType
}
843 $self -> kill_param
or return;
846 elsif ($formdata -> {$name {$_}} -> {type
} eq 'http-url' and not is_URL
$val => 'http') {
848 spec
=> 'wrong_http_url',
850 type
=> $formdata -> {$name {$_}} -> {errorType
}
852 $self -> kill_param
or return;
855 elsif ($formdata -> {$name {$_}} -> {type
} eq 'url' and not is_URL
$val => ':ALL') {
859 type
=> $formdata -> {$name {$_}} -> {errorType
}
861 $self -> kill_param
or return;
865 if (exists ($formdata -> {$name {$_}} -> {values})
866 and not exists ({map {$_ => undef} @
{$formdata -> {$name {$_}} -> {values}}} -> {$val})) {
870 type
=> $formdata -> {$name {$_}} -> {errorType
}
872 $self -> kill_param
or return;
879 ### sub kill_param #############################################################
881 # kill the param (set it on '') if wrong and declared as 'kill' in config file
883 # Return: true if killed
889 if ($self -> {conf
} -> {form_data
} -> {$self -> {error
} -> {desc
}} -> {errorType
} eq 'kill') {
890 $self -> {cgi_object
} -> param
($self -> {conf
} -> {form_data
} -> {$self -> {error
} -> {desc
}} -> {name
} => '');
891 $self -> {error
} = {};
898 ### sub fetch ##################################################################
900 # fetch "missing" keys from parent posting
904 my $q = $self -> {cgi_object
};
905 my $formdata = $self -> {conf
} -> {form_data
};
907 if (@
{$self -> {fetch
}}) {
908 my $filename = $self -> {conf
} -> {message_path
}.'t'.$self -> {fup_tid
}.'.xml';
910 if (lock_file
($filename)) {
911 my $xml = parse_xml_file
($filename);
912 violent_unlock_file
($filename) unless unlock_file
($filename);
915 my $mnode = get_message_node
($xml, 't'.$self -> {fup_tid
}, 'm'.$self -> {fup_mid
});
917 my $header = get_message_header
($mnode);
919 $q -> param
($formdata -> {$_} -> {name
} => $header -> {$formdata -> {$_} -> {header
}})
920 for (@
{$self -> {fetch
}});
929 # fillout the values with an empty string
931 $q -> param
($formdata -> {$_} -> {name
} => '')
932 for (@
{$self -> {fetch
}});
937 ### sub decode_param ###########################################################
939 # convert submitted form data into UTF-8
940 # unless it's not encoded yet
942 # Return: Status Code (Bool)
943 # false if unknown encoding (like UTF-7 for instance)
948 my $q = $self -> {cgi_object
};
949 my $formdata = $self -> {conf
} -> {form_data
};
951 my $code = $q -> param
($formdata -> {quoteChar
} -> {name
});
954 # Latin 1 (we hope so - there's no real way to find out :-( )
955 if ($code =~ /^\377/) {
956 $q -> param
($_ => map {toUTF8
($_)} $q -> param
($_)) for ($q -> param
);
959 # UTF-8 is (probably) correct,
960 # other encodings we don't know and fail
961 return unless $code =~ /^\303\277/;
964 # remove the ÿ (encoded as UTF-8) from quotechars
965 $q -> param
($formdata -> {quoteChar
} -> {name
}
966 => substr $q -> param
($formdata -> {quoteChar
} -> {name
}),2);
968 # ok, params now should be UTF-8 encoded
973 my $text = $_[1] || 'An error has occurred.';
975 Content-type: text/plain
982 We will fix it as soon as possible. Thank you for your patience.
991 ### end of fo_posting.pl #######################################################
patrick-canterino.de