1 /* Monolith bulletins (recent news spool).
2 * - by Richard W.M. Jones <rich@annexia.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 * $Id: ml_bulletins.c,v 1.12 2003/02/22 15:34:32 rich Exp $
33 #include <pthr_iolib.h>
36 #include "ml_widget.h"
37 #include "ml_button.h"
38 #include "ml_window.h"
39 #include "ml_table_layout.h"
40 #include "ml_form_layout.h"
41 #include "ml_text_label.h"
42 #include "ml_dialog.h"
44 #include "ml_form_input.h"
45 #include "ml_form_textarea.h"
46 #include "ml_form_select.h"
47 #include "ml_form_text.h"
48 #include "ml_form_submit.h"
49 #include "ml_bulletins.h"
51 static void repaint (void *, ml_session, const char *, io_handle);
53 struct ml_widget_operations bulletins_ops =
60 struct ml_widget_operations *ops;
61 pool pool; /* Pool for allocations. */
62 ml_session session; /* Current session. */
63 const char *conninfo; /* Database connection. */
64 int sectionid; /* Which section? */
65 int first_item; /* First item to display. */
66 int nr_items; /* Number of items to display on each page. */
67 ml_button post, home, prev, next; /* Buttons along the bottom. */
69 /* These are used during posting. */
70 ml_form_textarea post_item;
71 ml_form_select post_type;
72 ml_form_text post_link;
73 ml_form_text post_link_text;
76 static int can_post (db_handle dbh, int sectionid, int userid);
77 static void post (ml_session, void *vw);
78 static void update_buttons (ml_bulletins w);
79 static void home_button (ml_session, void *vw);
80 static void prev_button (ml_session, void *vw);
81 static void next_button (ml_session, void *vw);
82 static void post_button (ml_session, void *vw);
85 new_ml_bulletins (pool pool, ml_session session, const char *conninfo,
86 const char *section_name)
88 ml_bulletins w = pmalloc (pool, sizeof *w);
92 w->ops = &bulletins_ops;
95 w->conninfo = conninfo;
99 /* Get the sectionid. */
100 dbh = get_db_handle (conninfo, DBI_THROW_ERRORS);
102 sth = st_prepare_cached
104 "select resid from ml_resources where name = ?", DBI_STRING);
105 st_execute (sth, section_name);
107 st_bind (sth, 0, w->sectionid, DBI_INT);
109 if (!st_fetch (sth)) return 0;
113 /* Create the buttons for the bottom of the page. The home/prev/next
114 * buttons get enabled (possibly) in update_buttons. The post button
115 * is always enabled, but only shown to eligible posters.
117 w->post = new_ml_button (pool, "Post");
118 ml_button_set_callback (w->post, post_button, session, w);
119 ml_button_set_popup (w->post, "bulletins_post_window");
120 ml_button_set_popup_size (w->post, 400, 300);
122 w->home = new_ml_button (pool, "Most recent");
123 w->prev = new_ml_button (pool, "<<");
124 w->next = new_ml_button (pool, ">>");
130 /* Callback for the "home" button. */
132 home_button (ml_session session, void *vw)
134 ml_bulletins w = (ml_bulletins) vw;
140 /* Callback for the "prev" button. */
142 prev_button (ml_session session, void *vw)
144 ml_bulletins w = (ml_bulletins) vw;
146 w->first_item -= w->nr_items;
147 if (w->first_item < 0) w->first_item = 0;
151 /* Callback for the "next" button. */
153 next_button (ml_session session, void *vw)
155 ml_bulletins w = (ml_bulletins) vw;
157 w->first_item += w->nr_items;
162 enable_home (ml_bulletins w)
164 ml_button_set_callback (w->home, home_button, w->session, w);
168 disable_home (ml_bulletins w)
170 ml_button_set_callback (w->home, 0, w->session, 0);
174 enable_prev (ml_bulletins w)
176 ml_button_set_callback (w->prev, prev_button, w->session, w);
180 disable_prev (ml_bulletins w)
182 ml_button_set_callback (w->prev, 0, w->session, 0);
186 enable_next (ml_bulletins w)
188 ml_button_set_callback (w->next, next_button, w->session, w);
192 disable_next (ml_bulletins w)
194 ml_button_set_callback (w->next, 0, w->session, 0);
197 /* This function updates the state of each button. It consults the
198 * database to find out how many articles are present.
201 update_buttons (ml_bulletins w)
207 /* Get a database handle. */
208 dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
210 /* Find out how many articles are present. */
211 sth = st_prepare_cached
212 (dbh, "select count (*) from ml_bulletins");
215 st_bind (sth, 0, count, DBI_INT);
217 pth_die ("select count(*) returned no rows!");
219 /* Make sure first_item is sensible. */
220 if (w->first_item >= count)
221 w->first_item = count - w->nr_items;
222 if (w->first_item < 0)
225 /* Decide which buttons to enable. */
226 if (w->first_item > w->nr_items)
231 if (w->first_item >= w->nr_items)
236 if (w->first_item + w->nr_items < count)
243 post_button (ml_session session, void *vw)
245 ml_bulletins w = (ml_bulletins) vw;
252 /* Get a database handle. */
253 dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
255 /* Is the current user allowed to post? It can happen that this
256 * function is called even if the user is not a legitimate poster.
258 * (1) User displays the front page, with "Post" button, then logs
259 * out in another window, then presses the "Post" button.
260 * (2) User is attempting to predict action IDs (note that the "Post"
261 * button is registered, even if it not displayed).
262 * It's always a good idea to check permissions inside callback
265 if (!can_post (dbh, w->sectionid, ml_session_userid (session)))
269 "You do not have permission to post in this section.",
270 ML_DIALOG_CLOSE_BUTTON);
274 /* Display the posting form. */
275 win = new_ml_window (session, w->pool);
276 form = new_ml_form (w->pool);
277 ml_form_set_callback (form, post, session, w);
278 ml_widget_set_property (form, "method", "GET");
279 tbl = new_ml_form_layout (w->pool);
281 w->post_item = new_ml_form_textarea (w->pool, form, 3, 40);
282 ml_form_layout_pack (tbl, "Message:", w->post_item);
284 w->post_type = new_ml_form_select (w->pool, form);
285 ml_form_select_push_back (w->post_type, "Plain text");
286 ml_form_select_push_back (w->post_type, "*Smart* text");
287 ml_form_select_push_back (w->post_type, "HTML");
288 ml_form_select_set_selection (w->post_type, 1); /* XXX From preferences. */
289 ml_form_layout_pack (tbl, 0, w->post_type);
291 w->post_link = new_ml_form_text (w->pool, form);
292 ml_form_layout_pack (tbl, "URL:", w->post_link);
294 w->post_link_text = new_ml_form_text (w->pool, form);
295 ml_form_layout_pack (tbl, "Link text:", w->post_link_text);
297 sub = new_ml_form_submit (w->pool, form, "Post");
298 ml_form_layout_pack (tbl, 0, sub);
300 /* Pack everything up. */
301 ml_form_pack (form, tbl);
302 ml_window_pack (win, form);
306 post (ml_session session, void *vw)
308 ml_bulletins w = (ml_bulletins) vw;
311 const char *item, *item_type, *link, *link_text;
314 /* Verify the details of the posting, otherwise do nothing, which just
315 * represents the form back to the user.
317 item = ml_form_input_get_value (w->post_item);
318 link = ml_form_input_get_value (w->post_link);
319 link_text = ml_form_input_get_value (w->post_link_text);
321 if (!item || strlen (item) == 0) return;
323 type = ml_form_select_get_selection (w->post_type);
325 /* Turn empty strings into nulls for the database. */
326 if (strlen (link) == 0) link = 0;
327 if (strlen (link_text) == 0) link_text = 0;
329 /* Set the item type. */
331 case 0: item_type = "p"; break;
332 case 1: item_type = "s"; break;
333 case 2: item_type = "h"; break;
334 default: item_type = "s";
337 /* Get a database handle. */
338 dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
340 /* Verify the user can post. See notes above. */
341 userid = ml_session_userid (session);
342 if (!can_post (dbh, w->sectionid, userid))
346 "You do not have permission to post in this section.",
347 ML_DIALOG_CLOSE_BUTTON);
351 /* Insert the posting. */
352 sth = st_prepare_cached
354 "insert into ml_bulletins "
355 "(sectionid, authorid, item, item_type, link, link_text) "
356 "values (?, ?, ?, ?, ?, ?)",
357 DBI_INT, DBI_INT, DBI_STRING, DBI_STRING, DBI_STRING, DBI_STRING);
358 st_execute (sth, w->sectionid, userid, item, item_type, link, link_text);
360 /* Commit to the database. */
364 /* Present a confirmation page. */
365 ml_ok_window (w->pool, session,
366 "Item was successfully posted.",
367 ML_DIALOG_CLOSE_BUTTON | ML_DIALOG_CLOSE_RELOAD_OPENER);
371 show_item (io_handle io, int n, char *item, char *item_type, char *username,
372 char *posted_date, char *timediff, char *link, char *link_text)
374 /* XXX Lots of issues in this block:
375 * (2) parsing/printing of dates
376 * (3) smart text support
377 * (4) escaping of link text
378 * (5) styling of the whole thing
380 io_fprintf (io, "<table width=\"100%%\"><tr>"
381 "<td rowspan=\"3\" valign=\"top\">%d.</td>",
383 io_fprintf (io, "<td>Posted by <strong>%s</strong> on "
384 "<strong>%s</strong></td></tr>",
385 username, posted_date);
386 io_fprintf (io, "<tr><td>%s</td></tr>", item);
387 if (link && link_text)
388 io_fprintf (io, "<tr><td align=\"right\">"
389 "<a href=\"%s\">%s</a></td></tr>",
392 io_fprintf (io, "<tr><td align=\"right\">"
393 "<a href=\"%s\">%s</a></td></tr>",
396 io_fprintf (io, "<tr><td></td></tr>");
397 io_fprintf (io, "</table>");
401 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
403 ml_bulletins w = (ml_bulletins) vw;
406 char *item, *item_type, *username, *posted_date, *timediff,
410 /* Get a database handle. */
411 dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
413 /* Is the current user allowed to post/remove articles? */
414 is_poster = can_post (dbh, w->sectionid, ml_session_userid (session));
416 /* Pull out the headlines. */
417 sth = st_prepare_cached
419 "select b.item, b.item_type, u.username, b.posted_date, "
420 " current_timestamp - b.posted_date, "
421 " b.link, b.link_text "
422 "from ml_bulletins b, ml_users u "
423 "where b.sectionid = ? and b.authorid = u.userid "
427 DBI_INT, DBI_INT, DBI_INT);
428 st_execute (sth, w->sectionid, w->nr_items, w->first_item);
430 st_bind (sth, 0, item, DBI_STRING);
431 st_bind (sth, 1, item_type, DBI_STRING);
432 st_bind (sth, 2, username, DBI_STRING);
433 st_bind (sth, 3, posted_date, DBI_STRING);
434 st_bind (sth, 4, timediff, DBI_STRING);
435 st_bind (sth, 5, link, DBI_STRING);
436 st_bind (sth, 6, link_text, DBI_STRING);
439 io_fprintf (io, "<table><tr><td><table>");
441 n = w->first_item + 1;
443 while (st_fetch (sth))
445 io_fprintf (io, "<tr><td>");
447 show_item (io, n, item, item_type, username, posted_date, timediff,
450 io_fprintf (io, "</td></tr>");
455 /* Finish off the page with the buttons at the bottom. */
456 io_fprintf (io, "</table></td></tr><tr><td align=\"right\">");
458 ml_widget_repaint (w->post, session, windowid, io);
459 ml_widget_repaint (w->home, session, windowid, io);
460 ml_widget_repaint (w->prev, session, windowid, io);
461 ml_widget_repaint (w->next, session, windowid, io);
462 io_fprintf (io, "</td></tr></table>");
464 /* Be polite: give back the database handle. */
469 can_post (db_handle dbh, int sectionid, int userid)
475 sth = st_prepare_cached
477 "select 1 from ml_bulletins_posters "
478 "where sectionid = ? and userid = ?", DBI_INT, DBI_INT);
479 st_execute (sth, sectionid, userid);
481 return st_fetch (sth);