pum.pl 12 KB

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