liquid_feedback_frontend

diff app/main/draft/_action/add.lua @ 1496:ed3c40911ae1

Completed image attachments feature
author bsw
date Tue Feb 11 15:24:36 2020 +0100 (2020-02-11)
parents 17e7082c377a
children 770ab0a7f79b
line diff
     1.1 --- a/app/main/draft/_action/add.lua	Mon Feb 10 21:10:49 2020 +0100
     1.2 +++ b/app/main/draft/_action/add.lua	Tue Feb 11 15:24:36 2020 +0100
     1.3 @@ -1,24 +1,270 @@
     1.4 -local draft_text = param.get("content")
     1.5 +local initiative
     1.6 +local new_initiative
     1.7 +local draft_id
     1.8 +local status
     1.9 +
    1.10 +if param.get("initiative_id", atom.integer) then
    1.11 +
    1.12 +  local function donew()
    1.13 +    local draft_text = param.get("content")
    1.14 +
    1.15 +    if not draft_text then
    1.16 +      return false
    1.17 +    end
    1.18 +
    1.19 +    local draft_text = util.wysihtml_preproc(draft_text)
    1.20 +
    1.21 +    local valid_html, error_message = util.html_is_safe(draft_text)
    1.22 +    if not valid_html then
    1.23 +      slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
    1.24 +      return false
    1.25 +    end
    1.26 +
    1.27 +    if config.initiative_abstract then
    1.28 +      local abstract = param.get("abstract")
    1.29 +      if not abstract then
    1.30 +        return false
    1.31 +      end
    1.32 +      abstract = encode.html(abstract)
    1.33 +      draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
    1.34 +    end
    1.35 +
    1.36 +    draft_id = Draft:update_content(
    1.37 +      app.session.member.id, 
    1.38 +      param.get("initiative_id", atom.integer),
    1.39 +      param.get("formatting_engine"),
    1.40 +      draft_text,
    1.41 +      nil,
    1.42 +      param.get("preview") or param.get("edit")
    1.43 +    )
    1.44 +    return draft_id and true or false
    1.45 +  end
    1.46 +
    1.47 +  status = donew()
    1.48 +
    1.49 +else
    1.50 +
    1.51 +  local function donew()
    1.52 +    local issue
    1.53 +    local area
    1.54  
    1.55 -if not draft_text then
    1.56 -  return false
    1.57 -end
    1.58 +    local issue_id = param.get("issue_id", atom.integer)
    1.59 +    if issue_id then
    1.60 +      issue = Issue:new_selector():add_where{"id=?",issue_id}:for_share():single_object_mode():exec()
    1.61 +      if issue.closed then
    1.62 +        slot.put_into("error", _"This issue is already closed.")
    1.63 +        return false
    1.64 +      elseif issue.fully_frozen then 
    1.65 +        slot.put_into("error", _"Voting for this issue has already begun.")
    1.66 +        return false
    1.67 +      elseif issue.phase_finished then
    1.68 +        slot.put_into("error", _"Current phase is already closed.")
    1.69 +        return false
    1.70 +      end
    1.71 +      area = issue.area
    1.72 +    else
    1.73 +      local area_id = param.get("area_id", atom.integer)
    1.74 +      area = Area:new_selector():add_where{"id=?",area_id}:single_object_mode():exec()
    1.75 +      if not area.active then
    1.76 +        slot.put_into("error", "Invalid area.")
    1.77 +        return false
    1.78 +      end
    1.79 +    end
    1.80 +
    1.81 +    if not app.session.member:has_voting_right_for_unit_id(area.unit_id) then
    1.82 +      return execute.view { module = "index", view = "403" }
    1.83 +    end
    1.84 +
    1.85 +    local policy_id = param.get("policy_id", atom.integer)
    1.86 +    local policy
    1.87 +    if policy_id then
    1.88 +      policy = Policy:by_id(policy_id)
    1.89 +    end
    1.90  
    1.91 -local draft_text = util.wysihtml_preproc(draft_text)
    1.92 +    if not issue then
    1.93 +      if policy_id == -1 then
    1.94 +        slot.put_into("error", _"Please choose a policy")
    1.95 +        return false
    1.96 +      end
    1.97 +      if not policy.active then
    1.98 +        slot.put_into("error", "Invalid policy.")
    1.99 +        return false
   1.100 +      end
   1.101 +      if policy.polling and not app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   1.102 +        return execute.view { module = "index", view = "403" }
   1.103 +      end
   1.104 +      if not area:get_reference_selector("allowed_policies")
   1.105 +        :add_where{ "policy.id = ?", policy_id }
   1.106 +        :optional_object_mode()
   1.107 +        :exec()
   1.108 +      then
   1.109 +        slot.put_into("error", "policy not allowed")
   1.110 +        return false
   1.111 +      end
   1.112 +    end
   1.113 +
   1.114 +    local is_polling = (issue and param.get("polling", atom.boolean)) or (policy and policy.polling) or false
   1.115 +
   1.116 +    local tmp = db:query({ "SELECT text_entries_left, initiatives_left FROM member_contingent_left WHERE member_id = ? AND polling = ?", app.session.member.id, is_polling }, "opt_object")
   1.117 +    if not tmp or tmp.initiatives_left < 1 then
   1.118 +      slot.put_into("error", _"Sorry, your contingent for creating initiatives has been used up. Please try again later.")
   1.119 +      return false
   1.120 +    end
   1.121 +    if tmp and tmp.text_entries_left < 1 then
   1.122 +      slot.put_into("error", _"Sorry, you have reached your personal flood limit. Please be slower...")
   1.123 +      return false
   1.124 +    end
   1.125 +
   1.126 +    local name = param.get("name")
   1.127 +
   1.128 +    local name = util.trim(name)
   1.129 +
   1.130 +    if #name < 3 then
   1.131 +      slot.put_into("error", _"Please enter a meaningful title for your initiative!")
   1.132 +      return false
   1.133 +    end
   1.134 +
   1.135 +    if #name > 140 then
   1.136 +      slot.put_into("error", _"This title is too long!")
   1.137 +      return false
   1.138 +    end
   1.139  
   1.140 -local valid_html, error_message = util.html_is_safe(draft_text)
   1.141 -if not valid_html then
   1.142 -  slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
   1.143 -  return false
   1.144 -end
   1.145 +    local timing
   1.146 +    if not issue and policy.free_timeable then
   1.147 +      local free_timing_string = util.trim(param.get("free_timing"))
   1.148 +      if not free_timing_string or #free_timing_string < 1 then
   1.149 +        slot.put_into("error", _"Choose timing")
   1.150 +        return false
   1.151 +      end
   1.152 +      local available_timings
   1.153 +      if config.free_timing and config.free_timing.available_func then
   1.154 +        available_timings = config.free_timing.available_func(policy)
   1.155 +        if available_timings == false then
   1.156 +          slot.put_into("error", "error in free timing config")
   1.157 +          return false
   1.158 +        end
   1.159 +      end
   1.160 +      if available_timings then
   1.161 +        local timing_available = false
   1.162 +        for i, available_timing in ipairs(available_timings) do
   1.163 +          if available_timing.id == free_timing_string then
   1.164 +      	    timing_available = true
   1.165 +          end
   1.166 +        end
   1.167 +        if not timing_available then
   1.168 +          slot.put_into("error", _"Invalid timing")
   1.169 +          return false
   1.170 +        end
   1.171 +      end
   1.172 +      timing = config.free_timing.calculate_func(policy, free_timing_string)
   1.173 +      if not timing then
   1.174 +        slot.put_into("error", "error in free timing config")
   1.175 +        return false
   1.176 +      end
   1.177 +    end
   1.178 +
   1.179 +    local draft_text = param.get("content")
   1.180 +
   1.181 +    if not draft_text then
   1.182 +      slot.put_into("error", "no draft text")
   1.183 +      return false
   1.184 +    end
   1.185 +
   1.186 +    local draft_text = util.wysihtml_preproc(draft_text)
   1.187 +
   1.188 +    local valid_html, error_message = util.html_is_safe(draft_text)
   1.189 +    if not valid_html then
   1.190 +      slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) )
   1.191 +      return false
   1.192 +    end
   1.193 +
   1.194 +    if config.initiative_abstract then
   1.195 +      local abstract = param.get("abstract")
   1.196 +      if not abstract then
   1.197 +        slot.put_into("error", "no abstract")
   1.198 +        return false
   1.199 +      end
   1.200 +      abstract = encode.html(abstract)
   1.201 +      draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
   1.202 +    end
   1.203 +
   1.204 +    local location = param.get("location")
   1.205 +    if location == "" then
   1.206 +      location = nil
   1.207 +    end
   1.208 +
   1.209 +    if param.get("preview") or param.get("edit") then
   1.210 +      return false
   1.211 +    end
   1.212  
   1.213 -if config.initiative_abstract then
   1.214 -  local abstract = param.get("abstract")
   1.215 -  if not abstract then
   1.216 -    return false
   1.217 +    initiative = Initiative:new()
   1.218 +
   1.219 +    if not issue then
   1.220 +      issue = Issue:new()
   1.221 +      issue.area_id = area.id
   1.222 +      issue.policy_id = policy_id
   1.223 +      
   1.224 +      if policy.polling then
   1.225 +        issue.accepted = 'now'
   1.226 +        issue.state = 'discussion'
   1.227 +        initiative.polling = true
   1.228 +        
   1.229 +        if policy.free_timeable then
   1.230 +          issue.discussion_time = timing.discussion
   1.231 +          issue.verification_time = timing.verification
   1.232 +          issue.voting_time = timing.voting
   1.233 +        end
   1.234 +        
   1.235 +      end
   1.236 +      
   1.237 +      issue:save()
   1.238 +
   1.239 +      if config.etherpad then
   1.240 +        local result = net.curl(
   1.241 +          config.etherpad.api_base 
   1.242 +          .. "api/1/createGroupPad?apikey=" .. config.etherpad.api_key
   1.243 +          .. "&groupID=" .. config.etherpad.group_id
   1.244 +          .. "&padName=Issue" .. tostring(issue.id)
   1.245 +          .. "&text=" .. request.get_absolute_baseurl() .. "issue/show/" .. tostring(issue.id) .. ".html"
   1.246 +        )
   1.247 +      end
   1.248 +    end
   1.249 +
   1.250 +    if param.get("polling", atom.boolean) and app.session.member:has_polling_right_for_unit_id(area.unit_id) then
   1.251 +      initiative.polling = true
   1.252 +    end
   1.253 +    initiative.issue_id = issue.id
   1.254 +    initiative.name = name
   1.255 +    initiative:save()
   1.256 +
   1.257 +    new_initiative = initiative
   1.258 +
   1.259 +    local draft = Draft:new()
   1.260 +    draft.initiative_id = initiative.id
   1.261 +    draft.formatting_engine = formatting_engine
   1.262 +    draft.content = draft_text
   1.263 +    draft.location = location
   1.264 +    draft.author_id = app.session.member.id
   1.265 +    draft:save()
   1.266 +
   1.267 +    draft_id = draft.id
   1.268 +
   1.269 +    local initiator = Initiator:new()
   1.270 +    initiator.initiative_id = initiative.id
   1.271 +    initiator.member_id = app.session.member.id
   1.272 +    initiator.accepted = true
   1.273 +    initiator:save()
   1.274 +
   1.275 +    if not is_polling then
   1.276 +      local supporter = Supporter:new()
   1.277 +      supporter.initiative_id = initiative.id
   1.278 +      supporter.member_id = app.session.member.id
   1.279 +      supporter.draft_id = draft.id
   1.280 +      supporter:save()
   1.281 +    end
   1.282 +
   1.283    end
   1.284 -  abstract = encode.html(abstract)
   1.285 -  draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text
   1.286 +  status = donew()
   1.287  end
   1.288  
   1.289  if config.attachments then
   1.290 @@ -80,76 +326,78 @@
   1.291    fh:write(json.export(file_uploads))
   1.292    fh:write("\n")
   1.293    fh:close()
   1.294 -end
   1.295 +
   1.296 +  if draft_id then
   1.297 +    local file_upload_session = param.get("file_upload_session")
   1.298 +    file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   1.299  
   1.300 -local draft_id = Draft:update_content(
   1.301 -  app.session.member.id, 
   1.302 -  param.get("initiative_id", atom.integer),
   1.303 -  param.get("formatting_engine"),
   1.304 -  draft_text,
   1.305 -  nil,
   1.306 -  param.get("preview") or param.get("edit")
   1.307 -)
   1.308 +    local draft_attachments = DraftAttachment:new_selector()
   1.309 +      :add_where{ "draft_attachment.draft_id = ?", draft_id }
   1.310 +      :exec()
   1.311 +
   1.312 +    for i, draft_attachment in ipairs(draft_attachments) do
   1.313 +      if param.get("file_delete_" .. draft_attachment.file_id, atom.boolean) then
   1.314 +        draft_attachment:destroy()
   1.315 +      end
   1.316 +    end
   1.317  
   1.318 -if draft_id and config.attachments then
   1.319 -  local file_upload_session = param.get("file_upload_session")
   1.320 -  file_upload_session = string.gsub(file_upload_session, "[^A-Za-z0-9]", "")
   1.321 +    local file_uploads = json.array()
   1.322 +    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   1.323 +    local fh = io.open(filename, "r")
   1.324 +    if fh then
   1.325 +      file_uploads = json.import(fh:read("*a"))
   1.326 +    end
   1.327 +    for i, file_upload in ipairs(file_uploads) do
   1.328 +      local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".jpg")
   1.329 +      local data
   1.330 +      local fh = io.open(filename, "r")
   1.331 +      if fh then
   1.332 +        data = fh:read("*a")
   1.333 +      end
   1.334 +      local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".preview.jpg")
   1.335 +      local data_preview
   1.336 +      local fh = io.open(filename, "r")
   1.337 +      if fh then
   1.338 +        data_preview = fh:read("*a")
   1.339 +      end
   1.340 +
   1.341 +      local hash = moonhash.sha3_512(data)
   1.342  
   1.343 -  local draft_attachments = DraftAttachment:new_selector()
   1.344 -    :add_where{ "draft_attachment.draft_id = ?", draft_id }
   1.345 -    :exec()
   1.346 +      local file = File:new_selector()
   1.347 +        :add_where{ "hash = ?", hash }
   1.348 +        :add_where{ "content_type = ?", "image/jpeg" }
   1.349 +        :optional_object_mode()
   1.350 +        :exec()
   1.351  
   1.352 -  for i, draft_attachment in ipairs(draft_attachments) do
   1.353 -    if param.get("file_delete_" .. draft_attachment.file_id, atom.boolean) then
   1.354 -      draft_attachment:destroy()
   1.355 +      if not file then
   1.356 +        file = File:new()
   1.357 +        file.content_type = "image/jpeg"
   1.358 +        file.hash = hash
   1.359 +        file.data = data
   1.360 +        file.preview_content_type = "image/jpeg"
   1.361 +        file.preview_data = data_preview
   1.362 +        file:save()
   1.363 +      end
   1.364 +
   1.365 +      local draft_attachment = DraftAttachment:new()
   1.366 +      draft_attachment.draft_id = draft_id
   1.367 +      draft_attachment.file_id = file.id
   1.368 +      draft_attachment.title = file_upload.title
   1.369 +      draft_attachment.description = file_upload.description
   1.370 +      draft_attachment:save()
   1.371      end
   1.372    end
   1.373  
   1.374 -  local file_uploads = json.array()
   1.375 -  local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. ".json")
   1.376 -  local fh = io.open(filename, "r")
   1.377 -  if fh then
   1.378 -    file_uploads = json.import(fh:read("*a"))
   1.379 -  end
   1.380 -  for i, file_upload in ipairs(file_uploads) do
   1.381 -    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".jpg")
   1.382 -    local data
   1.383 -    local fh = io.open(filename, "r")
   1.384 -    if fh then
   1.385 -      data = fh:read("*a")
   1.386 -    end
   1.387 -    local filename = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "file_upload-" .. file_upload_session .. "-" .. file_upload.id .. ".preview.jpg")
   1.388 -    local data_preview
   1.389 -    local fh = io.open(filename, "r")
   1.390 -    if fh then
   1.391 -      data_preview = fh:read("*a")
   1.392 -    end
   1.393 -
   1.394 -    local hash = moonhash.sha3_512(data)
   1.395 -
   1.396 -    local file = File:new_selector()
   1.397 -      :add_where{ "hash = ?", hash }
   1.398 -      :add_where{ "content_type = ?", "image/jpeg" }
   1.399 -      :optional_object_mode()
   1.400 -      :exec()
   1.401 -
   1.402 -    if not file then
   1.403 -      file = File:new()
   1.404 -      file.content_type = "image/jpeg"
   1.405 -      file.hash = hash
   1.406 -      file.data = data
   1.407 -      file.preview_content_type = "image/jpeg"
   1.408 -      file.preview_data = data_preview
   1.409 -      file:save()
   1.410 -    end
   1.411 -
   1.412 -    local draft_attachment = DraftAttachment:new()
   1.413 -    draft_attachment.draft_id = draft_id
   1.414 -    draft_attachment.file_id = file.id
   1.415 -    draft_attachment.title = file_upload.title
   1.416 -    draft_attachment.description = file_upload.description
   1.417 -    draft_attachment:save()
   1.418 -  end
   1.419  end
   1.420  
   1.421 -return draft_id and true or false
   1.422 +print(new_initiative, status)
   1.423 +if new_initiative and status ~= false then
   1.424 +  request.redirect{
   1.425 +    module = "initiative",
   1.426 +    view = "show",
   1.427 +    id = new_initiative.id
   1.428 +  }
   1.429 +end
   1.430 +
   1.431 +return status
   1.432 +

Impressum / About Us