diff --git a/SQL/migrate/V016__poll_update.sql b/SQL/migrate/V016__poll_update.sql new file mode 100644 index 00000000000..2f75fc584cf --- /dev/null +++ b/SQL/migrate/V016__poll_update.sql @@ -0,0 +1,7 @@ +-- +-- Implemented in PR #4429 +-- + +ALTER TABLE `ss13_poll_question` + ADD COLUMN `createdby_ckey` VARCHAR(50) NULL DEFAULT NULL AFTER `adminonly`, + ADD COLUMN `createdby_ip` VARCHAR(50) NULL DEFAULT NULL AFTER `createdby_ckey`; diff --git a/aurorastation.dme b/aurorastation.dme index 7d7e1bf3b53..503864878fd 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1013,6 +1013,7 @@ #include "code\modules\admin\banjob.dm" #include "code\modules\admin\create_mob.dm" #include "code\modules\admin\create_object.dm" +#include "code\modules\admin\create_poll.dm" #include "code\modules\admin\create_turf.dm" #include "code\modules\admin\holder2.dm" #include "code\modules\admin\ipintel.dm" diff --git a/code/datums/api.dm b/code/datums/api.dm index e0a5b7d1af5..8c86dca5ebc 100644 --- a/code/datums/api.dm +++ b/code/datums/api.dm @@ -147,7 +147,7 @@ proc/api_update_command_database() var/versionstring = null //The Version Number follows SemVer http://semver.org/ version["major"] = 2 //Major Version Number --> Increment when implementing breaking changes - version["minor"] = 1 //Minor Version Number --> Increment when adding features + version["minor"] = 2 //Minor Version Number --> Increment when adding features version["patch"] = 0 //Patchlevel --> Increment when fixing bugs versionstring = "[version["major"]].[version["minor"]].[version["patch"]]" @@ -1030,7 +1030,7 @@ proc/api_update_command_database() ) /datum/topic_command/cargo_reload/run_command(queryparams) - var/force = sanitize(queryparams["force"]) + var/force = text2num(queryparams["force"]) if(!SScargo.get_order_count()) SScargo.load_from_sql() message_admins("Cargo has been reloaded via the API.") @@ -1046,3 +1046,156 @@ proc/api_update_command_database() statuscode = 500 response = "Orders have been placed. Use force parameter to overwrite." return 1 + +//Gets a overview of all polls (title, id, type) +/datum/topic_command/get_polls + name = "get_polls" + description = "Gets a overview of all polls." + params = list( + "current_only" = list("name"="current_only","desc"="Only get information about the current polls","type"="int","req"=0), + "admin_only" = list("name"="admin_only","desc"="Only get information about the admin_only polls","type"="int","req"=0) + ) + +/datum/topic_command/get_polls/run_command(queryparams) + var/current_only = text2num(queryparams["current_only"]) + var/admin_only = text2num(queryparams["admin_only"]) + + if(!establish_db_connection(dbcon)) + statuscode = 500 + response = "DB-Connection unavailable" + return 1 + + var/list/polldata = list() + + var/DBQuery/select_query = dbcon.NewQuery("SELECT id, polltype, starttime, endtime, question, multiplechoiceoptions, adminonly FROM ss13_poll_question [(current_only || admin_only) ? "WHERE" : ""] [(admin_only ? "adminonly = true " : "")][(current_only && admin_only ? "AND " : "")][(current_only ? "Now() BETWEEN starttime AND endtime" : "")]") + select_query.Execute() + while(select_query.NextRow()) + polldata["[select_query.item[1]]"] = list( + "id"=select_query.item[1], + "polltype"=select_query.item[2], + "starttime"=select_query.item[3], + "endtime"=select_query.item[4], + "question"=select_query.item[5], + "multiplechoiceoptions"=select_query.item[6], + "adminonly"=select_query.item[7] + ) + + statuscode = 200 + response = "Polldata sent" + data = polldata + return 1 + + +// Gets infos about a poll +/datum/topic_command/get_poll_info + name = "get_poll_info" + description = "Gets Information about a poll." + params = list( + "poll_id" = list("name"="poll_id","desc"="The poll id that should be queried","type"="int","req"=1) + ) + +/datum/topic_command/get_poll_info/run_command(queryparams) + var/poll_id = text2num(queryparams["poll_id"]) + + if(!establish_db_connection(dbcon)) + statuscode = 500 + response = "DB-Connection unavailable" + return 1 + + //Get general data about the poll + var/DBQuery/select_query = dbcon.NewQuery("SELECT id, polltype, starttime, endtime, question, multiplechoiceoptions, adminonly FROM ss13_poll_question WHERE id = :poll_id:") + select_query.Execute(list("poll_id"=poll_id)) + + //Check if the poll exists + if(!select_query.NextRow()) + statuscode = 404 + response = "The requested poll does not exist" + data = null + return 1 + var/list/poll_data = list( + "id"=select_query.item[1], + "polltype"=select_query.item[2], + "starttime"=select_query.item[3], + "endtime"=select_query.item[4], + "question"=select_query.item[5], + "multiplechoiceoptions"=select_query.item[6], + "adminonly"=select_query.item[7] + ) + + var/list/result_data = list() + + /** Return different data based on the poll type: */ + //If we have a option or a multiple choice poll, return the number of options + if(poll_data["polltype"] == "OPTION" || poll_data["polltype"] == "MULTICHOICE") + var/DBQuery/result_query = dbcon.NewQuery({"SELECT ss13_poll_vote.optionid, ss13_poll_option.text, COUNT(*) as option_count + FROM ss13_poll_vote + LEFT JOIN ss13_poll_option ON ss13_poll_vote.optionid = ss13_poll_option.id + WHERE ss13_poll_vote.pollid = :poll_id: + GROUP BY ss13_poll_vote.optionid"}) + result_query.Execute(list("poll_id"=poll_id)) + + while(result_query.NextRow()) + result_data["[result_query.item[1]]"] = list( + "option_id"=result_query.item[1], + "option_question"=result_query.item[2], + "option_count"=result_query.item[3] + ) + if(!length(result_data)) + statuscode = 500 + response = "No data returned by result query." + data = null + return 1 + + //If we have a numval poll, return the options with the min, max, and average + else if(poll_data["polltype"] == "NUMVAL") + var/DBQuery/result_query = dbcon.NewQuery({"SELECT ss13_poll_vote.optionid, ss13_poll_option.text, ss13_poll_option.minval, ss13_poll_option.maxval, ss13_poll_option.descmin, ss13_poll_option.descmid, ss13_poll_option.descmax, AVG(rating) as option_rating_avg, MIN(rating) as option_rating_min, MAX(rating) as option_rating_max + FROM ss13_poll_vote + LEFT JOIN ss13_poll_option ON ss13_poll_vote.optionid = ss13_poll_option.id + WHERE ss13_poll_vote.pollid = :poll_id: + GROUP BY ss13_poll_vote.optionid"}) + result_query.Execute(list("poll_id"=poll_id)) + while(result_query.NextRow()) + result_data["[result_query.item[1]]"] = list( + "option_id"=result_query.item[1], + "option_question"=result_query.item[2], + "option_minval"=result_query.item[3], + "option_maxval"=result_query.item[4], + "option_descmin"=result_query.item[5], + "option_descmid"=result_query.item[6], + "option_descmax"=result_query.item[7], + "option_rating_min"=result_query.item[8], + "option_rating_max"=result_query.item[9], + "option_rating_avg"=result_query.item[10] //TODO: Expand that with MEDIAN once we upgrade mariadb + ) + if(!length(result_data)) + statuscode = 500 + response = "No data returned by result query." + data = null + return 1 + + //If we have a textpoll, return the number of answers + else if(poll_data["polltype"] == "TEXT") + var/DBQuery/result_query = dbcon.NewQuery({"SELECT COUNT(*) as count FROM ss13_poll_textreply WHERE pollid = :poll_id:"}) + result_query.Execute(list("poll_id"=poll_id)) + if(result_query.NextRow()) + result_data = list( + "response_count"=result_query.item[1] + ) + else + statuscode = 500 + response = "No data returned by result query." + data = null + return 1 + else + statuscode = 500 + response = "Unknown Poll Type" + data = poll_data + return 1 + + + poll_data["results"] = result_data + + statuscode = 200 + response = "Poll data fetched" + data = poll_data + return 1 \ No newline at end of file diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 7f1d2241f4f..05a9f1ff36c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -99,7 +99,8 @@ var/list/admin_verbs_admin = list( /client/proc/clear_toxins, /client/proc/wipe_ai, // allow admins to force-wipe AIs /client/proc/fix_player_list, - /client/proc/reset_openturf + /client/proc/reset_openturf, + /client/proc/create_poll //Allows to create polls ) var/list/admin_verbs_ban = list( /client/proc/unban_panel, diff --git a/code/modules/admin/create_poll.dm b/code/modules/admin/create_poll.dm new file mode 100644 index 00000000000..c379338fc6e --- /dev/null +++ b/code/modules/admin/create_poll.dm @@ -0,0 +1,126 @@ +/client/proc/create_poll() + set category = "Special Verbs" + set name = "Create Poll" + + if(!check_rights(R_ADMIN|R_DEV)) + return + if(!establish_db_connection(dbcon)) + to_chat(src,"Failed to establish database connection.") + return + var/polltype = input("Choose poll type.","Poll Type") in list("Single Option","Text Reply","Rating","Multiple Choice") + var/choice_amount = 0 + switch(polltype) + if("Single Option") + polltype = "OPTION" + if("Text Reply") + polltype = "TEXT" + if("Rating") + polltype = "NUMVAL" + if("Multiple Choice") + polltype = "MULTICHOICE" + choice_amount = input("How many choices should be allowed?","Select choice amount") as num + var/starttime = SQLtime() + var/endtime = input("Set end time for poll as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than starting time for obvious reasons.", "Set end time", SQLtime()) as text|null + if(!endtime) + return + endtime = sanitizeSQL(endtime) + var/DBQuery/query_validate_time = dbcon.NewQuery("SELECT STR_TO_DATE('[endtime]','%Y-%c-%d %T')") + if(!query_validate_time.Execute()) + var/err = query_validate_time.ErrorMsg() + to_chat(src, "SQL ERROR validating endtime. Error : \[[err]\]\n") + return + if(query_validate_time.NextRow()) + endtime = query_validate_time.item[1] + if(!endtime) + to_chat(src, "Datetime entered is invalid.") + return + var/DBQuery/query_time_later = dbcon.NewQuery("SELECT DATE('[endtime]') < NOW()") + if(!query_time_later.Execute()) + var/err = query_time_later.ErrorMsg() + to_chat(src, "SQL ERROR comparing endtime to NOW(). Error : \[[err]\]\n") + return + if(query_time_later.NextRow()) + var/checklate = text2num(query_time_later.item[1]) + if(checklate) + to_chat(src, "Datetime entered is not later than current server time.") + return + var/adminonly + switch(alert("Admin only poll?",,"Yes","No","Cancel")) + if("Yes") + adminonly = 1 + if("No") + adminonly = 0 + else + return + var/sql_ckey = sanitizeSQL(ckey) + var/question = input("Write your question","Question") as message + if(!question) + return + question = sanitizeSQL(question) + var/DBQuery/query_polladd_question = dbcon.NewQuery("INSERT INTO ss13_poll_question (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', '[address]')") + if(!query_polladd_question.Execute()) + var/err = query_polladd_question.ErrorMsg() + to_chat(src,"SQL ERROR adding new poll question to table. Error : \[[err]\]\n") + return + var/pollid = 0 + var/DBQuery/query_get_id = dbcon.NewQuery("SELECT id FROM ss13_poll_question WHERE question = '[question]' AND starttime = '[starttime]' AND endtime = '[endtime]' AND createdby_ckey = '[sql_ckey]' AND createdby_ip = '[address]'") + if(!query_get_id.Execute()) + var/err = query_get_id.ErrorMsg() + to_chat(src,"SQL ERROR obtaining id from poll_question table. Error : \[[err]\]\n") + return + if(query_get_id.NextRow()) + pollid = query_get_id.item[1] + + log_admin("[key_name(src)] created the poll with id [pollid].") + message_admins("[key_name_admin(src)] created the poll with id [pollid].") + + var/add_option = 1 + if(polltype == "TEXT") + add_option = 0 + while(add_option) + var/option = input("Write your option","Option") as message + if(!option) + return + option = sanitizeSQL(option) + var/percentagecalc + switch(alert("Calculate option results as percentage?",,"Yes","No","Cancel")) + if("Yes") + percentagecalc = 1 + if("No") + percentagecalc = 0 + else + return + var/minval = 0 + var/maxval = 0 + var/descmin = "" + var/descmid = "" + var/descmax = "" + if(polltype == "NUMVAL") + minval = input("Set minimum rating value.","Minimum rating") as num + if(!minval) + return + maxval = input("Set maximum rating value.","Maximum rating") as num + if(!maxval) + return + if(minval >= maxval) + to_chat(src, "Minimum rating value can't be more than maximum rating value") + return + descmin = input("Optional: Set description for minimum rating","Minimum rating description") as message + if(descmin) + descmin = sanitizeSQL(descmin) + descmid = input("Optional: Set description for median rating","Median rating description") as message + if(descmid) + descmid = sanitizeSQL(descmid) + descmax = input("Optional: Set description for maximum rating","Maximum rating description") as message + if(descmax) + descmax = sanitizeSQL(descmax) + var/DBQuery/query_polladd_option = dbcon.NewQuery("INSERT INTO ss13_poll_option (pollid, text, percentagecalc, minval, maxval, descmin, descmid, descmax) VALUES ('[pollid]', '[option]', '[percentagecalc]', '[minval]', '[maxval]', '[descmin]', '[descmid]', '[descmax]')") + if(!query_polladd_option.Execute()) + var/err = query_polladd_option.ErrorMsg() + to_chat(src, "SQL ERROR adding new poll option to table. Error : \[[err]\]\n") + return + switch(alert(" ",,"Add option","Finish")) + if("Add option") + add_option = 1 + if("Finish") + add_option = 0 \ No newline at end of file