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)
