You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

772 lines
40 KiB

  1. #define MAX_ADMINBANS_PER_ADMIN 1
  2. #define MAX_ADMINBANS_PER_HEADMIN 3
  3. //checks client ban cache or DB ban table if ckey is banned from one or more roles
  4. //doesn't return any details, use only for if statements
  5. /proc/is_banned_from(player_ckey, roles)
  6. if(!player_ckey)
  7. return
  8. var/client/C = GLOB.directory[player_ckey]
  9. if(C)
  10. if(!C.ban_cache)
  11. build_ban_cache(C)
  12. if(islist(roles))
  13. for(var/R in roles)
  14. if(R in C.ban_cache)
  15. return TRUE //they're banned from at least one role, no need to keep checking
  16. else if(roles in C.ban_cache)
  17. return TRUE
  18. else
  19. player_ckey = sanitizeSQL(player_ckey)
  20. var/admin_where
  21. if(GLOB.admin_datums[player_ckey] || GLOB.deadmins[player_ckey])
  22. admin_where = " AND applies_to_admins = 1"
  23. var/sql_roles
  24. if(islist(roles))
  25. sql_roles = jointext(roles, "', '")
  26. else
  27. sql_roles = roles
  28. sql_roles = sanitizeSQL(sql_roles)
  29. var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("ban")] WHERE ckey = '[player_ckey]' AND role IN ('[sql_roles]') AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())[admin_where]")
  30. if(!query_check_ban.warn_execute())
  31. qdel(query_check_ban)
  32. return
  33. if(query_check_ban.NextRow())
  34. qdel(query_check_ban)
  35. return TRUE
  36. qdel(query_check_ban)
  37. //checks DB ban table if a ckey, ip and/or cid is banned from a specific role
  38. //returns an associative nested list of each matching row's ban id, bantime, ban round id, expiration time, ban duration, applies to admins, reason, key, ip, cid and banning admin's key in that order
  39. /proc/is_banned_from_with_details(player_ckey, player_ip, player_cid, role)
  40. if(!player_ckey && !player_ip && !player_cid)
  41. return
  42. role = sanitizeSQL(role)
  43. var/list/where_list = list()
  44. if(player_ckey)
  45. player_ckey = sanitizeSQL(player_ckey)
  46. where_list += "ckey = '[player_ckey]'"
  47. if(player_ip)
  48. player_ip = sanitizeSQL(player_ip)
  49. where_list += "ip = INET_ATON('[player_ip]')"
  50. if(player_cid)
  51. player_cid = sanitizeSQL(player_cid)
  52. where_list += "computerid = '[player_cid]'"
  53. var/where = "([where_list.Join(" OR ")])"
  54. var/datum/DBQuery/query_check_ban = SSdbcore.NewQuery("SELECT id, bantime, round_id, expiration_time, TIMESTAMPDIFF(MINUTE, bantime, expiration_time), applies_to_admins, reason, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), INET_NTOA(ip), computerid, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey) FROM [format_table_name("ban")] WHERE role = '[role]' AND [where] AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW()) ORDER BY bantime DESC")
  55. if(!query_check_ban.warn_execute())
  56. qdel(query_check_ban)
  57. return
  58. . = list()
  59. while(query_check_ban.NextRow())
  60. . += list(list("id" = query_check_ban.item[1], "bantime" = query_check_ban.item[2], "round_id" = query_check_ban.item[3], "expiration_time" = query_check_ban.item[4], "duration" = query_check_ban.item[5], "applies_to_admins" = query_check_ban.item[6], "reason" = query_check_ban.item[7], "key" = query_check_ban.item[8], "ip" = query_check_ban.item[9], "computerid" = query_check_ban.item[10], "admin_key" = query_check_ban.item[11]))
  61. qdel(query_check_ban)
  62. /proc/build_ban_cache(client/C)
  63. if(!SSdbcore.Connect())
  64. return
  65. if(C && istype(C))
  66. C.ban_cache = list()
  67. var/player_key = sanitizeSQL(C.ckey)
  68. var/is_admin = FALSE
  69. if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey])
  70. is_admin = TRUE
  71. var/datum/DBQuery/query_build_ban_cache = SSdbcore.NewQuery("SELECT role, applies_to_admins FROM [format_table_name("ban")] WHERE ckey = '[player_key]' AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())")
  72. if(!query_build_ban_cache.warn_execute())
  73. qdel(query_build_ban_cache)
  74. return
  75. while(query_build_ban_cache.NextRow())
  76. if(is_admin && !text2num(query_build_ban_cache.item[2]))
  77. continue
  78. C.ban_cache[query_build_ban_cache.item[1]] = TRUE
  79. qdel(query_build_ban_cache)
  80. /datum/admins/proc/ban_panel(player_key, player_ip, player_cid, role, duration = 1440, applies_to_admins, reason, edit_id, page, admin_key)
  81. var/panel_height = 620
  82. if(edit_id)
  83. panel_height = 240
  84. var/datum/browser/panel = new(usr, "banpanel", "Banning Panel", 910, panel_height)
  85. panel.add_stylesheet("banpanelcss", 'html/admin/banpanel.css')
  86. if(usr.client.prefs.tgui_fancy) //some browsers (IE8) have trouble with unsupported css3 elements and DOM methods that break the panel's functionality, so we won't load those if a user is in no frills tgui mode since that's for similar compatability support
  87. panel.add_stylesheet("banpanelcss3", 'html/admin/banpanel_css3.css')
  88. panel.add_script("banpaneljs", 'html/admin/banpanel.js')
  89. var/list/output = list("<form method='get' action='?src=[REF(src)]'>[HrefTokenFormField()]")
  90. output += {"<input type='hidden' name='src' value='[REF(src)]'>
  91. <label class='inputlabel checkbox'>Key:
  92. <input type='checkbox' id='keycheck' name='keycheck' value='1'[player_key ? " checked": ""]>
  93. <div class='inputbox'></div></label>
  94. <input type='text' name='keytext' size='26' value='[player_key]'>
  95. <label class='inputlabel checkbox'>IP:
  96. <input type='checkbox' id='ipcheck' name='ipcheck' value='1'[isnull(duration) ? " checked" : ""]>
  97. <div class='inputbox'></div></label>
  98. <input type='text' name='iptext' size='18' value='[player_ip]'>
  99. <label class='inputlabel checkbox'>CID:
  100. <input type='checkbox' id='cidcheck' name='cidcheck' value='1' checked>
  101. <div class='inputbox'></div></label>
  102. <input type='text' name='cidtext' size='14' value='[player_cid]'>
  103. <br>
  104. <label class='inputlabel checkbox'>Use IP and CID from last connection of key
  105. <input type='checkbox' id='lastconn' name='lastconn' value='1' [(isnull(duration) && !player_ip) || (!player_cid) ? " checked": ""]>
  106. <div class='inputbox'></div></label>
  107. <label class='inputlabel checkbox'>Applies to Admins
  108. <input type='checkbox' id='applyadmins' name='applyadmins' value='1'[applies_to_admins ? " checked": ""]>
  109. <div class='inputbox'></div></label>
  110. <input type='submit' value='Submit'>
  111. <br>
  112. <div class='row'>
  113. <div class='column left'>
  114. Duration type
  115. <br>
  116. <label class='inputlabel radio'>Permanent
  117. <input type='radio' id='permanent' name='radioduration' value='permanent'[isnull(duration) ? " checked" : ""]>
  118. <div class='inputbox'></div></label>
  119. <br>
  120. <label class='inputlabel radio'>Temporary
  121. <input type='radio' id='temporary' name='radioduration' value='temporary'[duration ? " checked" : ""]>
  122. <div class='inputbox'></div></label>
  123. <input type='text' name='duration' size='7' value='[duration]'>
  124. <div class="select">
  125. <select name='intervaltype'>
  126. <option value='SECOND'>Seconds</option>
  127. <option value='MINUTE' selected>Minutes</option>
  128. <option value='HOUR'>Hours</option>
  129. <option value='DAY'>Days</option>
  130. <option value='WEEK'>Weeks</option>
  131. <option value='MONTH'>Months</option>
  132. <option value='YEAR'>Years</option>
  133. </select>
  134. </div>
  135. </div>
  136. <div class='column middle'>
  137. Ban type
  138. <br>
  139. <label class='inputlabel radio'>Server
  140. <input type='radio' id='server' name='radioban' value='server'[role == "Server" ? " checked" : ""][edit_id ? " disabled" : ""]>
  141. <div class='inputbox'></div></label>
  142. <br>
  143. <label class='inputlabel radio'>Role
  144. <input type='radio' id='role' name='radioban' value='role'[role == "Server" ? "" : " checked"][edit_id ? " disabled" : ""]>
  145. <div class='inputbox'></div></label>
  146. </div>
  147. <div class='column right'>
  148. Severity
  149. <br>
  150. <label class='inputlabel radio'>None
  151. <input type='radio' id='none' name='radioseverity' value='none'[edit_id ? " disabled" : ""]>
  152. <div class='inputbox'></div></label>
  153. <label class='inputlabel radio'>Medium
  154. <input type='radio' id='medium' name='radioseverity' value='medium'[edit_id ? " disabled" : ""]>
  155. <div class='inputbox'></div></label>
  156. <br>
  157. <label class='inputlabel radio'>Minor
  158. <input type='radio' id='minor' name='radioseverity' value='minor'[edit_id ? " disabled" : ""]>
  159. <div class='inputbox'></div></label>
  160. <label class='inputlabel radio'>High
  161. <input type='radio' id='high' name='radioseverity' value='high'[edit_id ? " disabled" : ""]>
  162. <div class='inputbox'></div></label>
  163. </div>
  164. <div class='column'>
  165. Reason
  166. <br>
  167. <textarea class='reason' name='reason'>[reason]</textarea>
  168. </div>
  169. </div>
  170. "}
  171. if(edit_id)
  172. output += {"<label class='inputlabel checkbox'>Mirror edits to matching bans
  173. <input type='checkbox' id='mirroredit' name='mirroredit' value='1'>
  174. <div class='inputbox'></div></label>
  175. <input type='hidden' name='editid' value='[edit_id]'>
  176. <input type='hidden' name='oldkey' value='[player_key]'>
  177. <input type='hidden' name='oldip' value='[player_ip]'>
  178. <input type='hidden' name='oldcid' value='[player_cid]'>
  179. <input type='hidden' name='oldapplies' value='[applies_to_admins]'>
  180. <input type='hidden' name='oldduration' value='[duration]'>
  181. <input type='hidden' name='oldreason' value='[reason]'>
  182. <input type='hidden' name='page' value='[page]'>
  183. <input type='hidden' name='adminkey' value='[admin_key]'>
  184. <br>
  185. When ticked, edits here will also affect bans created with matching ckey, IP, CID and time. Use this to edit all role bans which were made at the same time.
  186. "}
  187. else
  188. output += "<input type='hidden' name='roleban_delimiter' value='1'>"
  189. //there's not always a client to use the bancache of so to avoid many individual queries from using is_banned_form we'll build a cache to use here
  190. var/banned_from = list()
  191. if(player_key)
  192. var/player_ckey = sanitizeSQL(ckey(player_key))
  193. var/datum/DBQuery/query_get_banned_roles = SSdbcore.NewQuery("SELECT role FROM [format_table_name("ban")] WHERE ckey = '[player_ckey]' AND role <> 'server' AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())")
  194. if(!query_get_banned_roles.warn_execute())
  195. qdel(query_get_banned_roles)
  196. return
  197. while(query_get_banned_roles.NextRow())
  198. banned_from += query_get_banned_roles.item[1]
  199. qdel(query_get_banned_roles)
  200. var/break_counter = 0
  201. output += "<div class='row'><div class='column'><label class='rolegroup command'><input type='checkbox' name='Command' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_dep\")'" : ""]>Command</label><div class='content'>"
  202. //all heads are listed twice so have a javascript call to toggle both their checkboxes when one is pressed
  203. //for simplicity this also includes the captain even though it doesn't do anything
  204. for(var/job in GLOB.command_positions)
  205. if(break_counter > 0 && (break_counter % 3 == 0))
  206. output += "<br>"
  207. output += {"<label class='inputlabel checkbox'>[job]
  208. <input type='checkbox' id='[job]_com' name='[job]' class='Command' value='1'[usr.client.prefs.tgui_fancy ? " onClick='toggle_head(this, \"_dep\")'" : ""]>
  209. <div class='inputbox[(job in banned_from) ? " banned" : ""]'></div></label>
  210. "}
  211. break_counter++
  212. output += "</div></div>"
  213. //standard departments all have identical handling
  214. var/list/job_lists = list("Security" = GLOB.security_positions,
  215. "Engineering" = GLOB.engineering_positions,
  216. "Medical" = GLOB.medical_positions,
  217. "Science" = GLOB.science_positions,
  218. "Supply" = GLOB.supply_positions)
  219. for(var/department in job_lists)
  220. //the first element is the department head so they need the same javascript call as above
  221. output += "<div class='column'><label class='rolegroup [ckey(department)]'><input type='checkbox' name='[department]' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_com\")'" : ""]>[department]</label><div class='content'>"
  222. output += {"<label class='inputlabel checkbox'>[job_lists[department][1]]
  223. <input type='checkbox' id='[job_lists[department][1]]_dep' name='[job_lists[department][1]]' class='[department]' value='1'[usr.client.prefs.tgui_fancy ? " onClick='toggle_head(this, \"_com\")'" : ""]>
  224. <div class='inputbox[(job_lists[department][1] in banned_from) ? " banned" : ""]'></div></label>
  225. "}
  226. break_counter = 1
  227. for(var/job in job_lists[department] - job_lists[department][1]) //skip the first element since it's already been done
  228. if(break_counter % 3 == 0)
  229. output += "<br>"
  230. output += {"<label class='inputlabel checkbox'>[job]
  231. <input type='checkbox' name='[job]' class='[department]' value='1'>
  232. <div class='inputbox[(job in banned_from) ? " banned" : ""]'></div></label>
  233. "}
  234. break_counter++
  235. output += "</div></div>"
  236. //departments/groups that don't have command staff would throw a javascript error since there's no corresponding reference for toggle_head()
  237. var/list/headless_job_lists = list("Silicon" = GLOB.nonhuman_positions,
  238. "Abstract" = list("Appearance", "Emote", "Deadchat", "OOC"))
  239. for(var/department in headless_job_lists)
  240. output += "<div class='column'><label class='rolegroup [ckey(department)]'><input type='checkbox' name='[department]' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_com\")'" : ""]>[department]</label><div class='content'>"
  241. break_counter = 0
  242. for(var/job in headless_job_lists[department])
  243. if(break_counter > 0 && (break_counter % 3 == 0))
  244. output += "<br>"
  245. output += {"<label class='inputlabel checkbox'>[job]
  246. <input type='checkbox' name='[job]' class='[department]' value='1'>
  247. <div class='inputbox[(job in banned_from) ? " banned" : ""]'></div></label>
  248. "}
  249. break_counter++
  250. output += "</div></div>"
  251. var/list/long_job_lists = list("Service" = GLOB.service_positions,
  252. "Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
  253. "Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
  254. ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
  255. ROLE_DEVIL, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
  256. ROLE_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
  257. ROLE_OVERTHROW, ROLE_REV, ROLE_REVENANT,
  258. ROLE_REV_HEAD, ROLE_SYNDICATE,
  259. ROLE_TRAITOR, ROLE_WIZARD, ROLE_HIVE)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV
  260. for(var/department in long_job_lists)
  261. output += "<div class='column'><label class='rolegroup long [ckey(department)]'><input type='checkbox' name='[department]' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_com\")'" : ""]>[department]</label><div class='content'>"
  262. break_counter = 0
  263. for(var/job in long_job_lists[department])
  264. if(break_counter > 0 && (break_counter % 10 == 0))
  265. output += "<br>"
  266. output += {"<label class='inputlabel checkbox'>[job]
  267. <input type='checkbox' name='[job]' class='[department]' value='1'>
  268. <div class='inputbox[(job in banned_from) ? " banned" : ""]'></div></label>
  269. "}
  270. break_counter++
  271. output += "</div></div>"
  272. output += "</div>"
  273. output += "</form>"
  274. panel.set_content(jointext(output, ""))
  275. panel.open()
  276. /datum/admins/proc/ban_parse_href(list/href_list)
  277. if(!check_rights(R_BAN))
  278. return
  279. if(!SSdbcore.Connect())
  280. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  281. return
  282. var/list/error_state = list()
  283. var/player_key
  284. var/ip_check = FALSE
  285. var/player_ip
  286. var/cid_check = FALSE
  287. var/player_cid
  288. var/use_last_connection = FALSE
  289. var/applies_to_admins = FALSE
  290. var/duration
  291. var/interval
  292. var/severity
  293. var/reason
  294. var/mirror_edit
  295. var/edit_id
  296. var/old_key
  297. var/old_ip
  298. var/old_cid
  299. var/old_applies
  300. var/page
  301. var/admin_key
  302. var/list/changes = list()
  303. var/list/roles_to_ban = list()
  304. if(href_list["keycheck"])
  305. player_key = href_list["keytext"]
  306. if(!player_key)
  307. error_state += "Key was ticked but none was provided."
  308. if(href_list["ipcheck"])
  309. ip_check = TRUE
  310. if(href_list["cidcheck"])
  311. cid_check = TRUE
  312. if(href_list["lastconn"])
  313. if(player_key)
  314. use_last_connection = TRUE
  315. else
  316. if(ip_check)
  317. player_ip = href_list["iptext"]
  318. if(!player_ip && !use_last_connection)
  319. error_state += "IP was ticked but none was provided."
  320. if(cid_check)
  321. player_cid = href_list["cidtext"]
  322. if(!player_cid && !use_last_connection)
  323. error_state += "CID was ticked but none was provided."
  324. if(!use_last_connection && !player_ip && !player_cid && !player_key)
  325. error_state += "At least a key, IP or CID must be provided."
  326. if(use_last_connection && !ip_check && !cid_check)
  327. error_state += "Use last connection was ticked, but neither IP nor CID was."
  328. if(href_list["applyadmins"])
  329. applies_to_admins = TRUE
  330. switch(href_list["radioduration"])
  331. if("permanent")
  332. duration = null
  333. if("temporary")
  334. duration = href_list["duration"]
  335. interval = href_list["intervaltype"]
  336. if(!duration)
  337. error_state += "Temporary ban was selected but no duration was provided."
  338. else
  339. error_state += "No duration was selected."
  340. reason = href_list["reason"]
  341. if(!reason)
  342. error_state += "No reason was provided."
  343. if(href_list["editid"])
  344. edit_id = href_list["editid"]
  345. if(href_list["mirroredit"])
  346. mirror_edit = TRUE
  347. old_key = href_list["oldkey"]
  348. old_ip = href_list["oldip"]
  349. old_cid = href_list["oldcid"]
  350. page = href_list["page"]
  351. admin_key = href_list["adminkey"]
  352. if(player_key != old_key)
  353. changes += list("Key" = "[old_key] to [player_key]")
  354. if(player_ip != old_ip)
  355. changes += list("IP" = "[old_ip] to [player_ip]")
  356. if(player_cid != old_cid)
  357. changes += list("CID" = "[old_cid] to [player_cid]")
  358. old_applies = text2num(href_list["oldapplies"])
  359. if(applies_to_admins != old_applies)
  360. changes += list("Applies to admins" = "[old_applies] to [applies_to_admins]")
  361. if(duration != href_list["oldduration"])
  362. changes += list("Duration" = "[href_list["oldduration"]] MINUTE to [duration] [interval]")
  363. if(reason != href_list["oldreason"])
  364. changes += list("Reason" = "[href_list["oldreason"]]<br>to<br>[reason]")
  365. if(!changes.len)
  366. error_state += "No changes were detected."
  367. else
  368. severity = href_list["radioseverity"]
  369. if(!severity)
  370. error_state += "No severity was selected."
  371. switch(href_list["radioban"])
  372. if("server")
  373. roles_to_ban += "Server"
  374. if("role")
  375. href_list.Remove("Command", "Security", "Engineering", "Medical", "Science", "Supply", "Silicon", "Abstract", "Service", "Ghost and Other Roles", "Antagonist Positions") //remove the role banner hidden input values
  376. if(href_list[href_list.len] == "roleban_delimiter")
  377. error_state += "Role ban was selected but no roles to ban were selected."
  378. else
  379. var/delimiter_pos = href_list.Find("roleban_delimiter")
  380. href_list.Cut(1, delimiter_pos+1)//remove every list element before and including roleban_delimiter so we have a list of only the roles to ban
  381. for(var/key in href_list) //flatten into a list of only unique keys
  382. roles_to_ban |= key
  383. else
  384. error_state += "No ban type was selected."
  385. if(error_state.len)
  386. to_chat(usr, "<span class='danger'>Ban not [edit_id ? "edited" : "created"] because the following errors were present:\n[error_state.Join("\n")]</span>", confidential = TRUE)
  387. return
  388. if(edit_id)
  389. edit_ban(edit_id, player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, reason, mirror_edit, old_key, old_ip, old_cid, old_applies, page, admin_key, changes)
  390. else
  391. create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, roles_to_ban)
  392. /datum/admins/proc/create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, list/roles_to_ban)
  393. if(!check_rights(R_BAN))
  394. return
  395. if(!SSdbcore.Connect())
  396. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  397. return
  398. var/player_ckey = sanitizeSQL(ckey(player_key))
  399. player_ip = sanitizeSQL(player_ip)
  400. player_cid = sanitizeSQL(player_cid)
  401. if(player_ckey)
  402. var/datum/DBQuery/query_create_ban_get_player = SSdbcore.NewQuery("SELECT byond_key, INET_NTOA(ip), computerid FROM [format_table_name("player")] WHERE ckey = '[player_ckey]'")
  403. if(!query_create_ban_get_player.warn_execute())
  404. qdel(query_create_ban_get_player)
  405. return
  406. if(query_create_ban_get_player.NextRow())
  407. player_key = query_create_ban_get_player.item[1]
  408. if(use_last_connection)
  409. if(ip_check)
  410. player_ip = query_create_ban_get_player.item[2]
  411. if(cid_check)
  412. player_cid = query_create_ban_get_player.item[3]
  413. else
  414. if(use_last_connection)
  415. if(alert(usr, "[player_key]/([player_ckey]) has not been seen before, unable to use IP and CID from last connection. Are you sure you want to create a ban for them?", "Unknown key", "Yes", "No", "Cancel") != "Yes")
  416. qdel(query_create_ban_get_player)
  417. return
  418. else
  419. if(alert(usr, "[player_key]/([player_ckey]) has not been seen before, are you sure you want to create a ban for them?", "Unknown key", "Yes", "No", "Cancel") != "Yes")
  420. qdel(query_create_ban_get_player)
  421. return
  422. qdel(query_create_ban_get_player)
  423. var/admin_ckey = sanitizeSQL(usr.client.ckey)
  424. if(applies_to_admins)
  425. var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery("SELECT COUNT(DISTINCT bantime) FROM [format_table_name("ban")] WHERE a_ckey = '[admin_ckey]' AND applies_to_admins = 1 AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())")
  426. if(!query_check_adminban_count.warn_execute()) //count distinct bantime to treat rolebans made at the same time as one ban
  427. qdel(query_check_adminban_count)
  428. return
  429. if(query_check_adminban_count.NextRow())
  430. var/adminban_count = text2num(query_check_adminban_count.item[1])
  431. var/max_adminbans = MAX_ADMINBANS_PER_ADMIN
  432. if(R_EVERYTHING && !(R_EVERYTHING & rank.can_edit_rights)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
  433. max_adminbans = MAX_ADMINBANS_PER_HEADMIN
  434. if(adminban_count >= max_adminbans)
  435. to_chat(usr, "<span class='danger'>You've already logged [max_adminbans] admin ban(s) or more. Do not abuse this function!</span>", confidential = TRUE)
  436. qdel(query_check_adminban_count)
  437. return
  438. qdel(query_check_adminban_count)
  439. var/admin_ip = sanitizeSQL(usr.client.address)
  440. var/admin_cid = sanitizeSQL(usr.client.computer_id)
  441. duration = text2num(duration)
  442. if(interval)
  443. interval = sanitizeSQL(interval)
  444. else
  445. interval = "MINUTE"
  446. var/time_message = "[duration] [lowertext(interval)]" //no DisplayTimeText because our duration is of variable interval type
  447. if(duration > 1) //pluralize the interval if necessary
  448. time_message += "s"
  449. var/note_reason = "Banned from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"] [isnull(duration) ? "permanently" : "for [time_message]"] - [reason]"
  450. reason = sanitizeSQL(reason)
  451. var/list/clients_online = GLOB.clients.Copy()
  452. var/list/admins_online = list()
  453. for(var/client/C in clients_online)
  454. if(C.holder) //deadmins aren't included since they wouldn't show up on adminwho
  455. admins_online += C
  456. var/who = clients_online.Join(", ")
  457. var/adminwho = admins_online.Join(", ")
  458. var/kn = key_name(usr)
  459. var/kna = key_name_admin(usr)
  460. var/sql_ban
  461. for(var/role in roles_to_ban)
  462. sql_ban += list(list("bantime" = "NOW()",
  463. "server_ip" = "INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]'))",
  464. "server_port" = sanitizeSQL(world.port),
  465. "round_id" = sanitizeSQL(GLOB.round_id),
  466. "role" = "'[sanitizeSQL(role)]'",
  467. "expiration_time" = "IF('[duration]' LIKE '', NULL, NOW() + INTERVAL [duration ? "[duration]" : "0"] [interval])",
  468. "applies_to_admins" = sanitizeSQL(applies_to_admins),
  469. "reason" = "'[reason]'",
  470. "ckey" = "IF('[player_ckey]' LIKE '', NULL, '[player_ckey]')",
  471. "ip" = "INET_ATON(IF('[player_ip]' LIKE '', NULL, '[player_ip]'))",
  472. "computerid" = "IF('[player_cid]' LIKE '', NULL, '[player_cid]')",
  473. "a_ckey" = "'[admin_ckey]'",
  474. "a_ip" = "INET_ATON(IF('[admin_ip]' LIKE '', NULL, '[admin_ip]'))",
  475. "a_computerid" = "'[admin_cid]'",
  476. "who" = "'[who]'",
  477. "adminwho" = "'[adminwho]'"
  478. ))
  479. if(!SSdbcore.MassInsert(format_table_name("ban"), sql_ban, warn = 1))
  480. return
  481. var/target = ban_target_string(player_key, player_ip, player_cid)
  482. var/msg = "has created a [isnull(duration) ? "permanent" : "temporary [time_message]"] [applies_to_admins ? "admin " : ""][roles_to_ban[1] == "Server" ? "server ban" : "role ban from [roles_to_ban.len] roles"] for [target]."
  483. log_admin_private("[kn] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join(", ")]"] Reason: [reason]")
  484. message_admins("[kna] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join("\n")]"]\nReason: [reason]")
  485. if(applies_to_admins)
  486. send2tgs("BAN ALERT","[kn] [msg]")
  487. if(player_ckey)
  488. create_message("note", player_ckey, admin_ckey, note_reason, null, null, 0, 0, null, 0, severity)
  489. var/client/C = GLOB.directory[player_ckey]
  490. var/datum/admin_help/AH = admin_ticket_log(player_ckey, msg)
  491. var/appeal_url = "No ban appeal url set!"
  492. appeal_url = CONFIG_GET(string/banappeals)
  493. var/is_admin = FALSE
  494. if(C)
  495. build_ban_cache(C)
  496. to_chat(C, "<span class='boldannounce'>You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason]</span><br><span class='danger'>This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] The round ID is [GLOB.round_id].</span><br><span class='danger'>To appeal this ban go to [appeal_url]</span>", confidential = TRUE)
  497. if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey])
  498. is_admin = TRUE
  499. if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
  500. qdel(C)
  501. if(roles_to_ban[1] == "Server" && AH)
  502. AH.Resolve()
  503. for(var/client/i in GLOB.clients - C)
  504. if(i.address == player_ip || i.computer_id == player_cid)
  505. build_ban_cache(i)
  506. to_chat(i, "<span class='boldannounce'>You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason]</span><br><span class='danger'>This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] The round ID is [GLOB.round_id].</span><br><span class='danger'>To appeal this ban go to [appeal_url]</span>", confidential = TRUE)
  507. if(GLOB.admin_datums[i.ckey] || GLOB.deadmins[i.ckey])
  508. is_admin = TRUE
  509. if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
  510. qdel(i)
  511. /datum/admins/proc/unban_panel(player_key, admin_key, player_ip, player_cid, page = 0)
  512. if(!check_rights(R_BAN))
  513. return
  514. if(!SSdbcore.Connect())
  515. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  516. return
  517. var/datum/browser/unban_panel = new(usr, "unbanpanel", "Unbanning Panel", 850, 600)
  518. unban_panel.add_stylesheet("unbanpanelcss", 'html/admin/unbanpanel.css')
  519. var/list/output = list("<div class='searchbar'>")
  520. output += {"<form method='get' action='?src=[REF(src)]'>[HrefTokenFormField()]
  521. <input type='hidden' name='src' value='[REF(src)]'>
  522. Key:<input type='text' name='searchunbankey' size='18' value='[player_key]'>
  523. Admin Key:<input type='text' name='searchunbanadminkey' size='18' value='[admin_key]'>
  524. IP:<input type='text' name='searchunbanip' size='12' value='[player_ip]'>
  525. CID:<input type='text' name='searchunbancid' size='10' value='[player_cid]'>
  526. <input type='submit' value='Search'>
  527. </form>
  528. </div>
  529. <div class='main'>
  530. "}
  531. if(player_key || admin_key || player_ip || player_cid)
  532. var/list/searchlist = list()
  533. if(player_key)
  534. searchlist += "ckey = '[sanitizeSQL(ckey(player_key))]'"
  535. if(admin_key)
  536. searchlist += "a_ckey = '[sanitizeSQL(ckey(admin_key))]'"
  537. if(player_ip)
  538. searchlist += "ip = INET_ATON('[sanitizeSQL(player_ip)]')"
  539. if(player_cid)
  540. searchlist += "computerid = '[sanitizeSQL(player_cid)]'"
  541. var/search = searchlist.Join(" AND ")
  542. var/bancount = 0
  543. var/bansperpage = 10
  544. page = text2num(page)
  545. var/datum/DBQuery/query_unban_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]")
  546. if(!query_unban_count_bans.warn_execute())
  547. qdel(query_unban_count_bans)
  548. return
  549. if(query_unban_count_bans.NextRow())
  550. bancount = text2num(query_unban_count_bans.item[1])
  551. qdel(query_unban_count_bans)
  552. if(bancount > bansperpage)
  553. output += "<b>Page: </b>"
  554. var/pagecount = 1
  555. var/list/pagelist = list()
  556. while(bancount > 0)
  557. pagelist += "<a href='?_src_=holder;[HrefToken()];unbanpagecount=[pagecount - 1];unbankey=[player_key];unbanadminkey=[admin_key];unbanip=[player_ip];unbancid=[player_cid]'>[pagecount == page ? "<b>\[[pagecount]\]</b>" : "\[[pagecount]\]"]</a>"
  558. bancount -= bansperpage
  559. pagecount++
  560. output += pagelist.Join(" | ")
  561. var/limit = " LIMIT [bansperpage * page], [bansperpage]"
  562. var/datum/DBQuery/query_unban_search_bans = SSdbcore.NewQuery({"SELECT id, bantime, round_id, role, expiration_time, TIMESTAMPDIFF(MINUTE, bantime, expiration_time), IF(expiration_time < NOW(), 1, NULL), applies_to_admins, reason, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), INET_NTOA(ip), computerid, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), IF(edits IS NOT NULL, 1, NULL), unbanned_datetime, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY id DESC[limit]"})
  563. if(!query_unban_search_bans.warn_execute())
  564. qdel(query_unban_search_bans)
  565. return
  566. while(query_unban_search_bans.NextRow())
  567. var/ban_id = query_unban_search_bans.item[1]
  568. var/ban_datetime = query_unban_search_bans.item[2]
  569. var/ban_round_id = query_unban_search_bans.item[3]
  570. var/role = query_unban_search_bans.item[4]
  571. //make the href for unban here so only the search parameters are passed
  572. var/unban_href = "<a href='?_src_=holder;[HrefToken()];unbanid=[ban_id];unbankey=[player_key];unbanadminkey=[admin_key];unbanip=[player_ip];unbancid=[player_cid];unbanrole=[role];unbanpage=[page]'>Unban</a>"
  573. var/expiration_time = query_unban_search_bans.item[5]
  574. //we don't cast duration as num because if the duration is large enough to be converted to scientific notation by byond then the + character gets lost when passed through href causing SQL to interpret '4.321e 007' as '4'
  575. var/duration = query_unban_search_bans.item[6]
  576. var/expired = query_unban_search_bans.item[7]
  577. var/applies_to_admins = text2num(query_unban_search_bans.item[8])
  578. var/reason = query_unban_search_bans.item[9]
  579. player_key = query_unban_search_bans.item[10]
  580. player_ip = query_unban_search_bans.item[11]
  581. player_cid = query_unban_search_bans.item[12]
  582. admin_key = query_unban_search_bans.item[13]
  583. var/edits = query_unban_search_bans.item[14]
  584. var/unban_datetime = query_unban_search_bans.item[15]
  585. var/unban_key = query_unban_search_bans.item[16]
  586. var/unban_round_id = query_unban_search_bans.item[17]
  587. var/target = ban_target_string(player_key, player_ip, player_cid)
  588. output += "<div class='banbox'><div class='header [unban_datetime ? "unbanned" : "banned"]'><b>[target]</b>[applies_to_admins ? " <b>ADMIN</b>" : ""] banned by <b>[admin_key]</b> from <b>[role]</b> on <b>[ban_datetime]</b> during round <b>#[ban_round_id]</b>.<br>"
  589. if(!expiration_time)
  590. output += "<b>Permanent ban</b>."
  591. else
  592. output += "Duration of <b>[DisplayTimeText(text2num(duration) MINUTES)]</b>, <b>[expired ? "expired" : "expires"]</b> on <b>[expiration_time]</b>."
  593. if(unban_datetime)
  594. output += "<br>Unbanned by <b>[unban_key]</b> on <b>[unban_datetime]</b> during round <b>#[unban_round_id]</b>."
  595. output += "</div><div class='container'><div class='reason'>[reason]</div><div class='edit'>"
  596. if(!expired && !unban_datetime)
  597. output += "<a href='?_src_=holder;[HrefToken()];editbanid=[ban_id];editbankey=[player_key];editbanip=[player_ip];editbancid=[player_cid];editbanrole=[role];editbanduration=[duration];editbanadmins=[applies_to_admins];editbanreason=[url_encode(reason)];editbanpage=[page];editbanadminkey=[admin_key]'>Edit</a><br>[unban_href]"
  598. if(edits)
  599. output += "<br><a href='?_src_=holder;[HrefToken()];unbanlog=[ban_id]'>Edit log</a>"
  600. output += "</div></div></div>"
  601. qdel(query_unban_search_bans)
  602. output += "</div>"
  603. unban_panel.set_content(jointext(output, ""))
  604. unban_panel.open()
  605. /datum/admins/proc/unban(ban_id, player_key, player_ip, player_cid, role, page, admin_key)
  606. if(!check_rights(R_BAN))
  607. return
  608. if(!SSdbcore.Connect())
  609. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  610. return
  611. var/target = ban_target_string(player_key, player_ip, player_cid)
  612. if(alert(usr, "Please confirm unban of [target] from [role].", "Unban confirmation", "Yes", "No") == "No")
  613. return
  614. ban_id = sanitizeSQL(ban_id)
  615. var/admin_ckey = sanitizeSQL(usr.client.ckey)
  616. var/admin_ip = sanitizeSQL(usr.client.address)
  617. var/admin_cid = sanitizeSQL(usr.client.computer_id)
  618. var/kn = key_name(usr)
  619. var/kna = key_name_admin(usr)
  620. var/datum/DBQuery/query_unban = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET unbanned_datetime = NOW(), unbanned_ckey = '[admin_ckey]', unbanned_ip = INET_ATON('[admin_ip]'), unbanned_computerid = '[admin_cid]', unbanned_round_id = '[GLOB.round_id]' WHERE id = [ban_id]")
  621. if(!query_unban.warn_execute())
  622. qdel(query_unban)
  623. return
  624. qdel(query_unban)
  625. log_admin_private("[kn] has unbanned [target] from [role].")
  626. message_admins("[kna] has unbanned [target] from [role].")
  627. var/client/C = GLOB.directory[player_key]
  628. if(C)
  629. build_ban_cache(C)
  630. to_chat(C, "<span class='boldannounce'>[usr.client.key] has removed a ban from [role] for your key.</span>", confidential = TRUE)
  631. for(var/client/i in GLOB.clients - C)
  632. if(i.address == player_ip || i.computer_id == player_cid)
  633. build_ban_cache(i)
  634. to_chat(i, "<span class='boldannounce'>[usr.client.key] has removed a ban from [role] for your IP or CID.</span>", confidential = TRUE)
  635. unban_panel(player_key, admin_key, player_ip, player_cid, page)
  636. /datum/admins/proc/edit_ban(ban_id, player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, reason, mirror_edit, old_key, old_ip, old_cid, old_applies, admin_key, page, list/changes)
  637. if(!check_rights(R_BAN))
  638. return
  639. if(!SSdbcore.Connect())
  640. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  641. return
  642. ban_id = sanitizeSQL(ban_id)
  643. var/player_ckey = sanitizeSQL(ckey(player_key))
  644. player_ip = sanitizeSQL(player_ip)
  645. player_cid = sanitizeSQL(player_cid)
  646. var/bantime
  647. if(player_ckey)
  648. var/datum/DBQuery/query_edit_ban_get_player = SSdbcore.NewQuery("SELECT byond_key, (SELECT bantime FROM [format_table_name("ban")] WHERE id = [ban_id]), ip, computerid FROM [format_table_name("player")] WHERE ckey = '[player_ckey]'")
  649. if(!query_edit_ban_get_player.warn_execute())
  650. qdel(query_edit_ban_get_player)
  651. return
  652. if(query_edit_ban_get_player.NextRow())
  653. player_key = query_edit_ban_get_player.item[1]
  654. bantime = query_edit_ban_get_player.item[2]
  655. if(use_last_connection)
  656. if(ip_check)
  657. player_ip = query_edit_ban_get_player.item[3]
  658. if(cid_check)
  659. player_cid = query_edit_ban_get_player.item[4]
  660. else
  661. if(use_last_connection)
  662. if(alert(usr, "[player_key]/([player_ckey]) has not been seen before, unable to use IP and CID from last connection. Are you sure you want to edit a ban for them?", "Unknown key", "Yes", "No", "Cancel") != "Yes")
  663. qdel(query_edit_ban_get_player)
  664. return
  665. else
  666. if(alert(usr, "[player_key]/([player_ckey]) has not been seen before, are you sure you want to edit a ban for them?", "Unknown key", "Yes", "No", "Cancel") != "Yes")
  667. qdel(query_edit_ban_get_player)
  668. return
  669. qdel(query_edit_ban_get_player)
  670. if(applies_to_admins && (applies_to_admins != old_applies))
  671. var/admin_ckey = sanitizeSQL(usr.client.ckey)
  672. var/datum/DBQuery/query_check_adminban_count = SSdbcore.NewQuery("SELECT COUNT(DISTINCT bantime) FROM [format_table_name("ban")] WHERE a_ckey = '[admin_ckey]' AND applies_to_admins = 1 AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())")
  673. if(!query_check_adminban_count.warn_execute()) //count distinct bantime to treat rolebans made at the same time as one ban
  674. qdel(query_check_adminban_count)
  675. return
  676. if(query_check_adminban_count.NextRow())
  677. var/adminban_count = text2num(query_check_adminban_count.item[1])
  678. var/max_adminbans = MAX_ADMINBANS_PER_ADMIN
  679. if(R_EVERYTHING && !(R_EVERYTHING & rank.can_edit_rights)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
  680. max_adminbans = MAX_ADMINBANS_PER_HEADMIN
  681. if(adminban_count >= max_adminbans)
  682. to_chat(usr, "<span class='danger'>You've already logged [max_adminbans] admin ban(s) or more. Do not abuse this function!</span>", confidential = TRUE)
  683. qdel(query_check_adminban_count)
  684. return
  685. qdel(query_check_adminban_count)
  686. applies_to_admins = sanitizeSQL(applies_to_admins)
  687. duration = sanitizeSQL(duration)
  688. if(interval)
  689. interval = sanitizeSQL(interval)
  690. else
  691. interval = "MINUTE"
  692. reason = sanitizeSQL(reason)
  693. var/kn = key_name(usr)
  694. var/kna = key_name_admin(usr)
  695. var/list/changes_text= list()
  696. var/list/changes_keys = list()
  697. for(var/i in changes)
  698. changes_text += "[sanitizeSQL(i)]: [sanitizeSQL(changes[i])]"
  699. changes_keys += i
  700. var/where = "id = [sanitizeSQL(ban_id)]"
  701. if(text2num(mirror_edit))
  702. var/list/wherelist = list("bantime = '[bantime]'")
  703. if(old_key)
  704. wherelist += "ckey = '[sanitizeSQL(ckey(old_key))]'"
  705. if(old_ip)
  706. old_ip = sanitizeSQL(old_ip)
  707. wherelist += "ip = INET_ATON(IF('[old_ip]' LIKE '', NULL, '[old_ip]'))"
  708. if(old_cid)
  709. wherelist += "computerid = '[sanitizeSQL(old_cid)]'"
  710. where = wherelist.Join(" AND ")
  711. var/datum/DBQuery/query_edit_ban = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET expiration_time = IF('[duration]' LIKE '', NULL, bantime + INTERVAL [duration ? "[duration]" : "0"] [interval]), applies_to_admins = [applies_to_admins], reason = '[reason]', ckey = IF('[player_ckey]' LIKE '', NULL, '[player_ckey]'), ip = INET_ATON(IF('[player_ip]' LIKE '', NULL, '[player_ip]')), computerid = IF('[player_cid]' LIKE '', NULL, '[player_cid]'), edits = CONCAT(IFNULL(edits,''),'[sanitizeSQL(usr.client.key)] edited the following [jointext(changes_text, ", ")]<hr>') WHERE [where]")
  712. if(!query_edit_ban.warn_execute())
  713. qdel(query_edit_ban)
  714. return
  715. qdel(query_edit_ban)
  716. var/changes_keys_text = jointext(changes_keys, ", ")
  717. log_admin_private("[kn] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].") //if a ban doesn't have a key it must have an ip and/or a cid to have reached this point normally
  718. message_admins("[kna] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].")
  719. if(changes["Applies to admins"])
  720. send2tgs("BAN ALERT","[kn] has edited a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"] to [applies_to_admins ? "" : "not"]affect admins")
  721. var/client/C = GLOB.directory[old_key]
  722. if(C)
  723. build_ban_cache(C)
  724. to_chat(C, "<span class='boldannounce'>[usr.client.key] has edited the [changes_keys_text] of a ban for your key.</span>", confidential = TRUE)
  725. for(var/client/i in GLOB.clients - C)
  726. if(i.address == old_ip || i.computer_id == old_cid)
  727. build_ban_cache(i)
  728. to_chat(i, "<span class='boldannounce'>[usr.client.key] has edited the [changes_keys_text] of a ban for your IP or CID.</span>", confidential = TRUE)
  729. unban_panel(player_key, null, null, null, page)
  730. /datum/admins/proc/ban_log(ban_id)
  731. if(!check_rights(R_BAN))
  732. return
  733. if(!SSdbcore.Connect())
  734. to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>", confidential = TRUE)
  735. return
  736. ban_id = sanitizeSQL(ban_id)
  737. var/datum/DBQuery/query_get_ban_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("ban")] WHERE id = '[ban_id]'")
  738. if(!query_get_ban_edits.warn_execute())
  739. qdel(query_get_ban_edits)
  740. return
  741. if(query_get_ban_edits.NextRow())
  742. var/edits = query_get_ban_edits.item[1]
  743. var/datum/browser/edit_log = new(usr, "baneditlog", "Ban edit log")
  744. edit_log.set_content(edits)
  745. edit_log.open()
  746. qdel(query_get_ban_edits)
  747. /datum/admins/proc/ban_target_string(player_key, player_ip, player_cid)
  748. . = list()
  749. if(player_key)
  750. . += player_key
  751. else
  752. if(player_ip)
  753. . += player_ip
  754. else
  755. . += "NULL"
  756. if(player_cid)
  757. . += player_cid
  758. else
  759. . += "NULL"
  760. . = jointext(., "/")