moved qdb here because matt is lazy
[public/www-new.git] / pub / qdb / src / modules / Chirpy / UI / WebApp / Administration.pm
1 ###############################################################################\r
2 # Chirpy! 0.3, a quote management system                                      #\r
3 # Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
4 ###############################################################################\r
5 # This program is free software; you can redistribute it and/or modify it     #\r
6 # under the terms of the GNU General Public License as published by the Free  #\r
7 # Software Foundation; either version 2 of the License, or (at your option)   #\r
8 # any later version.                                                          #\r
9 #                                                                             #\r
10 # This program is distributed in the hope that it will be useful, but WITHOUT #\r
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
12 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
13 # more details.                                                               #\r
14 #                                                                             #\r
15 # You should have received a copy of the GNU General Public License along     #\r
16 # with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
17 # Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
18 ###############################################################################\r
19 \r
20 ###############################################################################\r
21 # $Id:: Administration.pm 305 2007-02-09 01:06:56Z ceetee                   $ #\r
22 ###############################################################################\r
23 \r
24 =head1 NAME\r
25 \r
26 Chirpy::UI::WebApp::Administration - Administration-section related routines\r
27 of L<Chirpy::UI::WebApp>\r
28 \r
29 =head1 TODO\r
30 \r
31 Make this template-based and avoid inline calls to parents.\r
32 \r
33 =head1 AUTHOR\r
34 \r
35 Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>\r
36 \r
37 =head1 SEE ALSO\r
38 \r
39 L<Chirpy::UI::WebApp>, L<Chirpy::UI>, L<Chirpy>,\r
40 L<http://chirpy.sourceforge.net/>\r
41 \r
42 =head1 COPYRIGHT\r
43 \r
44 Copyright 2005-2007 Tim De Pauw. All rights reserved.\r
45 \r
46 This program is free software; you can redistribute it and/or modify it under\r
47 the terms of the GNU General Public License as published by the Free Software\r
48 Foundation; either version 2 of the License, or (at your option) any later\r
49 version.\r
50 \r
51 This program is distributed in the hope that it will be useful, but WITHOUT ANY\r
52 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\r
53 PARTICULAR PURPOSE.  See the GNU General Public License for more details.\r
54 \r
55 =cut\r
56 \r
57 package Chirpy::UI::WebApp::Administration;\r
58 \r
59 use strict;\r
60 use warnings;\r
61 \r
62 use vars qw($VERSION);\r
63 \r
64 $VERSION = '0.3';\r
65 \r
66 use Chirpy 0.3;\r
67 use Chirpy::UI::WebApp 0.3;\r
68 use Chirpy::Event 0.3;\r
69 \r
70 sub new {\r
71         my ($class, $parent) = @_;\r
72         return bless { 'parent' => $parent }, $class;\r
73 }\r
74 \r
75 sub parent {\r
76         my $self = shift;\r
77         return $self->{'parent'};\r
78 }\r
79 \r
80 sub output {\r
81         my ($self, %params) = @_;\r
82         my $event_log_allowed = $self->parent()->administration_allowed(\r
83                 Chirpy::UI::VIEW_EVENT_LOG);\r
84         if ($event_log_allowed && $self->parent()->_wants_xml()) {\r
85                 $self->_serve_event_log_table_data();\r
86                 return;\r
87         }\r
88         my $template = $self->parent()->_load_template('administration');\r
89         my $locale = $self->parent()->locale();\r
90         my ($upd_url, $upd_text);\r
91         if (my $update = $self->parent()->{'available_update'}) {\r
92                 $template->param('UPDATE_AVAILABLE'\r
93                         => &_text_to_xhtml($locale->get_string('update_available')));\r
94                 $template->param('UPDATE_AVAILABLE_TEXT' => &_text_to_xhtml(\r
95                         $locale->get_string('update_available_text',\r
96                         $update->[0], $update->[1])));\r
97                 $template->param('UPDATE_LINK_TEXT'\r
98                         => &_text_to_xhtml($locale->get_string('update_link_text')));\r
99                 $template->param('UPDATE_URL' => &_text_to_xhtml($update->[2]));\r
100         }\r
101         elsif (my $errmsg = $self->parent()->{'update_check_error'}) {\r
102                 $template->param('UPDATE_CHECK_FAILED'\r
103                         => &_text_to_xhtml($locale->get_string('update_check_failed')));\r
104                 $template->param('UPDATE_CHECK_FAILED_TEXT' => &_text_to_xhtml(\r
105                         $locale->get_string('update_check_failed_text')));\r
106                 $template->param('UPDATE_CHECK_ERROR_MESSAGE'\r
107                         => &_text_to_xhtml($errmsg));\r
108         }\r
109         $template->param(\r
110                 'PAGE_TITLE' => &_text_to_xhtml(\r
111                         $locale->get_string('administration')),\r
112                 'APPROVE_QUOTES' => &_text_to_xhtml(\r
113                         $locale->get_string('approve_quotes')),\r
114                 'APPROVE_QUOTES_HTML'\r
115                         => sub { return $self->get_approve_quotes_html(%params) },\r
116                 'APPROVE_QUOTES_ALLOWED'\r
117                         => $self->parent()->administration_allowed(Chirpy::UI::MANAGE_UNAPPROVED_QUOTES),\r
118                 'APPROVE_QUOTES_NOT_ALLOWED_HTML'\r
119                         => sub { return $self->get_access_disallowed_html() },\r
120                 'FLAGGED_QUOTES' => &_text_to_xhtml(\r
121                         $locale->get_string('flagged_quotes')),\r
122                 'FLAGGED_QUOTES_HTML'\r
123                         => sub { return $self->get_flagged_quotes_html(%params) },\r
124                 'FLAGGED_QUOTES_ALLOWED'\r
125                         => $self->parent()->administration_allowed(Chirpy::UI::MANAGE_FLAGGED_QUOTES),\r
126                 'FLAGGED_QUOTES_NOT_ALLOWED_HTML'\r
127                         => sub { return $self->get_access_disallowed_html() },\r
128                 'MANAGE_QUOTES' => &_text_to_xhtml(\r
129                         $locale->get_string('manage_quotes')),\r
130                 'MANAGE_QUOTES_HTML'\r
131                         => sub { return $self->get_manage_quotes_html(%params) },\r
132                 'MANAGE_QUOTES_ALLOWED'\r
133                         => $self->parent()->administration_allowed(Chirpy::UI::EDIT_QUOTE)\r
134                                 && $self->parent()->administration_allowed(Chirpy::UI::REMOVE_QUOTE),\r
135                 'MANAGE_QUOTES_NOT_ALLOWED_HTML'\r
136                         => sub { return $self->get_access_disallowed_html() },\r
137                 'MANAGE_NEWS' => &_text_to_xhtml(\r
138                         $locale->get_string('manage_news')),\r
139                 'MANAGE_NEWS_HTML'\r
140                         => sub { return $self->get_manage_news_html(%params) },\r
141                 'MANAGE_NEWS_ALLOWED'\r
142                         => $self->parent()->administration_allowed(Chirpy::UI::ADD_NEWS)\r
143                                 && $self->parent()->administration_allowed(Chirpy::UI::EDIT_NEWS)\r
144                                 && $self->parent()->administration_allowed(Chirpy::UI::REMOVE_NEWS),\r
145                 'MANAGE_NEWS_NOT_ALLOWED_HTML'\r
146                         => sub { return $self->get_access_disallowed_html() },\r
147                 'MANAGE_ACCOUNTS' => &_text_to_xhtml(\r
148                         $locale->get_string('manage_accounts')),\r
149                 'MANAGE_ACCOUNTS_HTML'\r
150                         => sub { return $self->get_manage_accounts_html(%params) },\r
151                 'MANAGE_ACCOUNTS_ALLOWED'\r
152                         => $self->parent()->administration_allowed(Chirpy::UI::ADD_ACCOUNT)\r
153                                 && $self->parent()->administration_allowed(Chirpy::UI::EDIT_ACCOUNT)\r
154                                 && $self->parent()->administration_allowed(Chirpy::UI::REMOVE_ACCOUNT),\r
155                 'MANAGE_ACCOUNTS_NOT_ALLOWED_HTML'\r
156                         => sub { return $self->get_access_disallowed_html() },\r
157                 'VIEW_EVENT_LOG' => &_text_to_xhtml(\r
158                         $locale->get_string('view_event_log')),\r
159                 'VIEW_EVENT_LOG_HTML'\r
160                         => sub { return $self->get_event_log_html(%params) },\r
161                 'VIEW_EVENT_LOG_ALLOWED'\r
162                         => $event_log_allowed,\r
163                 'VIEW_EVENT_LOG_NOT_ALLOWED_HTML'\r
164                         => sub { return $self->get_access_disallowed_html() },\r
165                 'CHANGE_PASSWORD' => &_text_to_xhtml(\r
166                         $locale->get_string('change_password')),\r
167                 'CHANGE_PASSWORD_HTML'\r
168                         => sub { return $self->get_change_password_html(%params) },\r
169                 'ACTION_IS_' . $self->parent()->_admin_action() => 1\r
170         );\r
171         $self->parent()->_output_template($template);\r
172 }\r
173 \r
174 sub get_approve_quotes_html {\r
175         my ($self, %params) = @_;\r
176         my $locale = $self->parent()->locale();\r
177         my $quotes = $self->parent()->parent()->get_unapproved_quotes();\r
178         if (defined $quotes) {\r
179                 my $html = '<script type="text/javascript" src="'\r
180                         . &_text_to_xhtml($self->parent()->_resources_url())\r
181                         . '/js/administration.js"></script>' . $/\r
182                         . '<form method="post" action="'\r
183                         . $self->parent()->_url(\r
184                                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'MANAGE_UNAPPROVED_QUOTES'},\r
185                                 1\r
186                         ) . '">' . $/\r
187                         . '<ul id="unapproved-quotes-list" class="quote-list">' . $/;\r
188                 foreach my $quote (@$quotes) {\r
189                         my $id = $quote->get_id();\r
190                         my $notes = $quote->get_notes();\r
191                         my $tags = $quote->get_tags();\r
192                         $html .= '<li>' . $/\r
193                                 . '<div class="quote-container">' . $/\r
194                                 . '<h3 class="quote-header">'\r
195                                 . '<span class="quote-id">#' . $quote->get_id() . '</span> '\r
196                                 . ($self->parent()->moderation_queue_is_public()\r
197                                         ? '<span class="quote-rating">'\r
198                                         . Chirpy::Util::format_quote_rating($quote->get_rating())\r
199                                         . '</span>'\r
200                                         . '<span class="quote-vote-count">/<span>'\r
201                                         . $quote->get_vote_count()\r
202                                         . '</span></span> '\r
203                                         : '')\r
204                                 . '<span class="quote-date">'\r
205                                 . $self->parent()->format_date_time($quote->get_date_submitted())\r
206                                 . '</span>' . $/\r
207                                 . '<a href="javascript:editQuote(' . $id . ');" '\r
208                                 . 'class="quote-edit" id="quote-edit-' . $id . '">['\r
209                                 . &_text_to_xhtml($locale->get_string('edit'))\r
210                                 . ']</a>'\r
211                                 . '</h3>' . $/\r
212                                 . '<div class="quote-data" id="quote-data-' . $id . '">' . $/\r
213                                 . '<blockquote class="quote-body">' . $/\r
214                                 . '<p id="quote-body-' . $id . '">' . &_text_to_xhtml($quote->get_body())\r
215                                 . '</p>' . $/\r
216                                 . '</blockquote>' . $/\r
217                                 . (defined $notes || @$tags ?\r
218                                   '<div class="quote-footer">' . (defined $notes\r
219                                         ? '<div class="quote-notes">' . $/\r
220                                                 . '<p><em class="quote-notes-title">Notes:</em>' . $/\r
221                                                 . '<span id="quote-notes-' . $id . '">'\r
222                                                 . &_text_to_xhtml($notes)\r
223                                                 . '</span></p>' . $/\r
224                                                 . '</div>' . $/\r
225                                         : '')\r
226                                 . (@$tags\r
227                                         ? '<div class="quote-tags">' . $/\r
228                                                 . '<p><em class="quote-tags-title">'\r
229                                                 . &_text_to_xhtml(\r
230                                                         $locale->get_string('quote_tags_title'))\r
231                                                 . '</em>' . $/\r
232                                                 . '<span id="quote-tags-' . $id . '">'\r
233                                                 . &_text_to_xhtml(join(' ', @$tags))\r
234                                                 . '</span></p>' . $/\r
235                                                 . '</div>' . $/\r
236                                         : '')\r
237                                   . '</div>' : '')\r
238                                 . '</div>' . $/\r
239                                 . '</div>' . $/\r
240                                 . '<div class="approval-options">' . $/\r
241                                 . '<input type="radio" class="approve-do-nothing-rb" name="action_' . $id\r
242                                 . '" value="0" id="a' . $id . '-0" '\r
243                                 . 'checked="checked" /> <label '\r
244                                 . 'for="a' . $id . '-0">'\r
245                                 . &_text_to_xhtml(\r
246                                         $locale->get_string('do_nothing'))\r
247                                 . '</label>' . $/\r
248                                 . '<input type="radio" class="approve-rb" name="action_' . $id\r
249                                 . '" value="1" id="a' . $id . '-1" /> <label '\r
250                                 . 'for="a' . $id . '-1">'\r
251                                 . &_text_to_xhtml(\r
252                                         $locale->get_string('approve_unapproved_quote'))\r
253                                 . '</label>' . $/\r
254                                 . '<input type="radio" class="discard-rb" name="action_' . $id\r
255                                 . '" value="2" id="a' . $id . '-2" /> <label '\r
256                                 . 'for="a' . $id . '-2">'\r
257                                 . &_text_to_xhtml(\r
258                                         $locale->get_string('discard_unapproved_quote'))\r
259                                 . '</label>' . $/\r
260                                 . '</div>' . $/\r
261                                 . '</li>' . $/;\r
262                 }\r
263                 $html .= '</ul>' . $/\r
264                         . '<div id="approve-submit-container">' . $/\r
265                         . '<div id="mass-approve-container">' . $/\r
266                         . '<input type="button" value="'\r
267                         . &_text_to_xhtml(\r
268                                 $locale->get_string('approve_all_unapproved_quotes'))\r
269                         . '" id="approve-all-button" onclick="if (confirm(\''\r
270                         . &_text_to_xhtml(\r
271                                 $locale->get_string('approve_all_unapproved_quotes_confirm'))\r
272                         . '\')) { var a = document.getElementsByTagName(\'input\'); '\r
273                         . 'for (var i = 0; i &lt; a.length; i++) '\r
274                         . 'if (a[i].className == \'approve-rb\') a[i].checked = true; '\r
275                         . 'submit(); }" />' . $/\r
276                         . '<input type="button" value="'\r
277                         . &_text_to_xhtml(\r
278                                 $locale->get_string('discard_all_unapproved_quotes'))\r
279                         . '" id="discard-all-button" onclick="if (confirm(\''\r
280                         . &_text_to_xhtml(\r
281                                 $locale->get_string('discard_all_unapproved_quotes_confirm'))\r
282                         . '\')) { var a = document.getElementsByTagName(\'input\'); '\r
283                         . 'for (var i = 0; i &lt; a.length; i++) '\r
284                         . 'if (a[i].className == \'discard-rb\') a[i].checked = true; '\r
285                         . 'submit(); }" />' . $/\r
286                         . '</div>' . $/\r
287                         . '<input type="submit" value="'\r
288                         . &_text_to_xhtml(\r
289                                 $locale->get_string('update_database'))\r
290                         . '" id="approve-submit-button" />' . $/\r
291                         . '<input type="reset" value="'\r
292                         . &_text_to_xhtml(\r
293                                 $locale->get_string('reset_form'))\r
294                         . '" id="approve-reset-button" onclick="'\r
295                         . 'var a = document.getElementsByTagName(\'input\'); '\r
296                         . 'for (var i = 0; i &lt; a.length; i++) '\r
297                         . 'if (a[i].className == \'approve-do-nothing-rb\') a[i].checked = true; '\r
298                         . 'return false;" />' . $/\r
299                         . '</div>' . $/\r
300                         . '</form>';\r
301                 return $html;\r
302         }\r
303         else {\r
304                 return '<p>' . &_text_to_xhtml(\r
305                         $locale->get_string('no_unapproved_quotes')) . '</p>';\r
306         }\r
307 }\r
308 \r
309 sub get_flagged_quotes_html {\r
310         my ($self, %params) = @_;\r
311         my $locale = $self->parent()->locale();\r
312         my $quotes = $self->parent()->parent()->get_flagged_quotes();\r
313         if (defined $quotes) {\r
314                 my $html = '<form method="post" action="'\r
315                         . $self->parent()->_url(\r
316                                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'MANAGE_FLAGGED_QUOTES'},\r
317                                 1\r
318                         ) . '">' . $/\r
319                         . '<ul id="flagged-quotes-list" class="quote-list">' . $/;\r
320                 foreach my $quote (@$quotes) {\r
321                         my $id = $quote->get_id();\r
322                         my $notes = $quote->get_notes();\r
323                         my $tags = $quote->get_tags();\r
324                         $html .= '<li>' . $/\r
325                                 . '<div class="quote-container">' . $/\r
326                                 . '<h3 class="quote-header">'\r
327                                 . '<span class="quote-id">#' . $quote->get_id() . '</span> '\r
328                                 . '<span class="quote-rating">'\r
329                                         . Chirpy::Util::format_quote_rating($quote->get_rating())\r
330                                         . '</span>'\r
331                                         . '<span class="quote-vote-count">/<span>'\r
332                                         . $quote->get_vote_count()\r
333                                         . '</span></span> '\r
334                                 . '<span class="quote-date">'\r
335                                 . $self->parent()->format_date_time($quote->get_date_submitted())\r
336                                 . '</span>'\r
337                                 . '</h3>' . $/\r
338                                 . '<blockquote class="quote-body">' . $/\r
339                                 . '<p>' . &_text_to_xhtml($quote->get_body())\r
340                                 . '</p>' . $/\r
341                                 . '</blockquote>' . $/\r
342                                 . (defined $notes || @$tags ?\r
343                                   '<div class="quote-footer">' . (defined $notes\r
344                                         ? '<div class="quote-notes">' . $/\r
345                                                 . '<p><em class="quote-notes-title">'\r
346                                                 . &_text_to_xhtml(\r
347                                                         $locale->get_string('quote_notes_title'))\r
348                                                 . '</em>' . $/\r
349                                                 . &_text_to_xhtml($notes)\r
350                                                 . '</p>' . $/\r
351                                                 . '</div>' . $/\r
352                                         : '')\r
353                                 . (@$tags\r
354                                         ? '<div class="quote-tags">' . $/\r
355                                                 . '<p><em class="quote-tags-title">'\r
356                                                 . &_text_to_xhtml(\r
357                                                         $locale->get_string('quote_tags_title'))\r
358                                                 . '</em>' . $/\r
359                                                 . &_text_to_xhtml(join(' ', @$tags))\r
360                                                 . '</p>' . $/\r
361                                                 . '</div>' . $/\r
362                                         : '')\r
363                                   . '</div>' : '')\r
364                                 . '</div>' . $/\r
365                                 . '<div class="flag-options">' . $/\r
366                                 . '<input type="radio" name="action_' . $id\r
367                                 . '" value="0" id="a' . $id . '-0" '\r
368                                 . 'checked="checked" /> <label '\r
369                                 . 'for="a' . $id . '-0">'\r
370                                 . &_text_to_xhtml(\r
371                                         $locale->get_string('do_nothing'))\r
372                                 . '</label>' . $/\r
373                                 . '<input type="radio" class="keep-rb" name="action_' . $id\r
374                                 . '" value="1" id="a' . $id . '-1" /> <label '\r
375                                 . 'for="a' . $id . '-1">'\r
376                                 . &_text_to_xhtml(\r
377                                         $locale->get_string('keep_flagged_quote'))\r
378                                 . '</label>' . $/\r
379                                 . '<input type="radio" class="remove-rb" name="action_' . $id\r
380                                 . '" value="2" id="a' . $id . '-2" /> <label '\r
381                                 . 'for="a' . $id . '-2">'\r
382                                 . &_text_to_xhtml(\r
383                                         $locale->get_string('remove_flagged_quote'))\r
384                                 . '</label>' . $/\r
385                                 . '</div>' . $/\r
386                                 . '</li>' . $/;\r
387                 }\r
388                 $html .= '</ul>' . $/\r
389                         . '<div id="flag-submit-container">' . $/\r
390                         . '<div id="mass-unflag-container">' . $/\r
391                         . '<input type="button" value="'\r
392                         . &_text_to_xhtml(\r
393                                 $locale->get_string('keep_all_flagged_quotes'))\r
394                         . '" id="keep-all-button" onclick="if (confirm(\''\r
395                         . &_text_to_xhtml(\r
396                                 $locale->get_string('keep_all_flagged_quotes_confirm'))\r
397                         . '\')) { var a = document.getElementsByTagName(\'input\'); '\r
398                         . 'for (var i = 0; i &lt; a.length; i++) '\r
399                         . 'if (a[i].className == \'keep-rb\') a[i].checked = true; '\r
400                         . 'submit(); }" />' . $/\r
401                         . '<input type="button" value="'\r
402                         . &_text_to_xhtml(\r
403                                 $locale->get_string('remove_all_flagged_quotes'))\r
404                         . '" id="discard-all-button" onclick="if (confirm(\''\r
405                         . &_text_to_xhtml(\r
406                                 $locale->get_string('remove_all_flagged_quotes_confirm'))\r
407                         . '\')) { var a = document.getElementsByTagName(\'input\'); '\r
408                         . 'for (var i = 0; i &lt; a.length; i++) '\r
409                         . 'if (a[i].className == \'remove-rb\') a[i].checked = true; '\r
410                         . 'submit(); }" />' . $/\r
411                         . '</div>' . $/\r
412                         . '<input type="submit" value="'\r
413                         . &_text_to_xhtml(\r
414                                 $locale->get_string('update_database'))\r
415                         . '" id="flag-submit-button" />' . $/\r
416                         . '<input type="reset" value="'\r
417                         . &_text_to_xhtml(\r
418                                 $locale->get_string('reset_form'))\r
419                         . '" id="flag-reset-button" />' . $/\r
420                         . '</div>' . $/\r
421                         . '</form>';\r
422                 return $html;\r
423         }\r
424         else {\r
425                 return '<p>' . &_text_to_xhtml(\r
426                         $locale->get_string('no_flagged_quotes')) . '</p>';\r
427         }\r
428 }\r
429 \r
430 sub get_manage_quotes_html {\r
431         my ($self, %params) = @_;\r
432         my $locale = $self->parent()->locale();\r
433         if (my $quote = $params{'edit_quote'}) {\r
434                 my $notes = $quote->get_notes();\r
435                 my $tags = $quote->get_tags();\r
436                 return '<div id="edit-quote-form">' . $/\r
437                         . '<form method="post" action="'\r
438                         . $self->parent()->_url(\r
439                                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_QUOTE'},\r
440                                 1,\r
441                                 'id' => $quote->get_id()\r
442                         ) . '"><div id="quote-container">' . $/\r
443                         . '<label for="quote-field">'\r
444                         . &_text_to_xhtml(\r
445                                 $locale->get_string('submission_title'))\r
446                         . '</label>' . $/\r
447                         . '<textarea name="quote" id="quote-field"' . $/\r
448                         . 'rows="8" cols="80">'\r
449                         . Chirpy::Util::encode_xml_entities($quote->get_body())\r
450                         . '</textarea></div>' . $/\r
451                         . '<div id="notes-container">' . $/\r
452                         . '<label for="notes-field">'\r
453                         . &_text_to_xhtml(\r
454                                 $locale->get_string('notes_title'))\r
455                         . '</label>' . $/\r
456                         . '<textarea name="notes" id="notes-field"' . $/\r
457                         . 'rows="3" cols="80">'\r
458                         . (defined $notes ? Chirpy::Util::encode_xml_entities($notes) : '')\r
459                         . '</textarea></div>' . $/\r
460                         . '<div id="tags-container">' . $/\r
461                         . '<label for="tags-field">'\r
462                         . &_text_to_xhtml(\r
463                                 $locale->get_string('tags_title'))\r
464                         . '</label>' . $/\r
465                         . '<input type="text" name="tags" value="'\r
466                         . (@$tags ? &_text_to_xhtml(join(' ', @$tags)) : '')\r
467                         . '" id="tags-field" /></div>' . $/\r
468                         . '<div id="edit-quote-submit-container">' . $/\r
469                         . '<input type="submit" value="'\r
470                         . &_text_to_xhtml($locale->get_string('save_quote'))\r
471                         . '" id="edit-quote-submit-button" />' . $/\r
472                         . '<input type="reset" value="'\r
473                         . &_text_to_xhtml($locale->get_string('reset_form'))\r
474                         . '" id="edit-quote-reset-button" />' . $/\r
475                         . '</div></form></div>';\r
476         }\r
477         if (my $quote = $params{'confirm_quote_removal'}) {\r
478                 my ($body, $notes, $tags) = $self->parent()->_format_quote($quote);\r
479                 return '<div id="quote-removal-confirmation-form">' . $/\r
480                         . '<form method="post" action="'\r
481                         . $self->parent()->_url(\r
482                                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'REMOVE_QUOTE'},\r
483                                 1,\r
484                                 'id' => $quote->get_id()\r
485                         ) . '">' . $/\r
486                         . '<p id="quote-removal-confirmation-request">'\r
487                         . &_text_to_xhtml($locale->get_string('quote_removal_confirmation'))\r
488                         . '</p>' . $/\r
489                         . '<blockquote class="quote-body"><p>'\r
490                         . $body . '</p></blockquote>' . $/\r
491                         . '<div id="quote-removal-confirmation-submit-container">' . $/\r
492                         . '<input type="submit" name="confirm" value="'\r
493                         . &_text_to_xhtml($locale->get_string('remove_quote'))\r
494                         . '" id="quote-removal-confirmation-submit-button" />' . $/\r
495                         . '<input type="button" value="'\r
496                         . &_text_to_xhtml($locale->get_string('cancel'))\r
497                         . '" id="quote-removal-confirmation-cancel-button"'\r
498                         . ' onclick="history.go(-1);" />' . $/\r
499                         . '</div></form></div>';\r
500         }\r
501         my $result;\r
502         if ($params{'quote_removed'}) {\r
503                 $result = $locale->get_string('quote_removed');\r
504         }\r
505         elsif ($params{'quote_to_edit_not_found'}) {\r
506                 $result = $locale->get_string('quote_to_edit_not_found');\r
507         }\r
508         elsif ($params{'quote_to_remove_not_found'}) {\r
509                 $result = $locale->get_string('quote_to_remove_not_found');\r
510         }\r
511         elsif ($params{'quote_modified'}) {\r
512                 $result = $locale->get_string('quote_modified');\r
513         }\r
514         return (defined $result\r
515                 ? '<p id="manage-quotes-result">'\r
516                         . &_text_to_xhtml($result) . '</p>'\r
517                 : '')\r
518                 . '<p id="manage-quote-instructions">'\r
519                 . &_text_to_xhtml(\r
520                         $locale->get_string('webapp.manage_quote_instructions'))\r
521                 . '</p>' . $/\r
522                 . '<div id="quick-manage-quote-form">' . $/\r
523                 . '<form method="post" action="'\r
524                 . $self->parent()->_url(undef, 1)\r
525                 . '" onsubmit="return (!document.getElementById'\r
526                 . '(\'quote-remove\').checked || confirm(&quot;'\r
527                 . &_text_to_xhtml($locale->get_string(\r
528                         'webapp.remove_quote_without_viewing_confirmation'))\r
529                 . '&quot;))">' . $/\r
530                 . '<div id="quick-manage-quote-id-container"><label' . $/\r
531                 . 'for="quick-manage-quote-id-field">'\r
532                 . &_text_to_xhtml(\r
533                         $locale->get_string('quote_id_title')) . '</label>' . $/\r
534                 . '<input name="id" id="quick-manage-quote-id-field" />' . $/\r
535                 . '</div>' . $/\r
536                 . '<div id="quick-manage-quote-options">' . $/\r
537                 . '<input type="radio" name="admin_action" value="'\r
538                 . Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_QUOTE'} . '"' . $/\r
539                 . 'id="quote-edit" checked="checked" /> <label for="quote-edit">'\r
540                 . &_text_to_xhtml($locale->get_string('edit'))\r
541                 . '</label>' . $/\r
542                 . '<input type="radio" name="admin_action" value="'\r
543                 . Chirpy::UI::WebApp::ADMIN_ACTIONS->{'REMOVE_QUOTE'} . '"' . $/\r
544                 . 'id="quote-remove" /> <label for="quote-remove">'\r
545                 . &_text_to_xhtml($locale->get_string('remove'))\r
546                 . '</label></div>' . $/\r
547                 . '<div id="quick-manage-quote-submit">'\r
548                 . '<input type="submit" value="'\r
549                 . &_text_to_xhtml($locale->get_string('go'))\r
550                 . '&rarr;" /></div>' . $/\r
551                 . '</form></div>';\r
552 }\r
553 \r
554 sub get_manage_news_html {\r
555         my ($self, %params) = @_;\r
556         my $locale = $self->parent()->locale();\r
557         if (my $item = $params{'edit_news_item'}) {\r
558                 my $html = '<div id="edit-news-item-form">' . $/\r
559                         . '<form method="post" action="'\r
560                         . $self->parent()->_url(\r
561                                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_NEWS'},\r
562                                 1,\r
563                                 'id' => $item->get_id())\r
564                         . '">' . $/\r
565                         . '<div id="news-item-container">' . $/\r
566                         . '<textarea name="body" id="news-item-field"' . $/\r
567                         . 'rows="8" cols="80">'\r
568                         . Chirpy::Util::encode_xml_entities($item->get_body())\r
569                         . '</textarea></div>' . $/\r
570                         . '<div id="news-item-poster-container">' . $/\r
571                         . '<label for="news-item-poster-select">'\r
572                         . &_text_to_xhtml(\r
573                                 $locale->get_string('news_poster_title'))\r
574                         . '</label>' . $/\r
575                         . '<select name="poster" id="news-item-poster-select">' . $/\r
576                         . '<option>('\r
577                         . &_text_to_xhtml(\r
578                                 $locale->get_string('unknown'))\r
579                         . ')</option>' . $/;\r
580                 my $posters = $self->parent()->get_news_posters();\r
581                 if (defined $posters) {\r
582                         foreach my $account (@$posters) {\r
583                                 my $p = $item->get_poster();\r
584                                 my $level = $account->get_level();\r
585                                 $html .= '<option value="' . $account->get_id() . '"'\r
586                                         . (defined $p && $account->get_id() == $p->get_id()\r
587                                                 ? ' selected="selected"' : '')\r
588                                         . ' class="user-level-' . $level . '">'\r
589                                         . $account->get_username()\r
590                                         . ' &lt;' . $self->parent()->parent()->user_level_name($level)\r
591                                         . '&gt;</option>' . $/;\r
592                         }\r
593                 }\r
594                 return $html . '</select>'\r
595                         . '</div>' . $/\r
596                         . '<div id="edit-news-item-submit-container">' . $/\r
597                         . '<input type="submit" value="'\r
598                         . &_text_to_xhtml(\r
599                                 $locale->get_string('save_news_item'))\r
600                         . '" id="edit-news-item-submit-button" />' . $/\r
601                         . '<input type="reset" value="'\r
602                         . &_text_to_xhtml(\r
603                                 $locale->get_string('reset_form'))\r
604                         . '"' . $/\r
605                         . 'id="edit-news-item-reset-button" />' . $/\r
606                         . '</div></form></div>';\r
607         }\r
608         my $result;\r
609         if ($params{'news_item_added'}) {\r
610                 $result = $locale->get_string('news_item_added');\r
611         }\r
612         elsif ($params{'news_item_modified'}) {\r
613                 $result = $locale->get_string('news_item_modified');\r
614         }\r
615         elsif ($params{'news_item_to_edit_not_found'}) {\r
616                 $result = $locale->get_string('news_item_to_edit_not_found');\r
617         }\r
618         elsif ($params{'news_item_removed'}) {\r
619                 $result = $locale->get_string('news_item_removed');\r
620         }\r
621         elsif ($params{'news_item_to_remove_not_found'}) {\r
622                 $result = $locale->get_string('news_item_to_remove_not_found');\r
623         }\r
624         return (defined $result\r
625                 ? '<p id="manage-news-items-result">'\r
626                         . &_text_to_xhtml($result) . '</p>'\r
627                 : '')\r
628                 . '<div id="post-news-form">' . $/\r
629                 . '<form method="post" action="'\r
630                 . $self->parent()->_url(\r
631                         Chirpy::UI::WebApp::ADMIN_ACTIONS->{'ADD_NEWS'},\r
632                         1\r
633                 ) . '">' . $/\r
634                 . '<div id="news-container">' . $/\r
635                 . '<label for="news-field">'\r
636                 . &_text_to_xhtml(\r
637                         $locale->get_string('new_news_item_title'))\r
638                 . '</label>' . $/\r
639                 . '<textarea name="news" id="news-field" rows="8" cols="80">' \r
640                 . '</textarea>' . $/\r
641                 . '</div>' . $/\r
642                 . '<div id="submit-news-container">' . $/\r
643                 . '<input type="submit" value="'\r
644                 . &_text_to_xhtml($locale->get_string('add_news_item'))\r
645                 . '" id="submit-news-button" />' . $/\r
646                 . '<input type="reset" value="'\r
647                 . &_text_to_xhtml($locale->get_string('reset_form'))\r
648                 . '" id="reset-news-button" />' . $/\r
649                 . '</div>' . $/\r
650                 . '</form>' . $/\r
651                 . '</div>' . $/\r
652                 . '<p id="news-instructions">'\r
653                 . &_text_to_xhtml(\r
654                         $locale->get_string('webapp.manage_news_instructions'))\r
655                 . '</p>';\r
656 }\r
657 \r
658 sub get_manage_accounts_html {\r
659         my ($self, %params) = @_;\r
660         my $locale = $self->parent()->locale();\r
661         my $status_message;\r
662         if ($params{'account_to_modify_not_found'}) {\r
663                 $status_message = $locale->get_string('account_to_modify_not_found');\r
664         }\r
665         elsif ($params{'account_to_remove_not_found'}) {\r
666                 $status_message = $locale->get_string('account_to_remove_not_found');\r
667         }\r
668         elsif ($params{'last_owner_account_removal'}) {\r
669                 $status_message = $locale->get_string(\r
670                         'last_owner_account_removal_error');\r
671         }\r
672         elsif ($params{'modified_account_information_required'}) {\r
673                 $status_message = $locale->get_string(\r
674                         'modified_account_information_required');\r
675         }\r
676         elsif ($params{'invalid_username'}) {\r
677                 $status_message = $locale->get_string('invalid_username');\r
678         }\r
679         elsif ($params{'username_exists'}) {\r
680                 $status_message = $locale->get_string('username_exists');\r
681         }\r
682         elsif ($params{'invalid_password'}) {\r
683                 $status_message = $locale->get_string('invalid_password');\r
684         }\r
685         elsif ($params{'different_passwords'}) {\r
686                 $status_message = $locale->get_string('different_passwords');\r
687         }\r
688         elsif ($params{'invalid_user_level'}) {\r
689                 $status_message = $locale->get_string('invalid_user_level');\r
690         }\r
691         elsif ($params{'account_removed'}) {\r
692                 $status_message = $locale->get_string('account_removed');\r
693         }\r
694         elsif ($params{'account_modified'}) {\r
695                 $status_message = $locale->get_string('account_modified');\r
696         }\r
697         elsif ($params{'account_created'}) {\r
698                 $status_message = $locale->get_string('account_created');\r
699         }\r
700         my $html = '<form method="post" action="'\r
701                 . $self->parent()->_url(\r
702                         Chirpy::UI::WebApp::ADMIN_ACTIONS->{'ADD_ACCOUNT'},\r
703                         1\r
704                 ) . '">' . $/\r
705                 . '<div id="account-manager">' . $/\r
706                 . '<div id="username-select-container">' . $/\r
707                 . '<select name="id" id="username-select" size="16">' . $/\r
708                 . '<option value="-1" id="username-new-user" selected="selected">'\r
709                 . '&lt;&lt; '\r
710                 . &_text_to_xhtml($locale->get_string('new_account'))\r
711                 . ' &gt;&gt;</option>' . $/;\r
712         my $users = $self->parent()->parent()->get_accounts();\r
713         if (defined $users) {\r
714                 foreach my $user (@$users) {\r
715                         $html .= '<option value="' . $user->get_id()\r
716                                 . '" class="user-level-' . $user->get_level()\r
717                                 . '">' . $user->get_username() . ' &lt;'\r
718                                 . &_text_to_xhtml(\r
719                                         $self->parent()->parent()->user_level_name($user->get_level()))\r
720                                 . '&gt;</option>' . $/;\r
721                 }\r
722         }\r
723         $html .= '</select>' . $/\r
724                 . '</div>' . $/\r
725                 . '<div id="username-container">' . $/\r
726                 . '<label for="username-field">'\r
727                 . &_text_to_xhtml(\r
728                         $locale->get_string('new_username_title'))\r
729                 . '</label>' . $/\r
730                 . '<input name="new_username" id="username-field" />' . $/\r
731                 . '</div>' . $/\r
732                 . '<div id="password-container">' . $/\r
733                 . '<label for="password-field">'\r
734                 . &_text_to_xhtml(\r
735                         $locale->get_string('new_password_title'))\r
736                 . '</label>' . $/\r
737                 . '<input type="password" name="new_password" '\r
738                 . 'id="password-field" />' . $/\r
739                 . '</div>' . $/\r
740                 . '<div id="repeat-password-container">' . $/\r
741                 . '<label for="repeat-password-field">'\r
742                 . &_text_to_xhtml(\r
743                         $locale->get_string('repeat_new_password_title'))\r
744                 . '</label>' . $/\r
745                 . '<input type="password" name="new_password_repeat" '\r
746                 . 'id="repeat-password-field" />' . $/\r
747                 . '</div>' . $/\r
748                 . '<div id="level-container">' . $/\r
749                 . '<label for="level-select">'\r
750                 . &_text_to_xhtml(\r
751                         $locale->get_string('new_user_level_title'))\r
752                 . '</label>' . $/\r
753                 . '<select name="new_level" id="level-select" size="1">' . $/\r
754                 . '<option value="-1" selected="selected">&lt;'\r
755                 . &_text_to_xhtml(\r
756                         $locale->get_string('no_change'))\r
757                 . '&gt;</option>' . $/;\r
758         foreach my $level ($self->parent()->parent()->user_levels()) {\r
759                 $html .= '<option value="' . $level . '" class="user-level-'\r
760                         . $level . '">' . &_text_to_xhtml(\r
761                                 $self->parent()->parent()->user_level_name($level))\r
762                         . ' &lt;' . $level . '&gt;</option>' . $/;\r
763         }\r
764         $html .= '</select>' . $/\r
765                 . '</div>' . $/\r
766                 . '<div id="account-submit-container">' . $/\r
767                 . '<input type="submit" value="'\r
768                 . &_text_to_xhtml($locale->get_string('update_accounts'))\r
769                 . '" id="account-submit-button" />' . $/\r
770                 . '<input type="submit" name="account_remove" value="'\r
771                 . &_text_to_xhtml(\r
772                         $locale->get_string('remove_account'))\r
773                 . '" id="modify-account-remove-button" onclick="return confirm(&quot;'\r
774                 . &_text_to_xhtml(\r
775                         $locale->get_string('account_removal_confirmation'))\r
776                 . '&quot;)" />' . $/\r
777                 . '</div>' . $/\r
778                 . (defined $status_message\r
779                         ? '<div id="account-manager-result">' . $/\r
780                                 . &_text_to_xhtml($status_message)\r
781                                 . $/ . '</div>' . $/\r
782                         : '')\r
783                 . '</div>' . $/\r
784                 . '<div style="clear: both;"></div>' . $/\r
785                 . '</form>' . $/;\r
786         return $html;\r
787 }\r
788 \r
789 sub get_event_log_html {\r
790         my $self = shift;\r
791         my $locale = $self->parent()->locale();\r
792         my $resurl = $self->parent()->_resources_url();\r
793         my $url = $self->parent()->_url(\r
794                 Chirpy::UI::WebApp::ADMIN_ACTIONS->{'VIEW_EVENT_LOG'},\r
795                 1);\r
796         $url .= ($url =~ /\?/ ? '&amp;' : '?');\r
797         my $html = '<script type="text/javascript" src="'\r
798                 . $resurl . '/js/ajax.js"></script>' . $/\r
799                 . '<script type="text/javascript" src="'\r
800                 . $resurl . '/js/administration.js"></script>' . $/\r
801                 . '<script type="text/javascript">' . $/\r
802                 . 'var eventLogURL = "' . $url . '";' . $/\r
803                 . 'var eventLogLocale = new Array();' . $/;\r
804         foreach my $col (qw(id date username event empty)) {\r
805                 $html .= 'eventLogLocale["' . $col . '"] = "'\r
806                         .  &_text_to_xhtml($locale->get_string($col)) . '";' . $/;\r
807         }\r
808         $html .= 'eventLogLocale["previous"] = "' . &_text_to_xhtml(\r
809                 $locale->get_string('webapp.previous_page_title')) . '";' . $/\r
810                 . 'eventLogLocale["next"] = "' . &_text_to_xhtml(\r
811                 $locale->get_string('webapp.next_page_title')) . '";' . $/\r
812                 . 'eventLogLocale["current"] = "' . &_text_to_xhtml(\r
813                 $locale->get_string('webapp.current_page_title')) . '";' . $/\r
814                 . 'eventLogLocale["loading"] = "' . &_text_to_xhtml(\r
815                 $locale->get_string('processing')) . '";' . $/\r
816                 . 'eventLogLocale["guest"] = "' . &_text_to_xhtml(\r
817                 $locale->get_string('guest')) . '";' . $/;\r
818         foreach my $id (qw/code data user asc/) {\r
819                 my $val = $self->parent()->_cgi_param($id);\r
820                 next unless (defined $val);\r
821                 $html .= 'eventLogURLParam["' . $id . '"] = "'\r
822                         . &_text_to_xhtml($val) . '";' . $/;\r
823         }\r
824         $html .= '</script>' . $/\r
825                 . '<div id="event-log-placeholder"></div>';\r
826         return $html;\r
827 }\r
828 \r
829 sub _serve_event_log_table_data {\r
830         my $self = shift;\r
831         my $locale = $self->parent()->locale();\r
832         my $count = $self->parent()->_cgi_param('count');\r
833         $count = (defined $count ? int $count : undef);\r
834         my $start = $self->parent()->_cgi_param('start');\r
835         $start = 0 unless (defined $start && $start >= 0);\r
836         my $desc = ($self->parent()->_cgi_param('asc') ? 0 : 1);\r
837         my $user = $self->parent()->_cgi_param('user');\r
838         $user = undef if (defined $user && $user !~ /^\d+$/);   \r
839         my $event = $self->parent()->_cgi_param('code');\r
840         $event = undef if (defined $event && $event !~ /^\d+$/);\r
841         my $filter = $self->parent()->_cgi_param('data');\r
842         if (defined $filter && $filter =~ /^([^=]+)=(.*)$/s) {\r
843                 $filter = { $1 => $2 };\r
844         }\r
845         else {\r
846                 $filter = undef;\r
847         }\r
848         my ($events, $leading, $trailing) = $self->parent()->parent()->get_events(\r
849                 $start, $count, $desc, $event, $user, $filter);\r
850         my @events = ();\r
851         foreach my $event (@$events) {\r
852                 my $id = $event->get_id();\r
853                 my $date = $self->parent()->format_date_time($event->get_date());\r
854                 my $user = $event->get_user();\r
855                 my $username;\r
856                 if (defined $user) {\r
857                         my $acct = $self->parent()->parent()->get_account_by_id($user);\r
858                         if (defined $acct) {\r
859                                 $username = $acct->get_username();\r
860                         }\r
861                 }\r
862                 my $description = &_text_to_xhtml($locale->get_string(\r
863                         'event_' . $event->get_code() . '_name'));\r
864                 my $data = $event->get_data();\r
865                 my $result = {\r
866                         'id' => $id,\r
867                         'date' => $date,\r
868                         (defined $username ? ('username' => $username) : ()),\r
869                         'userid' => (defined $user ? $user : 0),\r
870                         'description' => $description,\r
871                         'code' => $event->get_code()\r
872                 };\r
873                 my @data = ();\r
874                 foreach my $key (sort keys %$data) {\r
875                         my $value = $data->{$key};\r
876                         push @data, {\r
877                                 'name' => &_text_to_xhtml($key),\r
878                                 'value' => &_text_to_xhtml($value)\r
879                         };\r
880                 }\r
881                 $result->{'data'} = \@data;\r
882                 push @events, $result;\r
883         }\r
884         $self->parent()->_output_xml('result', {\r
885                 'event' => \@events,\r
886                 ($leading ? ('leading' => 'true') : ()),\r
887                 ($trailing ? ('trailing' => 'true') : ())\r
888         });\r
889 }\r
890 \r
891 sub get_access_disallowed_html {\r
892         my $self = shift;\r
893         return '<p id="insufficient-administrator-privileges-notification">'\r
894                 . &_text_to_xhtml($self->parent()->locale()->get_string(\r
895                         'insufficient_administrative_privileges'))\r
896                 . '</p>';\r
897 }\r
898 \r
899 sub get_change_password_html {\r
900         my ($self, %params) = @_;\r
901         my $locale = $self->parent()->locale();\r
902         if ($params{'password_changed'}) {\r
903                 return '<div id="change-password-result">' . $/\r
904                         . '<p>' . $locale->get_string('password_changed_text') . '</p>' . $/\r
905                         . '</div>';\r
906         }\r
907         my $html = '';\r
908         if (my $error = $params{'password_change_error'}) {\r
909                 $html = '<div id="change-password-error">' . $/ . '<p>'\r
910                         . &_text_to_xhtml($locale->get_string(\r
911                                 $error == Chirpy::UI::NEW_PASSWORD_INVALID\r
912                                         ? 'change_password_new_password_invalid_text'\r
913                                         : ($error == Chirpy::UI::PASSWORDS_DIFFER\r
914                                                 ? 'change_password_passwords_differ_text'\r
915                                                 : ($error == Chirpy::UI::CURRENT_PASSWORD_INVALID\r
916                                                         ? 'change_password_current_password_invalid_text'\r
917                                                         : undef))))\r
918                         . '</p>' . $/ . '</div>' . $/;\r
919         }\r
920         return $html . '<div id="change-password-form">' . $/\r
921                 . '<form method="post" action="'\r
922                 . $self->parent()->_url(Chirpy::UI::WebApp::ADMIN_ACTIONS->{'CHANGE_PASSWORD'}, 1)\r
923                 . '">' . $/\r
924                 . '<div id="current-password-container">' . $/\r
925                 . '<label for="current-password-field">'\r
926                 . &_text_to_xhtml(\r
927                         $locale->get_string('current_password_title'))\r
928                 . '</label>' . $/\r
929                 . '<input type="password" name="current_password" '\r
930                 . 'id="current-password-field" />' . $/\r
931                 . '</div>' . $/\r
932                 . '<div id="new-password-container">' . $/\r
933                 . '<label for="new-password-field">'\r
934                 . &_text_to_xhtml(\r
935                         $locale->get_string('new_password_title'))\r
936                 . '</label>' . $/\r
937                 . '<input type="password" name="new_password" '\r
938                 . 'id="new-password-field" />' . $/\r
939                 . '</div>' . $/\r
940                 . '<div id="repeat-new-password-container">' . $/\r
941                 . '<label for="repeat-new-password-field">'\r
942                 . &_text_to_xhtml(\r
943                         $locale->get_string('repeat_new_password_title'))\r
944                 . '</label>' . $/\r
945                 . '<input type="password" name="repeat_new_password" '\r
946                 . 'id="repeat-new-password-field" />' . $/\r
947                 . '</div>' . $/\r
948                 . '<div id="submit-container">' . $/\r
949                 . '<input type="submit" value="'\r
950                 . &_text_to_xhtml(\r
951                         $locale->get_string('change_password_button_label'))\r
952                 . '" id="change-password-button" />' . $/\r
953                 . '</div>' . $/\r
954                 . '</form>' . $/\r
955                 . '</div>';\r
956 }\r
957 \r
958 *_text_to_xhtml = \&Chirpy::UI::WebApp::_text_to_xhtml;\r
959 \r
960 1;\r
961 \r
962 ###############################################################################