liquid_feedback_frontend
view app/main/initiative/show.lua @ 11:77d58efe99fd
Version beta7
Important security fixes:
- Added missing HTML encoding to postal address of member
- Link to discussion URL only if it starts with http(s)://
Other bugfixes:
- Fixed wrong display of 2nd level delegating voters for an initiative
- Do not display invited initiators as initiators while voting
- Added missing translation
New features:
- Public message of the day
- Both direct and indirect supporter count is shown in tab heads
- Support shown in initiative lists
Language chooser at the login page has been added (again)
Important security fixes:
- Added missing HTML encoding to postal address of member
- Link to discussion URL only if it starts with http(s)://
Other bugfixes:
- Fixed wrong display of 2nd level delegating voters for an initiative
- Do not display invited initiators as initiators while voting
- Added missing translation
New features:
- Public message of the day
- Both direct and indirect supporter count is shown in tab heads
- Support shown in initiative lists
Language chooser at the login page has been added (again)
author | bsw |
---|---|
date | Fri Jan 22 12:00:00 2010 +0100 (2010-01-22) |
parents | 72c5e0ee7c98 |
children | 00d1004545f1 |
line source
1 local initiative = Initiative:new_selector():add_where{ "id = ?", param.get_id()}:single_object_mode():exec()
3 slot.select("actions", function()
4 ui.link{
5 content = function()
6 ui.image{ static = "icons/16/script.png" }
7 slot.put(_"Show all initiatives")
8 end,
9 module = "issue",
10 view = "show",
11 id = initiative.issue.id
12 }
13 end)
15 execute.view{
16 module = "issue",
17 view = "_show_head",
18 params = { issue = initiative.issue }
19 }
21 if initiative.revoked then
22 ui.container{
23 attr = { class = "revoked_info" },
24 content = function()
25 slot.put(_("This initiative has been revoked at #{revoked}", { revoked = format.timestamp(initiative.revoked) }))
26 local suggested_initiative = initiative.suggested_initiative
27 if suggested_initiative then
28 slot.put("<br /><br />")
29 slot.put(_("The initiators suggest to support the following initiative:"))
30 slot.put("<br />")
31 ui.link{
32 content = _("Issue ##{id}", { id = suggested_initiative.issue.id } ) .. ": " .. encode.html(suggested_initiative.name),
33 module = "initiative",
34 view = "show",
35 id = suggested_initiative.id
36 }
37 end
38 end
39 }
40 end
42 local initiator = Initiator:by_pk(initiative.id, app.session.member.id)
44 --slot.put_into("html_head", '<link rel="alternate" type="application/rss+xml" title="RSS" href="../show/' .. tostring(initiative.id) .. '.rss" />')
47 slot.select("actions", function()
48 if not initiative.issue.fully_frozen and not initiative.issue.closed then
49 ui.link{
50 attr = { class = "action" },
51 content = function()
52 ui.image{ static = "icons/16/script_add.png" }
53 slot.put(_"Create alternative initiative")
54 end,
55 module = "initiative",
56 view = "new",
57 params = { issue_id = initiative.issue.id }
58 }
59 end
60 end)
62 slot.put_into("sub_title", encode.html(_"Initiative: '#{name}'":gsub("#{name}", initiative.shortened_name) ))
64 slot.select("support", function()
65 ui.container{
66 attr = { class = "actions" },
67 content = function()
68 execute.view{
69 module = "supporter",
70 view = "_show_box",
71 params = { initiative = initiative }
72 }
73 if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then
74 ui.link{
75 attr = { class = "action", style = "float: left;" },
76 content = function()
77 ui.image{ static = "icons/16/script_delete.png" }
78 slot.put(_"Revoke initiative")
79 end,
80 module = "initiative",
81 view = "revoke",
82 id = initiative.id
83 }
84 end
85 end
86 }
87 end)
89 util.help("initiative.show")
91 if initiator and initiator.accepted == nil then
92 ui.container{
93 attr = { class = "initiator_invite_info" },
94 content = function()
95 slot.put(_"You are invited to become initiator of this initiative.")
96 slot.put(" ")
97 ui.link{
98 content = function()
99 ui.image{ static = "icons/16/tick.png" }
100 slot.put(_"Accept invitation")
101 end,
102 module = "initiative",
103 action = "accept_invitation",
104 id = initiative.id,
105 routing = {
106 default = {
107 mode = "redirect",
108 module = request.get_module(),
109 view = request.get_view(),
110 id = param.get_id_cgi(),
111 params = param.get_all_cgi()
112 }
113 }
114 }
115 slot.put(" ")
116 ui.link{
117 content = function()
118 ui.image{ static = "icons/16/cross.png" }
119 slot.put(_"Refuse invitation")
120 end,
121 module = "initiative",
122 action = "reject_initiator_invitation",
123 params = {
124 initiative_id = initiative.id,
125 member_id = app.session.member.id
126 },
127 routing = {
128 default = {
129 mode = "redirect",
130 module = request.get_module(),
131 view = request.get_view(),
132 id = param.get_id_cgi(),
133 params = param.get_all_cgi()
134 }
135 }
136 }
137 end
138 }
139 slot.put("<br />")
140 end
142 if (initiative.discussion_url and #initiative.discussion_url > 0)
143 or (initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked) then
144 ui.container{
145 attr = { class = "vertical" },
146 content = function()
147 ui.container{
148 attr = { class = "ui_field_label" },
149 content = _"Discussion with initiators"
150 }
151 ui.tag{
152 tag = "span",
153 content = function()
154 if initiative.discussion_url:find("^https?://") then
155 if initiative.discussion_url and #initiative.discussion_url > 0 then
156 ui.link{
157 attr = {
158 class = "actions",
159 target = "_blank",
160 title = initiative.discussion_url
161 },
162 content = function()
163 slot.put(encode.html(initiative.discussion_url))
164 end,
165 external = initiative.discussion_url
166 }
167 end
168 else
169 slot.put(encode.html(initiative.discussion_url))
170 end
171 slot.put(" ")
172 if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then
173 ui.link{
174 attr = { class = "actions" },
175 content = _"(change URL)",
176 module = "initiative",
177 view = "edit",
178 id = initiative.id
179 }
180 end
181 end
182 }
183 end
184 }
185 end
188 ui.container{
189 attr = { id = "add_suggestion_form", class = "hidden_inline_form" },
190 content = function()
192 ui.link{
193 content = _"Close",
194 attr = {
195 onclick = "document.getElementById('add_suggestion_form').style.display='none';return(false)",
196 style = "float: right;"
197 }
198 }
200 ui.field.text{ attr = { class = "head" }, value = _"Add new suggestion" }
203 ui.form{
204 module = "suggestion",
205 action = "add",
206 params = { initiative_id = initiative.id },
207 routing = {
208 default = {
209 mode = "redirect",
210 module = "initiative",
211 view = "show",
212 id = initiative.id,
213 params = { tab = "suggestion" }
214 }
215 },
216 attr = { class = "vertical" },
217 content = function()
218 local supported = Supporter:by_pk(initiative.id, app.session.member.id) and true or false
219 if not supported then
220 ui.field.text{
221 attr = { class = "warning" },
222 value = _"You are currently not supporting this initiative. By adding suggestions to this initiative you will automatically become a potential supporter."
223 }
224 end
225 ui.field.text{ label = _"Title (80 chars max)", name = "name" }
226 ui.field.text{ label = _"Description", name = "description", multiline = true }
227 ui.field.select{
228 label = _"Degree",
229 name = "degree",
230 foreign_records = {
231 { id = 1, name = _"should"},
232 { id = 2, name = _"must"},
233 },
234 foreign_id = "id",
235 foreign_name = "name"
236 }
237 ui.submit{ text = _"Commit suggestion" }
238 end
239 }
240 end
241 }
243 local supporter = app.session.member:get_reference_selector("supporters")
244 :add_where{ "initiative_id = ?", initiative.id }
245 :optional_object_mode()
246 :exec()
248 if supporter then
249 local old_draft_id = supporter.draft_id
250 local new_draft_id = initiative.current_draft.id
251 if old_draft_id ~= new_draft_id then
252 ui.container{
253 attr = { class = "draft_updated_info" },
254 content = function()
255 slot.put(_"The draft of this initiative has been updated!")
256 slot.put(" ")
257 ui.link{
258 content = _"Show diff",
259 module = "draft",
260 view = "diff",
261 params = {
262 old_draft_id = old_draft_id,
263 new_draft_id = new_draft_id
264 }
265 }
266 slot.put(" ")
267 ui.link{
268 content = _"Refresh support to current draft",
269 module = "initiative",
270 action = "add_support",
271 id = initiative.id,
272 routing = {
273 default = {
274 mode = "redirect",
275 module = "initiative",
276 view = "show",
277 id = initiative.id
278 }
279 }
280 }
281 end
282 }
283 end
284 end
287 local current_draft_name = _"Current draft"
288 if initiative.issue.half_frozen then
289 current_draft_name = _"Voting proposal"
290 end
292 if initiative.issue.state == "finished" then
293 current_draft_name = _"Voted proposal"
294 end
296 local tabs = {
297 {
298 name = "current_draft",
299 label = current_draft_name,
300 content = function()
301 if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then
302 ui.link{
303 content = function()
304 ui.image{ static = "icons/16/script_add.png" }
305 slot.put(_"Edit draft")
306 end,
307 module = "draft",
308 view = "new",
309 params = { initiative_id = initiative.id }
310 }
311 end
312 execute.view{ module = "draft", view = "_show", params = { draft = initiative.current_draft } }
313 end
314 }
315 }
317 if initiative.issue.ranks_available then
318 tabs[#tabs+1] = {
319 name = "voter",
320 label = _"Voter",
321 content = function()
322 execute.view{
323 module = "member",
324 view = "_list",
325 params = {
326 initiative = initiative,
327 members_selector = initiative.issue:get_reference_selector("direct_voters")
328 :left_join("vote", nil, { "vote.initiative_id = ? AND vote.member_id = member.id", initiative.id })
329 :add_field("direct_voter.weight as voter_weight")
330 :add_field("coalesce(vote.grade, 0) as grade")
331 }
332 }
333 end
334 }
335 end
337 local suggestion_count = initiative:get_reference_selector("suggestions"):count()
339 tabs[#tabs+1] = {
340 name = "suggestion",
341 label = _"Suggestions" .. " (" .. tostring(suggestion_count) .. ")",
342 content = function()
343 execute.view{
344 module = "suggestion",
345 view = "_list",
346 params = {
347 initiative = initiative,
348 suggestions_selector = initiative:get_reference_selector("suggestions")
349 }
350 }
351 slot.put("<br />")
352 if not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then
353 ui.link{
354 content = function()
355 ui.image{ static = "icons/16/comment_add.png" }
356 slot.put(_"Add new suggestion")
357 end,
358 attr = { onclick = "document.getElementById('add_suggestion_form').style.display='block';return(false)" },
359 static = "#"
360 }
361 end
362 end
363 }
365 local members_selector = initiative:get_reference_selector("supporting_members_snapshot")
366 :join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id")
367 :join("direct_interest_snapshot", nil, "direct_interest_snapshot.event = issue.latest_snapshot_event AND direct_interest_snapshot.issue_id = issue.id AND direct_interest_snapshot.member_id = member.id")
368 :add_field("direct_interest_snapshot.weight")
369 :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
370 :add_where("direct_supporter_snapshot.satisfied")
372 local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
373 local direct_satisfied_supporter_count = tmp.count
374 local indirect_satisfied_supporter_count = (tmp.weight or 0) - tmp.count
376 local count_string
377 if indirect_satisfied_supporter_count > 0 then
378 count_string = "(" .. tostring(direct_satisfied_supporter_count) .. "+" .. tostring(indirect_satisfied_supporter_count) .. ")"
379 else
380 count_string = "(" .. tostring(direct_satisfied_supporter_count) .. ")"
381 end
383 tabs[#tabs+1] = {
384 name = "satisfied_supporter",
385 label = _"Supporter" .. " " .. count_string,
386 content = function()
387 execute.view{
388 module = "member",
389 view = "_list",
390 params = {
391 initiative = initiative,
392 members_selector = members_selector
393 }
394 }
395 end
396 }
398 local members_selector = initiative:get_reference_selector("supporting_members_snapshot")
399 :join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id")
400 :join("direct_interest_snapshot", nil, "direct_interest_snapshot.event = issue.latest_snapshot_event AND direct_interest_snapshot.issue_id = issue.id AND direct_interest_snapshot.member_id = member.id")
401 :add_field("direct_interest_snapshot.weight")
402 :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
403 :add_where("NOT direct_supporter_snapshot.satisfied")
405 local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
406 local direct_potential_supporter_count = tmp.count
407 local indirect_potential_supporter_count = (tmp.weight or 0) - tmp.count
409 local count_string
410 if indirect_potential_supporter_count > 0 then
411 count_string = "(" .. tostring(direct_potential_supporter_count) .. "+" .. tostring(indirect_potential_supporter_count) .. ")"
412 else
413 count_string = "(" .. tostring(direct_potential_supporter_count) .. ")"
414 end
416 tabs[#tabs+1] = {
417 name = "supporter",
418 label = _"Potential supporter" .. " " .. count_string,
419 content = function()
420 execute.view{
421 module = "member",
422 view = "_list",
423 params = {
424 initiative = initiative,
425 members_selector = members_selector
426 }
427 }
428 end
429 }
431 local initiator_count = initiative:get_reference_selector("initiators"):add_where("accepted"):count()
433 tabs[#tabs+1] = {
434 name = "initiators",
435 label = _"Initiators" .. " (" .. tostring(initiator_count) .. ")",
436 content = function()
437 if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then
438 ui.link{
439 attr = { class = "action" },
440 content = function()
441 ui.image{ static = "icons/16/user_add.png" }
442 slot.put(_"Invite initiator")
443 end,
444 module = "initiative",
445 view = "add_initiator",
446 params = { initiative_id = initiative.id }
447 }
448 if initiator_count > 1 then
449 ui.link{
450 content = function()
451 ui.image{ static = "icons/16/user_delete.png" }
452 slot.put(_"Remove initiator")
453 end,
454 module = "initiative",
455 view = "remove_initiator",
456 params = { initiative_id = initiative.id }
457 }
458 end
459 end
460 if initiator and initiator.accepted == false then
461 ui.link{
462 content = function()
463 ui.image{ static = "icons/16/user_delete.png" }
464 slot.put(_"Cancel refuse of invitation")
465 end,
466 module = "initiative",
467 action = "remove_initiator",
468 params = {
469 initiative_id = initiative.id,
470 member_id = app.session.member.id
471 },
472 routing = {
473 ok = {
474 mode = "redirect",
475 module = "initiative",
476 view = "show",
477 id = initiative.id
478 }
479 }
480 }
481 end
482 local members_selector = initiative:get_reference_selector("initiating_members")
483 :add_field("initiator.accepted", "accepted")
484 if not (initiator and initiator.accepted) then
485 members_selector:add_where("accepted")
486 end
487 execute.view{
488 module = "member",
489 view = "_list",
490 params = {
491 members_selector = members_selector,
492 initiator = initiator
493 }
494 }
495 end
496 }
498 local drafts_count = initiative:get_reference_selector("drafts"):count()
500 tabs[#tabs+1] = {
501 name = "drafts",
502 label = _"Draft history" .. " (" .. tostring(drafts_count) .. ")",
503 content = function()
504 execute.view{ module = "draft", view = "_list", params = { drafts = initiative.drafts } }
505 end
506 }
508 tabs[#tabs+1] = {
509 name = "details",
510 label = _"Details",
511 content = function()
512 ui.form{
513 attr = { class = "vertical" },
514 record = initiative,
515 readonly = true,
516 content = function()
517 ui.field.text{ label = _"Issue policy", value = initiative.issue.policy.name }
518 ui.field.text{
519 label = _"Created at",
520 value = tostring(initiative.created)
521 }
522 ui.field.text{
523 label = _"Created at",
524 value = format.timestamp(initiative.created)
525 }
526 -- ui.field.date{ label = _"Revoked at", name = "revoked" }
527 ui.field.boolean{ label = _"Admitted", name = "admitted" }
528 end
529 }
530 end
531 }
534 ui.tabs(tabs)