# HG changeset patch # User bsw # Date 1330167097 -3600 # Node ID c587d8762e62a96a5caf7eb480a4dab41c2d184f # Parent 6c88b4bfb56c5b913274e2e117697609b49c408d Registration process updated for Core 2.0, lockable member fields, notification settings diff -r 6c88b4bfb56c -r c587d8762e62 app/main/_filter/20_session.lua --- a/app/main/_filter/20_session.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/_filter/20_session.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,5 +1,3 @@ -request.set_cookie{ path = "/", name = "sessionID", value = "s.lmb0lkEkMu16dO0y" } - if cgi.cookies.liquid_feedback_session then app.session = Session:by_ident(cgi.cookies.liquid_feedback_session) end @@ -13,6 +11,11 @@ request.set_csrf_secret(app.session.additional_secret) -locale.set{lang = app.session.lang or config.default_lang or "en"} +if not app.session.member.lang then + app.session.member.lang = app.session.lang or config.default_lang or "en" + app.session.member:save() +end + +locale.set{lang = app.session.member.lang } execute.inner() diff -r 6c88b4bfb56c -r c587d8762e62 app/main/_layout/default.html --- a/app/main/_layout/default.html Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/_layout/default.html Sat Feb 25 11:51:37 2012 +0100 @@ -60,9 +60,8 @@
- -
- +
+
diff -r 6c88b4bfb56c -r c587d8762e62 app/main/admin/_action/member_update.lua --- a/app/main/admin/_action/member_update.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/admin/_action/member_update.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,18 +1,9 @@ local member = Member:by_id(param.get_id()) or Member:new() -param.update(member, "login", "admin", "name") - -if param.get("activated", atom.boolean) then - member.activated = "now" -end +param.update(member, "identification", "notify_email", "admin") -local password = param.get("password") -if password == "********" or #password == 0 then - password = nil -end - -if password then - member:set_password(password) +if param.get("invite_member", atom.boolean) then + member:send_invitation() end local err = member:try_save() diff -r 6c88b4bfb56c -r c587d8762e62 app/main/admin/member_edit.lua --- a/app/main/admin/member_edit.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/admin/member_edit.lua Sat Feb 25 11:51:37 2012 +0100 @@ -23,11 +23,10 @@ } }, content = function() - ui.field.text{ label = _"Login", name = "login" } - ui.field.text{ label = _"Name", name = "name" } - ui.field.password{ label = _"Password", name = "password", value = (member and member.password) and "********" or "" } + ui.field.text{ label = _"Identification", name = "identification" } + ui.field.text{ label = _"Notification email", name = "notify_email" } ui.field.boolean{ label = _"Admin?", name = "admin" } - ui.field.boolean{ label = _"Activated?", name = "activated" } + ui.field.boolean{ label = _"Send invite?", name = "invite_member" } ui.submit{ text = _"Save" } end } diff -r 6c88b4bfb56c -r c587d8762e62 app/main/index/_action/login.lua --- a/app/main/index/_action/login.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/index/_action/login.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,5 +1,55 @@ local member = Member:by_login_and_password(param.get('login'), param.get('password')) +function do_etherpad_auth(member) + local result = net.curl( + config.etherpad.api_base + .. "api/1/createAuthorIfNotExistsFor?apikey=" .. config.etherpad.api_key + .. "&name=" .. encode.url_part(member.name) .. "&authorMapper=" .. tostring(member.id) + ) + + if not result then + slot.put_into("error", _"Etherpad authentication failed" .. " 1") + return false + end + + local etherpad_author_id = string.match(result, '"authorID"%s*:%s*"([^"]+)"') + + if not etherpad_author_id then + slot.put_into("error", _"Etherpad authentication failed" .. " 2") + return false + end + + local time_in_24h = os.time() + 24 * 60 * 60 + + local result = net.curl( + config.etherpad.api_base + .. "api/1/createSession?apikey=" .. config.etherpad.api_key + .. "&groupID=" .. config.etherpad.group_id + .. "&authorID=" .. etherpad_author_id + .. "&validUntil=" .. time_in_24h + ) + + if not result then + slot.put_into("error", _"Etherpad authentication failed" .. " 3") + return false + end + + local etherpad_sesion_id = string.match(result, '"sessionID"%s*:%s*"([^"]+)"') + + if not etherpad_sesion_id then + slot.put_into("error", _"Etherpad authentication failed" .. " 4") + return false + end + + request.set_cookie{ + path = config.etherpad.cookie_path, + name = "sessionID", + value = etherpad_sesion_id + } + +end + + if member then member.last_login = "now" member.last_activity = "now" @@ -11,6 +61,9 @@ ui.tag{ content = _'Login successful!' } end) trace.debug('User authenticated') + if config.etherpad then + do_etherpad_auth(member) + end else slot.select("error", function() ui.tag{ content = _'Invalid username or password!' } diff -r 6c88b4bfb56c -r c587d8762e62 app/main/index/_action/logout.lua --- a/app/main/index/_action/logout.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/index/_action/logout.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,4 +1,11 @@ if app.session then app.session:destroy() slot.put_into("notice", _"Logout successful") + if config.etherpad then + request.set_cookie{ + path = config.etherpad.cookie_path, + name = "sessionID", + value = "invalid" + } + end end diff -r 6c88b4bfb56c -r c587d8762e62 app/main/index/_action/register.lua --- a/app/main/index/_action/register.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/index/_action/register.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,13 +1,14 @@ local code = util.trim(param.get("code")) -local invite_code = InviteCode:new_selector() - :add_where{ "code = ?", code } +local member = Member:new_selector() + :add_where{ "invite_code = ?", code } + :add_where{ "activated ISNULL" } :optional_object_mode() :for_update() :exec() -if not invite_code or invite_code.used then - slot.put_into("error", _"The code you've entered is invalid" .. ": '" .. code .. "'") +if not member then + slot.put_into("error", _"The code you've entered is invalid") request.redirect{ mode = "forward", module = "index", @@ -18,201 +19,206 @@ local notify_email = param.get("notify_email") -if invite_code and not notify_email then - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { code = invite_code.code } - } - return false +if not config.locked_profile_fields.notify_email and notify_email then + if #notify_email < 5 then + slot.put_into("error", _"Email address too short!") + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { code = member.invite_code } + } + return false + end + member.notify_email = notify_email end -if #notify_email < 5 then - slot.put_into("error", _"Email address too short!") +if member and not member.notify_email then request.redirect{ mode = "redirect", module = "index", view = "register", - params = { code = invite_code.code } + params = { code = member.invite_code, step = 1 } } return false end -local name = param.get("name") + +local name = util.trim(param.get("name")) + +if not config.locked_profile_fields.name and name then -if notify_email and not name then - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email + if #name < 3 then + slot.put_into("error", _"This username is too short!") + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { + code = member.invite_code, + notify_email = member.notify_email, + step = 1 + } } - } - return false + return false + end + + if Member:by_name(name) then + slot.put_into("error", _"This name is already taken, please choose another one!") + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { + code = member.invite_code, + notify_email = member.notify_email, + step = 1 + } + } + return false + end + + member.name = name + end -name = util.trim(name) - -if #name < 3 then - slot.put_into("error", _"This username is too short!") +if member.notify_email and not member.name then request.redirect{ mode = "redirect", module = "index", view = "register", params = { - code = invite_code.code, - notify_email = notify_email - } - } - return false -end - -if Member:by_name(name) then - slot.put_into("error", _"This name is already taken, please choose another one!") - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email - } - } - return false -end - -local login = param.get("login") - -if name and not login then - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email, - name = name - } - } - return false -end - -login = util.trim(login) - -if #login < 3 then - slot.put_into("error", _"This login is too short!") - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email, - name = name + code = member.invite_code, + notify_email = member.notify_email, + step = 1 } } return false end -if Member:by_login(login) then - slot.put_into("error", _"This login is already taken, please choose another one!") - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email, - name = name + +local login = util.trim(param.get("login")) + +if not config.locked_profile_fields.login and login then + if #login < 3 then + slot.put_into("error", _"This login is too short!") + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { + code = member.invite_code, + notify_email = member.notify_email, + name = member.name, + step = 1 + } } - } - return false + return false + end + + if Member:by_login(login) then + slot.put_into("error", _"This login is already taken, please choose another one!") + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { + code = member.invite_code, + notify_email = member.notify_email, + name = member.name, + step = 1 + } + } + return false + end + member.login = login end -if login and param.get("step") ~= "5" then - request.redirect{ - mode = "redirect", - module = "index", - view = "register", - params = { - code = invite_code.code, - notify_email = notify_email, - name = name, - login = login - } - } - return false -end - -for i, checkbox in ipairs(config.use_terms_checkboxes) do - local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) - if not accepted then - slot.put_into("error", checkbox.not_accepted_error) - return false - end -end - -local password1 = param.get("password1") -local password2 = param.get("password2") - -if login and not password1 then +if member.name and not member.login then request.redirect{ mode = "redirect", module = "index", view = "register", params = { - code = invite_code.code, - notify_email = notify_email, - name = name, - login = login + code = member.invite_code, + notify_email = member.notify_email, + name = member.name, + step = 1 } } ---]] - return false -end - -if password1 ~= password2 then - slot.put_into("error", _"Passwords don't match!") - return false -end - -if #password1 < 8 then - slot.put_into("error", _"Passwords must consist of at least 8 characters!") return false end -local member = Member:new() +local step = param.get("step", atom.integer) + +if step > 2 then -member.login = login -member.name = name + for i, checkbox in ipairs(config.use_terms_checkboxes) do + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) + if not accepted then + slot.put_into("error", checkbox.not_accepted_error) + return false + end + end + + local password1 = param.get("password1") + local password2 = param.get("password2") -local success = member:set_notify_email(notify_email) -if not success then - slot.put_into("error", _"Can't send confirmation email") - return -end + if login and not password1 then + request.redirect{ + mode = "redirect", + module = "index", + view = "register", + params = { + code = member.invite_code, + notify_email = member.notify_email, + name = member.name, + login = member.login + } + } + --]] + return false + end -member:set_password(password1) -member:save() - -local now = db:query("SELECT now() AS now", "object").now + if password1 ~= password2 then + slot.put_into("error", _"Passwords don't match!") + return false + end -for i, checkbox in ipairs(config.use_terms_checkboxes) do - local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) - member:set_setting("use_terms_checkbox_" .. checkbox.name, "accepted at " .. tostring(now)) -end + if #password1 < 8 then + slot.put_into("error", _"Passwords must consist of at least 8 characters!") + return false + end + + member.login = login + member.name = name + + local success = member:set_notify_email(notify_email) + if not success then + slot.put_into("error", _"Can't send confirmation email") + return + end + + member:set_password(password1) + + local now = db:query("SELECT now() AS now", "object").now -invite_code.member_id = member.id -invite_code.used = "now" -invite_code:save() + for i, checkbox in ipairs(config.use_terms_checkboxes) do + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) + member:set_setting("use_terms_checkbox_" .. checkbox.name, "accepted at " .. tostring(now)) + end -slot.put_into("notice", _"You've successfully registered and you can login now with your login and password!") + member.activated = 'now' + member.active = true + member.last_activity = 'now' + member:save() -request.redirect{ - mode = "redirect", - module = "index", - view = "login", -} + slot.put_into("notice", _"You've successfully registered and you can login now with your login and password!") + + request.redirect{ + mode = "redirect", + module = "index", + view = "login", + } +end + \ No newline at end of file diff -r 6c88b4bfb56c -r c587d8762e62 app/main/index/_action/set_lang.lua --- a/app/main/index/_action/set_lang.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/index/_action/set_lang.lua Sat Feb 25 11:51:37 2012 +0100 @@ -2,4 +2,8 @@ if lang == "de" or lang == "en" or lang == "eo" then app.session.lang = param.get("lang") app.session:save() + if app.session.member then + app.session.member.lang = app.session.lang + app.session.member:save() + end end \ No newline at end of file diff -r 6c88b4bfb56c -r c587d8762e62 app/main/index/register.lua --- a/app/main/index/register.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/index/register.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,6 +1,6 @@ slot.put_into("title", _"Registration") - +local step = param.get("step", atom.integer) local code = param.get("code") local notify_email = param.get("notify_email") local name = param.get("name") @@ -20,191 +20,176 @@ content = function() if not code then - slot.put_into("title", _"Step 1/5: Invite code") + slot.put_into("title", _"Step 1/3: Invite code") + ui.field.hidden{ name = "step", value = 1 } ui.tag{ tag = "p", content = _"Please enter the invite code you've received." } ui.field.text{ - label = _'Invite code', - name = 'code', + label = _'Invite code', + name = 'code', value = param.get("invite") } - elseif not notify_email then - slot.put_into("title", _"Step 2/5: Email address") - slot.select("actions", function() - ui.link{ - content = function() - ui.image{ static = "icons/16/resultset_previous.png" } - slot.put(_"One step back") - end, - module = "index", - view = "register", - params = { - } - } - end) - ui.tag{ - tag = "p", - content = _"Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link." - } - ui.field.text{ - label = _'Email address', - name = 'notify_email', - value = param.get("notify_email") - } - - elseif not name then - slot.put_into("title", _"Step 3/5: Username") - slot.select("actions", function() - ui.link{ - content = function() - ui.image{ static = "icons/16/resultset_previous.png" } - slot.put(_"One step back") - end, - module = "index", - view = "register", - params = { - code = code - } - } - end) - ui.tag{ - tag = "p", - content = _"Please choose a name, i.e. your real name or your nick name. This name will be shown to others to identify you." - } - ui.field.text{ - label = _'Name', - name = 'name', - value = param.get("name") - } - - elseif not login then - slot.put_into("title", _"Step 4/5: Login name") - slot.select("actions", function() - ui.link{ - content = function() - ui.image{ static = "icons/16/resultset_previous.png" } - slot.put(_"One step back") - end, - module = "index", - view = "register", - params = { - code = code, - notify_email = notify_email - } - } - end) - ui.tag{ - tag = "p", - content = _"Please choose a login name. This name will not be shown to others and is used only by you to login into the system. The login name is case sensitive." - } - ui.field.text{ - label = _'Login name', - name = 'login', - value = param.get("login") - } - else - ui.field.hidden{ name = "step", value = "5" } - slot.put_into("title", _"Step 5/5: Terms of use and password") - slot.select("actions", function() - ui.link{ - content = function() - ui.image{ static = "icons/16/resultset_previous.png" } - slot.put(_"One step back") - end, - module = "index", - view = "register", - params = { - code = code, - notify_email = notify_email, - name = name, + local member = Member:new_selector() + :add_where{ "invite_code = ?", code } + :add_where{ "activated ISNULL" } + :optional_object_mode() + :for_update() + :exec() + + if not member.notify_email and not notify_email or not member.name and not name or not member.login and not login or step == 1 then + slot.put_into("title", _"Step 2/3: Personal information") + ui.field.hidden{ name = "step", value = 2 } + slot.select("actions", function() + ui.link{ + content = function() + ui.image{ static = "icons/16/resultset_previous.png" } + slot.put(_"One step back") + end, + module = "index", + view = "register", + params = { + } } + end) + + ui.tag{ + tag = "p", + content = _"This invite key is connected with the following information:" } - end) - ui.container{ - attr = { class = "wiki use_terms" }, - content = function() - if config.use_terms_html then - slot.put(config.use_terms_html) - else - slot.put(format.wiki_text(config.use_terms)) - end - end - } + + execute.view{ module = "member", view = "_profile", params = { member = member, include_private_data = true } } - for i, checkbox in ipairs(config.use_terms_checkboxes) do - slot.put("
") - ui.tag{ - tag = "div", + if not config.locked_profile_fields.notify_email then + ui.tag{ + tag = "p", + content = _"Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link." + } + ui.field.text{ + label = _'Email address', + name = 'notify_email', + value = param.get("notify_email") or member.notify_email + } + end + if not config.locked_profile_fields.name then + ui.tag{ + tag = "p", + content = _"Please choose a name, i.e. your real name or your nick name. This name will be shown to others to identify you." + } + ui.field.text{ + label = _'Screen name', + name = 'name', + value = param.get("name") or member.name + } + end + if not config.locked_profile_fields.login then + ui.tag{ + tag = "p", + content = _"Please choose a login name. This name will not be shown to others and is used only by you to login into the system. The login name is case sensitive." + } + ui.field.text{ + label = _'Login name', + name = 'login', + value = param.get("login") or member.login + } + end + else + + ui.field.hidden{ name = "step", value = "3" } + slot.put_into("title", _"Step 3/3: Terms of use and password") + slot.select("actions", function() + ui.link{ + content = function() + ui.image{ static = "icons/16/resultset_previous.png" } + slot.put(_"One step back") + end, + module = "index", + view = "register", + params = { + code = code, + notify_email = notify_email, + name = name, + login = login, + step = 1 + } + } + end) + ui.container{ + attr = { class = "wiki use_terms" }, content = function() - ui.tag{ - tag = "input", - attr = { - type = "checkbox", - name = "use_terms_checkbox_" .. checkbox.name, - value = "1", - style = "float: left;", - checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil - } - } - slot.put(" ") - slot.put(checkbox.html) + if config.use_terms_html then + slot.put(config.use_terms_html) + else + slot.put(format.wiki_text(config.use_terms)) + end end } - end - slot.put("
") + for i, checkbox in ipairs(config.use_terms_checkboxes) do + slot.put("
") + ui.tag{ + tag = "div", + content = function() + ui.tag{ + tag = "input", + attr = { + type = "checkbox", + name = "use_terms_checkbox_" .. checkbox.name, + value = "1", + style = "float: left;", + checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil + } + } + slot.put(" ") + slot.put(checkbox.html) + end + } + end - ui.field.text{ - label = _'Email address', - value = param.get("notify_email"), - readonly = true - } - ui.field.text{ - label = _'Name', - value = param.get("name"), - readonly = true - } - ui.field.text{ - label = _'Login name', - value = param.get("login"), - readonly = true - } + slot.put("
") - ui.tag{ - tag = "p", - content = _"Please choose a password and enter it twice. The password is case sensitive." - } - ui.field.password{ - label = _'Password', - name = 'password1', - } - ui.field.password{ - label = _'Password (repeat)', - name = 'password2', - } + member.notify_email = notify_email or member.notify_email + member.name = name or member.name + member.login = login or member.login + + execute.view{ module = "member", view = "_profile", params = { + member = member, include_private_data = true + } } + + ui.tag{ + tag = "p", + content = _"Please choose a password and enter it twice. The password is case sensitive." + } + ui.field.password{ + label = _'Password', + name = 'password1', + } + ui.field.password{ + label = _'Password (repeat)', + name = 'password2', + } + end end - ui.submit{ - text = _'Register' - } + ui.submit{ + text = _'Register' + } - slot.put_into("title", ")") - slot.select("actions", function() - ui.link{ - content = function() - ui.image{ static = "icons/16/cancel.png" } - slot.put(_"Cancel registration") - end, - module = "index", - view = "index" - } - end) - + slot.put_into("title", ")") + slot.select("actions", function() + ui.link{ + content = function() + ui.image{ static = "icons/16/cancel.png" } + slot.put(_"Cancel registration") + end, + module = "index", + view = "index" + } + end) end } diff -r 6c88b4bfb56c -r c587d8762e62 app/main/initiative/_action/create.lua --- a/app/main/initiative/_action/create.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/initiative/_action/create.lua Sat Feb 25 11:51:37 2012 +0100 @@ -87,6 +87,16 @@ issue.area_id = area.id issue.policy_id = policy_id issue:save() + + if config.etherpad then + local result = net.curl( + config.etherpad.api_base + .. "api/1/createGroupPad?apikey=" .. config.etherpad.api_key + .. "&groupID=" .. config.etherpad.group_id + .. "&padName=Issue" .. tostring(issue.id) + .. "&text=" .. config.absolute_base_url .. "issue/show/" .. tostring(issue.id) .. ".html" + ) + end end initiative.issue_id = issue.id diff -r 6c88b4bfb56c -r c587d8762e62 app/main/initiative/_show.lua --- a/app/main/initiative/_show.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/initiative/_show.lua Sat Feb 25 11:51:37 2012 +0100 @@ -32,87 +32,86 @@ attr = { class = "initiative_name" }, content = _("Initiative i#{id}: #{name}", { id = initiative.id, name = initiative.name }) } - if app.session.member_id or config.public_access == "pseudonym" or config.public_access == "full" then - ui.tag{ - attr = { class = "initiator_names" }, - content = function() - for i, initiator in ipairs(initiators) do - slot.put(" ") - ui.link{ - content = function () - execute.view{ - module = "member_image", - view = "_show", - params = { - member = initiator, - image_type = "avatar", - show_dummy = true, - class = "micro_avatar", - popup_text = text - } +end ) + +if app.session.member_id or config.public_access == "pseudonym" or config.public_access == "full" then + ui.tag{ + attr = { class = "initiator_names" }, + content = function() + for i, initiator in ipairs(initiators) do + slot.put(" ") + ui.link{ + content = function () + execute.view{ + module = "member_image", + view = "_show", + params = { + member = initiator, + image_type = "avatar", + show_dummy = true, + class = "micro_avatar", + popup_text = text } - end, - module = "member", view = "show", id = initiator.id - } - slot.put(" ") - ui.link{ - text = initiator.name, - module = "member", view = "show", id = initiator.id - } - if not initiator.accepted then - ui.tag{ attr = { title = _"Not accepted yet" }, content = "?" } - end + } + end, + module = "member", view = "show", id = initiator.id + } + slot.put(" ") + ui.link{ + text = initiator.name, + module = "member", view = "show", id = initiator.id + } + if not initiator.accepted then + ui.tag{ attr = { title = _"Not accepted yet" }, content = "?" } end end - } - end + end + } +end - if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then +if initiator and initiator.accepted and not initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.revoked then + slot.put(" · ") + ui.link{ + attr = { class = "action" }, + content = function() + slot.put(_"Invite initiator") + end, + module = "initiative", + view = "add_initiator", + params = { initiative_id = initiative.id } + } + if #initiators > 1 then slot.put(" · ") ui.link{ - attr = { class = "action" }, content = function() - slot.put(_"Invite initiator") + slot.put(_"Remove initiator") end, module = "initiative", - view = "add_initiator", + view = "remove_initiator", params = { initiative_id = initiative.id } } - if #initiators > 1 then - slot.put(" · ") - ui.link{ - content = function() - slot.put(_"Remove initiator") - end, - module = "initiative", - view = "remove_initiator", - params = { initiative_id = initiative.id } - } - end end - if initiator and initiator.accepted == false then - slot.put(" · ") - ui.link{ - text = _"Cancel refuse of invitation", - module = "initiative", - action = "remove_initiator", - params = { - initiative_id = initiative.id, - member_id = app.session.member.id - }, - routing = { - ok = { - mode = "redirect", - module = "initiative", - view = "show", - id = initiative.id - } +end +if initiator and initiator.accepted == false then + slot.put(" · ") + ui.link{ + text = _"Cancel refuse of invitation", + module = "initiative", + action = "remove_initiator", + params = { + initiative_id = initiative.id, + member_id = app.session.member.id + }, + routing = { + ok = { + mode = "redirect", + module = "initiative", + view = "show", + id = initiative.id } } - end - - -end ) + } +end util.help("initiative.show") @@ -308,3 +307,4 @@ end + diff -r 6c88b4bfb56c -r c587d8762e62 app/main/initiative/_show_voting.lua --- a/app/main/initiative/_show_voting.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/initiative/_show_voting.lua Sat Feb 25 11:51:37 2012 +0100 @@ -12,6 +12,8 @@ params = { initiative = initiative } } + slot.put("
") + ui.container{ attr = { class = "heading" }, content = _"Member voting" @@ -32,4 +34,37 @@ } } + slot.put("
") + + ui.container{ + attr = { class = "heading" }, + content = _"Voting details" + } + + ui.form{ + attr = { class = "vertical" }, + content = function() + + ui.field.boolean{ label = _"Direct majority", value = initiative.direct_majority } + ui.field.boolean{ label = _"Indirect majority", value = initiative.indirect_majority } + ui.field.text{ label = _"Schulze rank", value = tostring(initiative.schulze_rank) .. " (" .. _("Status quo: #{rank}", { rank = initiative.issue.status_quo_schulze_rank }) .. ")" } + local texts = {} + if initiative.reverse_beat_path then + texts[#texts+1] = _"reverse beat path to status quo (including ties)" + end + if initiative.multistage_majority then + texts[#texts+1] = _"possibly instable result caused by multistage majority" + end + if #texts == 0 then + texts[#texts+1] = _"none" + end + ui.field.text{ + label = _"Other failures", + value = table.concat(texts, ", ") + } + ui.field.boolean{ label = _"Eligible as winner", value = initiative.eligible } + end +} + + end diff -r 6c88b4bfb56c -r c587d8762e62 app/main/issue/_filters.lua --- a/app/main/issue/_filters.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/issue/_filters.lua Sat Feb 25 11:51:37 2012 +0100 @@ -12,6 +12,13 @@ selector_modifier = function(selector) end }, { + name = "open", + label = _"Open", + selector_modifier = function(selector) + selector:add_where("issue.closed ISNULL") + end + }, + { name = "new", label = _"New", selector_modifier = function(selector) @@ -94,89 +101,90 @@ end - -filters[#filters+1] = { - name = "filter_interest", - { - name = "any", - label = _"Any", - selector_modifier = function() end - }, - { - name = "my", - label = _"Interested", - selector_modifier = function() end - }, - { - name = "supported", - label = _"Supported", - selector_modifier = function() end - }, - { - name = "potentially_supported", - label = _"Potentially supported", - selector_modifier = function() end - }, - { - name = "initiated", - label = _"Initiated", - selector_modifier = function(selector) - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN initiator ON initiator.initiative_id = initiative.id AND initiator.member_id = ? AND initiator.accepted WHERE initiative.issue_id = issue.id)", member.id }) - end - }, -} - -local filter_interest = param.get_all_cgi()["filter_interest"] - -if filter_interest ~= "any" and filter_interest ~= nil and filter_interest ~= "initiated" then +if app.session.member then filters[#filters+1] = { - name = "filter_delegation", + name = "filter_interest", { name = "any", - label = _"Direct and by delegation", - selector_modifier = function(selector) - if filter_interest == "my" then - selector:left_join("delegating_interest_snapshot", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? AND filter_interest.event = issue.latest_snapshot_event", member.id }) - selector:left_join("interest", "filter_delegating_interest", { "filter_delegating_interest.issue_id = issue.id AND filter_delegating_interest.member_id = ? ", member.id }) - selector:add_where{ "filter_interest.member_id NOTNULL OR filter_delegating_interest.member_id NOTNULL" } - elseif filter_interest == "supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? LEFT JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id AND critical_opinion.member_id ISNULL LIMIT 1) OR EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id, member.id }) - elseif filter_interest == "potentially_supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id LIMIT 1) OR EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND NOT direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id, member.id, member.id }) - end - end + label = _"Any", + selector_modifier = function() end + }, + { + name = "my", + label = _"Interested", + selector_modifier = function() end + }, + { + name = "supported", + label = _"Supported", + selector_modifier = function() end + }, + { + name = "potentially_supported", + label = _"Potentially supported", + selector_modifier = function() end }, { - name = "direct", - label = _"Direct", + name = "initiated", + label = _"Initiated", selector_modifier = function(selector) - if filter_interest == "my" then - selector:join("interest", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? ", member.id }) - elseif filter_interest == "supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? LEFT JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id AND critical_opinion.member_id ISNULL LIMIT 1)", member.id, member.id }) - elseif filter_interest == "potentially_supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id LIMIT 1)", member.id, member.id }) - end + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN initiator ON initiator.initiative_id = initiative.id AND initiator.member_id = ? AND initiator.accepted WHERE initiative.issue_id = issue.id)", member.id }) end }, - { - name = "delegated", - label = _"By delegation", - selector_modifier = function(selector) - if filter_interest == "my" then - selector:join("delegating_interest_snapshot", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? AND filter_interest.event = issue.latest_snapshot_event", member.id }) - elseif filter_interest == "supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND direct_supporter_snapshot.satisfied LIMIT 1)", member.id }) - elseif filter_interest == "potentially_supported" then - selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND NOT direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id }) + } + + local filter_interest = param.get_all_cgi()["filter_interest"] + + if filter_interest ~= "any" and filter_interest ~= nil and filter_interest ~= "initiated" then + filters[#filters+1] = { + name = "filter_delegation", + { + name = "any", + label = _"Direct and by delegation", + selector_modifier = function(selector) + if filter_interest == "my" then + selector:left_join("delegating_interest_snapshot", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? AND filter_interest.event = issue.latest_snapshot_event", member.id }) + selector:left_join("interest", "filter_delegating_interest", { "filter_delegating_interest.issue_id = issue.id AND filter_delegating_interest.member_id = ? ", member.id }) + selector:add_where{ "filter_interest.member_id NOTNULL OR filter_delegating_interest.member_id NOTNULL" } + elseif filter_interest == "supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? LEFT JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id AND critical_opinion.member_id ISNULL LIMIT 1) OR EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id, member.id }) + elseif filter_interest == "potentially_supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id LIMIT 1) OR EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND NOT direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id, member.id, member.id }) + end end - end + }, + { + name = "direct", + label = _"Direct", + selector_modifier = function(selector) + if filter_interest == "my" then + selector:join("interest", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? ", member.id }) + elseif filter_interest == "supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? LEFT JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id AND critical_opinion.member_id ISNULL LIMIT 1)", member.id, member.id }) + elseif filter_interest == "potentially_supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? JOIN critical_opinion ON critical_opinion.initiative_id = initiative.id AND critical_opinion.member_id = ? WHERE initiative.issue_id = issue.id LIMIT 1)", member.id, member.id }) + end + end + }, + { + name = "delegated", + label = _"By delegation", + selector_modifier = function(selector) + if filter_interest == "my" then + selector:join("delegating_interest_snapshot", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? AND filter_interest.event = issue.latest_snapshot_event", member.id }) + elseif filter_interest == "supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND direct_supporter_snapshot.satisfied LIMIT 1)", member.id }) + elseif filter_interest == "potentially_supported" then + selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN direct_supporter_snapshot ON direct_supporter_snapshot.initiative_id = initiative.id AND direct_supporter_snapshot.event = issue.latest_snapshot_event JOIN delegating_interest_snapshot ON delegating_interest_snapshot.delegate_member_ids[array_upper(delegating_interest_snapshot.delegate_member_ids, 1)] = direct_supporter_snapshot.member_id AND delegating_interest_snapshot.issue_id = issue.id AND delegating_interest_snapshot.member_id = ? AND delegating_interest_snapshot.event = issue.latest_snapshot_event WHERE initiative.issue_id = issue.id AND NOT direct_supporter_snapshot.satisfied LIMIT 1)", member.id, member.id }) + end + end + } } - } + end + end - -if member.id == app.session.member_id and param.get_all_cgi()["filter"] == "frozen" then +if app.session.member and member.id == app.session.member_id and (param.get_all_cgi()["filter"] == "frozen" or param.get_all_cgi()["filter"] == "finished") then filters[#filters+1] = { name = "filter_voting", { diff -r 6c88b4bfb56c -r c587d8762e62 app/main/issue/_list.lua --- a/app/main/issue/_list.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/issue/_list.lua Sat Feb 25 11:51:37 2012 +0100 @@ -100,6 +100,7 @@ end } ui.tag{ + attr = { class = "issue_policy_info" }, tag = "div", content = function() diff -r 6c88b4bfb56c -r c587d8762e62 app/main/issue/_show_head.lua --- a/app/main/issue/_show_head.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/issue/_show_head.lua Sat Feb 25 11:51:37 2012 +0100 @@ -114,6 +114,19 @@ end, } end + + if config.etherpad and app.session.member then + local url = config.etherpad.base_url .. "p/" .. config.etherpad.group_id .. "$Issue" .. issue.id + ui.link{ + attr = { target = "_blank" }, + external = url, + content = function() + ui.image{ static = "icons/16/comments.png" } + slot.put(_"Issue pad") + end, + } + end + end) if app.session.member_id and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_action/update.lua --- a/app/main/member/_action/update.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_action/update.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,4 +1,4 @@ -param.update(app.session.member, +local fields = { "organizational_unit", "internal_posts", "realname", @@ -13,12 +13,22 @@ "external_memberships", "external_posts", "statement" -) +} + +local update_args = { app.session.member } -if tostring(app.session.member.birthday) == "invalid_date" then - app.session.member.birthday = nil - slot.put_into("error", _"Date format is not valid. Please use following format: YYYY-MM-DD") - return false +for i, field in ipairs(fields) do + if not config.locked_profile_fields[field] then + param.update(app.session.member, field) + end +end + +if not config.locked_profile_fields.birthday then + if tostring(app.session.member.birthday) == "invalid_date" then + app.session.member.birthday = nil + slot.put_into("error", _"Date format is not valid. Please use following format: YYYY-MM-DD") + return false + end end app.session.member:save() diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_action/update_email.lua --- a/app/main/member/_action/update_email.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_action/update_email.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,5 +1,9 @@ local resend = param.get("resend", atom.boolean) +if not resend and config.locked_profile_fields.notify_email then + error("access denied") +end + if app.session.member.notify_email_locked then if resend then slot.put_into("error", _"We have sent an email with activation link already in the last hour. Please try again later.") diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_action/update_login.lua --- a/app/main/member/_action/update_login.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_action/update_login.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,3 +1,7 @@ +if config.locked_profile_fields.login then + error("access denied") +end + local login = param.get("login") login = util.trim(login) diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_action/update_name.lua --- a/app/main/member/_action/update_name.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_action/update_name.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,3 +1,7 @@ +if config.locked_profile_fields.name then + error("access denied") +end + local name = param.get("name") name = util.trim(name) diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_list.lua --- a/app/main/member/_list.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_list.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,4 +1,6 @@ local members_selector = param.get("members_selector", "table") +members_selector:add_where("member.activated NOTNULL") + local initiative = param.get("initiative", "table") local issue = param.get("issue", "table") local trustee = param.get("trustee", "table") diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/_profile.lua --- a/app/main/member/_profile.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/_profile.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,5 +1,7 @@ local member = param.get("member", "table") +local include_private_data = param.get("include_private_data", atom.boolean) + if not member then local member_id = param.get("member_id", atom.integer) if member_id then @@ -70,12 +72,18 @@ end } - - if member.ident_number then - ui.field.text{ label = _"Ident number", name = "ident_number" } + + if member.identification then + ui.field.text{ label = _"Identification", name = "identification" } end - ui.field.text{ label = _"Name", name = "name" } - + if member.name then + ui.field.text{ label = _"Screen name", name = "name" } + end + if include_private_data and member.login then + ui.field.text{ label = _"Login name", name = "login" } + ui.field.text{ label = _"Notification email", name = "notify_email" } + end + if member.realname and #member.realname > 0 then ui.field.text{ label = _"Real name", name = "realname" } end @@ -128,8 +136,16 @@ end if member.external_posts and #member.external_posts > 0 then ui.field.text{ label = _"Posts", name = "external_posts", multiline = true } + end + if member.admin then + ui.field.boolean{ label = _"Admin?", name = "admin" } end - slot.put('
') + if member.locked then + ui.field.boolean{ label = _"Locked?", name = "locked" } + end + if member.last_activity then + ui.field.text{ label = _"Last activity (updated daily)", value = format.date(member.last_activity) or _"not yet" } + end if member.statement and #member.statement > 0 then ui.container{ @@ -139,14 +155,5 @@ end } end - - if member.admin then - ui.field.boolean{ label = _"Admin?", name = "admin" } - end - if member.locked then - ui.field.boolean{ label = _"Locked?", name = "locked" } - end - ui.field.text{ label = _"Last activity (updated daily)", value = format.date(member.last_activity) or _"not yet" } - end } diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/edit.lua --- a/app/main/member/edit.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/edit.lua Sat Feb 25 11:51:37 2012 +0100 @@ -26,21 +26,22 @@ } }, content = function() - ui.field.text{ label = _"Organizational unit", name = "organizational_unit" } - ui.field.text{ label = _"Internal posts", name = "internal_posts" } - ui.field.text{ label = _"Real name", name = "realname" } - ui.field.text{ label = _"Birthday" .. " YYYY-MM-DD ", name = "birthday", attr = { id = "profile_birthday" } } + ui.field.text{ label = _"Identification", name = "identification", readonly = true } + ui.field.text{ label = _"Organizational unit", name = "organizational_unit", readonly = config.locked_profile_fields.organizational_unit } + ui.field.text{ label = _"Internal posts", name = "internal_posts", readonly = config.locked_profile_fields.internal_posts } + ui.field.text{ label = _"Real name", name = "realname", readonly = config.locked_profile_fields.realname } + ui.field.text{ label = _"Birthday" .. " YYYY-MM-DD ", name = "birthday", attr = { id = "profile_birthday" }, readonly = config.locked_profile_fields.birthday } ui.script{ static = "gregor.js/gregor.js" } util.gregor("profile_birthday", "document.getElementById('timeline_search_date').form.submit();") - ui.field.text{ label = _"Address", name = "address", multiline = true } - ui.field.text{ label = _"email", name = "email" } - ui.field.text{ label = _"xmpp", name = "xmpp_address" } - ui.field.text{ label = _"Website", name = "website" } - ui.field.text{ label = _"Phone", name = "phone" } - ui.field.text{ label = _"Mobile phone", name = "mobile_phone" } - ui.field.text{ label = _"Profession", name = "profession" } - ui.field.text{ label = _"External memberships", name = "external_memberships", multiline = true } - ui.field.text{ label = _"External posts", name = "external_posts", multiline = true } + ui.field.text{ label = _"Address", name = "address", multiline = true, readonly = config.locked_profile_fields.address } + ui.field.text{ label = _"email", name = "email", readonly = config.locked_profile_fields.email } + ui.field.text{ label = _"xmpp", name = "xmpp_address", readonly = config.locked_profile_fields.xmpp_address } + ui.field.text{ label = _"Website", name = "website", readonly = config.locked_profile_fields.website } + ui.field.text{ label = _"Phone", name = "phone", readonly = config.locked_profile_fields.phone } + ui.field.text{ label = _"Mobile phone", name = "mobile_phone", readonly = config.locked_profile_fields.mobile_phone } + ui.field.text{ label = _"Profession", name = "profession", readonly = config.locked_profile_fields.profession } + ui.field.text{ label = _"External memberships", name = "external_memberships", multiline = true, readonly = config.locked_profile_fields.external_memberships } + ui.field.text{ label = _"External posts", name = "external_posts", multiline = true, readonly = config.locked_profile_fields.external_posts } ui.field.select{ label = _"Wiki engine for statement", diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/settings.lua --- a/app/main/member/settings.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/settings.lua Sat Feb 25 11:51:37 2012 +0100 @@ -16,16 +16,23 @@ content = _"You can change the following settings:" } -local pages = { - { module = "member", view = "edit", text = _"Edit profile" }, - { module = "member", view = "edit_images", text = _"Upload images" }, - { view = "settings_display", text = _"Display settings" }, - { view = "settings_email", text = _"Change your notification email address" }, - { view = "settings_name", text = _"Change your name" }, - { view = "settings_login", text = _"Change your login" }, - { view = "settings_password", text = _"Change your password" }, - { view = "developer_settings", text = _"Developer settings" }, -} +local pages = {} + +pages[#pages+1] = { module = "member", view = "edit", text = _"Edit profile" } +pages[#pages+1] = { module = "member", view = "edit_images", text = _"Upload images" } +pages[#pages+1] = { view = "settings_notification", text = _"Notification settings" } +pages[#pages+1] = { view = "settings_display", text = _"Display settings" } +if not config.locked_profile_fields.notify_email then + pages[#pages+1] = { view = "settings_email", text = _"Change your notification email address" } +end +if not config.locked_profile_fields.name then + pages[#pages+1] = { view = "settings_name", text = _"Change your screen name" } +end +if not config.locked_profile_fields.login then + pages[#pages+1] = { view = "settings_login", text = _"Change your login" } +end +pages[#pages+1] = { view = "settings_password", text = _"Change your password" } +pages[#pages+1] = { view = "developer_settings", text = _"Developer settings" } ui.list{ attr = { class = "menu_list" }, diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/settings_notification.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/main/member/settings_notification.lua Sat Feb 25 11:51:37 2012 +0100 @@ -0,0 +1,185 @@ +function send_notification(event) + + local url + + local body = "" + + body = body .. _(" Unit: #{name}\n", { name = event.issue.area.unit.name }) + body = body .. _(" Area: #{name}\n", { name = event.issue.area.name }) + body = body .. _(" Issue: ##{id}\n", { id = event.issue_id }) + body = body .. _(" Policy: #{phase}\n", { phase = event.issue.policy.name }) + body = body .. _(" Phase: #{phase}\n\n", { phase = event.state }) + body = body .. _(" Event: #{event}\n\n", { event = event.event }) + + if event.initiative_id then + url = request.get_absolute_baseurl() .. "initiative/show/" .. event.initiative_id .. ".html" + elseif event.suggestion_id then + url = request.get_absolute_baseurl() .. "suggestion/show/" .. event.suggestion_id .. ".html" + else + url = request.get_absolute_baseurl() .. "issue/show/" .. event.issue_id .. ".html" + end + + body = body .. _(" URL: #{url}\n\n", { url = url }) + + if event.initiative_id then + local initiative = Initiative:by_id(event.initiative_id) + body = body .. _("i#{id}: #{name}\n\n", { id = initiative.id, name = initiative.name }) + else + local initiative_count = Initiative:new_selector() + :add_where{ "initiative.issue_id = ?", event.issue_id } + :count() + local initiatives = Initiative:new_selector() + :add_where{ "initiative.issue_id = ?", event.issue_id } + :add_order_by("initiative.supporter_count DESC") + :limit(3) + :exec() + for i, initiative in ipairs(initiatives) do + body = body .. _("i#{id}: #{name}\n", { id = initiative.id, name = initiative.name }) + end + if initiative_count - 3 > 0 then + body = body .. _("and #{count} more initiatives\n", { count = initiative_count }) + end + body = body .. "\n" + end + + if event.suggestion_id then + local suggestion = Suggestion:by_id(event.suggestion_id) + body = body .. _("#{name}\n\n", { name = suggestion.name }) + end + + slot.put("
", encode.html_newlines(body), "
") + slot.put("
") +end + + + +slot.put_into("title", _"Notification settings") + +slot.select("actions", function() + ui.link{ + content = function() + ui.image{ static = "icons/16/cancel.png" } + slot.put(_"Cancel") + end, + module = "member", + view = "settings" + } +end) + + +util.help("member.settings.notification", _"Notification settings") + +ui.form{ + attr = { class = "vertical" }, + module = "member", + action = "update_notification", + routing = { + ok = { + mode = "redirect", + module = "index", + view = "index" + } + }, + content = function() + ui.tag{ tag = "p", _"Send me notifications about issues in following phases:" } + + ui.container{ content = function() + ui.tag{ + tag = "input", + attr = { type = "radio", name = "notification_level", value = "voting" } + } + ui.tag{ content = _"Voting phase" } + ui.tag{ tag = "ul", content = function() + ui.tag{ tag = "li", content = _"Voting of an issue in one of my areas or I'm interested in starts" } + end } + end } + + ui.container{ content = function() + ui.tag{ + tag = "input", + attr = { type = "radio", name = "notification_level", value = "frozen" } + } + ui.tag{ content = _"Frozen and voting phase" } + ui.tag{ tag = "ul", content = function() + ui.tag{ tag = "li", content = _"An issue in one of my areas or I'm interested in enters phase 'frozen'" } + ui.tag{ tag = "li", content = _"A new initiative is created in an issue I'm interested in" } + ui.tag{ tag = "li", content = _"Voting of an issue in one of my areas or I'm interested in starts" } + end } + end } + + ui.container{ content = function() + ui.tag{ + tag = "input", + attr = { type = "radio", name = "notification_level", value = "discussion" } + } + ui.tag{ content = _"Discussion, frozen and voting phase" } + ui.tag{ tag = "ul", content = function() + ui.tag{ tag = "li", content = _"An issue in one of my areas or I'm interested in enters phase 'discussion'" } + ui.tag{ tag = "li", content = _"A new initiative is created in an issue I'm interested in" } + ui.tag{ tag = "li", content = _"The draft of an initiative I'm supporting is updated" } + ui.tag{ tag = "li", content = _"An initiative I was supporting is revoked" } + ui.tag{ tag = "li", content = _"A new suggestion is created in an initiative I'm supporting" } + ui.tag{ tag = "li", content = _"An issue in one of my areas or I'm interested in enters phase 'frozen'" } + ui.tag{ tag = "li", content = _"Voting of an issue in one of my areas or I'm interested in starts" } + end } + end } + + ui.container{ content = function() + ui.tag{ + tag = "input", + attr = { type = "radio", name = "notification_level", value = "any" } + } + ui.tag{ content = _"Any phase" } + ui.tag{ tag = "ul", content = function() + ui.tag{ tag = "li", content = _"A new issue is created in one of my areas" } + ui.tag{ tag = "li", content = _"An issue in one of my areas or i'm interested in enters phase 'discussion'" } + ui.tag{ tag = "li", content = _"A new initiative is created in an issue I'm interested in" } + ui.tag{ tag = "li", content = _"The draft of an initiative I'm supporting is updated" } + ui.tag{ tag = "li", content = _"An initiative I was supporting is revoked" } + ui.tag{ tag = "li", content = _"A new suggestion is created in an initiative I'm supporting" } + ui.tag{ tag = "li", content = _"An issue in one of my areas or I'm interested in enters phase 'frozen'" } + ui.tag{ tag = "li", content = _"Voting of an issue in one of my areas or I'm interested in starts" } + end } + end } + + ui.container{ content = function() + ui.tag{ + tag = "input", + attr = { type = "radio", name = "notification_level", value = "none" } + } + ui.tag{ content = _"No notifications at all" } + end } + + + + ui.submit{ value = _"Change display settings" } + end +} + +local last_id = 6000; + +while last_id < 6050 do + + local event = Event:new_selector() + :add_where{ "event.id > ?", last_id } + :add_order_by("event.id") + :limit(1) + :optional_object_mode() + :exec() + + last_id = nil + if event then + last_id = event.id + local members_to_notify = Member:new_selector() + :join("event_seen_by_member", nil, { "event_seen_by_member.seen_by_member_id = member.id AND event_seen_by_member.notify_level <= member.notify_level AND event_seen_by_member.id = ?", event.id } ) + :add_where("member.activated NOTNULL AND member.notify_email NOTNULL") + :exec() + ui.container{ content = _("Event #{id} -> #{num} members", { id = event.id, num = #members_to_notify }) } + + send_notification(event) + + end +end + + +-- select event.id, event.occurrence, membership.member_id NOTNULL as membership, interest.member_id NOTNULL as interest, supporter.member_id NOTNULL as supporter, event.event, event.state, issue.id, initiative.name FROM event JOIN issue ON issue.id = event.issue_id LEFT JOIN membership ON membership.area_id = issue.area_id AND membership.member_id = 41 LEFT JOIN interest ON interest.issue_id = issue.id AND interest.member_id = 41 LEFT JOIN initiative ON initiative.id = event.initiative_id LEFT JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = 41 WHERE (((event.event = 'issue_state_changed' OR event.event = 'initiative_created_in_new_issue') AND membership.member_id NOTNULL OR interest.member_id NOTNULL) OR (event.event = 'initiative_created_in_existing_issue' AND interest.member_id NOTNULL) OR ((event.event = 'initiative_revoked' OR event.event = 'new_draft_created' OR event.event = 'suggestion_created') AND supporter.member_id NOTNULL)) AND event.id > 7000 ORDER by event.id ASC LIMIT 1; \ No newline at end of file diff -r 6c88b4bfb56c -r c587d8762e62 app/main/member/show.lua --- a/app/main/member/show.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/member/show.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,5 +1,9 @@ local member = Member:by_id(param.get_id()) +if not member or not member.activated then + error("access denied") +end + app.html_title.title = member.name app.html_title.subtitle = _("Member") diff -r 6c88b4bfb56c -r c587d8762e62 app/main/policy/show.lua --- a/app/main/policy/show.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/policy/show.lua Sat Feb 25 11:51:37 2012 +0100 @@ -22,10 +22,36 @@ value = "≥ " .. tostring(policy.initiative_quorum_num) .. "/" .. tostring(policy.initiative_quorum_den) } ui.field.text{ - label = _"Majority", - value = (policy.majority_strict and ">" or "≥" ) .. " " .. tostring(policy.majority_num) .. "/" .. tostring(policy.majority_den) + label = _"Direct majority", + value = + (policy.direct_majority_strict and ">" or "≥" ) .. " " + .. tostring(policy.direct_majority_num) .. "/" + .. tostring(policy.direct_majority_den) + .. (policy.direct_majority_positive > 1 and ", " .. _("at least #{count} approvals", { count = policy.direct_majority_positive }) or "") + .. (policy.direct_majority_non_negative > 1 and ", " .. _("at least #{count} approvals or abstentions", { count = policy.direct_majority_non_negative }) or "") } + ui.field.text{ + label = _"Indirect majority", + value = + (policy.indirect_majority_strict and ">" or "≥" ) .. " " + .. tostring(policy.indirect_majority_num) .. "/" + .. tostring(policy.indirect_majority_den) + .. (policy.indirect_majority_positive > 1 and ", " .. _("at least #{count} approvals", { count = policy.indirect_majority_positive }) or "") + .. (policy.indirect_majority_non_negative > 1 and ", " .. _("at least #{count} approvals or abstentions", { count = policy.indirect_majority_non_negative }) or "") + } + + local texts = {} + if policy.no_reverse_beat_path then + texts[#texts+1] = _"no reverse beat path to status quo (including ties)" + end + if policy.no_multistage_majority then + texts[#texts+1] = _"prohibit potentially instable results caused by multistage majorities" + end + ui.field.text{ + label = _"Options", + value = table.concat(texts, ", ") + } ui.container{ attr = { class = "suggestion_content wiki" }, content = function() diff -r 6c88b4bfb56c -r c587d8762e62 app/main/unit/_list.lua --- a/app/main/unit/_list.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/unit/_list.lua Sat Feb 25 11:51:37 2012 +0100 @@ -5,6 +5,9 @@ columns = { { content = function(unit) + for i = 1, unit.depth - 1 do + slot.put("     ") + end ui.link{ text = unit.name, module = "area", view = "list", params = { unit_id = unit.id } } end } diff -r 6c88b4bfb56c -r c587d8762e62 app/main/vote/list.lua --- a/app/main/vote/list.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/app/main/vote/list.lua Sat Feb 25 11:51:37 2012 +0100 @@ -401,16 +401,32 @@ content = function() ui.tag{ content = "i" .. initiative.id .. ": " } ui.tag{ content = initiative.shortened_name } - if #initiators > 1 then - ui.container{ - attr = { style = "font-size: 80%;" }, - content = _"Initiators" .. ": " .. initiator_names_string + slot.put("
") + for i, initiator in ipairs(initiators) do + ui.link{ + attr = { class = "clickable" }, + content = function () + execute.view{ + module = "member_image", + view = "_show", + params = { + member = initiator, + image_type = "avatar", + show_dummy = true, + class = "micro_avatar", + popup_text = text + } + } + end, + module = "member", view = "show", id = initiator.id } - else - ui.container{ - attr = { style = "font-size: 80%;" }, - content = _"Initiator" .. ": " .. initiator_names_string + slot.put(" ") + ui.link{ + attr = { class = "clickable" }, + text = initiator.name, + module = "member", view = "show", id = initiator.id } + slot.put(" ") end end } diff -r 6c88b4bfb56c -r c587d8762e62 config/default.lua --- a/config/default.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/config/default.lua Sat Feb 25 11:51:37 2012 +0100 @@ -20,6 +20,10 @@ } } +config.locked_profile_fields = { + field_name = true, + notify_email = true +} config.member_image_content_type = "image/jpeg" config.member_image_convert_func = { diff -r 6c88b4bfb56c -r c587d8762e62 config/development.lua --- a/config/development.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/config/development.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,4 +1,4 @@ -config.absolute_base_url = "http://10.8.33.34/lf/" +config.absolute_base_url = "http://10.1.11.5/lf/" execute.config("default") @@ -42,3 +42,11 @@ -- "custom_script", -- "document.getElementById('trace_show').onclick();" --) + +config.etherpad = { + base_url = "http://10.1.11.5:9001/", + api_base = "http://127.0.0.1:9001/", + api_key = "g5XAVrRb5EgPuEqIdVrRNt2Juipx3PoH", + group_id = "g.7WDKN3StkEyuWkyN", + cookie_path = "/" +} diff -r 6c88b4bfb56c -r c587d8762e62 env/net/curl.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/net/curl.lua Sat Feb 25 11:51:37 2012 +0100 @@ -0,0 +1,10 @@ +function net.curl(url) + local stdout, errmsg, status = os.pfilter(nil, "curl", url) + if not stdout then + error("Error while executing curl: " .. errmsg) + end + if status ~= 0 then + return nil + end + return stdout +end \ No newline at end of file diff -r 6c88b4bfb56c -r c587d8762e62 env/ui/tabs.lua --- a/env/ui/tabs.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/env/ui/tabs.lua Sat Feb 25 11:51:37 2012 +0100 @@ -1,6 +1,8 @@ function ui.tabs(tabs) + local attr = tabs.attr or {} + attr.class = (attr.class and attr.class .. " " or "") .. "ui_tabs" ui.container{ - attr = { class = "ui_tabs" }, + attr = attr, content = function() local params = param.get_all_cgi() local current_tab = params["tab"] diff -r 6c88b4bfb56c -r c587d8762e62 model/event.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/model/event.lua Sat Feb 25 11:51:37 2012 +0100 @@ -0,0 +1,10 @@ +Event = mondelefant.new_class() +Event.table = 'event' + +Event:add_reference{ + mode = 'm1', + to = "Issue", + this_key = 'issue_id', + that_key = 'id', + ref = 'issue', +} \ No newline at end of file diff -r 6c88b4bfb56c -r c587d8762e62 model/event_seen_by_member.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/model/event_seen_by_member.lua Sat Feb 25 11:51:37 2012 +0100 @@ -0,0 +1,4 @@ +EventSeenByMember = mondelefant.new_class() +EventSeenByMember.table = 'event_seen_by_member' + + diff -r 6c88b4bfb56c -r c587d8762e62 model/member.lua --- a/model/member.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/model/member.lua Sat Feb 25 11:51:37 2012 +0100 @@ -325,6 +325,30 @@ :add_where("active") end +function Member.object:send_invitation() + trace.disable() + self.invite_code = multirand.string( 24, "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" ) + local content = slot.use_temporary(function() + slot.put(_"Hello\n\n") + slot.put(_"You are invited to LiquidFeedback. To register please click the following link:\n\n") + slot.put(config.absolute_base_url .. "index/register.html?invite_key=" .. self.invite_code .. "\n\n") + slot.put(_"If this link is not working, please open following url in your web browser:\n\n") + slot.put(config.absolute_base_url .. "index/register.html\n\n") + slot.put(_"On that page please enter the invite key:\n\n") + slot.put(self.invite_code .. "\n\n") + end) + local success = net.send_mail{ + envelope_from = config.mail_envelope_from, + from = config.mail_from, + reply_to = config.mail_reply_to, + to = self.notify_email_unconfirmed or self.notify_email, + subject = config.mail_subject_prefix .. _"Invitation to LiquidFeedback", + content_type = "text/plain; charset=UTF-8", + content = content + } + return success +end + function Member.object:set_notify_email(notify_email) trace.disable() local expiry = db:query("SELECT now() + '7 days'::interval as expiry", "object").expiry diff -r 6c88b4bfb56c -r c587d8762e62 model/unit.lua --- a/model/unit.lua Fri Feb 17 15:16:02 2012 +0100 +++ b/model/unit.lua Sat Feb 25 11:51:37 2012 +0100 @@ -21,8 +21,39 @@ ref = 'members' } -function Unit:get_flattened_tree() - -- TODO implement +function recursive_add_child_units(units, parent_unit) + parent_unit.childs = {} + for i, unit in ipairs(units) do + if unit.parent_id == parent_unit.id then + parent_unit.childs[#(parent_unit.childs)+1] = unit + recursive_add_child_units(units, unit) + end + end +end + +function recursive_get_child_units(units, parent_unit, depth) + for i, unit in ipairs(parent_unit.childs) do + unit.depth = depth + units[#units+1] = unit + recursive_get_child_units(units, unit, depth + 1) + end +end - return Unit:new_selector():exec() +function Unit:get_flattened_tree() + local units = Unit:new_selector():add_order_by("name"):exec() + local unit_tree = {} + for i, unit in ipairs(units) do + if not unit.parent_id then + unit_tree[#unit_tree+1] = unit + recursive_add_child_units(units, unit) + end + end + local depth = 1 + local units = {} + for i, unit in ipairs(unit_tree) do + unit.depth = depth + units[#units+1] = unit + recursive_get_child_units(units, unit, depth + 1) + end + return units end diff -r 6c88b4bfb56c -r c587d8762e62 static/style.css --- a/static/style.css Fri Feb 17 15:16:02 2012 +0100 +++ b/static/style.css Sat Feb 25 11:51:37 2012 +0100 @@ -269,9 +269,7 @@ .slot_initiative_head { background: -webkit-gradient(linear, left top, left bottom, - /*color-stop(0%,#AFEFB9), color-stop(100%,#ffffff) */ - color-stop(0%, #fff), color-stop(15%,#e7e7e7), color-stop(100%,#fff) - ); + color-stop(0%,#e7e7e7), color-stop(66%,#fff)); margin-top: 2ex; padding-left: 1em; padding-top: 2ex; @@ -328,7 +326,9 @@ } - +.member_image_photo { + border-radius: 8px; +} /************************************************************************* * vote info / delegation @@ -469,7 +469,6 @@ margin-right: 1em; } - /************************************************************************* * ui.tab */ @@ -823,6 +822,10 @@ font-size: 125%; } +.issues .issue .issue_policy_info { + font-style: italic; +} + .issues .issue .interest_by_delegation { float: right; } @@ -851,7 +854,7 @@ .initiative_link.supported, .initiative_link.potentially_supported { - background-color: #C9FFD1; + background-color: #cdf; border-radius: 5px; background: -webkit-radial-gradient(center, ellipse cover, #cdf 50%,#fff 100%); /* Chrome10+,Safari5.1+ */ } @@ -975,6 +978,9 @@ border-radius: 8px; } +.member_statement { + margin-right: 250px; +} #suggestion_description { height: 15ex; @@ -1021,6 +1027,7 @@ background-color: #dfd; padding: 1ex; margin-bottom: 2ex; + border-radius: 8px; } .not_admitted_info, @@ -1153,15 +1160,21 @@ * Voting */ +#voting_form { + margin-top: 20px; +} + #voting { position: relative; } + #voting .approval, .abstention, .disapproval { border: 2px black solid; - margin-top: 5ex; + margin-top: 2ex; margin-bottom: 5ex; padding: 1ex; padding-bottom: 2ex; + border-radius: 8px; } #voting .approval { background-color: #9f9; @@ -1187,8 +1200,9 @@ #voting .movable { position: relative; border: 1px black solid; - margin: 1ex; + margin-top: 1ex; padding: 0.5ex; + border-radius: 8px; } #voting .voting_form_active .movable { cursor: pointer;