pum.pl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/perl -w
  2. ###
  3. ### pisg user manager version 3.1
  4. ###
  5. ### Copyleft (C) 2005 by Axel 'XTaran' Beckert <abe@deuxchevaux.org>
  6. ### Copyleft (C) 2005 by Torbjörn 'Azoff' Svensson <azoff@se.linux.org>
  7. ###
  8. #
  9. # This is complete reimplementation from scratch of addalias script
  10. # 2.2 by deadlock which itself was based on the original addalias
  11. # program by Doomshammer
  12. #
  13. # The purpose of this script is to let users manage themself their
  14. # info for the pisg ircstats program by mbrix.
  15. #
  16. # This program may be used, copied and distributed under the terms of
  17. # the GNU General Public License (GPL) version 2 or later. See
  18. # http://www.gnu.org/copyleft/gpl.txt or the file COPYING for the full
  19. # license text.
  20. #
  21. # Credits from XTaran to
  22. # + Christoph 'Myon' Berg for motivating me to rewrite addalias.pl
  23. # + #plant on IRCNet (and again Myon ;-) without which I probably
  24. # never would have used addalias.pl and therefore never felt the
  25. # urge to rewrite it from scratch. ;-)
  26. # + The Debian Project for the operating system running on my 133 MHz
  27. # IBM ThinkPad, on which I developed my parts of this piece of Open
  28. # Source Software (although I have other machines around, but I
  29. # entirely developed the script while sitting on the toilet, in bed
  30. # or in the bath tub. ;-)
  31. # + Larry for Perl
  32. # + RMS for GNU Emacs
  33. use Data::Dumper;
  34. use AppConfig qw(ARGCOUNT_ONE);
  35. use CGI qw(:standard *table);
  36. use CGI::Carp qw(fatalsToBrowser carpout);
  37. ###
  38. ### BEGIN CONFIG
  39. ###
  40. my $config_file = "pum.conf";
  41. ###
  42. ### END CONFIG
  43. ###
  44. ###
  45. ### BEGIN INIT
  46. ###
  47. my $VERSION = '3.1';
  48. my $title_prefix = "pisg IRC Statistics User Manager $VERSION";
  49. my $script_uri = $ENV{SCRIPT_NAME};
  50. my %data = ();
  51. my @attributes = qw(nick alias link sex pic bigpic ignore);
  52. param( -name => 'op', -value => 'list' ) unless defined param('op');
  53. # print the default css
  54. if (param('op') eq 'css') {
  55. print <<EOF
  56. Content-type: text/css
  57. table {
  58. border: 0;
  59. border-spacing: 2;
  60. }
  61. td {
  62. background-color: #E5E5E5;
  63. }
  64. #num {
  65. text-align: right;
  66. }
  67. EOF
  68. ;
  69. exit(0);
  70. }
  71. print header();
  72. my $config = AppConfig->new({ GLOBAL => { ARGCOUNT => ARGCOUNT_ONE }});
  73. $config->define('cgi_css', { DEFAULT => '' });
  74. $config->define('cgi_debug', { DEFAULT => 0 });
  75. $config->define('cgi_alias_disp', { DEFAULT => 30 });
  76. $config->define('cgi_user_del', { DEFAULT => 0 });
  77. $config->define('cgi_pics_prefix', { DEFAULT => '' });
  78. $config->define('backup_enable', { DEFAULT => '1' });
  79. $config->define('backup_dir', { DEFAULT => '/tmp' });
  80. $config->define('backup_suffix', { DEFAULT => '%t' });
  81. $config->define('pisg_user_config', { DEFAULT => 'users.conf' });
  82. -e $config_file or die "Configuration file $config_file doesn't exist";
  83. -f _ or die "Configuration file $config_file is no file";
  84. -r _ or die "Configuration file $config_file is not readable";
  85. $config->file($config_file);
  86. ###
  87. ### END INIT
  88. ###
  89. my $title = $title_prefix;
  90. my $css = $config->get('cgi_css');
  91. if (param('op') eq 'show') {
  92. $title .= ": Show user '".param('nick')."'";
  93. } elsif (param('op') eq 'edit') {
  94. $title .= ": Edit user '".param('nick')."'";
  95. } elsif (param('op') eq 'list') {
  96. $title .= ": List all known nicknames";
  97. } elsif (param('op') eq 'del') {
  98. $title .= ": Delete user '".param('nick')."'";
  99. }
  100. print start_html(-title => $title,
  101. -style => { src => ($css ? $css : "$script_uri?op=css")});
  102. print "\n" . h1($title) . "\n";
  103. if (param('op') eq 'show') {
  104. &show_data;
  105. print _p(a({ href => $script_uri.'?op=edit&nick='.param('nick') },
  106. "Edit this data set"));
  107. } elsif (param('op') eq 'edit') {
  108. &show_data_form;
  109. } elsif (param('op') eq 'save' or param('op') eq 'create') {
  110. &save_data;
  111. } elsif (param('op') eq 'list') {
  112. &show_nicks;
  113. } elsif (param('op') eq 'del' and $config->get('cgi_user_del')) {
  114. &del_nick;
  115. } else {
  116. print _p("Error: Unknown operation!");
  117. }
  118. if (not (param('op') eq 'del' and not param('confirm') and
  119. $config->get('cgi_user_del'))) {
  120. print _p(a({ href => "$script_uri?op=edit" }, 'Create new nick'));
  121. print _p(a({ href => "$script_uri?op=list" }, 'List all known nicks'));
  122. print _p('Back to the '.a({ href => $script_uri }, 'pum start page'));
  123. }
  124. print hr,pre(Dumper({ map { $_ => param($_) } param() },\%data,\%ENV))
  125. if ($config->get('cgi_debug') or param('debug'));
  126. print end_html();
  127. ###
  128. ### functions
  129. ###
  130. # make html readable
  131. sub _table { return table(@_) . "\n"; }
  132. sub _th { return th(@_) . "\n"; }
  133. sub _Tr { return Tr(@_) . "\n"; }
  134. sub _td { return td(@_) . "\n"; }
  135. sub _start_form { return start_form(@_) . "\n"; }
  136. sub _hidden { return hidden(@_) . "\n"; }
  137. sub _submit { return submit(@_) . "\n"; }
  138. sub _reset { return reset(@_) . "\n"; }
  139. sub _end_form { return "</form>\n"; }
  140. sub _p { return p(@_) . "\n"; }
  141. sub read_config {
  142. my ($user) = @_;
  143. my $filename = &get_user_config;
  144. open(CFG, '<', $filename) or
  145. die "Can't open pisg user configuration file '$filename' for reading: $!";
  146. while (my $line = <CFG>) {
  147. chomp($line);
  148. next if $line =~ /^(|#.*)$/;
  149. die "Unknown pisg user configuration file syntax: '$line'"
  150. unless $line =~ m|^\s*<user\s+(.*?)/?>\s*$|i;
  151. my $line_data_string = $1;
  152. my %line_data = ();
  153. while ($line_data_string =~ s/^(\w+)="([^\"]+)"\s*//) {
  154. $line_data{lc($1)} = $2;
  155. }
  156. my $nick = $line_data{nick};
  157. die "No nickname(s) found in '$line'" unless $nick;
  158. $data{lc($nick)} = \%line_data;
  159. last if lc($user) eq lc($nick);
  160. }
  161. close(CFG);
  162. }
  163. sub write_config {
  164. my $filename = &get_user_config;
  165. if ($config->get('backup_enable')) {
  166. use File::Basename;
  167. use File::Copy;
  168. my $time = time();
  169. my $dir = $config->get('backup_dir');
  170. my $name = basename($filename);
  171. my $suffix = $config->get('backup_suffix');
  172. my $newfile = "$dir/$name.$suffix";
  173. $newfile =~ s/\%t/$time/;
  174. copy($filename, $newfile) or
  175. warn "Couldn't copy '$filename' to $newfile': $!";
  176. }
  177. open(CFG, '>', $filename) or
  178. die "Can't open pisg user configuration file '$filename' for writing: $!";
  179. foreach my $key (sort { lc($a) cmp lc($b) } keys %data) {
  180. my $set = $data{$key};
  181. print CFG qq{<user};
  182. die "Data set without nick found: ".Dumper($set) unless $set->{nick};
  183. foreach my $attr (@attributes) {
  184. print CFG qq[ $attr="$set->{$attr}"] if $set->{$attr};
  185. }
  186. print CFG qq{>\n};
  187. }
  188. close(CFG);
  189. }
  190. sub get_user_config {
  191. my $filename = $config->get('pisg_user_config') or
  192. die "Can't find key user_config in section pisg in config file $config_file";
  193. return $filename;
  194. }
  195. sub save_data {
  196. die "No nick given" unless param('nick');
  197. die "Nick may be only changed in capitalisation"
  198. if lc(param('nick')) ne lc(param('old_nick')) and param('op') ne 'create';
  199. my %new_data = ();
  200. foreach my $attr (@attributes) {
  201. my $value = param($attr);
  202. next unless $value;
  203. next if $attr eq 'sex' and $value eq '-';
  204. die "No double quotes allowed in data: '$value'"
  205. if $value =~ /\"/;
  206. warn "Waka waka in data: '$value'"
  207. if $value =~ /[<>]/;
  208. $new_data{$attr} = $value;
  209. }
  210. my $nick = $new_data{nick};
  211. die "No nick in data found" unless $nick;
  212. &read_config;
  213. die "Data for nick '".lc($nick)."' already exists"
  214. if param('op') eq 'create' and $data{lc($nick)};
  215. $data{lc($nick)} = \%new_data;
  216. &write_config;
  217. print _p('Data successfully saved.');
  218. &show_data;
  219. &show_data_form;
  220. }
  221. sub show_data {
  222. my $this = shift;
  223. unless ($this) {
  224. my $nick = lc(param('nick'));
  225. read_config($nick);
  226. $this = $data{$nick};
  227. }
  228. my $pp = $config->get('cgi_pics_prefix');
  229. print start_table;
  230. print _Tr(_th('Nickname'), _th($this->{nick}));
  231. print _Tr(_td('Alias(ses)'), _td($this->{alias}));
  232. print _Tr(_td('Link'), _td(defined($this->{link}) and
  233. $this->{link} =~ m(^http://)i ?
  234. a({ href => $this->{link}}, $this->{link}) :
  235. $this->{link} ?
  236. a({ href => "mailto:$this->{link}"},
  237. $this->{link}) : '(unset)'));
  238. print _Tr(_td('Sex'), _td(defined $this->{sex} ?
  239. $this->{sex} eq 'm' ? 'male' :
  240. $this->{sex} eq 'f' ? 'female' :
  241. $this->{sex} eq 'b' ? 'bot' : '(unset)' :
  242. '(unset)'));
  243. print _Tr(_td('Picture'), _td($this->{pic} ?
  244. img({ src => $pp.$this->{pic},
  245. alt => $this->{pic} }) :
  246. '(unset)'));
  247. print _Tr(_td('Big picture'), _td($this->{bigpic} ?
  248. a({href => $pp.$this->{bigpic}},
  249. $this->{bigpic}) :
  250. '(unset)'));
  251. print _Tr(_td('Ignore'), _td($this->{ignore} ? 'True' : 'False'));
  252. print end_table;
  253. }
  254. sub show_data_form {
  255. my $nick = lc(param('nick'));
  256. read_config($nick) if $nick;
  257. my $this = $data{$nick};
  258. my $pp = $config->get('cgi_pics_prefix');
  259. print _start_form('GET', $script_uri);
  260. print _hidden( -name => 'op', -value => ( ($nick or param('op') eq
  261. 'create') ? 'save' : 'create' ), -override => 1);
  262. print _hidden('old_nick', $nick);
  263. print _table(_Tr(_td('Nickname'), _td(textfield('nick',$this->{nick},9))),
  264. _Tr(_td('Alias(ses)'), _td(textfield('alias',$this->{alias},30))),
  265. _Tr(_td('Link'), _td(textfield('link',$this->{link},30))),
  266. _Tr(_td('Sex'), _td(radio_group('sex',['f','m','b','-'],
  267. $this->{sex} || '-','',
  268. { f => 'female',
  269. m => 'male',
  270. b => 'bot',
  271. '-' => 'unspecified' }))),
  272. _Tr(_td('Picture'), _td(textfield('pic',$this->{pic},30))),
  273. _Tr(_td('Big picture'), _td(textfield('bigpic',$this->{bigpic},30))),
  274. _Tr(_td('Ignore'), _td(checkbox('ignore',
  275. ($this->{ignore} ? 'checked' : ''), 'y', ''))));
  276. print _submit('submit', 'Save data set');
  277. print _reset('reset', 'Reset form');
  278. print _end_form();
  279. if (defined $data{lc($nick)}) {
  280. print _start_form('GET', $script_uri);
  281. print _hidden( -name => 'op', -value => 'del', -override => 1);
  282. print _hidden('nick', $nick);
  283. print _submit('submit', "Remove data for '$nick'");
  284. print _end_form();
  285. }
  286. }
  287. sub _get_op($$) {
  288. my $op = shift;
  289. my $nick = shift;
  290. return a({ href => "$script_uri?op=$op&nick=".escapeHTML($nick) }, $op);
  291. }
  292. sub show_nicks {
  293. read_config();
  294. print start_table;
  295. my $i=1;
  296. my $alias_disp = $config->get('cgi_alias_disp');
  297. foreach my $nick (sort keys %data) {
  298. my $alias = $data{$nick}{alias} || '';
  299. $nick = $data{$nick}{nick};
  300. if (length($alias) > $alias_disp) {
  301. $alias = substr($alias, 0, $alias_disp) . '...';
  302. }
  303. print _Tr(
  304. _td({id => 'num'}, $i),
  305. _td(&_get_op('show', $nick)),
  306. _td(&_get_op('edit', $nick)),
  307. ($config->get('cgi_user_del') ? _td(&_get_op('del', $nick)) : '' ),
  308. _td(escapeHTML($nick.($alias ? " ($alias)" : ''))),
  309. );
  310. $i++;
  311. }
  312. print end_table;
  313. }
  314. sub del_nick {
  315. die "No nick given" unless param('nick');
  316. if (param('confirm')) {
  317. &read_config;
  318. die "No such nick '".param('nick')."'."
  319. unless defined $data{lc(param('nick'))};
  320. delete $data{lc(param('nick'))};
  321. &write_config;
  322. print _p('Data successfully updated.');
  323. &show_nicks;
  324. } else {
  325. print _p("Are you sure you want to delete the user '".
  326. param('nick')."'?");
  327. print _p(a({href => "$script_uri?op=del&confirm=1&nick=".
  328. escapeHTML(param('nick'))}, 'Yes'),
  329. a({href => ($ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} :
  330. "$script_uri?op=edit&nick=".
  331. escapeHTML(param('nick')))}, 'No'));
  332. }
  333. }