Convert over to JSON from XML for Ajax
jdarwood007

jdarwood007 commited on 2018-07-18 08:33:37
Showing 5 changed files, with 290 additions and 194 deletions.


Signed-off-by: jdarwood007 <unmonitored+github@sleepycode.com>
... ...
@@ -60,6 +60,10 @@
60 60
 	cursor: pointer;
61 61
 }
62 62
 
63
+.stats .shd_assignees_list {
64
+	width: 100%;
65
+}
66
+
63 67
 .information li dt {
64 68
 	color: gray;
65 69
 	font-weight: bold;
... ...
@@ -462,10 +466,10 @@ form#postmodify .shd_ticket_roundframe {
462 466
 }
463 467
 
464 468
 /* Urgency stuff */
465
-.generic_icons.urgency_increase {
469
+.generic_icons.urgency_increase::before {
466 470
 	background-position: -161px -5px;
467 471
 }
468
-.generic_icons.urgency_decrease {
472
+.generic_icons.urgency_decrease::before {
469 473
 	background-position: -213px -5px;
470 474
 }
471 475
 
... ...
@@ -1,5 +1,37 @@
1 1
 /* Javascript for the main Helpdesk */
2 2
 
3
+/* Implant a JSON handler for Ajax */
4
+function shd_getJSONDocument(sUrl, funcCallback, sMethod)
5
+{
6
+	ajax_indicator(true);
7
+
8
+	if (typeof(sMethod) == 'undefined')
9
+		sMethod = 'GET';
10
+
11
+	var oCaller = this;
12
+	var oMyDoc = $.ajax({
13
+		method: sMethod,
14
+		url: sUrl,
15
+		cache: false,
16
+		dataType: 'json',
17
+		success: function(responseJSON) {
18
+			if (typeof(funcCallback) != 'undefined')
19
+			{
20
+				ajax_indicator(false);
21
+
22
+				funcCallback.call(oCaller, responseJSON);
23
+			}
24
+		},
25
+	});
26
+
27
+	return oMyDoc;
28
+}
29
+
30
+function shd_sendJSONDocument(sUrl, funcCallback)
31
+{
32
+	return shd_getJSONDocument(sUrl, funcCallback, 'POST');
33
+}
34
+
3 35
 /* The privacy toggle in AJAX */
4 36
 function shd_privacyControl(oOpts)
5 37
 {
... ...
@@ -19,33 +51,18 @@ shd_privacyControl.prototype.init = function ()
19 51
 
20 52
 shd_privacyControl.prototype.action = function ()
21 53
 {
22
-	ajax_indicator(true);
23
-	getXMLDocument(shd_privacyControl.prototype.opts.sUrl + ';' + shd_privacyControl.prototype.opts.sSession, shd_privacyControl.prototype.callback);
54
+	shd_getJSONDocument(shd_privacyControl.prototype.opts.sUrl + ';' + shd_privacyControl.prototype.opts.sSession, shd_privacyControl.prototype.callback);
24 55
 	return false;
25 56
 }
26 57
 
27 58
 shd_privacyControl.prototype.callback = function (oRecvd)
28 59
 {
29
-	ajax_indicator(false);
30
-	if (oRecvd)
31
-	{
32
-		var errors = oRecvd.getElementsByTagName('error');
33
-		if (errors.length > 0)
34
-		{
35
-			var msg = "";
36
-			for (var i = 0; i < errors.length; i++)
37
-				msg += errors[i].firstChild.nodeValue + "\n";
38
-			alert(msg);
39
-		}
40
-		else
41
-		{
42
-			var msg = oRecvd.getElementsByTagName('message');
43
-			if (msg.length > 0)
60
+	if (oRecvd && oRecvd.success == false)
61
+		alert(oRecvd.error);
62
+	else if (oRecvd && oRecvd.message)
44 63
 	{
45 64
 		var oSpan = document.getElementById(shd_privacyControl.prototype.opts.sDestSpan);
46
-				oSpan.firstChild.nodeValue = msg[0].firstChild.nodeValue;
47
-			}
48
-		}
65
+		oSpan.firstChild.nodeValue = oRecvd.message;
49 66
 	}
50 67
 	else
51 68
 		if (confirm(shd_ajax_problem))
... ...
@@ -91,46 +108,31 @@ shd_urgencyControl.prototype.actionDown = function ()
91 108
 
92 109
 shd_urgencyControl.prototype.action = function (direction)
93 110
 {
94
-	ajax_indicator(true);
95 111
 	shd_urgencyControl.prototype.opts.direction = direction;
96
-	getXMLDocument(shd_urgencyControl.prototype.opts.sUrl + shd_urgencyControl.prototype.opts.aButtonOps[direction] + ';' + shd_urgencyControl.prototype.opts.sSession, shd_urgencyControl.prototype.callback);
112
+	shd_getJSONDocument(shd_urgencyControl.prototype.opts.sUrl + shd_urgencyControl.prototype.opts.aButtonOps[direction] + ';' + shd_urgencyControl.prototype.opts.sSession, shd_urgencyControl.prototype.callback);
97 113
 	return false;
98 114
 }
99 115
 
100 116
 shd_urgencyControl.prototype.callback = function (oRecvd)
101 117
 {
102
-	ajax_indicator(false);
103
-	if (oRecvd)
104
-	{
105
-		var errors = oRecvd.getElementsByTagName('error');
106
-		if (errors.length > 0)
107
-		{
108
-			var msg = "";
109
-			for (var i = 0; i < errors.length; i++)
110
-				msg += errors[i].firstChild.nodeValue + "\n";
111
-			alert(msg);
112
-		}
113
-		else
114
-		{
115
-			var msg = oRecvd.getElementsByTagName('message');
116
-			if (msg.length > 0)
118
+	if (oRecvd && oRecvd.success == false)
119
+		alert(oRecvd.error);
120
+	else if (oRecvd && oRecvd.message)
117 121
 	{
118 122
 		var oSpan = document.getElementById(shd_urgencyControl.prototype.opts.sDestSpan);
119
-				setInnerHTML(oSpan, msg[0].firstChild.nodeValue);
120
-			}
121
-			// Now to reset the buttons
122
-			var btn_set = [ "increase", "decrease" ];
123
+		setInnerHTML(document.getElementById(shd_urgencyControl.prototype.opts.sDestSpan), oRecvd.message);
123 124
 
125
+		var btn_set = [ "increase", "decrease" ];
124 126
 		for (var i in btn_set)
125 127
 		{
126
-				var oBtn = oRecvd.getElementsByTagName(btn_set[i]);
128
+			var oBtn = oRecvd[btn_set[i]];
127 129
 			var oSpan = document.getElementById('urgency_' + btn_set[i]);
128
-				setInnerHTML(oSpan, (oBtn.length !== 0 ? oBtn[0].firstChild.nodeValue : ''));
130
+			setInnerHTML(oSpan, (oBtn ? oBtn : ''));
129 131
 		}
132
+
130 133
 		// Attach JS events to new links
131 134
 		shd_urgencyControl.prototype.init();
132 135
 	}
133
-	}
134 136
 	else
135 137
 		if (confirm(shd_ajax_problem))
136 138
 			window.location = smf_scripturl + '?action=helpdesk;sa=urgencychange;ticket=' + shd_urgencyControl.prototype.opts.ticket + ';change=' + shd_urgencyControl.prototype.opts.aButtonOps[shd_urgencyControl.prototype.opts.direction] + ';' + shd_urgencyControl.prototype.opts.sSession;
... ...
@@ -274,29 +276,13 @@ function QuickReply(oOptions)
274 276
 // When a user presses quote, put it in the quick reply box (if expanded).
275 277
 QuickReply.prototype.quote = function (iMessageId, sSessionId, sSessionVar, bTemplateUpgraded)
276 278
 {
277
-	// Add backwards compatibility with old themes.
278
-	if (sSessionVar === true)
279
-	{
280
-		bTemplateUpgraded = true;
281
-		sSessionVar = 'sesc';
282
-	}
283
-
284 279
 	if (this.bCollapsed)
285 280
 	{
286
-		// This is for compatibility.
287
-		if (bTemplateUpgraded)
288
-			return true;
289
-
290 281
 		window.location.href = smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=reply;quote=' + iMessageId + ';ticket=' + this.opt.iTicketId + '.' + this.opt.iStart + ';' + sSessionVar + '=' + sSessionId;
291 282
 		return false;
292 283
 	}
293 284
 
294
-	// Doing it the XMLhttp way?
295
-	if (window.XMLHttpRequest)
296
-	{
297
-		ajax_indicator(true);
298
-		getXMLDocument(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=quote;quote=' + iMessageId + ';' + sSessionVar + '=' + sSessionId + ';xml' + ';mode=' + (oEditorHandle_shd_message.bRichTextEnabled ? 1 : 0), this.onQuoteReceived);
299
-	}
285
+	shd_getJSONDocument(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=quote;quote=' + iMessageId + ';' + sSessionVar + '=' + sSessionId + ';json' + ';mode=' + (oEditorHandle_shd_message.bRichTextEnabled ? 1 : 0), this.onQuoteReceived);
300 286
 
301 287
 	// Move the view to the quick reply box.
302 288
 	if (navigator.appName == 'Microsoft Internet Explorer')
... ...
@@ -307,17 +293,11 @@ QuickReply.prototype.quote = function (iMessageId, sSessionId, sSessionVar, bTem
307 293
 	return false;
308 294
 }
309 295
 
310
-// This is the callback function used after the XMLhttp request.
311
-QuickReply.prototype.onQuoteReceived = function (oXMLDoc)
296
+// This is the callback function used after the json request.
297
+QuickReply.prototype.onQuoteReceived = function (oRecvd)
312 298
 {
313
-	var sQuoteText = '';
314
-
315
-	for (var i = 0; i < oXMLDoc.getElementsByTagName('quote')[0].childNodes.length; i++)
316
-		sQuoteText += oXMLDoc.getElementsByTagName('quote')[0].childNodes[i].nodeValue;
317
-
318
-	oEditorHandle_shd_message.insertText(sQuoteText, false, true);
319
-
320
-	ajax_indicator(false);
299
+	if (oRecvd && oRecvd.success == true && oRecvd.message)
300
+		oEditorHandle_shd_message.insertText(oRecvd.message, false, true);
321 301
 }
322 302
 
323 303
 // The function handling the swapping of the quick reply.
... ...
@@ -343,27 +323,16 @@ CannedReply.prototype.getReply = function ()
343 323
 	if (!iReplyId || parseInt(iReplyId, 10) < 1)
344 324
 		return false;
345 325
 
346
-	// Doing it the XMLhttp way?
347
-	if (window.XMLHttpRequest)
348
-	{
349
-		ajax_indicator(true);
350
-		getXMLDocument(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=canned;ticket=' + this.opt.iTicketId + ';reply=' + iReplyId + ';' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';xml' + ';mode=' + (oEditorHandle_shd_message.bRichTextEnabled ? 1 : 0), this.onReplyReceived);
351
-	}
326
+	shd_getJSONDocument(smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=canned;ticket=' + this.opt.iTicketId + ';reply=' + iReplyId + ';' + this.opt.sSessionVar + '=' + this.opt.sSessionId + ';json' + ';mode=' + (oEditorHandle_shd_message.bRichTextEnabled ? 1 : 0), this.onReplyReceived);
352 327
 
353 328
 	return false;
354 329
 }
355 330
 
356
-// This is the callback function used after the XMLhttp request.
357
-CannedReply.prototype.onReplyReceived = function (oXMLDoc)
331
+// This is the callback function used after the json request.
332
+CannedReply.prototype.onReplyReceived = function (oRecvd)
358 333
 {
359
-	var sQuoteText = '';
360
-
361
-	for (var i = 0; i < oXMLDoc.getElementsByTagName('quote')[0].childNodes.length; i++)
362
-		sQuoteText += oXMLDoc.getElementsByTagName('quote')[0].childNodes[i].nodeValue;
363
-
364
-	oEditorHandle_shd_message.insertText(sQuoteText, false, true);
365
-
366
-	ajax_indicator(false);
334
+	if (oRecvd && oRecvd.success == true && oRecvd.message)
335
+		oEditorHandle_shd_message.insertText(oRecvd.message, false, true);
367 336
 }
368 337
 
369 338
 // The quick jump function
... ...
@@ -435,36 +404,29 @@ AjaxAssign.prototype.expand = function ()
435 404
 	img.setAttribute('src', this.opt.sImagesUrl + "/" + this.opt.sImageExpanded);
436 405
 
437 406
 	// Fetch the list of items
438
-	ajax_indicator(true);
439
-	getXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign;ticket=' + this.opt.iTicketId + ';' + sSessV + '=' + sSessI, this.expand_callback);
407
+	shd_getJSONDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign;ticket=' + this.opt.iTicketId + ';' + sSessV + '=' + sSessI, this.expand_callback);
440 408
 }
441 409
 
442
-AjaxAssign.prototype.expand_callback = function (XMLDoc)
443
-{
444
-	// Receive the list of assignees
445
-	ajax_indicator(false);
446
-	var errors = XMLDoc.getElementsByTagName('error');
447
-	if (errors.length > 0)
410
+AjaxAssign.prototype.expand_callback = function (oRecvd)
448 411
 {
449
-		alert(errors[0].childNodes[0].nodeValue);
450
-		this.collapse();
451
-	}
452
-	else
412
+	if (oRecvd && oRecvd.success == false)
413
+		alert(oRecvd.error);
414
+	else if (oRecvd && oRecvd.members)
453 415
 	{
454 416
 		var assign_list = document.getElementById(this.opt.sListId);
455 417
 		assign_list.innerHTML = '';
456 418
 		assign_list.setAttribute('style', 'display:block');
457 419
 
458
-		var elements = XMLDoc.getElementsByTagName('member');
459
-		// We could, in all honesty, sit and build the content normally with document.createElement.
460
-		// But really, this is quicker, not just for us but for the browser too.
461 420
 		var newhtml = '';
462
-		for (var i = 0, n = elements.length; i < n; i++)
421
+		for (var i in oRecvd.members)
463 422
 		{
464
-			newhtml += '<li class="shd_assignees" onclick="' + this.opt.sSelf + '.assign(' + elements[i].getAttribute('uid') + ');">';
465
-			if (elements[i].getAttribute('admin'))
466
-				newhtml += '<img src="' + smf_default_theme_url + '/images/simpledesk/' + (elements[i].getAttribute('admin') == 'yes' ? 'admin' : 'staff') + '.png" alt="" class="shd_smallicon"> ';
467
-			newhtml += elements[i].childNodes[0].nodeValue + '</li>';
423
+			cur = oRecvd.members[i];
424
+			newhtml += '<li class="shd_assignees" onclick="' + this.opt.sSelf + '.assign(' + cur.uid + ');">';
425
+
426
+			if (cur.admin)
427
+				newhtml += '<img src="' + smf_default_theme_url + '/images/simpledesk/' + (cur.admin == 'yes' ? 'admin' : 'staff') + '.png" alt="" class="shd_smallicon"> ';
428
+
429
+			newhtml += cur.name + '</li>';
468 430
 		}
469 431
 
470 432
 		assign_list.innerHTML = newhtml;
... ...
@@ -475,23 +437,20 @@ AjaxAssign.prototype.assign = function (uid)
475 437
 {
476 438
 	// Click handler for the assignment list, to issue the assign
477 439
 	ajax_indicator(true);
478
-	getXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign2;ticket=' + this.opt.iTicketId + ';to_user=' + uid + ';' + sSessV + '=' + sSessI, this.assign_callback);
440
+	shd_getJSONDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + 'action=helpdesk;sa=ajax;op=assign2;ticket=' + this.opt.iTicketId + ';to_user=' + uid + ';' + sSessV + '=' + sSessI, this.assign_callback);
479 441
 }
480 442
 
481
-AjaxAssign.prototype.assign_callback = function(XMLDoc)
443
+AjaxAssign.prototype.assign_callback = function(oRecvd)
482 444
 {
483 445
 	// Click handler callback for assignment, to handle once the request has been made
484
-	ajax_indicator(false);
485 446
 	this.collapse();
486
-	var errors = XMLDoc.getElementsByTagName('error');
487
-	if (errors.length > 0)
488
-		alert(errors[0].childNodes[0].nodeValue);
489
-	else
447
+
448
+	if (oRecvd && oRecvd.success == false)
449
+		alert(oRecvd.error);
450
+	else if (oRecvd && oRecvd.assigned)
490 451
 	{
491
-		var elements = XMLDoc.getElementsByTagName('assigned');
492
-		document.getElementById(this.opt.sAssignedSpan).innerHTML = elements[0].childNodes[0].nodeValue;
452
+		document.getElementById(this.opt.sAssignedSpan).innerHTML = oRecvd.assigned;
493 453
 	}
494
-	this.collapse();
495 454
 }
496 455
 
497 456
 AjaxAssign.prototype.collapse = function ()
... ...
@@ -504,3 +463,110 @@ AjaxAssign.prototype.collapse = function ()
504 463
 	var img = document.getElementById('assign_' + this.opt.sSelf);
505 464
 	img.setAttribute('src', this.opt.sImagesUrl + "/" + this.opt.sImageCollapsed);
506 465
 }
466
+
467
+/* Ajax Notifications List */
468
+function shd_notifications(iTicketId, oOptions)
469
+{
470
+	this.ticketId = iTicketId;
471
+	this.opt = oOptions;
472
+	this.init();
473
+	return false;
474
+}
475
+
476
+shd_notifications.prototype.init = function ()
477
+{
478
+	var oLink = document.getElementById(this.opt.sLinkId);
479
+
480
+	oLink.instanceRef = this;
481
+	oLink.onclick = function (oEvent) {return this.instanceRef.receiveNotifications(oEvent);};
482
+
483
+	document.getElementById(this.opt.sContainerId).style.display = "";
484
+}
485
+
486
+shd_notifications.prototype.receiveNotifications = function ()
487
+{
488
+	shd_getJSONDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + "action=helpdesk;sa=ajax;op=notify;ticket=" + this.ticketId + ";" + this.opt.sSessionVar + '=' + this.opt.sSessionId + (this.opt.sPinglist ? ";" + this.opt.sPinglist : ''), this.onReceiveNotifications);
489
+	return false;
490
+}
491
+
492
+shd_notifications.prototype.onReceiveNotifications = function (oRecvd)
493
+{
494
+	if (typeof(oRecvd) == 'undefined')
495
+		return;
496
+
497
+	if (oRecvd && oRecvd.success == false)
498
+		alert(oRecvd.error);
499
+
500
+	var newhtml = '';
501
+	var template = this.opt.oMainTemplate;
502
+	if (oRecvd.being_notified)
503
+	{
504
+		var subtemplate = this.opt.oNotifiedTemplate;
505
+
506
+		var temphtml = '';
507
+		var member = '';
508
+		for (var i in oRecvd.being_notified)
509
+		{
510
+			cur = oRecvd.being_notified[i];
511
+			member = subtemplate.replace('%name%', cur);
512
+
513
+			temphtml += cur;
514
+		}
515
+
516
+		newhtml = template.replace('%title%', oRecvd.being_notified_txt).replace('%subtemplate%', temphtml);
517
+	}
518
+
519
+	if (oRecvd.optional)
520
+	{
521
+		var subtemplate = this.opt.oOptionalTemplate;
522
+
523
+		var temphtml = '';
524
+		var member = '';
525
+		for (var i in oRecvd.optional)
526
+		{
527
+			cur = oRecvd.optional[i];
528
+
529
+			member = subtemplate.replace('%name%', cur);
530
+			member = member.replace('%index%', i);
531
+			member = member.replace('%index%', i);
532
+
533
+			for (var j in oRecvd.selected)
534
+			{
535
+				var k = oRecvd.selected[j];
536
+				member = member.replace('%checked%', i == k ? ' checked' : '');
537
+			}
538
+
539
+			temphtml += member;
540
+		}
541
+
542
+		newhtml += template.replace('%title%', oRecvd.optional_txt).replace('%subtemplate%', temphtml);
543
+	}
544
+
545
+
546
+	if (oRecvd.optional_butoff)
547
+	{
548
+		var subtemplate = this.opt.oOptionalOffTemplate;
549
+
550
+		var temphtml = '';
551
+		var member = '';
552
+		for (var i in oRecvd.optional_butoff)
553
+		{
554
+			cur = oRecvd.optional_butoff[i];
555
+			member = subtemplate.replace('%name%', cur);
556
+			member = member.replace('%index%', i);
557
+			member = member.replace('%index%', i);
558
+
559
+			for (var j in oRecvd.selected)
560
+			{
561
+				var k = oRecvd.selected[j];
562
+				member = member.replace('%checked%', i == k ? ' checked' : '');
563
+			}
564
+
565
+			temphtml += member;
566
+		}
567
+
568
+		newhtml += template.replace('%title%', oRecvd.optional_butoff_txt).replace('%subtemplate%', temphtml);
569
+	}
570
+
571
+	document.getElementById("shd_notifications_div").innerHTML = newhtml;
572
+}
507 573
\ No newline at end of file
... ...
@@ -73,6 +73,26 @@ function shd_ajax()
73 73
 	if (!empty($_REQUEST['op']) && !empty($subactions[$_REQUEST['op']]))
74 74
 		$subactions[$_REQUEST['op']]();
75 75
 
76
+	// Json support.
77
+	if (isset($context['ajax_type']) && $context['ajax_type'] == 'json')
78
+	{
79
+		header('Content-Type: application/json; charset=UTF8');
80
+
81
+		if (empty($context['ajax_raw']) && is_array($context['ajax_return']) && isset($context['ajax_return']['success']))
82
+			echo json_encode($context['ajax_return']);
83
+		elseif (empty($context['ajax_raw']) && is_array($context['ajax_return']))
84
+			echo json_encode(array_merge(array('success' => true), $context['ajax_return']));
85
+		elseif (!empty($context['ajax_raw']) && is_array($context['ajax_raw']))
86
+			echo json_encode($context['ajax_return']);
87
+		elseif (!empty($context['ajax_raw']))
88
+			echo $context['ajax_raw'];
89
+		else
90
+			echo json_encode(array('success' => false));
91
+
92
+		obExit(false);
93
+	}
94
+	else
95
+	{
76 96
 		header('Content-Type: text/xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
77 97
 		echo '<?xml version="1.0" encoding="', $context['character_set'], '"?' . '>';
78 98
 
... ...
@@ -106,6 +126,7 @@ function shd_ajax()
106 126
 		}
107 127
 		obExit(false);
108 128
 	}
129
+}
109 130
 
110 131
 /**
111 132
  *	Handles AJAX updates to privacy.
... ...
@@ -128,17 +149,20 @@ function shd_ajax_privacy()
128 149
 {
129 150
 	global $smcFunc, $user_info, $context, $txt, $sourcedir;
130 151
 
152
+	// Use Json for the response.
153
+	$context['ajax_type'] = 'json';
154
+
131 155
 	$session_check = checkSession('get', '', false); // check the session but don't die fatally.
132 156
 	if (!empty($session_check))
133 157
 	{
134
-		$context['ajax_return'] = array('error' => $txt[$session_check]);
158
+		$context['ajax_return'] = array('success' => false, 'error' => $txt[$session_check]);
135 159
 		return;
136 160
 	}
137 161
 
138 162
 	// First, figure out the state of the ticket - is it private or not? Can we even see it?
139 163
 	if (empty($context['ticket_id']))
140 164
 	{
141
-		$context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
165
+		$context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_ticket']);
142 166
 		return;
143 167
 	}
144 168
 
... ...
@@ -156,7 +180,7 @@ function shd_ajax_privacy()
156 180
 	{
157 181
 		if (in_array($row['status'], array(TICKET_STATUS_CLOSED, TICKET_STATUS_DELETED)) || !shd_allowed_to('shd_alter_privacy_any', $row['id_dept']) && (!shd_allowed_to('shd_alter_privacy_own', $row['id_dept']) || $row['id_member_started'] != $user_info['id']))
158 182
 		{
159
-			$context['ajax_return'] = array('error' => $txt['shd_cannot_change_privacy']);
183
+			$context['ajax_return'] = array('success' => false, 'error' => $txt['shd_cannot_change_privacy']);
160 184
 			return;
161 185
 		}
162 186
 
... ...
@@ -185,11 +209,11 @@ function shd_ajax_privacy()
185 209
 		// Make sure we recalculate the number of tickets on next page load
186 210
 		shd_clear_active_tickets($row['id_dept']);
187 211
 
188
-		$context['ajax_return'] = array('message' => $new ? $txt['shd_ticket_private'] : $txt['shd_ticket_notprivate']);
212
+		$context['ajax_return'] = array('success' => true, 'message' => $new ? $txt['shd_ticket_private'] : $txt['shd_ticket_notprivate']);
189 213
 	}
190 214
 	else
191 215
 	{
192
-		$context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
216
+		$context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_ticket']);
193 217
 		return;
194 218
 	}
195 219
 }
... ...
@@ -216,17 +240,20 @@ function shd_ajax_urgency()
216 240
 {
217 241
 	global $smcFunc, $user_info, $context, $txt, $scripturl, $settings, $sourcedir;
218 242
 
243
+	// Use Json for the response.
244
+	$context['ajax_type'] = 'json';
245
+
219 246
 	$session_check = checkSession('get', '', false); // check the session but don't die fatally.
220 247
 	if (!empty($session_check))
221 248
 	{
222
-		$context['ajax_return'] = array('error' => $txt[$session_check]);
249
+		$context['ajax_return'] = array('success' => false, 'error' => $txt[$session_check]);
223 250
 		return;
224 251
 	}
225 252
 
226 253
 	// First, figure out the state of the ticket - is it private or not? Can we even see it?
227 254
 	if (empty($context['ticket_id']))
228 255
 	{
229
-		$context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
256
+		$context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_ticket']);
230 257
 		return;
231 258
 	}
232 259
 
... ...
@@ -246,7 +273,7 @@ function shd_ajax_urgency()
246 273
 
247 274
 		if (empty($_GET['change']) || empty($can_urgency[$_GET['change']]))
248 275
 		{
249
-			$context['ajax_return'] = array('error' => $txt['shd_cannot_change_urgency']);
276
+			$context['ajax_return'] = array('success' => false, 'error' => $txt['shd_cannot_change_urgency']);
250 277
 			return;
251 278
 		}
252 279
 
... ...
@@ -274,6 +301,7 @@ function shd_ajax_urgency()
274 301
 		$new_options = shd_can_alter_urgency($new_urgency, $row['id_member_started'], ($row['status'] == TICKET_STATUS_CLOSED), ($row['status'] == TICKET_STATUS_DELETED), $row['id_dept']);
275 302
 
276 303
 		$context['ajax_return'] = array(
304
+			'success' => true,
277 305
 			'message' => $new_urgency > TICKET_URGENCY_HIGH ? '<span class="error">' . $txt['shd_urgency_' . $new_urgency] . '</span>' : $txt['shd_urgency_' . $new_urgency],
278 306
 		);
279 307
 
... ...
@@ -315,6 +343,9 @@ function shd_ajax_quote()
315 343
 	global $modSettings, $user_info, $txt, $settings, $context;
316 344
 	global $sourcedir, $smcFunc;
317 345
 
346
+	// Use Json for the response.
347
+	$context['ajax_type'] = 'json';
348
+
318 349
 	loadLanguage('Post');
319 350
 	checkSession('get');
320 351
 
... ...
@@ -354,7 +385,7 @@ function shd_ajax_quote()
354 385
 				require_once($sourcedir . '/Subs-Editor.php');
355 386
 				$row['body'] = strtr($row['body'], array('&lt;' => '#smlt#', '&gt;' => '#smgt#', '&amp;' => '#smamp#'));
356 387
 				$row['body'] = bbc_to_html($row['body']);
357
-				$lb = '<br>';
388
+				$lb = "\n";
358 389
 			}
359 390
 			else
360 391
 				$lb = "\n";
... ...
@@ -367,9 +398,7 @@ function shd_ajax_quote()
367 398
 		}
368 399
 	}
369 400
 
370
-	$message = strtr($message, array('&nbsp;' => '&#160;', '<' => '&lt;', '>' => '&gt;'));
371
-
372
-	$context['ajax_raw'] = '<quote>' . $message . '</quote>';
401
+	$context['ajax_return'] = array('success' => true, 'message' => $message);
373 402
 }
374 403
 
375 404
 /**
... ...
@@ -391,6 +420,9 @@ function shd_ajax_canned()
391 420
 	global $modSettings, $user_info, $txt, $settings, $context;
392 421
 	global $sourcedir, $smcFunc;
393 422
 
423
+	// Use Json for the response.
424
+	$context['ajax_type'] = 'json';
425
+
394 426
 	loadLanguage('Post');
395 427
 	checkSession('get');
396 428
 
... ...
@@ -457,7 +489,7 @@ function shd_ajax_canned()
457 489
 
458 490
 	$message = strtr($message, array('&nbsp;' => '&#160;', '<' => '&lt;', '>' => '&gt;'));
459 491
 
460
-	$context['ajax_raw'] = '<quote>' . $message . '</quote>';
492
+	$context['ajax_return'] = array('success' => true, 'message' => $message);
461 493
 }
462 494
 
463 495
 /**
... ...
@@ -477,6 +509,9 @@ function shd_ajax_assign()
477 509
 {
478 510
 	global $context, $smcFunc, $txt, $sourcedir, $user_profile;
479 511
 
512
+	// Use Json for the response.
513
+	$context['ajax_type'] = 'json';
514
+
480 515
 	checkSession('get');
481 516
 
482 517
 	if (!empty($context['ticket_id']))
... ...
@@ -495,17 +530,17 @@ function shd_ajax_assign()
495 530
 		$smcFunc['db_free_result']($query);
496 531
 	}
497 532
 	if (empty($valid))
498
-		return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
533
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_ticket']);
499 534
 
500 535
 	require_once($sourcedir . '/sd_source/SimpleDesk-Assign.php');
501 536
 	$assignees = shd_get_possible_assignees($private, $ticket_starter, $dept);
502 537
 	array_unshift($assignees, 0); // add the unassigned option in at the start
503 538
 
504 539
 	if (empty($assignees))
505
-		return $context['ajax_return'] = array('error' => $txt['shd_no_staff_assign']);
540
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_staff_assign']);
506 541
 
507 542
 	if (!shd_allowed_to('shd_assign_ticket_any', $dept) || $status == TICKET_STATUS_CLOSED || $status == TICKET_STATUS_DELETED)
508
-		return $context['ajax_return'] = array('error' => $txt['shd_cannot_assign']);
543
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_cannot_assign']);
509 544
 
510 545
 	// OK, so we have the general values we need. Let's get user names, and get ready to kick this back to the user. We'll build the XML here though.
511 546
 	loadMemberData($assignees);
... ...
@@ -513,13 +548,16 @@ function shd_ajax_assign()
513 548
 	// Just out of interest, who's an admin?
514 549
 	$admins = shd_members_allowed_to('admin_helpdesk', $dept);
515 550
 
516
-	$context['ajax_raw'] = '<response>';
551
+	$context['ajax_return'] = array('success' => true, 'members' => array());
517 552
 	foreach ($assignees as $assignee)
518
-		$context['ajax_raw'] .= '
519
-<member uid="' . $assignee . '"' . (!empty($assignee) ? (in_array($assignee, $admins) ? ' admin="yes"' : ' admin="no"') : '') . ($ticket_assigned == $assignee ? ' assigned="yes"' : '') . '><![CD' . 'ATA[' . (empty($assignee) ? '<span class="error">' . $txt['shd_unassigned'] . '</span>' : $user_profile[$assignee]['member_name']) . ']' . ']></member>';
553
+		$context['ajax_return']['members'][$assignee] = array(
554
+			'uid' => $assignee,
555
+			'admin' => in_array($assignee, $admins) ? 'yes' : 'no',
556
+			'assigned' => $ticket_assigned == $assignee ? 'yes' : 'no',
557
+			'name' => empty($assignee) ? '<span class="error">' . $txt['shd_unassigned'] . '</span>' : $user_profile[$assignee]['member_name'],
558
+		);
520 559
 
521
-	$context['ajax_raw'] .= '
522
-</response>';
560
+	return $context['ajax_return'];
523 561
 }
524 562
 
525 563
 /**
... ...
@@ -539,6 +577,9 @@ function shd_ajax_assign2()
539 577
 {
540 578
 	global $context, $smcFunc, $txt, $sourcedir, $user_profile;
541 579
 
580
+	// Use Json for the response.
581
+	$context['ajax_type'] = 'json';
582
+
542 583
 	checkSession('get');
543 584
 
544 585
 	if (!empty($context['ticket_id']))
... ...
@@ -560,10 +601,10 @@ function shd_ajax_assign2()
560 601
 		return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
561 602
 
562 603
 	if (!isset($_GET['to_user']) || !is_numeric($_GET['to_user']))
563
-		return $context['ajax_return'] = array('error' => $txt['shd_assigned_not_permitted'] . 'line459');
604
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_assigned_not_permitted'] . 'line459');
564 605
 
565 606
 	if (!shd_allowed_to('shd_assign_ticket_any', $dept) || $status == TICKET_STATUS_CLOSED || $status == TICKET_STATUS_DELETED)
566
-		return $context['ajax_return'] = array('error' => $txt['shd_cannot_assign']);
607
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_cannot_assign']);
567 608
 
568 609
 	$_GET['to_user'] = isset($_GET['to_user']) ? (int) $_GET['to_user'] : 0;
569 610
 
... ...
@@ -572,7 +613,7 @@ function shd_ajax_assign2()
572 613
 	array_unshift($assignees, 0); // add the unassigned option in at the start
573 614
 
574 615
 	if (!in_array($_GET['to_user'], $assignees))
575
-		return $context['ajax_return'] = array('error' => $txt['shd_assigned_not_permitted']);
616
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_assigned_not_permitted']);
576 617
 
577 618
 	if (!empty($_GET['to_user']))
578 619
 		loadMemberData($_GET['to_user']);
... ...
@@ -592,7 +633,7 @@ function shd_ajax_assign2()
592 633
 		shd_commit_assignment($context['ticket_id'], $_GET['to_user'], true);
593 634
 	}
594 635
 
595
-	return $context['ajax_return'] = array('assigned' => $user_name);
636
+	return $context['ajax_return'] = array('success' => true, 'assigned' => $user_name);
596 637
 }
597 638
 
598 639
 /**
... ...
@@ -605,9 +646,12 @@ function shd_ajax_notify()
605 646
 {
606 647
 	global $txt, $context, $smcFunc, $user_profile, $modSettings, $sourcedir;
607 648
 
649
+	// Use Json for the response.
650
+	$context['ajax_type'] = 'json';
651
+
608 652
 	$session_check = checkSession('get', '', false); // check the session but don't die fatally.
609 653
 	if (!empty($session_check))
610
-		return $context['ajax_return'] = array('error' => $txt[$session_check]);
654
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt[$session_check]);
611 655
 
612 656
 	shd_load_language('sd_language/SimpleDeskNotifications');
613 657
 	require_once($sourcedir . '/sd_source/SimpleDesk-Notifications.php');
... ...
@@ -628,7 +672,7 @@ function shd_ajax_notify()
628 672
 		$smcFunc['db_free_result']($query);
629 673
 	}
630 674
 	if (empty($ticket) || !shd_allowed_to('shd_singleton_email', $ticket['id_dept']) || $ticket['status'] == TICKET_STATUS_CLOSED || $ticket['status'] == TICKET_STATUS_DELETED)
631
-		return $context['ajax_return'] = array('error' => $txt['shd_no_ticket']);
675
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_no_ticket']);
632 676
 
633 677
 	// So, we need to start figuring out who's going to be notified, who won't be and who we might be interested in notifying.
634 678
 	$notify_list = array(
... ...
@@ -797,12 +841,9 @@ function shd_ajax_notify()
797 841
 	}
798 842
 
799 843
 	if (empty($notify_list) || empty($members))
800
-		return $context['ajax_raw'] = '<notify><![C' . 'DATA[' . cleanXml($txt['shd_ping_none']) . ']' . ']></notify>';
844
+		return $context['ajax_return'] = array('success' => false, 'error' => $txt['shd_ping_none']);
801 845
 	else
802 846
 	{
803
-		ob_start();
804
-		echo '<notify><![C', 'DATA[';
805
-
806 847
 		$selected = array();
807 848
 		if (!empty($_GET['list']))
808 849
 		{
... ...
@@ -812,33 +853,16 @@ function shd_ajax_notify()
812 853
 					$selected[] = (int) $id;
813 854
 		}
814 855
 
815
-		if (!empty($notify_list['being_notified']))
816
-			echo '<span class="shd_ajax_head">', $txt['shd_ping_already_' . (count($notify_list['being_notified']) == 1 ? '1' : 'n')], '</span><br>', implode(', ', $notify_list['being_notified']);
817
-
818
-		if (!empty($notify_list['optional']))
819
-		{
820
-			if (!empty($notify_list['being_notified']))
821
-				echo '<br><br>';
822
-
823
-			echo '<span class="shd_ajax_head">', $txt['shd_ping_' . (count($notify_list['optional']) == 1 ? '1' : 'n')], '</span><br>';
824
-			foreach ($notify_list['optional'] as $id => $member)
825
-				echo '<div class="shd_ajaxnotify"><input type="checkbox" name="notify[', $id, ']" value="', $id, '"', in_array($id, $selected) ? ' checked="checked"' : '', ' > ', $member, '</div>';
826
-		}
827
-
828
-		if (!empty($notify_list['optional_butoff']))
829
-		{
830
-			if (!empty($notify_list['being_notified']) || !empty($notify_list['optional_butoff']))
831
-				echo '<br><br>';
832
-
833
-			echo '<span class="shd_ajax_head">', $txt['shd_ping_none_' . (count($notify_list['optional_butoff']) == 1 ? '1' : 'n')], '</span><br>';
834
-			foreach ($notify_list['optional_butoff'] as $id => $member)
835
-				echo '<div class="shd_ajaxnotify"><input type="checkbox" name="notify[', $id, ']" value="', $id, '"', in_array($id, $selected) ? ' checked="checked"' : '', ' > ', $member, '</div>';
836
-		}
837
-
838
-		echo '<br class="clear">]', ']></notify>';
839
-
840
-		$content = ob_get_clean();
841
-		return $context['ajax_raw'] = cleanXml($content);
856
+		return $context['ajax_return'] = array(
857
+			'success' => true,
858
+			'being_notified_txt' => !empty($notify_list['being_notified']) ? $txt['shd_ping_already_' . (count($notify_list['being_notified']) == 1 ? '1' : 'n')] : '',
859
+			'being_notified' => !empty($notify_list['being_notified']) ? $notify_list['being_notified'] : array(),
860
+			'optional_txt' => !empty($notify_list['optional']) ? $txt['shd_ping_' . (count($notify_list['optional']) == 1 ? '1' : 'n')] : '',
861
+			'optional' => !empty($notify_list['optional']) ? $notify_list['optional'] : array(),
862
+			'optional_butoff_txt' => !empty($notify_list['optional_butoff']) ? $txt['shd_ping_none_' . (count($notify_list['optional_butoff']) == 1 ? '1' : 'n')] : '',
863
+			'optional_butoff' => !empty($notify_list['optional_butoff']) ? $notify_list['optional_butoff'] : array(),
864
+			'selected' => $selected,
865
+		);
842 866
 	}
843 867
 }
844 868
 
... ...
@@ -81,7 +81,7 @@ function template_viewticket()
81 81
 						</dd>
82 82
 						<dt><img src="', $settings['default_images_url'], '/simpledesk/staff.png" alt="" class="shd_smallicon"> ', $txt['shd_ticket_assignedto'], ':</dt>
83 83
 						<dd><span id="assigned_to">', $context['ticket']['assigned']['link'], '</span><span id="assigned_button"></span></dd>
84
-						<dt class="full_width">
84
+						<dt class="shd_assignees_list">
85 85
 							<ul id="assigned_list" style="display:none;"></ul>
86 86
 						</dt>
87 87
 						<dt><img src="', $settings['default_images_url'], '/simpledesk/status.png" alt="" class="shd_smallicon"/> ', $txt['shd_ticket_status'], ':</dt>
... ...
@@ -784,7 +784,7 @@ function template_singleton_email()
784 784
 
785 785
 	echo '
786 786
 						<div id="shd_notifications_div" style="display:none;">
787
-							<a href="#" onclick="getAjaxNotifications(); return false;">', $txt['shd_select_notifications'], '</a>';
787
+							<a href="#" id="shd_getAjaxNotifications">', $txt['shd_select_notifications'], '</a>';
788 788
 
789 789
 	if (!empty($context['notification_ping_list']))
790 790
 		echo '
... ...
@@ -794,21 +794,23 @@ function template_singleton_email()
794 794
 							<br class="clear">
795 795
 						</div>
796 796
 						<script type="text/javascript"><!-- // --><![CDATA[
797
-	document.getElementById("shd_notifications_div").style.display = "";
798
-
799
-	function getAjaxNotifications()
800
-	{
801
-		ajax_indicator(true);
802
-		getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + "action=helpdesk;sa=ajax;op=notify;ticket=', $context['ticket_id'], ';', $context['session_var'], '=', $context['session_id'], !empty($context['notification_ping_list']) ? ';list=' . $context['notification_ping_list'] : '', '", handleAjaxNotifications);
803
-	}
804
-
805
-	function handleAjaxNotifications(XMLDoc)
806
-	{
807
-		ajax_indicator(false);
808
-		var notify = XMLDoc.getElementsByTagName("notify");
809
-		if (notify.length == 1)
810
-			document.getElementById("shd_notifications_div").innerHTML = notify[0].firstChild.nodeValue;
811
-	}
797
+	var shd_oNotifications = new shd_notifications(', $context['ticket_id'], ', {
798
+		sContainerId: "shd_notifications_div",
799
+		sLinkId: "shd_getAjaxNotifications",
800
+		sPinglist: "', (!empty($context['notification_ping_list']) ? $context['notification_ping_list'] : ''), '",
801
+		sSessionId: smf_session_id,
802
+		sSessionVar: smf_session_var,
803
+		oMainTemplate: ', JavaScriptEscape('
804
+			<span class=\'shd_ajax_head\'>%title%</span><br>%subtemplate%<br>
805
+							'), ',
806
+		oNotifiedTemplate: ', JavaScriptEscape('%name%, '), ',
807
+		oOptionalTemplate: ', JavaScriptEscape('
808
+				<div class=\'shd_ajaxnotify\'><input type=\'checkbox\' name=\'notify[%index%]\' value=\'%index%\'%checked%>%name%</div>
809
+								'), ',
810
+		oOptionalOffTemplate: ', JavaScriptEscape('
811
+				<div class=\'shd_ajaxnotify\'><input type=\'checkbox\' name=\'notify[%index%]\' value=\'%index%\'%checked%>%name%</div>
812
+								'), ',
813
+	});
812 814
 						// ]]></script>';
813 815
 }
814 816
 
815 817