diff --git a/.gitignore b/.gitignore index 2c0568a588f2c2759a62bc94151c0597f8c09abe..b5c98c948d9c2eb75df122268b315b29f15a9e93 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ php53.cgi include/ost-config.php *.sw[a-z] .DS_Store +.vagrant +Vagrantfile diff --git a/assets/default/about-custom-themes.md b/assets/default/about-custom-themes.md new file mode 100644 index 0000000000000000000000000000000000000000..eecf1bbbed3d3002d3d4abe686f8ec6d82717094 --- /dev/null +++ b/assets/default/about-custom-themes.md @@ -0,0 +1,14 @@ +Customizng Your Theme +==== + +When modifying the default theme, it is recommended that you do not +edit the CSS files directly. Instead, you should use the included +LESS files and recompile the CSS file with your edits. + +Even if you decide to edit the CSS directly, we recommend that you +keep the LESS files, so that you can quickly rebuild the default +theme should you need to recover it, or for upgrade purposes. + +*Please do not submit any CSS edits to the official branch, unless +they are done within the LESS files.* + diff --git a/assets/default/css/print.css b/assets/default/css/print.css index 9f1c25746ca0486d2524e24ea850b1d2ffcd9e6c..aca3d50f9265e03c3a95d5a5dbbe78aa891b0a21 100644 --- a/assets/default/css/print.css +++ b/assets/default/css/print.css @@ -1,29 +1 @@ -#header, #nav, #meta, #footer, #reply, #pagination, .reload, .refresh, form, .thread, hr, #kbAttachments, .back { - display: none; -} - -th { - text-align: left; -} - -a { - color: #000; - text-decoration: none; -} - -caption { - text-align: left; - padding-bottom: 10px; - font-weight: bold; -} - -.message, .response { - border-bottom: 1px solid #000; - margin-bottom: 20px; - padding-bottom: 10px; -} -.message th, .response th { - font-size: 12pt; - font-weight: bold; - padding-bottom: 5px; -} +#header,#nav,#meta,#footer,#reply,#pagination,.reload,.refresh,form,.thread,hr,#kbAttachments,.back{display:none}th{text-align:left}a{color:#000;text-decoration:none}caption{text-align:left;padding-bottom:10px;font-weight:bold}.message,.response{border-bottom:1px solid #000;margin-bottom:20px;padding-bottom:10px}.message th,.response th{font-size:12pt;font-weight:bold;padding-bottom:5px} \ No newline at end of file diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index b59f6af8955857d2008dff647e8e4d83d4924e72..206cbe9fd0eb86d67fa1d16abae23e9a49aab9ca 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -1,31 +1,29 @@ -/* Based on Normalize.css - with tags we won't use removed. */ html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } - body { margin: 0; font-size: 13px; line-height: 1.231; padding: 0; } - -body, input, select, textarea { +body, +input, +select, +textarea { font-family: sans-serif; color: #000; } - -b, strong { +b, +strong { font-weight: bold; } - blockquote { margin: 1em 40px; } - hr { display: block; height: 1px; @@ -34,119 +32,120 @@ hr { margin: 1em 0; padding: 0; } - small { font-size: 85%; } - -ul, ol { +ul, +ol { margin: 1em 0; padding: 0 0 0 30px; } - img { border: 0; vertical-align: middle; } - form { margin: 0; } - fieldset { border: 0; margin: 0; padding: 0; } - label { cursor: pointer; } - -input, select, textarea { +input, +select, +textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } - input { line-height: normal; *overflow: visible; } - table input { *overflow: auto; } - -input[type="button"], input[type="reset"], input[type="submit"] { +input[type="button"], +input[type="reset"], +input[type="submit"] { cursor: pointer; -webkit-appearance: button; } - -input[type="checkbox"], input[type="radio"] { +input[type="checkbox"], +input[type="radio"] { box-sizing: border-box; } - input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } - textarea { overflow: auto; vertical-align: top; resize: vertical; } - table { border-collapse: collapse; border-spacing: 0; } - -th, td { +th, +td { vertical-align: top; } - -th { text-align: left; font-weight: normal; } - -h1, h2, h3, h4, h5, h6, form, fieldset { +th { + text-align: left; + font-weight: normal; +} +h1, +h2, +h3, +h4, +h5, +h6, +form, +fieldset { margin: 0; padding: 0; } - /* Typography */ a { color: #0072bc; text-decoration: none; } - h1 { color: #00AEEF; font-weight: normal; font-size: 20px; } - h3 { font-size: 16px; } - h2 { font-size: 16px; color: #999; } - /* Helpers */ -.centered { text-align: center;} - -.clear { clear:both; height: 1px; visibility: none;} - -.hidden { display: none;} - -.faded { color:#666;} - +.centered { + text-align: center; +} +.clear { + clear: both; + height: 1px; + visibility: none; +} +.hidden { + display: none; +} +.faded { + color: #666; +} /* Pagination */ #pagination { border: 0; @@ -161,24 +160,26 @@ h2 { list-style: none; display: inline; } -#pagination a { +#pagination li a { margin-right: 2px; display: block; float: left; padding: 3px 6px; text-decoration: none; } -#pagination a:hover { +#pagination li a:hover { color: #ff0084; } -#pagination .previousOff, #pagination .nextOff { +#pagination .previousOff, +#pagination .nextOff { color: #666; display: block; float: left; font-weight: bold; padding: 3px 4px; } -#pagination .next a, #pagination .previous a { +#pagination .next a, +#pagination .previous a { font-weight: bold; } #pagination .active { @@ -190,16 +191,34 @@ h2 { padding: 3px 6px; text-decoration: none; } - /* Alerts & Notices */ - -#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; } - -#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; } - -#msg_error { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; } - - +#msg_notice { + margin: 0; + padding: 5px 10px 5px 36px; + height: 16px; + line-height: 16px; + margin-bottom: 10px; + border: 1px solid #0a0; + background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; +} +#msg_warning { + margin: 0; + padding: 5px 10px 5px 36px; + height: 16px; + line-height: 16px; + margin-bottom: 10px; + border: 1px solid #f26522; + background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; +} +#msg_error { + margin: 0; + padding: 5px 10px 5px 36px; + height: 16px; + line-height: 16px; + margin-bottom: 10px; + border: 1px solid #a00; + background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; +} .warning { background: #ffc; font-style: italic; @@ -209,19 +228,64 @@ h2 { color: #a00; font-style: normal; } - .error { - color:#f00; + color: #f00; } - .error input { - border:1px solid #f00;} - -/* Main layout */ + border: 1px solid #f00; +} +.button, +.button:visited { + background: #222; + display: inline-block; + font-size: 16px; + padding: 8px 16px 6px 16px; + width: 160px; + text-align: center; + color: #fff; + font-weight: bold; + text-decoration: none; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); + border-bottom: 1px solid rgba(0, 0, 0, 0.25); + position: relative; + cursor: pointer; + font-family: helvetica, arial, sans-serif; +} +.button:hover { + background-color: #111; + color: #fff; +} +.button:active { + top: 1px; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; +} +.button, +.button:visited, +.green.button, +.green.button:visited { + background-color: #91bd09; +} +.green.button:hover { + background-color: #749a02; +} +.blue.button, +.blue.button:visited { + background-color: #00AEEF; +} +.blue.button:hover { + background-color: #0299d2; +} body { background: url('../images/page_bg.png') top left repeat-x #c8c8c8; } - #container { background: #fff; width: 840px; @@ -230,7 +294,6 @@ body { -moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); } - #header { position: relative; height: 71px; @@ -241,7 +304,6 @@ body { height: 71px; float: left; } - #header p { width: 400px; text-align: right; @@ -249,16 +311,15 @@ body { padding: 10px 0; float: right; } - #nav { margin: 0 20px; padding: 2px 10px; height: 20px; background: url('../images/nav_bg.png') top left repeat-x; border-top: 1px solid #aaa; - box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); - -moz-box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); - -webkit-box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); + box-shadow: 0 3px 2px rgba(0, 0, 0, 0.4); + -moz-box-shadow: 0 3px 2px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 0 3px 2px rgba(0, 0, 0, 0.4); } #nav li { margin: 0; @@ -282,7 +343,8 @@ body { background-position: 10px 50%; background-repeat: no-repeat; } -#nav li a.active, #nav li a:hover { +#nav li a.active, +#nav li a:hover { background-color: #dbefff; color: #000; } @@ -305,7 +367,6 @@ body { #nav li a.tickets { background-image: url('../images/icons/tix.png'); } - #content { padding: 20px 0; margin: 0 20px; @@ -313,7 +374,6 @@ body { height: 350px; min-height: 350px; } - #footer { text-align: center; font-size: 11px; @@ -322,7 +382,6 @@ body { #footer a { color: #333; } - #footer p { margin: 10px 0 0 0; } @@ -333,45 +392,23 @@ body { outline: none; text-indent: -9999px; margin: 0 auto; - background: url('../images/poweredby.png?1319571688') top left no-repeat; + background: url('../images/poweredby.png') top left no-repeat; } - -/* Landing page */ #landing_page #new_ticket { margin-top: 40px; width: 295px; padding-left: 75px; float: left; - background: url('../images/new_ticket_icon.png?1319577021') top left no-repeat; -} -#landing_page #new_ticket input[type=submit] { - background: url('../images/open_ticket_btn.png?1319566422') top left no-repeat; + background: url('../images/new_ticket_icon.png') top left no-repeat; } #landing_page #check_status { margin-top: 40px; width: 295px; padding-left: 75px; float: right; - background: url('../images/check_status_icon.png?1319577072') top left no-repeat; -} -#landing_page #check_status input[type=submit] { - background: url('../images/check_status_btn.png?1319571066') top left no-repeat; + background: url('../images/check_status_icon.png') top left no-repeat; } -#landing_page form div { - margin-bottom: 20px; - height: 60px; - min-height: 60px; -} -#landing_page form input[type=submit] { - display: block; - width: 192px; - height: 38px; - border: none; - margin: 0; - padding: 0; - text-indent: -9999px; -} - +/* Landing page FAQ not yet implemented. */ #faq { clear: both; margin: 0; @@ -381,136 +418,129 @@ body { font-size: 15px; margin-left: 0; padding-left: 0; - border-top:1px solid #ddd; + border-top: 1px solid #ddd; } #faq ol li { list-style: none; margin: 0; - padding:0; + padding: 0; color: #999; } #faq ol li a { - display:block; - padding:5px 0; - height:auto !important; - overflow:hidden; - margin:0; - border-bottom:1px solid #ddd; + display: block; + padding: 5px 0; + height: auto !important; + overflow: hidden; + margin: 0; + border-bottom: 1px solid #ddd; line-height: 16px; padding-left: 24px; - background: url('../images/icons/page.png?1319579499') 0 50% no-repeat; + background: url('../images/icons/page.png') 0 50% no-repeat; } #faq ol li a:hover { - background-color:#e9f5ff; + background-color: #e9f5ff; } - -.article-meta { - padding:5px; - background:#fafafa; +#faq .article-meta { + padding: 5px; + background: #fafafa; } - -/* Knowledgebase */ #kb { margin: 2px 0; padding: 5px; overflow: hidden; } - #kb > li { - padding:10px; - height:auto !important; - overflow:hidden; - margin:0; - background:url(../images/kb_category_bg.png) bottom left repeat-x; - border-bottom:1px solid #ddd; -} - -#kb li i { - display:block; - width:32px; - height:32px; - float:left; - margin-right:6px; - background:url(../images/kb_large_folder.png) top left no-repeat; -} - + padding: 10px; + height: auto !important; + overflow: hidden; + margin: 0; + background: url(../images/kb_category_bg.png) bottom left repeat-x; + border-bottom: 1px solid #ddd; +} #kb > li h4 { - padding-bottom:3px; - margin-bottom:3px; + padding-bottom: 3px; + margin-bottom: 3px; } - #kb > li h4 span { - color:#666; + color: #666; } - #kb > li h4 a { font-size: 14px; } - +#kb li i { + display: block; + width: 32px; + height: 32px; + float: left; + margin-right: 6px; + background: url(../images/kb_large_folder.png) top left no-repeat; +} #kb-search { - padding:10px 0; - overflow:hidden; + padding: 10px 0; + overflow: hidden; } - #kb-search div { - clear:both; - overflow:hidden; - padding-top:5px; + clear: both; + overflow: hidden; + padding-top: 5px; } - #kb-search #query { - margin:0; - display:inline-block; - float:left; - width:200px; - margin-right:5px; + margin: 0; + display: inline-block; + float: left; + width: 200px; + margin-right: 5px; } - #kb-search #cid { - margin:0; - display:inline-block; - float:left; - width:200px; - margin-right:5px; - position:relative; - top:2px; -} - + margin: 0; + display: inline-block; + float: left; + width: 200px; + margin-right: 5px; + position: relative; + top: 2px; +} #kb-search #topic-id { - margin:0; - display:inline-block; - float:left; - width:410px; + margin: 0; + display: inline-block; + float: left; + width: 410px; } - #kb-search #searchSubmit { - margin:0; - display:inline-block; - float:left; - position:relative; - top:2px; -} - -#breadcrumbs { + margin: 0; + display: inline-block; + float: left; + position: relative; + top: 2px; +} +#kb-search #breadcrumbs { color: #333; margin-bottom: 15px; } -#breadcrumbs a { +#kb-search #breadcrumbs #breadcrumbs a { color: #555; } - -/* New Ticket & Log In Forms */ -#ticketForm div, #clientLogin div { +#ticketForm div, +#clientLogin div { clear: both; padding: 3px 0; overflow: hidden; } -#ticketForm div label, #clientLogin div label { +#ticketForm div label, +#clientLogin div label { display: block; width: 140px; float: left; } -#ticketForm div input, #ticketForm div textarea, #clientLogin div input, #clientLogin div textarea { +#ticketForm div label.required, +#clientLogin div label.required { + font-weight: bold; + text-align: left; +} +#ticketForm div input, +#clientLogin div input, +#ticketForm div textarea, +#clientLogin div textarea { width: auto; border: 1px solid #aaa; background: #fff; @@ -518,23 +548,40 @@ body { display: block; float: left; } - -#ticketForm div input[type=file] { +#ticketForm div input[type=file], +#clientLogin div input[type=file] { border: 0; } - -#ticketForm div select, #clientLogin div select { +#ticketForm div select, +#clientLogin div select { display: block; float: left; } -#ticketForm td textarea, #clientLogin div textarea { +#ticketForm div div.captchaRow, +#clientLogin div div.captchaRow { + line-height: 31px; +} +#ticketForm div div.captchaRow input, +#clientLogin div div.captchaRow input { + position: relative; + top: 6px; +} +#ticketForm td textarea, +#clientLogin td textarea, +#ticketForm div textarea, +#clientLogin div textarea { width: 600px; } - -#ticketForm td em, #clientLogin div em { +#ticketForm td em, +#clientLogin td em, +#ticketForm div em, +#clientLogin div em { color: #777; } -#ticketForm td .captcha, #clientLogin div .captcha { +#ticketForm td .captcha, +#clientLogin td .captcha, +#ticketForm div .captcha, +#clientLogin div .captcha { width: 88px; height: 31px; background: #000; @@ -542,44 +589,31 @@ body { float: left; margin-right: 20px; } -#ticketForm td label.inline, #clientLogin div label.inline { +#ticketForm td label.inline, +#clientLogin td label.inline, +#ticketForm div label.inline, +#clientLogin div label.inline { width: auto; padding: 0 10px; } - -#ticketTable table tr th { +#ticketForm div.error input, +#clientLogin div.error input { + border: 1px solid #a00; +} +#ticketForm div.error label, +#clientLogin div.error label { + color: #a00; +} +#ticketTable th { width: 160px; font-weight: normal; text-align: left; } - -#ticketForm table th.required, #ticketForm table td.required, #clientLogin div label.required { +#ticketTable th.required, +#ticketTable td.required { font-weight: bold; text-align: left; } - - - -#ticketForm tr.captchaRow, #clientLogin div.captchaRow { - line-height: 31px; -} - -.captchaRow td input, #clientLogin div.captchaRow input { - position: relative; - top: 6px; -} - -#ticketForm div.error input, #clientLogin div.error input { - border: 1px solid #a00; -} -#ticketForm div.error label, #clientLogin div.error label { - color: #a00; -} -#clientLogin p { - clear: both; - text-align: center; -} - #clientLogin { width: 400px; margin-top: 20px; @@ -587,6 +621,10 @@ body { border: 1px solid #ccc; background: url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6; } +#clientLogin p { + clear: both; + text-align: center; +} #clientLogin strong { font-size: 11px; color: #d00; @@ -601,59 +639,89 @@ body { width: 120px; margin-right: 0; } - -/* Ticket List */ +#reply { + margin-top: 20px; + padding: 10px 5px; + background: #f9f9f9; + border: 1px solid #ccc; +} +#reply h2 { + margin-bottom: 10px; +} +#reply table { + width: 800px; +} +#reply table td { + vertical-align: top; +} +#reply textarea { + width: 628px !important; +} +#reply input[type=text], +#reply #response_options textarea { + border: 1px solid #aaa; + background: #fff; +} +#reply .attachments .uploads div { + display: inline-block; + padding-right: 20px; +} +#reply .file { + display: inline-block; + padding-left: 20px; + margin-right: 20px; + background: url('../images/icons/file.gif') 0 50% no-repeat; +} +.uploads { + display: inline-block; + padding-right: 20px; +} +.uploads label { + padding: 3px; + padding-right: 10px; + width: auto !important; +} +/* Ticket icons */ .Icon { width: auto; padding-left: 20px; - background-position: left center; + background-position: top left; background-repeat: no-repeat; color: #006699; text-decoration: none; } - -a.Icon:hover { - text-decoration: underline; -} - .Icon.Ticket { - background: url('../images/icons/ticket.gif?1319654018') 0 0 no-repeat; + background-image: url('../images/icons/ticket.gif'); } - .Icon.webTicket { - background: url('../images/icons/ticket_source_web.gif?1319654283') 0 0 no-repeat; + background-image: url('../images/icons/ticket_source_web.gif'); } - .Icon.emailTicket { - background: url('../images/icons/ticket_source_email.gif?1319654484') 0 0 no-repeat; + background-image: url('../images/icons/ticket_source_email.gif'); } - .Icon.phoneTicket { - background: url('../images/icons/ticket_source_phone.gif?1319654401') 0 0 no-repeat; + background-image: url('../images/icons/ticket_source_phone.gif'); } - .Icon.otherTicket { - background: url('../images/icons/ticket_source_other.gif?1319654433') 0 0 no-repeat; + background-image: url('../images/icons/ticket_source_other.gif'); } - .Icon.attachment { - background-image: url('../images/icons/attachment.gif?1319556657'); + background-image: url('../images/icons/attachment.gif'); } - .Icon.file { - background-image: url('../images/icons/attachment.gif?1319556657'); + background-image: url('../images/icons/attachment.gif'); } - .Icon.refresh { - background-image: url('../images/icons/refresh.gif?1319556657'); + background-image: url('../images/icons/refresh.gif'); } - .Icon.thread { font-weight: bold; font-size: 1em; background-image: url('../images/icons/thread.gif?1319556657'); } - +.Icon:hover { + text-decoration: underline; +} #ticketTable { border: 1px solid #aaa; border-left: none; @@ -688,13 +756,11 @@ a.Icon:hover { #ticketTable tr.alt td { background: #f9f9f9; } - #ticketSearchForm { display: inline-block; float: left; padding: 0 0 5px 0; } - a.refresh { display: block; width: auto; @@ -708,16 +774,14 @@ a.refresh { color: #333; background-position: 5px 50%; background-repeat: no-repeat; - background-image: url('../images/icons/refresh.png?1319653435'); + background-image: url('../images/icons/refresh.png'); } - .infoTable { background: #F4FAFF; } .infoTable th { text-align: left; } - #ticketThread table { margin-top: 10px; border: 1px solid #aaa; @@ -729,13 +793,11 @@ a.refresh { font-size: 12px; padding: 5px; } - #ticketThread table th span { - font-weight:normal; - color:#888; - padding-left:20px; + font-weight: normal; + color: #888; + padding-left: 20px; } - #ticketThread table td { padding: 5px; } @@ -761,76 +823,3 @@ a.refresh { background-position: 0 50%; background-repeat: no-repeat; } - -#reply { - margin-top: 20px; - padding: 10px 5px; - background: #f9f9f9; - border: 1px solid #ccc; -} -#reply h2 { - margin-bottom: 10px; -} -#reply table { - width: 800px; -} -#reply table td { - vertical-align: top; -} -#reply textarea { - width: 628px !important; -} -#reply input[type=text], #reply #response_options textarea { - border: 1px solid #aaa; - background: #fff; -} -#reply .attachments .uploads div { - display: inline-block; - padding-right: 20px; -} -#reply .file { - display: inline-block; - padding-left: 20px; - margin-right: 20px; - background: url('../images/icons/file.gif') 0 50% no-repeat; -} - -.button, .button:visited { - background: #222; - display: inline-block; - font-size: 16px; - padding: 8px 16px 6px 16px; - width:160px; - text-align:center; - color: #fff; - font-weight:bold; - text-decoration: none; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - box-shadow: 0 1px 3px rgba(0,0,0,0.5); - -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); - text-shadow: 0 -1px 1px rgba(0,0,0,0.25); - border-bottom: 1px solid rgba(0,0,0,0.25); - position: relative; - cursor: pointer; - font-family:helvetica, arial, sans-serif; -} - - -.uploads { - display:inline-block; - padding-right:20px; -} - -.uploads label { padding:3px; padding-right:10px; width: auto !important } - -.button:hover { background-color: #111; color: #fff; } -.button:active { top: 1px; box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; } -.button, .button:visited, -.green.button, .green.button:visited { background-color: #91bd09; } -.green.button:hover { background-color: #749a02; } -.blue.button, .blue.button:visited { background-color: #00AEEF; } -.blue.button:hover { background-color: #0299d2; } - diff --git a/assets/default/css/theme.min.css b/assets/default/css/theme.min.css new file mode 100644 index 0000000000000000000000000000000000000000..8e747a44528fe4376371c967fd867ec835f01fce --- /dev/null +++ b/assets/default/css/theme.min.css @@ -0,0 +1 @@ +html{font-size:100%;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0;font-size:13px;line-height:1.231;padding:0}body,input,select,textarea{font-family:sans-serif;color:#000}b,strong{font-weight:bold}blockquote{margin:1em 40px}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}small{font-size:85%}ul,ol{margin:1em 0;padding:0 0 0 30px}img{border:0;vertical-align:middle}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}input{line-height:normal;*overflow:visible}table input{*overflow:auto}input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="checkbox"],input[type="radio"]{box-sizing:border-box}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}th,td{vertical-align:top}th{text-align:left;font-weight:normal}h1,h2,h3,h4,h5,h6,form,fieldset{margin:0;padding:0}a{color:#0072bc;text-decoration:none}h1{color:#00aeef;font-weight:normal;font-size:20px}h3{font-size:16px}h2{font-size:16px;color:#999}.centered{text-align:center}.clear{clear:both;height:1px;visibility:none}.hidden{display:none}.faded{color:#666}#pagination{border:0;margin:0 0 40px 0;padding:0}#pagination li{border:0;margin:0;padding:0;font-size:11px;list-style:none;display:inline}#pagination li a{margin-right:2px;display:block;float:left;padding:3px 6px;text-decoration:none}#pagination li a:hover{color:#ff0084}#pagination .previousOff,#pagination .nextOff{color:#666;display:block;float:left;font-weight:bold;padding:3px 4px}#pagination .next a,#pagination .previous a{font-weight:bold}#pagination .active{color:#000;font-weight:bold;margin-right:2px;display:block;float:left;padding:3px 6px;text-decoration:none}#msg_notice{margin:0;padding:5px 10px 5px 36px;height:16px;line-height:16px;margin-bottom:10px;border:1px solid #0a0;background:url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0}#msg_warning{margin:0;padding:5px 10px 5px 36px;height:16px;line-height:16px;margin-bottom:10px;border:1px solid #f26522;background:url('../images/icons/alert.png') 10px 50% no-repeat #ffd}#msg_error{margin:0;padding:5px 10px 5px 36px;height:16px;line-height:16px;margin-bottom:10px;border:1px solid #a00;background:url('../images/icons/error.png') 10px 50% no-repeat #fff0f0}.warning{background:#ffc;font-style:italic}.warning strong{text-transform:uppercase;color:#a00;font-style:normal}.error{color:#f00}.error input{border:1px solid #f00}.button,.button:visited{background:#222;display:inline-block;font-size:16px;padding:8px 16px 6px 16px;width:160px;text-align:center;color:#fff;font-weight:bold;text-decoration:none;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;box-shadow:0 1px 3px rgba(0,0,0,0.5);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.5);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.5);text-shadow:0 -1px 1px rgba(0,0,0,0.25);border-bottom:1px solid rgba(0,0,0,0.25);position:relative;cursor:pointer;font-family:helvetica,arial,sans-serif}.button:hover{background-color:#111;color:#fff}.button:active{top:1px;box-shadow:none;-moz-box-shadow:none;-webkit-box-shadow:none}.button,.button:visited,.green.button,.green.button:visited{background-color:#91bd09}.green.button:hover{background-color:#749a02}.blue.button,.blue.button:visited{background-color:#00aeef}.blue.button:hover{background-color:#0299d2}body{background:url('../images/page_bg.png') top left repeat-x #c8c8c8}#container{background:#fff;width:840px;margin:0 auto;box-shadow:0 0 6px rgba(0,0,0,0.5);-moz-box-shadow:0 0 6px rgba(0,0,0,0.5);-webkit-box-shadow:0 0 6px rgba(0,0,0,0.5)}#header{position:relative;height:71px;padding:0 20px}#header #logo{width:220px;height:71px;float:left}#header p{width:400px;text-align:right;margin:0;padding:10px 0;float:right}#nav{margin:0 20px;padding:2px 10px;height:20px;background:url('../images/nav_bg.png') top left repeat-x;border-top:1px solid #aaa;box-shadow:0 3px 2px rgba(0,0,0,0.4);-moz-box-shadow:0 3px 2px rgba(0,0,0,0.4);-webkit-box-shadow:0 3px 2px rgba(0,0,0,0.4)}#nav li{margin:0;padding:0;list-style:none;display:inline}#nav li a{display:block;width:auto;float:left;height:20px;line-height:20px;text-align:center;padding:0 10px 0 32px;margin-left:10px;color:#333;border-radius:20px;-webkit-border-radius:20px;-moz-border-radius:20px;background-position:10px 50%;background-repeat:no-repeat}#nav li a.active,#nav li a:hover{background-color:#dbefff;color:#000}#nav li a:hover{background-color:#ededed;color:#0054a6}#nav li a.home{background-image:url('../images/icons/home.png')}#nav li a.kb{background-image:url('../images/icons/kb.png')}#nav li a.new{background-image:url('../images/icons/new.png')}#nav li a.status{background-image:url('../images/icons/status.png')}#nav li a.tickets{background-image:url('../images/icons/tix.png')}#content{padding:20px 0;margin:0 20px;height:auto!important;height:350px;min-height:350px}#footer{text-align:center;font-size:11px;color:#333}#footer a{color:#333}#footer p{margin:10px 0 0 0}#footer #poweredBy{display:block;width:126px;height:23px;outline:0;text-indent:-9999px;margin:0 auto;background:url('../images/poweredby.png') top left no-repeat}#landing_page #new_ticket{margin-top:40px;width:295px;padding-left:75px;float:left;background:url('../images/new_ticket_icon.png') top left no-repeat}#landing_page #check_status{margin-top:40px;width:295px;padding-left:75px;float:right;background:url('../images/check_status_icon.png') top left no-repeat}#faq{clear:both;margin:0;padding:5px}#faq ol{font-size:15px;margin-left:0;padding-left:0;border-top:1px solid #ddd}#faq ol li{list-style:none;margin:0;padding:0;color:#999}#faq ol li a{display:block;padding:5px 0;height:auto!important;overflow:hidden;margin:0;border-bottom:1px solid #ddd;line-height:16px;padding-left:24px;background:url('../images/icons/page.png') 0 50% no-repeat}#faq ol li a:hover{background-color:#e9f5ff}#faq .article-meta{padding:5px;background:#fafafa}#kb{margin:2px 0;padding:5px;overflow:hidden}#kb>li{padding:10px;height:auto!important;overflow:hidden;margin:0;background:url(../images/kb_category_bg.png) bottom left repeat-x;border-bottom:1px solid #ddd}#kb>li h4{padding-bottom:3px;margin-bottom:3px}#kb>li h4 span{color:#666}#kb>li h4 a{font-size:14px}#kb li i{display:block;width:32px;height:32px;float:left;margin-right:6px;background:url(../images/kb_large_folder.png) top left no-repeat}#kb-search{padding:10px 0;overflow:hidden}#kb-search div{clear:both;overflow:hidden;padding-top:5px}#kb-search #query{margin:0;display:inline-block;float:left;width:200px;margin-right:5px}#kb-search #cid{margin:0;display:inline-block;float:left;width:200px;margin-right:5px;position:relative;top:2px}#kb-search #topic-id{margin:0;display:inline-block;float:left;width:410px}#kb-search #searchSubmit{margin:0;display:inline-block;float:left;position:relative;top:2px}#kb-search #breadcrumbs{color:#333;margin-bottom:15px}#kb-search #breadcrumbs #breadcrumbs a{color:#555}#ticketForm div,#clientLogin div{clear:both;padding:3px 0;overflow:hidden}#ticketForm div label,#clientLogin div label{display:block;width:140px;float:left}#ticketForm div label.required,#clientLogin div label.required{font-weight:bold;text-align:left}#ticketForm div input,#clientLogin div input,#ticketForm div textarea,#clientLogin div textarea{width:auto;border:1px solid #aaa;background:#fff;margin-right:10px;display:block;float:left}#ticketForm div input[type=file],#clientLogin div input[type=file]{border:0}#ticketForm div select,#clientLogin div select{display:block;float:left}#ticketForm div div.captchaRow,#clientLogin div div.captchaRow{line-height:31px}#ticketForm div div.captchaRow input,#clientLogin div div.captchaRow input{position:relative;top:6px}#ticketForm td textarea,#clientLogin td textarea,#ticketForm div textarea,#clientLogin div textarea{width:600px}#ticketForm td em,#clientLogin td em,#ticketForm div em,#clientLogin div em{color:#777}#ticketForm td .captcha,#clientLogin td .captcha,#ticketForm div .captcha,#clientLogin div .captcha{width:88px;height:31px;background:#000;display:block;float:left;margin-right:20px}#ticketForm td label.inline,#clientLogin td label.inline,#ticketForm div label.inline,#clientLogin div label.inline{width:auto;padding:0 10px}#ticketForm div.error input,#clientLogin div.error input{border:1px solid #a00}#ticketForm div.error label,#clientLogin div.error label{color:#a00}#ticketTable th{width:160px;font-weight:normal;text-align:left}#ticketTable th.required,#ticketTable td.required{font-weight:bold;text-align:left}#clientLogin{width:400px;margin-top:20px;padding:10px 100px 10px 10px;border:1px solid #ccc;background:url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6}#clientLogin p{clear:both;text-align:center}#clientLogin strong{font-size:11px;color:#d00;display:block;padding-left:140px}#clientLogin #email{width:250px;margin-right:0}#clientLogin #ticketno{width:120px;margin-right:0}#reply{margin-top:20px;padding:10px 5px;background:#f9f9f9;border:1px solid #ccc}#reply h2{margin-bottom:10px}#reply table{width:800px}#reply table td{vertical-align:top}#reply textarea{width:628px!important}#reply input[type=text],#reply #response_options textarea{border:1px solid #aaa;background:#fff}#reply .attachments .uploads div{display:inline-block;padding-right:20px}#reply .file{display:inline-block;padding-left:20px;margin-right:20px;background:url('../images/icons/file.gif') 0 50% no-repeat}.uploads{display:inline-block;padding-right:20px}.uploads label{padding:3px;padding-right:10px;width:auto!important}.Icon{width:auto;padding-left:20px;background-position:top left;background-repeat:no-repeat;color:#069;text-decoration:none}.Icon.Ticket{background-image:url('../images/icons/ticket.gif')}.Icon.webTicket{background-image:url('../images/icons/ticket_source_web.gif')}.Icon.emailTicket{background-image:url('../images/icons/ticket_source_email.gif')}.Icon.phoneTicket{background-image:url('../images/icons/ticket_source_phone.gif')}.Icon.otherTicket{background-image:url('../images/icons/ticket_source_other.gif')}.Icon.attachment{background-image:url('../images/icons/attachment.gif')}.Icon.file{background-image:url('../images/icons/attachment.gif')}.Icon.refresh{background-image:url('../images/icons/refresh.gif')}.Icon.thread{font-weight:bold;font-size:1em;background-image:url('../images/icons/thread.gif?1319556657')}.Icon:hover{text-decoration:underline}#ticketTable{border:1px solid #aaa;border-left:none;border-bottom:0}#ticketTable caption{padding:5px;text-align:left;color:#000;background:#ddd;border:1px solid #aaa;border-bottom:0;font-weight:bold}#ticketTable th{height:24px;line-height:24px;background:#e1f2ff;border:1px solid #aaa;border-right:0;border-top:0}#ticketTable th a{color:#000}#ticketTable td{padding:2px;border:1px solid #aaa;border-right:0;border-top:0}#ticketTable tr.alt td{background:#f9f9f9}#ticketSearchForm{display:inline-block;float:left;padding:0 0 5px 0}a.refresh{display:block;width:auto;float:right;height:20px;line-height:20px;text-align:center;padding:0 10px 0 28px;border:1px solid #aaa;margin-left:10px;color:#333;background-position:5px 50%;background-repeat:no-repeat;background-image:url('../images/icons/refresh.png')}.infoTable{background:#f4faff}.infoTable th{text-align:left}#ticketThread table{margin-top:10px;border:1px solid #aaa;border-bottom:2px solid #aaa}#ticketThread table th{text-align:left;border-bottom:1px solid #aaa;font-size:12px;padding:5px}#ticketThread table th span{font-weight:normal;color:#888;padding-left:20px}#ticketThread table td{padding:5px}#ticketThread .message th{background:#d8efff}#ticketThread .response th{background:#ddd}#ticketThread .info{padding:2px;background:#f9f9f9;border-top:1px solid #ddd;height:16px;line-height:16px}#ticketThread .info a{display:inline-block;margin:5px 10px 5px 0;padding-left:24px;height:16px;line-height:16px;background-position:0 50%;background-repeat:no-repeat} \ No newline at end of file diff --git a/assets/default/less/base.less b/assets/default/less/base.less new file mode 100644 index 0000000000000000000000000000000000000000..b6b4681574e7f11178100467eca70ab2ef0fa266 --- /dev/null +++ b/assets/default/less/base.less @@ -0,0 +1,131 @@ +/* Typography */ +a { + color: #0072bc; + text-decoration: none; +} + +h1 { + color: #00AEEF; + font-weight: normal; + font-size: 20px; +} + +h3 { + font-size: 16px; +} + +h2 { + font-size: 16px; + color: #999; +} + +/* Helpers */ +.centered { text-align: center;} + +.clear { clear:both; height: 1px; visibility: none;} + +.hidden { display: none;} + +.faded { color:#666;} + +/* Pagination */ +#pagination { + border: 0; + margin: 0 0 40px 0; + padding: 0; + li { + border: 0; + margin: 0; + padding: 0; + font-size: 11px; + list-style: none; + display: inline; + a { + margin-right: 2px; + display: block; + float: left; + padding: 3px 6px; + text-decoration: none; + } + a:hover { + color: #ff0084; + } + } + .previousOff, .nextOff { + color: #666; + display: block; + float: left; + font-weight: bold; + padding: 3px 4px; + } + .next a, .previous a { + font-weight: bold; + } + .active { + color: #000; + font-weight: bold; + margin-right: 2px; + display: block; + float: left; + padding: 3px 6px; + text-decoration: none; + } +} + +/* Alerts & Notices */ + +#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; } + +#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; } + +#msg_error { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; } + + +.warning { + background: #ffc; + font-style: italic; + strong { + text-transform: uppercase; + color: #a00; + font-style: normal; + } +} + +.error { + color:#f00; + input { + border:1px solid #f00; + } +} + + +.button, .button:visited { + background: #222; + display: inline-block; + font-size: 16px; + padding: 8px 16px 6px 16px; + width:160px; + text-align:center; + color: #fff; + font-weight:bold; + text-decoration: none; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.5); + -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); + text-shadow: 0 -1px 1px rgba(0,0,0,0.25); + border-bottom: 1px solid rgba(0,0,0,0.25); + position: relative; + cursor: pointer; + font-family:helvetica, arial, sans-serif; +} + +.button:hover { background-color: #111; color: #fff; } +.button:active { top: 1px; box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; } +.button, .button:visited, +.green.button, .green.button:visited { background-color: #91bd09; } +.green.button:hover { background-color: #749a02; } +.blue.button, .blue.button:visited { background-color: #00AEEF; } +.blue.button:hover { background-color: #0299d2; } diff --git a/assets/default/less/kb.less b/assets/default/less/kb.less new file mode 100644 index 0000000000000000000000000000000000000000..f31faa4d0b99d4decd83dfda5dff3adad24b5bba --- /dev/null +++ b/assets/default/less/kb.less @@ -0,0 +1,88 @@ +#kb { + margin: 2px 0; + padding: 5px; + overflow: hidden; + + > li { + padding:10px; + height:auto !important; + overflow:hidden; + margin:0; + background:url(../images/kb_category_bg.png) bottom left repeat-x; + border-bottom:1px solid #ddd; + h4 { + padding-bottom:3px; + margin-bottom:3px; + span { + color:#666; + } + a { + font-size: 14px; + } + } + } + + li { + i { + display:block; + width:32px; + height:32px; + float:left; + margin-right:6px; + background:url(../images/kb_large_folder.png) top left no-repeat; + } + } +} + +#kb-search { + padding:10px 0; + overflow:hidden; + + div { + clear:both; + overflow:hidden; + padding-top:5px; + } + + #query { + margin:0; + display:inline-block; + float:left; + width:200px; + margin-right:5px; + } + + #cid { + margin:0; + display:inline-block; + float:left; + width:200px; + margin-right:5px; + position:relative; + top:2px; + } + + #topic-id { + margin:0; + display:inline-block; + float:left; + width:410px; + } + + #searchSubmit { + margin:0; + display:inline-block; + float:left; + position:relative; + top:2px; + } + + #breadcrumbs { + color: #333; + margin-bottom: 15px; + + #breadcrumbs a { + color: #555; + } + } +} diff --git a/assets/default/less/landing-page.less b/assets/default/less/landing-page.less new file mode 100644 index 0000000000000000000000000000000000000000..cfcfcfcbf63ff4b7295372dc49b5124043e80d01 --- /dev/null +++ b/assets/default/less/landing-page.less @@ -0,0 +1,58 @@ +#landing_page { + #new_ticket { + margin-top: 40px; + width: 295px; + padding-left: 75px; + float: left; + background: url('../images/new_ticket_icon.png') top left no-repeat; + } + + #check_status { + margin-top: 40px; + width: 295px; + padding-left: 75px; + float: right; + background: url('../images/check_status_icon.png') top left no-repeat; + } +} + +/* Landing page FAQ not yet implemented. */ +#faq { + clear: both; + margin: 0; + padding: 5px; + + ol { + font-size: 15px; + margin-left: 0; + padding-left: 0; + border-top:1px solid #ddd; + + li { + list-style: none; + margin: 0; + padding:0; + color: #999; + a { + display:block; + padding:5px 0; + height:auto !important; + overflow:hidden; + margin:0; + border-bottom:1px solid #ddd; + line-height: 16px; + padding-left: 24px; + background: url('../images/icons/page.png') 0 50% no-repeat; + } + + a:hover { + background-color:#e9f5ff; + } + } + } + .article-meta { + padding:5px; + background:#fafafa; + } +} + diff --git a/assets/default/less/main-layout.less b/assets/default/less/main-layout.less new file mode 100644 index 0000000000000000000000000000000000000000..a40cac090aad2fa4f287fbfa8955d0da04f246de --- /dev/null +++ b/assets/default/less/main-layout.less @@ -0,0 +1,114 @@ +body { + background: url('../images/page_bg.png') top left repeat-x #c8c8c8; +} + +#container { + background: #fff; + width: 840px; + margin: 0 auto; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.5); +} + +#header { + position: relative; + height: 71px; + padding: 0 20px; + + #logo { + width: 220px; + height: 71px; + float: left; + } + + p { + width: 400px; + text-align: right; + margin: 0; + padding: 10px 0; + float: right; + } +} + +#nav { + margin: 0 20px; + padding: 2px 10px; + height: 20px; + background: url('../images/nav_bg.png') top left repeat-x; + border-top: 1px solid #aaa; + box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); + -moz-box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); + -webkit-box-shadow:0 3px 2px rgba(0, 0, 0, 0.4); + + li { + margin: 0; + padding: 0; + list-style: none; + display: inline; + a { + display: block; + width: auto; + float: left; + height: 20px; + line-height: 20px; + text-align: center; + padding: 0 10px 0 32px; + margin-left: 10px; + color: #333; + border-radius: 20px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + background-position: 10px 50%; + background-repeat: no-repeat; + } + + a.active, a:hover { + background-color: #dbefff; + color: #000; + } + + a:hover { + background-color: #ededed; + color: #0054a6; + } + + a.home { background-image: url('../images/icons/home.png'); } + a.kb { background-image: url('../images/icons/kb.png'); } + a.new { background-image: url('../images/icons/new.png'); } + a.status { background-image: url('../images/icons/status.png'); } + a.tickets { background-image: url('../images/icons/tix.png'); } + } +} + +#content { + padding: 20px 0; + margin: 0 20px; + height: auto !important; + height: 350px; + min-height: 350px; +} + +#footer { + text-align: center; + font-size: 11px; + color: #333; + + a { + color: #333; + } + + p { + margin: 10px 0 0 0; + } + + #poweredBy { + display: block; + width: 126px; + height: 23px; + outline: none; + text-indent: -9999px; + margin: 0 auto; + background: url('../images/poweredby.png') top left no-repeat; + } +} \ No newline at end of file diff --git a/assets/default/less/print.less b/assets/default/less/print.less new file mode 100644 index 0000000000000000000000000000000000000000..dbe21bc7e8d2d2f16cbc7f6db5974383903357f7 --- /dev/null +++ b/assets/default/less/print.less @@ -0,0 +1,30 @@ +#header, #nav, #meta, #footer, #reply, #pagination, .reload, .refresh, form, .thread, hr, #kbAttachments, .back { + display: none; +} + +th { + text-align: left; +} + +a { + color: #000; + text-decoration: none; +} + +caption { + text-align: left; + padding-bottom: 10px; + font-weight: bold; +} + +.message, .response { + border-bottom: 1px solid #000; + margin-bottom: 20px; + padding-bottom: 10px; + + th { + font-size: 12pt; + font-weight: bold; + padding-bottom: 5px; + } +} diff --git a/assets/default/less/reset.less b/assets/default/less/reset.less new file mode 100644 index 0000000000000000000000000000000000000000..8cf9581eb2516d2c21e5b7b2be0bf614e85d9592 --- /dev/null +++ b/assets/default/less/reset.less @@ -0,0 +1,117 @@ +html { + font-size: 100%; + overflow-y: scroll; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; + font-size: 13px; + line-height: 1.231; + padding: 0; +} + +body, input, select, textarea { + font-family: sans-serif; + color: #000; +} + +b, strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +small { + font-size: 85%; +} + +ul, ol { + margin: 1em 0; + padding: 0 0 0 30px; +} + +img { + border: 0; + vertical-align: middle; +} + +form { + margin: 0; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +label { + cursor: pointer; +} + +input, select, textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; + *vertical-align: middle; +} + +input { + line-height: normal; + *overflow: visible; +} + +table input { + *overflow: auto; +} + +input[type="button"], input[type="reset"], input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; +} + +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +th, td { + vertical-align: top; +} + +th { text-align: left; font-weight: normal; } + +h1, h2, h3, h4, h5, h6, form, fieldset { + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/assets/default/less/theme.less b/assets/default/less/theme.less new file mode 100644 index 0000000000000000000000000000000000000000..c3835f9e608d91533abb782a560b5f4c868cd675 --- /dev/null +++ b/assets/default/less/theme.less @@ -0,0 +1,7 @@ +@import 'reset'; +@import 'base'; +@import 'main-layout'; +@import 'landing-page'; +@import 'kb'; +@import 'ticket-forms'; +@import 'ticket'; diff --git a/assets/default/less/ticket-forms.less b/assets/default/less/ticket-forms.less new file mode 100644 index 0000000000000000000000000000000000000000..96db30b28a692d7db08e3389e2b35317f60b9859 --- /dev/null +++ b/assets/default/less/ticket-forms.less @@ -0,0 +1,170 @@ +#ticketForm, #clientLogin { + div { + clear: both; + padding: 3px 0; + overflow: hidden; + + label { + display: block; + width: 140px; + float: left; + } + + label.required { + font-weight: bold; + text-align: left; + } + + input, textarea { + width: auto; + border: 1px solid #aaa; + background: #fff; + margin-right: 10px; + display: block; + float: left; + } + + input[type=file] { + border: 0; + } + + select { + display: block; + float: left; + } + div.captchaRow { + line-height: 31px; + + input { + position: relative; + top: 6px; + } + } + + } + + td, div { + textarea { + width: 600px; + } + + em { + color: #777; + } + + .captcha { + width: 88px; + height: 31px; + background: #000; + display: block; + float: left; + margin-right: 20px; + } + + label.inline { + width: auto; + padding: 0 10px; + } + } + + div.error { + input { + border: 1px solid #a00; + } + label { + color: #a00; + } + } +} + +#ticketTable { + th { + width: 160px; + font-weight: normal; + text-align: left; + } + th.required, td.required { + font-weight: bold; + text-align: left; + } +} + +#clientLogin { + width: 400px; + margin-top: 20px; + padding: 10px 100px 10px 10px; + border: 1px solid #ccc; + background: url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6; + + p { + clear: both; + text-align: center; + } + + strong { + font-size: 11px; + color: #d00; + display: block; + padding-left: 140px; + } + + #email { + width: 250px; + margin-right: 0; + } + + #ticketno { + width: 120px; + margin-right: 0; + } +} + +#reply { + margin-top: 20px; + padding: 10px 5px; + background: #f9f9f9; + border: 1px solid #ccc; + + h2 { + margin-bottom: 10px; + } + + table { + width: 800px; + + td { + vertical-align: top; + } + } + + textarea { + width: 628px !important; + } + + input[type=text], #response_options textarea { + border: 1px solid #aaa; + background: #fff; + } + + .attachments .uploads div { + display: inline-block; + padding-right: 20px; + } + .file { + display: inline-block; + padding-left: 20px; + margin-right: 20px; + background: url('../images/icons/file.gif') 0 50% no-repeat; + } +} + +.uploads { + display:inline-block; + padding-right:20px; + + label { + padding:3px; + padding-right:10px; + width: auto !important + } +} \ No newline at end of file diff --git a/assets/default/less/ticket.less b/assets/default/less/ticket.less new file mode 100644 index 0000000000000000000000000000000000000000..d7f2d307930432c2b41e3396f716d77fca5bc3e9 --- /dev/null +++ b/assets/default/less/ticket.less @@ -0,0 +1,145 @@ +/* Ticket icons */ +.Icon { + width: auto; + padding-left: 20px; + background-position: top left; + background-repeat: no-repeat; + color: #006699; + text-decoration: none; +} + +.Icon.Ticket { background-image: url('../images/icons/ticket.gif') } +.Icon.webTicket { background-image: url('../images/icons/ticket_source_web.gif'); } +.Icon.emailTicket { background-image: url('../images/icons/ticket_source_email.gif'); } +.Icon.phoneTicket { background-image: url('../images/icons/ticket_source_phone.gif'); } +.Icon.otherTicket { background-image: url('../images/icons/ticket_source_other.gif'); } +.Icon.attachment { background-image: url('../images/icons/attachment.gif'); } +.Icon.file { background-image: url('../images/icons/attachment.gif'); } +.Icon.refresh { background-image: url('../images/icons/refresh.gif'); } + +.Icon.thread { + font-weight: bold; + font-size: 1em; + background-image: url('../images/icons/thread.gif?1319556657'); +} + +.Icon:hover { + text-decoration: underline; +} + + +#ticketTable { + border: 1px solid #aaa; + border-left: none; + border-bottom: none; + + caption { + padding: 5px; + text-align: left; + color: #000; + background: #ddd; + border: 1px solid #aaa; + border-bottom: none; + font-weight: bold; + } + + th { + height: 24px; + line-height: 24px; + background: #e1f2ff; + border: 1px solid #aaa; + border-right: none; + border-top: none; + + a { + color: #000; + } + } + + td { + padding: 2px; + border: 1px solid #aaa; + border-right: none; + border-top: none; + } + + tr.alt td { + background: #f9f9f9; + } +} + +#ticketSearchForm { + display: inline-block; + float: left; + padding: 0 0 5px 0; +} + +a.refresh { + display: block; + width: auto; + float: right; + height: 20px; + line-height: 20px; + text-align: center; + padding: 0 10px 0 28px; + border: 1px solid #aaa; + margin-left: 10px; + color: #333; + background-position: 5px 50%; + background-repeat: no-repeat; + background-image: url('../images/icons/refresh.png'); +} + +.infoTable { + background: #F4FAFF; + th { + text-align: left; + } +} + +#ticketThread { + table { + margin-top: 10px; + border: 1px solid #aaa; + border-bottom: 2px solid #aaa; + + th { + text-align: left; + border-bottom: 1px solid #aaa; + font-size: 12px; + padding: 5px; + + span { + font-weight:normal; + color:#888; + padding-left:20px; + } + } + + td { + padding: 5px; + } + } + + .message th { background: #d8efff; } + .response th { background: #ddd; } + + .info { + padding: 2px; + background: #f9f9f9; + border-top: 1px solid #ddd; + height: 16px; + line-height: 16px; + + a { + display: inline-block; + margin: 5px 10px 5px 0; + padding-left: 24px; + height: 16px; + line-height: 16px; + background-position: 0 50%; + background-repeat: no-repeat; + } + } +} + diff --git a/include/.htaccess b/include/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..3a42882788717c9ed1d5c2fcc3277d21ec13152b --- /dev/null +++ b/include/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/include/ajax.content.php b/include/ajax.content.php index e97e96a5c3d1d894f5d7cacfad702c97ced25cf6..e93030a9ed9734c586ce3df32979b4163a80be36 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -37,42 +37,46 @@ class ContentAjaxAPI extends AjaxController { function ticket_variables() { $content=' -<div style="width:600px;"> +<div style="width:680px;"> <h2>Ticket Variables</h2> - Please note that non-base variables depends on the context of use. + Please note that non-base variables depends on the context of use. Visit osTicket Wiki for up to date documentation. <br/> <table width="100%" border="0" cellspacing=1 cellpadding=2> - <tr><td width="50%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr> + <tr><td width="55%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr> <tr> - <td width="50%" valign="top"> + <td width="55%" valign="top"> <table width="100%" border="0" cellspacing=1 cellpadding=1> - <tr><td width="100">%id</td><td>Ticket ID (internal ID)</td></tr> - <tr><td>%ticket</td><td>Ticket number (external ID)</td></tr> - <tr><td>%email</td><td>Email address</td></tr> - <tr><td>%name</td><td>Full name</td></tr> - <tr><td>%subject</td><td>Subject</td></tr> - <tr><td>%topic</td><td>Help topic (web only)</td></tr> - <tr><td>%phone</td><td>Phone number | ext</td></tr> - <tr><td>%status</td><td>Status</td></tr> - <tr><td>%priority</td><td>Priority</td></tr> - <tr><td>%dept</td><td>Department</td></tr> - <tr><td>%assigned</td><td>Assigned staff or team (if any)</td></tr> - <tr><td>%createdate</td><td>Date created</td></tr> - <tr><td>%duedate</td><td>Due date</td></tr> - <tr><td>%closedate</td><td>Date closed</td></tr> + <tr><td width="130">%{ticket.id}</td><td>Ticket ID (internal ID)</td></tr> + <tr><td>%{ticket.number}</td><td>Ticket number (external ID)</td></tr> + <tr><td>%{ticket.email}</td><td>Email address</td></tr> + <tr><td>%{ticket.name}</td><td>Full name</td></tr> + <tr><td>%{ticket.subject}</td><td>Subject</td></tr> + <tr><td>%{ticket.phone}</td><td>Phone number | ext</td></tr> + <tr><td>%{ticket.status}</td><td>Status</td></tr> + <tr><td>%{ticket.priority}</td><td>Priority</td></tr> + <tr><td>%{ticket.assigned}</td><td>Assigned staff and/or team</td></tr> + <tr><td>%{ticket.create_date}</td><td>Date created</td></tr> + <tr><td>%{ticket.due_date}</td><td>Due date</td></tr> + <tr><td>%{ticket.close_date}</td><td>Date closed</td></tr> + <tr><td>%{ticket.auth_token}</td><td>Auth. token used for auto-login</td></tr> + <tr><td>%{ticket.client_link}</td><td>Client\'s ticket view link</td></tr> + <tr><td>%{ticket.staff_link}</td><td>Staff\'s ticket view link</td></tr> + <tr><td colspan="2" style="padding:5px 0 5px 0;"><em>Expandable Variables (See Wiki)</em></td></tr> + <tr><td>%{ticket.<b>topic</b>}</td><td>Help topic</td></tr> + <tr><td>%{ticket.<b>dept</b>}</td><td>Department</td></tr> + <tr><td>%{ticket.<b>staff</b>}</td><td>Assigned/closing staff</td></tr> + <tr><td>%{ticket.<b>team</b>}</td><td>Assigned/closing team</td></tr> </table> </td> <td valign="top"> <table width="100%" border="0" cellspacing=1 cellpadding=1> - <tr><td width="100">%message</td><td>Message (incoming)</td></tr> - <tr><td>%response</td><td>Response (outgoing)</td></tr> - <tr><td>%note</td><td>Internal/transfer note</td></tr> - <tr><td>%staff</td><td>Staff\'s name (alert/notices)</td></tr> - <tr><td>%assignee</td><td>Assigned staff</td></tr> - <tr><td>%assigner</td><td>Staff assigning the ticket</td></tr> - <tr><td>%url</td><td>osTicket\'s base url (FQDN)</td></tr> - <tr><td>%auth</td><td>Client authentication token</td></tr> - <tr><td>%clientlink</td><td>Client auto-login link</td></tr> + <tr><td width="100">%{message}</td><td>Incoming message</td></tr> + <tr><td>%{response}</td><td>Outgoing response</td></tr> + <tr><td>%{comments}</td><td>Assign/transfer comments</td></tr> + <tr><td>%{note}</td><td>Internal note <em>(expandable)</em></td></tr> + <tr><td>%{assignee}</td><td>Assigned staff/team</td></tr> + <tr><td>%{assigner}</td><td>Staff assigning the ticket</td></tr> + <tr><td>%{url}</td><td>osTicket\'s base url (FQDN)</td></tr> </table> </td> </tr> diff --git a/include/ajax.kbase.php b/include/ajax.kbase.php index b51d8781dff46f94409c7b797e59973862d222a9..576b50585a0da4e8006a87b92026286bd9ce2219 100644 --- a/include/ajax.kbase.php +++ b/include/ajax.kbase.php @@ -36,7 +36,7 @@ class KbaseAjaxAPI extends AjaxController { case 'json': $resp['id'] = $canned->getId(); $resp['ticket'] = $canned->getTitle(); - $resp['response'] = $ticket?$ticket->replaceTemplateVars($canned->getResponse()):$canned->getResponse(); + $resp['response'] = $ticket?$ticket->replaceVars($canned->getResponse()):$canned->getResponse(); $resp['files'] = $canned->getAttachments(); @@ -44,7 +44,7 @@ class KbaseAjaxAPI extends AjaxController { break; case 'txt': default: - $response =$ticket?$ticket->replaceTemplateVars($canned->getResponse()):$canned->getResponse(); + $response =$ticket?$ticket->replaceVars($canned->getResponse()):$canned->getResponse(); } diff --git a/include/class.banlist.php b/include/class.banlist.php index 27729b44a42f45cb0d187f1e8f2a8b731991784e..c620b105033fbc1275b6f0cb82ee69bb2c5306d3 100644 --- a/include/class.banlist.php +++ b/include/class.banlist.php @@ -27,7 +27,7 @@ class Banlist { } function isbanned($email) { - return EmailFilter::isBanned($email); + return TicketFilter::isBanned($email); } function includes($email) { @@ -50,7 +50,7 @@ class Banlist { 'name' => 'SYSTEM BAN LIST', 'isactive' => 1, 'match_all_rules' => false, - 'reject_email' => true, + 'reject_ticket' => true, 'rules' => array(), 'notes' => 'Internal list for email banning. Do not remove' ), $errors); diff --git a/include/class.canned.php b/include/class.canned.php index 52a487529c368bc158777359260d36b808dc1d90..c84950e7d6d19b062dbb3a53c0ccfe95eab96c48 100644 --- a/include/class.canned.php +++ b/include/class.canned.php @@ -35,8 +35,10 @@ class Canned { .' count(filter.id) as filters ' .' FROM '.CANNED_TABLE.' canned ' .' LEFT JOIN '.CANNED_ATTACHMENT_TABLE.' attach ON (attach.canned_id=canned.canned_id) ' - .' LEFT JOIN '.EMAIL_FILTER_TABLE.' filter ON (canned.canned_id = filter.canned_response_id) ' - .' WHERE canned.canned_id='.db_input($id); + .' LEFT JOIN '.FILTER_TABLE.' filter ON (canned.canned_id = filter.canned_response_id) ' + .' WHERE canned.canned_id='.db_input($id) + .' GROUP BY canned.canned_id'; + if(!($res=db_query($sql)) || !db_num_rows($res)) return false; @@ -100,7 +102,7 @@ class Canned { if (!$this->_filters) { $this->_filters = array(); $res = db_query( - 'SELECT name FROM '.EMAIL_FILTER_TABLE + 'SELECT name FROM '.FILTER_TABLE .' WHERE canned_response_id = '.db_input($this->getId()) .' ORDER BY name'); while ($row = db_fetch_row($res)) diff --git a/include/class.client.php b/include/class.client.php index 5c5f3035ee6b485d6862950b35924f00e0f90b97..087215ce7919e45331e5f6f813a7a56bd8e24156 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -4,7 +4,7 @@ Handles everything about client - NOTE: Please note that osTicket uses email address and ticket ID to authenticate the user*! + XXX: Please note that osTicket uses email address and ticket ID to authenticate the user*! Client is modeled on the info of the ticket used to login . Peter Rotich <peter@osticket.com> @@ -134,72 +134,94 @@ class Client { return (($id=self::getLastTicketIdByEmail($email)))?self::lookup($id, $email):null; } - /* static */ function tryLogin($ticketID, $email, $auth=null) { + /* static */ function login($ticketID, $email, $auth=null, &$errors=array()) { global $ost; + + $cfg = $ost->getConfig(); + $auth = trim($auth); + $email = trim($email); + $ticketID = trim($ticketID); # Only consider auth token for GET requests, and for GET requests, # REQUIRE the auth token - $auto_login = $_SERVER['REQUEST_METHOD'] == 'GET'; + $auto_login = ($_SERVER['REQUEST_METHOD'] == 'GET'); //Check time for last max failed login attempt strike. - $loginmsg='Invalid login'; - # XXX: SECURITY: Max attempts is enforced client-side via the PHP - # session cookie. if($_SESSION['_client']['laststrike']) { if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { - $loginmsg='Excessive failed login attempts'; - $errors['err']='You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>'; - }else{ //Timeout is over. + $errors['login'] = 'Excessive failed login attempts'; + $errors['err'] = 'You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>'; + $_SESSION['_client']['laststrike'] = time(); //renew the strike. + } else { //Timeout is over. //Reset the counter for next round of attempts after the timeout. - $_SESSION['_client']['laststrike']=null; - $_SESSION['_client']['strikes']=0; + $_SESSION['_client']['laststrike'] = null; + $_SESSION['_client']['strikes'] = 0; } } + + if($auto_login && !$auth) + $errors['login'] = 'Invalid method'; + elseif(!$ticketID || !Validator::is_email($email)) + $errors['login'] = 'Valid email and ticket number required'; + + //Bail out on error. + if($errors) return false; + //See if we can fetch local ticket id associated with the ID given - if(!$errors && is_numeric($ticketID) && Validator::is_email($email) && ($ticket=Ticket::lookupByExtId($ticketID))) { - //At this point we know the ticket is valid. + if(($ticket=Ticket::lookupByExtId($ticketID, $email)) && $ticket->getId()) { + //At this point we know the ticket ID is valid. //TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets?? //Check the email given. - # Require auth token for automatic logins - if (!$auto_login || $auth === $ticket->getAuthToken()) { - if($ticket->getId() && strcasecmp($ticket->getEmail(),$email)==0){ - //valid match...create session goodies for the client. - $user = new ClientSession($email,$ticket->getId()); - $_SESSION['_client']=array(); //clear. - $_SESSION['_client']['userID'] =$ticket->getEmail(); //Email - $_SESSION['_client']['key'] =$ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. - $_SESSION['_client']['token'] =$user->getSessionToken(); - $_SESSION['TZ_OFFSET']=$cfg->getTZoffset(); - $_SESSION['TZ_DST']=$cfg->observeDaylightSaving(); - //Log login info... - $msg=sprintf("%s/%s logged in [%s]",$ticket->getEmail(),$ticket->getExtId(),$_SERVER['REMOTE_ADDR']); - $ost->logDebug('User login', $msg); - //Redirect tickets.php - session_write_close(); - session_regenerate_id(); - @header("Location: tickets.php?id=".$ticket->getExtId()); - require_once('tickets.php'); //Just incase. of header already sent error. - exit; - } - } + + # Require auth token for automatic logins (GET METHOD). + if (!strcasecmp($ticket->getEmail(), $email) && (!$auto_login || $auth === $ticket->getAuthToken())) { + + //valid match...create session goodies for the client. + $user = new ClientSession($email,$ticket->getExtId()); + $_SESSION['_client'] = array(); //clear. + $_SESSION['_client']['userID'] = $ticket->getEmail(); //Email + $_SESSION['_client']['key'] = $ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. + $_SESSION['_client']['token'] = $user->getSessionToken(); + $_SESSION['TZ_OFFSET'] = $cfg->getTZoffset(); + $_SESSION['TZ_DST'] = $cfg->observeDaylightSaving(); + + //Log login info... + $msg=sprintf('%s/%s logged in [%s]', $ticket->getEmail(), $ticket->getExtId(), $_SERVER['REMOTE_ADDR']); + $ost->logDebug('User login', $msg); + + //Regenerate session ID. + $sid=session_id(); //Current session id. + session_regenerate_id(TRUE); //get new ID. + if(($session=$ost->getSession()) && is_object($session) && $sid) + $session->destroy($sid); + + session_write_close(); + + return $user; + + } } + //If we get to this point we know the login failed. + $errors['login'] = 'Invalid login'; $_SESSION['_client']['strikes']+=1; if(!$errors && $_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) { - $loginmsg='Access Denied'; - $errors['err']='Forgot your login info? Please <a href="open.php">open a new ticket</a>.'; - $_SESSION['_client']['laststrike']=time(); - $alert='Excessive login attempts by a client?'."\n". - 'Email: '.$_POST['lemail']."\n".'Ticket#: '.$_POST['lticket']."\n". + $errors['login'] = 'Access Denied'; + $errors['err'] = 'Forgot your login info? Please <a href="open.php">open a new ticket</a>.'; + $_SESSION['_client']['laststrike'] = time(); + $alert='Excessive login attempts by a user.'."\n". + 'Email: '.$email."\n".'Ticket#: '.$ticketID."\n". 'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n". 'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logError('Excessive login attempts (client)', $alert, ($cfg->alertONLoginError())); - }elseif($_SESSION['_client']['strikes']%2==0){ //Log every other failed login attempt as a warning. - $alert='Email: '.$_POST['lemail']."\n".'Ticket #: '.$_POST['lticket']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. + $ost->logError('Excessive login attempts (user)', $alert, ($cfg->alertONLoginError())); + } elseif($_SESSION['_client']['strikes']%2==0) { //Log every other failed login attempt as a warning. + $alert='Email: '.$email."\n".'Ticket #: '.$ticketID."\n".'IP: '.$_SERVER['REMOTE_ADDR']. "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logWarning('Failed login attempt (client)', $alert); + $ost->logWarning('Failed login attempt (user)', $alert); } + + return false; } } ?> diff --git a/include/class.config.php b/include/class.config.php index cc9b497682097d0e622dbbd410b0a4f5b0fae601..3aff085f77be0820d61c980a3445a889e7dc19d5 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -18,9 +18,8 @@ require_once(INCLUDE_DIR.'class.email.php'); class Config { - var $id=0; - var $mysqltzoffset=0; - var $config=array(); + var $id = 0; + var $config = array(); var $defaultDept; //Default Department var $defaultSLA; //Default SLA @@ -33,18 +32,27 @@ class Config { } function load($id=0) { + if(!$id && !($id=$this->getId())) return false; - $sql='SELECT * FROM '.CONFIG_TABLE + $sql='SELECT *, (TIME_TO_SEC(TIMEDIFF(NOW(), UTC_TIMESTAMP()))/3600) as db_tz_offset ' + .' FROM '.CONFIG_TABLE .' WHERE id='.db_input($id); + if(!($res=db_query($sql)) || !db_num_rows($res)) return false; - $this->config=db_fetch_array($res); - $this->id=$this->config['id']; - $this->setMysqlTZ(db_timezone()); + $this->config = db_fetch_array($res); + $this->id = $this->config['id']; + + //Get the default time zone + // We can't JOIN timezone table above due to upgrade support. + if($this->config['default_timezone_id']) + $this->config['tz_offset'] = Timezone::getOffsetById($this->config['default_timezone_id']); + else + $this->config['tz_offset'] = 0; return true; } @@ -92,17 +100,9 @@ class Config { return null; } - - function setMysqlTZ($tz) { - //TODO: Combine the 2 replace regex - if($tz=='SYSTEM') - $this->mysqltzoffset=preg_replace('/([+-]\d{2})(\d{2})/','\1',date('O')); - else - $this->mysqltzoffset=preg_replace('/([+-]\d{2})(:)(\d{2})/','\1',$tz); - } - function getMysqlTZoffset() { - return $this->mysqltzoffset; + function getDBTZoffset() { + return $this->config['db_tz_offset']; } /* Date & Time Formats */ @@ -149,7 +149,7 @@ class Config { } function getTZOffset() { - return $this->config['timezone_offset']; + return $this->config['tz_offset']; } function getPageSize() { @@ -543,35 +543,32 @@ class Config { return $this->config['upload_dir']; } - function updateSettings($vars,&$errors) { + function updateSettings($vars, &$errors) { if(!$vars || $errors) return false; switch(strtolower($vars['t'])) { - case 'general': - return $this->updateGeneralSetting($vars,$errors); - break; - case 'dates': - return $this->updateDateTimeSetting($vars,$errors); + case 'system': + return $this->updateSystemSettings($vars, $errors); break; case 'tickets': - return $this->updateTicketsSetting($vars,$errors); + return $this->updateTicketsSettings($vars, $errors); break; case 'emails': - return $this->updateEmailsSetting($vars,$errors); + return $this->updateEmailsSettings($vars, $errors); break; case 'attachments': return $this->updateAttachmentsSetting($vars,$errors); break; - case 'autoresponders': - return $this->updateAutoresponderSetting($vars,$errors); + case 'autoresp': + return $this->updateAutoresponderSettings($vars, $errors); break; case 'alerts': - return $this->updateAlertsSetting($vars,$errors); + return $this->updateAlertsSettings($vars, $errors); break; case 'kb': - return $this->updateKBSetting($vars,$errors); + return $this->updateKBSettings($vars, $errors); break; default: $errors['err']='Unknown setting option. Get technical support.'; @@ -580,7 +577,7 @@ class Config { return false; } - function updateGeneralSetting($vars, &$errors) { + function updateSystemSettings($vars, &$errors) { $f=array(); $f['helpdesk_url']=array('type'=>'string', 'required'=>1, 'error'=>'Helpdesk URl required'); @@ -589,6 +586,13 @@ class Config { $f['default_template_id']=array('type'=>'int', 'required'=>1, 'error'=>'You must select template.'); $f['staff_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes'); $f['client_session_timeout']=array('type'=>'int', 'required'=>1, 'error'=>'Enter idle time in minutes'); + //Date & Time Options + $f['time_format']=array('type'=>'string', 'required'=>1, 'error'=>'Time format required'); + $f['date_format']=array('type'=>'string', 'required'=>1, 'error'=>'Date format required'); + $f['datetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Datetime format required'); + $f['daydatetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Day, Datetime format required'); + $f['default_timezone_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Timezone required'); + if(!Validator::process($f, $vars, $errors) || $errors) return false; @@ -610,26 +614,6 @@ class Config { .',client_max_logins='.db_input($vars['client_max_logins']) .',client_login_timeout='.db_input($vars['client_login_timeout']) .',client_session_timeout='.db_input($vars['client_session_timeout']) - .',clickable_urls='.db_input(isset($vars['clickable_urls'])?1:0) - .',enable_auto_cron='.db_input(isset($vars['enable_auto_cron'])?1:0) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); - } - - function updateDateTimeSetting($vars,&$errors) { - - $f=array(); - $f['time_format']=array('type'=>'string', 'required'=>1, 'error'=>'Time format required'); - $f['date_format']=array('type'=>'string', 'required'=>1, 'error'=>'Date format required'); - $f['datetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Datetime format required'); - $f['daydatetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Day, Datetime format required'); - $f['default_timezone_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Timezone required'); - - if(!Validator::process($f,$vars,$errors) || $errors) - return false; - - $sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' .',time_format='.db_input($vars['time_format']) .',date_format='.db_input($vars['date_format']) .',datetime_format='.db_input($vars['datetime_format']) @@ -641,7 +625,7 @@ class Config { return (db_query($sql)); } - function updateTicketsSetting($vars,&$errors) { + function updateTicketsSettings($vars, &$errors) { $f=array(); @@ -658,7 +642,30 @@ class Config { $errors['enable_captcha']='PNG support required for Image Captcha'; } - if(!Validator::process($f,$vars,$errors) || $errors) + if($vars['allow_attachments']) { + + if(!ini_get('file_uploads')) + $errors['err']='The \'file_uploads\' directive is disabled in php.ini'; + + if(!is_numeric($vars['max_file_size'])) + $errors['max_file_size']='Maximum file size required'; + + if(!$vars['allowed_filetypes']) + $errors['allowed_filetypes']='Allowed file extentions required'; + + if(!($maxfileuploads=ini_get('max_file_uploads'))) + $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; + + if(!$vars['max_user_file_uploads'] || $vars['max_user_file_uploads']>$maxfileuploads) + $errors['max_user_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; + + if(!$vars['max_staff_file_uploads'] || $vars['max_staff_file_uploads']>$maxfileuploads) + $errors['max_staff_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; + } + + + + if(!Validator::process($f, $vars, $errors) || $errors) return false; $sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' @@ -676,14 +683,24 @@ class Config { .',show_answered_tickets='.db_input(isset($vars['show_answered_tickets'])?1:0) .',show_related_tickets='.db_input(isset($vars['show_related_tickets'])?1:0) .',show_notes_inline='.db_input(isset($vars['show_notes_inline'])?1:0) + .',clickable_urls='.db_input(isset($vars['clickable_urls'])?1:0) .',hide_staff_name='.db_input(isset($vars['hide_staff_name'])?1:0) + .',allow_attachments='.db_input(isset($vars['allow_attachments'])?1:0) + .',allowed_filetypes='.db_input(strtolower(preg_replace("/\n\r|\r\n|\n|\r/", '',trim($vars['allowed_filetypes'])))) + .',max_file_size='.db_input($vars['max_file_size']) + .',max_user_file_uploads='.db_input($vars['max_user_file_uploads']) + .',max_staff_file_uploads='.db_input($vars['max_staff_file_uploads']) + .',email_attachments='.db_input(isset($vars['email_attachments'])?1:0) + .',allow_email_attachments='.db_input(isset($vars['allow_email_attachments'])?1:0) + .',allow_online_attachments='.db_input(isset($vars['allow_online_attachments'])?1:0) + .',allow_online_attachments_onlogin='.db_input(isset($vars['allow_online_attachments_onlogin'])?1:0) .' WHERE id='.db_input($this->getId()); return (db_query($sql)); } - function updateEmailsSetting($vars,&$errors) { + function updateEmailsSettings($vars, &$errors) { $f=array(); $f['default_email_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default email required'); @@ -704,6 +721,7 @@ class Config { .',alert_email_id='.db_input($vars['alert_email_id']) .',default_smtp_id='.db_input($vars['default_smtp_id']) .',admin_email='.db_input($vars['admin_email']) + .',enable_auto_cron='.db_input(isset($vars['enable_auto_cron'])?1:0) .',enable_mail_polling='.db_input(isset($vars['enable_mail_polling'])?1:0) .',enable_email_piping='.db_input(isset($vars['enable_email_piping'])?1:0) .',strip_quoted_reply='.db_input(isset($vars['strip_quoted_reply'])?1:0) @@ -756,7 +774,7 @@ class Config { return (db_query($sql)); } - function updateAutoresponderSetting($vars,&$errors) { + function updateAutoresponderSettings($vars, &$errors) { if($errors) return false; @@ -771,7 +789,7 @@ class Config { } - function updateKBSetting($vars,&$errors) { + function updateKBSettings($vars, &$errors) { if($errors) return false; @@ -784,7 +802,7 @@ class Config { } - function updateAlertsSetting($vars,&$errors) { + function updateAlertsSettings($vars, &$errors) { if($vars['ticket_alert_active'] @@ -831,7 +849,6 @@ class Config { if($errors) return false; $sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',ticket_notice_active='.db_input($vars['ticket_notice_active']) .',ticket_alert_active='.db_input($vars['ticket_alert_active']) .',ticket_alert_admin='.db_input(isset($vars['ticket_alert_admin'])?1:0) .',ticket_alert_dept_manager='.db_input(isset($vars['ticket_alert_dept_manager'])?1:0) diff --git a/include/class.dept.php b/include/class.dept.php index 5516646129b348de5eba208d005c8ab87d2f4145..0eae779c9eb13f19c225f0c2ff6e15d366fb28b2 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -24,7 +24,7 @@ class Dept { var $ht; - function Dept($id){ + function Dept($id) { $this->id=0; $this->load($id); } @@ -55,24 +55,28 @@ class Dept { return true; } - function reload(){ + function reload() { return $this->load(); } - function getId(){ + function asVar() { + return $this->getName(); + } + + function getId() { return $this->id; } - function getName(){ + function getName() { return $this->ht['name']; } - function getEmailId(){ + function getEmailId() { return $this->ht['email_id']; } - function getEmail(){ + function getEmail() { if(!$this->email && $this->getEmailId()) $this->email=Email::lookup($this->getEmailId()); @@ -80,16 +84,16 @@ class Dept { return $this->email; } - function getNumStaff(){ + function getNumStaff() { return $this->ht['users']; } - function getNumUsers(){ + function getNumUsers() { return $this->getNumStaff(); } - function getNumMembers(){ + function getNumMembers() { return count($this->getMembers()); } @@ -117,11 +121,11 @@ class Dept { } - function getSLAId(){ + function getSLAId() { return $this->ht['sla_id']; } - function getSLA(){ + function getSLA() { if(!$this->sla && $this->getSLAId()) $this->sla=SLA::lookup($this->getSLAId()); @@ -164,11 +168,11 @@ class Dept { return ($this->getSignature() && $this->isPublic()); } - function getManagerId(){ + function getManagerId() { return $this->ht['manager_id']; } - function getManager(){ + function getManager() { if(!$this->manager && $this->getManagerId()) $this->manager=Staff::lookup($this->getManagerId()); @@ -176,19 +180,19 @@ class Dept { return $this->manager; } - function isPublic(){ + function isPublic() { return ($this->ht['ispublic']); } - function autoRespONNewTicket(){ + function autoRespONNewTicket() { return ($this->ht['ticket_auto_response']); } - function autoRespONNewMessage(){ + function autoRespONNewMessage() { return ($this->ht['message_auto_response']); } - function noreplyAutoResp(){ + function noreplyAutoResp() { return ($this->ht['noreply_autoresp']); } @@ -201,7 +205,7 @@ class Dept { return $this->ht; } - function getInfo(){ + function getInfo() { return $this->getHashtable(); } @@ -243,9 +247,9 @@ class Dept { } - function update($vars,&$errors){ + function update($vars, &$errors) { - if(!$this->save($this->getId(),$vars,$errors)) + if(!$this->save($this->getId(), $vars, $errors)) return false; $this->updateAllowedGroups($vars['groups']); @@ -262,7 +266,7 @@ class Dept { $id=$this->getId(); $sql='DELETE FROM '.DEPT_TABLE.' WHERE dept_id='.db_input($id).' LIMIT 1'; - if(db_query($sql) && ($num=db_affected_rows())){ + if(db_query($sql) && ($num=db_affected_rows())) { // DO SOME HOUSE CLEANING //Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes. db_query('UPDATE '.TICKET_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id)); @@ -287,7 +291,7 @@ class Dept { return $id; } - function lookup($id){ + function lookup($id) { return ($id && is_numeric($id) && ($dept = new Dept($id)) && $dept->getId()==$id)?$dept:null; } @@ -323,14 +327,14 @@ class Dept { return self::getDepartments(true); } - function create($vars,&$errors) { + function create($vars, &$errors) { if(($id=self::save(0, $vars, $errors)) && ($dept=self::lookup($id))) $dept->updateAllowedGroups($vars['groups']); return $id; } - function save($id,$vars,&$errors) { + function save($id, $vars, &$errors) { global $cfg; if($id && $id!=$vars['id']) @@ -342,11 +346,11 @@ class Dept { if(!is_numeric($vars['tpl_id'])) $errors['tpl_id']='Template selection required'; - if(!$vars['name']){ + if(!$vars['name']) { $errors['name']='Name required'; - }elseif(strlen($vars['name'])<4) { + } elseif(strlen($vars['name'])<4) { $errors['name']='Name is too short.'; - }elseif(($did=Dept::getIdByName($vars['name'])) && $did!=$id){ + } elseif(($did=Dept::getIdByName($vars['name'])) && $did!=$id) { $errors['name']='Department already exist'; } @@ -377,7 +381,7 @@ class Dept { $errors['err']='Unable to update '.Format::htmlchars($vars['name']).' Dept. Error occurred'; - }else{ + } else { $sql='INSERT INTO '.DEPT_TABLE.' '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; diff --git a/include/class.file.php b/include/class.file.php index e4012bb4b9f7665438cc28fbc3818162f48579a7..b44f7af4542343ccd55e93c2db199e06ea343278 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -27,7 +27,8 @@ class AttachmentFile { if(!$id && !($id=$this->getId())) return false; - $sql='SELECT f.*, count(DISTINCT c.canned_id) as canned, count(DISTINCT t.ticket_id) as tickets ' + $sql='SELECT id, type, size, name, hash, f.created, ' + .' count(DISTINCT c.canned_id) as canned, count(DISTINCT t.ticket_id) as tickets ' .' FROM '.FILE_TABLE.' f ' .' LEFT JOIN '.CANNED_ATTACHMENT_TABLE.' c ON(c.file_id=f.id) ' .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' t ON(t.file_id=f.id) ' @@ -90,12 +91,24 @@ class AttachmentFile { return $this->ht['hash']; } - function getBinary() { - return $this->ht['filedata']; + function open() { + return new AttachmentChunkedData($this->id); + } + + function sendData() { + $file = $this->open(); + while ($chunk = $file->read()) + echo $chunk; } function getData() { - return $this->getBinary(); + # XXX: This is horrible, and is subject to php's memory + # restrictions, etc. Don't use this function! + ob_start(); + $this->sendData(); + $data = &ob_get_contents(); + ob_end_clean(); + return $data; } function delete() { @@ -110,7 +123,7 @@ class AttachmentFile { header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); header('Content-Length: '.$this->getSize()); - echo $this->getData(); + $this->sendData(); exit(); } @@ -132,7 +145,7 @@ class AttachmentFile { header('Content-Transfer-Encoding: binary'); header('Content-Length: '.$this->getSize()); - echo $this->getBinary(); + $this->sendData(); exit(); } @@ -168,15 +181,9 @@ class AttachmentFile { if (!(db_query($sql) && ($id=db_insert_id()))) return false; - foreach (str_split($file['data'], 1024*100) as $chunk) { - $sql='UPDATE '.FILE_TABLE - .' SET filedata = CONCAT(filedata,'.db_input($chunk).')' - .' WHERE id='.db_input($id); - if(!db_query($sql)) { - db_query('DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($id).' LIMIT 1'); - return false; - } - } + $data = new AttachmentChunkedData($id); + if (!$data->write($file['data'])) + return false; return $id; } @@ -213,44 +220,56 @@ class AttachmentFile { .'SELECT file_id FROM '.FAQ_ATTACHMENT_TABLE .') still_loved' .')'); + AttachmentChunkedData::deleteOrphans(); return db_affected_rows(); } } -class AttachmentList { - function AttachmentList($table, $key) { - $this->table = $table; - $this->key = $key; +/** + * Attachments stored in the database are cut into 256kB chunks and stored + * in the FILE_CHUNK_TABLE to overcome the max_allowed_packet limitation of + * LOB fields in the MySQL database + */ +define('CHUNK_SIZE', 500*1024); # Beware if you change this... +class AttachmentChunkedData { + function AttachmentChunkedData($file) { + $this->_file = $file; + $this->_pos = 0; } - function all() { - if (!isset($this->list)) { - $this->list = array(); - $res=db_query('SELECT file_id FROM '.$this->table - .' WHERE '.$this->key); - while(list($id) = db_fetch_row($res)) { - $this->list[] = new AttachmentFile($id); - } - } - return $this->list; + function length() { + list($length) = db_fetch_row(db_query( + 'SELECT SUM(LENGTH(filedata)) FROM '.FILE_CHUNK_TABLE + .' WHERE file_id='.db_input($this->_file))); + return $length; } - - function getCount() { - return count($this->all()); + + function read() { + # Read requested length of data from attachment chunks + list($buffer) = @db_fetch_row(db_query( + 'SELECT filedata FROM '.FILE_CHUNK_TABLE.' WHERE file_id=' + .db_input($this->_file).' AND chunk_id='.$this->_pos++)); + return $buffer; } - function add($fileId) { - db_query( - 'INSERT INTO '.$this->table - .' SET '.$this->key - .' file_id='.db_input($fileId)); + function write($what, $chunk_size=CHUNK_SIZE) { + $offset=0; + for (;;) { + $block = substr($what, $offset, $chunk_size); + if (!$block) break; + if (!db_query('REPLACE INTO '.FILE_CHUNK_TABLE + .' SET filedata=0x'.bin2hex($block).', file_id=' + .db_input($this->_file).', chunk_id='.db_input($this->_pos++))) + return false; + $offset += strlen($block); + } + return true; } - function remove($fileId) { + function deleteOrpans() { db_query( - 'DELETE FROM '.$this->table - .' WHERE '.$this->key - .' AND file_id='.db_input($fileId)); + 'DELETE FROM '.FILE_CHUNK_TABLE.' WHERE file_id NOT IN ' + .'( SELECT id FROM '.FILE_TABLE.') still_loved'); + return db_affected_rows(); } } -?> diff --git a/include/class.filter.php b/include/class.filter.php index b6c81b42b60d0d4b8c67d3f7b33b1138695fa1e7..ce7d7d92cb273705bc458e32e6c7dd93cf8668e7 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -2,7 +2,7 @@ /********************************************************************* class.filter.php - Email Filter Class + Ticket Filter Peter Rotich <peter@osticket.com> Copyright (c) 2006-2012 osTicket @@ -18,7 +18,7 @@ class Filter { var $id; var $ht; - function Filter($id){ + function Filter($id) { $this->id=0; $this->load($id); } @@ -29,8 +29,8 @@ class Filter { return false; $sql='SELECT filter.*,count(rule.id) as rule_count ' - .' FROM '.EMAIL_FILTER_TABLE.' filter ' - .' LEFT JOIN '.EMAIL_FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) ' + .' FROM '.FILTER_TABLE.' filter ' + .' LEFT JOIN '.FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) ' .' WHERE filter.id='.db_input($id) .' GROUP BY filter.id'; @@ -47,27 +47,31 @@ class Filter { return $this->load($this->getId()); } - function getId(){ + function getId() { return $this->id; } - function getName(){ + function getTarget() { + return $this->ht['target']; + } + + function getName() { return $this->ht['name']; } - function getNotes(){ + function getNotes() { return $this->ht['notes']; } - function getInfo(){ + function getInfo() { return $this->ht; } - function getNumRules(){ + function getNumRules() { return $this->ht['rule_count']; } - function getExecOrder(){ + function getExecOrder() { return $this->ht['execorder']; } @@ -75,7 +79,7 @@ class Filter { return $this->ht['email_id']; } - function isActive(){ + function isActive() { return ($this->ht['isactive']); } @@ -83,23 +87,23 @@ class Filter { return !strcasecmp($this->getName(),'SYSTEM BAN LIST'); } - function getDeptId(){ + function getDeptId() { return $this->ht['dept_id']; } - function getPriorityId(){ + function getPriorityId() { return $this->ht['priority_id']; } - function getSLAId(){ + function getSLAId() { return $this->ht['sla_id']; } - function getStaffId(){ + function getStaffId() { return $this->ht['staff_id']; } - function getTeamId(){ + function getTeamId() { return $this->ht['team_id']; } @@ -107,36 +111,36 @@ class Filter { return $this->ht['canned_response_id']; } - function stopOnMatch(){ + function stopOnMatch() { return ($this->ht['stop_on_match']); } - function matchAllRules(){ + function matchAllRules() { return ($this->ht['match_all_rules']); } - function rejectEmail(){ - return ($this->ht['reject_email']); + function rejectOnMatch() { + return ($this->ht['reject_ticket']); } - function useReplyToEmail(){ + function useReplyToEmail() { return ($this->ht['use_replyto_email']); } - function disableAlerts(){ + function disableAlerts() { return ($this->ht['disable_autoresponder']); } - function sendAlerts(){ + function sendAlerts() { return (!$this->disableAlerts()); } - function getRules(){ + function getRules() { if (!$this->ht['rules']) { $rules=array(); //We're getting the rules...live because it gets cleared on update. - $sql='SELECT * FROM '.EMAIL_FILTER_RULE_TABLE.' WHERE filter_id='.db_input($this->getId()); - if(($res=db_query($sql)) && db_num_rows($res)){ + $sql='SELECT * FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($this->getId()); + if(($res=db_query($sql)) && db_num_rows($res)) { while($row=db_fetch_array($res)) $rules[]=array('w'=>$row['what'],'h'=>$row['how'],'v'=>$row['val']); } @@ -145,11 +149,11 @@ class Filter { return $this->ht['rules']; } - function getFlatRules(){ //Format used on html... I'm ashamed + function getFlatRules() { //Format used on html... I'm ashamed $info=array(); - if(($rules=$this->getRules())){ - foreach($rules as $k=>$rule){ + if(($rules=$this->getRules())) { + foreach($rules as $k=>$rule) { $i=$k+1; $info["rule_w$i"]=$rule['w']; $info["rule_h$i"]=$rule['h']; @@ -169,7 +173,7 @@ class Filter { function removeRule($what, $how, $val) { - $sql='DELETE FROM '.EMAIL_FILTER_RULE_TABLE + $sql='DELETE FROM '.FILTER_RULE_TABLE .' WHERE filter_id='.db_input($this->getId()) .' AND what='.db_input($what) .' AND how='.db_input($how) @@ -187,17 +191,19 @@ class Filter { } function containsRule($what, $how, $val) { + $val = trim($val); if (isset($this->ht['rules'])) { + $match = array("w"=>$what, "h"=>$how, "v"=>$val); foreach ($this->ht['rules'] as $rule) { - if (array("w"=>$what, "h"=>$how, "v"=>$val) == $rule) { - return True; - } + if ($match == $rule) + return true; } - return False; + return false; + } else { # Fetch from database return 0 != db_count( - "SELECT COUNT(*) FROM ".EMAIL_FILTER_RULE_TABLE + "SELECT COUNT(*) FROM ".FILTER_RULE_TABLE ." WHERE filter_id=".db_input($this->id) ." AND what=".db_input($what)." AND how=".db_input($how) ." AND val=".db_input($val) @@ -208,35 +214,41 @@ class Filter { * Simple true/false if the rules defined for this filter match the * incoming email * - * $email is an ARRAY, which has valid keys - * *from - email address of sender - * name - name of sender - * subject - subject line of the email - * body - body content of the email (no attachments, please) + * $info is an ARRAY, which has valid keys + * email - FROM email address of the ticket owner + * name - name of ticket owner + * subject - subject line of the ticket + * body - body content of the message (no attachments, please) * reply-to - reply-to email address * reply-to-name - name of sender to reply-to * headers - array of email headers - * emailid - osTicket email id of recipient + * emailId - osTicket system email id */ - function matches($email) { + function matches($info) { + + if(!$info || !is_array($info)) return false; + $what = array( - "email" => $email['email'], - "subject" => $email['subject'], + 'email' => $info['email'], + 'subject' => $info['subject'], # XXX: Support reply-to too ? - "name" => $email['name'], - "body" => $email['message'] + 'name' => $info['name'], + 'body' => $info['message'] # XXX: Support headers ); $how = array( # how => array(function, null or === this, null or !== this) - "equal" => array("strcmp", 0), - "not_equal" => array("strcmp", null, 0), - "contains" => array("strpos", null, false), - "dn_contain"=> array("strpos", false) + 'equal' => array('strcmp', 0), + 'not_equal' => array('strcmp', null, 0), + 'contains' => array('strpos', null, false), + 'dn_contain'=> array('strpos', false) ); + $match = false; # Respect configured filter email-id - if ($this->getEmailId() && $this->getEmailId() != $email['emailId']) + if ($this->getEmailId() + && !strcasecmp($this->getTarget(), 'Email') + && $this->getEmailId() != $info['emailId']) return false; foreach ($this->getRules() as $rule) { @@ -265,7 +277,7 @@ class Filter { * If the matches() method returns TRUE, send the initial ticket to this * method to apply the filter actions defined */ - function apply(&$ticket, $email=null) { + function apply(&$ticket, $info=null) { # TODO: Disable alerting # XXX: Does this imply turning it on as well? (via ->sendAlerts()) if ($this->disableAlerts()) $ticket['autorespond']=false; @@ -279,22 +291,24 @@ class Filter { # XXX: Unset the other (of staffId or teamId) (?) if ($this->getStaffId()) $ticket['staffId']=$this->getStaffId(); elseif ($this->getTeamId()) $ticket['teamId']=$this->getTeamId(); - # Override name with reply-to information from the EmailFilter + # Override name with reply-to information from the TicketFilter # match - if ($this->useReplyToEmail() && $email['reply-to']) { - $ticket['email'] = $email['reply-to']; - if ($email['reply-to-name']) - $ticket['name'] = $email['reply-to-name']; + if ($this->useReplyToEmail() && $info['reply-to']) { + $ticket['email'] = $info['reply-to']; + if ($info['reply-to-name']) + $ticket['name'] = $info['reply-to-name']; } + + # Use canned response. if ($this->getCannedResponse()) $ticket['cannedResponseId'] = $this->getCannedResponse(); } /* static */ function getSupportedMatches() { return array( - 'name'=> "Sender's Name", - 'email'=> "Sender's Email", - 'subject'=> 'Email Subject', - 'body'=> 'Email Body/Text' + 'name'=> 'Name', + 'email'=> 'Email', + 'subject'=> 'Subject', + 'body'=> 'Body/Text' ); } /* static */ function getSupportedMatchTypes() { @@ -306,7 +320,7 @@ class Filter { ); } - function update($vars,&$errors){ + function update($vars,&$errors) { if(!Filter::save($this->getId(),$vars,$errors)) return false; @@ -316,47 +330,55 @@ class Filter { return true; } - function delete(){ + function delete() { $id=$this->getId(); - $sql='DELETE FROM '.EMAIL_FILTER_TABLE.' WHERE id='.db_input($id).' LIMIT 1'; - if(db_query($sql) && ($num=db_affected_rows())){ - db_query('DELETE FROM '.EMAIL_FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id)); + $sql='DELETE FROM '.FILTER_TABLE.' WHERE id='.db_input($id).' LIMIT 1'; + if(db_query($sql) && ($num=db_affected_rows())) { + db_query('DELETE FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id)); } return $num; } /** static functions **/ - function create($vars,&$errors){ + function getTargets() { + return array( + 'Any' => 'Any', + 'Web' => 'Web Forms', + 'API' => 'API Calls', + 'Email' => 'Emails'); + } + + function create($vars,&$errors) { return Filter::save(0,$vars,$errors); } - function getIdByName($name){ + function getIdByName($name) { - $sql='SELECT id FROM '.EMAIL_FILTER_TABLE.' WHERE name='.db_input($name); + $sql='SELECT id FROM '.FILTER_TABLE.' WHERE name='.db_input($name); if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); return $id; } - function lookup($id){ + function lookup($id) { return ($id && is_numeric($id) && ($f= new Filter($id)) && $f->getId()==$id)?$f:null; } - function validate_rules($vars,&$errors){ + function validate_rules($vars,&$errors) { return self::save_rules(0,$vars,$errors); } - function save_rules($id,$vars,&$errors){ + function save_rules($id,$vars,&$errors) { $matches=array('name','email','subject','body','header'); $types=array('equal','not_equal','contains','dn_contain'); $rules=array(); for($i=1; $i<=25; $i++) { //Expecting no more than 25 rules... - if($vars["rule_w$i"] || $vars["rule_h$i"]){ + if($vars["rule_w$i"] || $vars["rule_h$i"]) { if(!$vars["rule_w$i"] || !in_array($vars["rule_w$i"],$matches)) $errors["rule_$i"]='Invalid match selection'; elseif(!$vars["rule_h$i"] || !in_array($vars["rule_h$i"],$types)) @@ -367,7 +389,7 @@ class Filter { $errors["rule_$i"]='Valid email required for the match type'; else //for everything-else...we assume it's valid. $rules[]=array('w'=>$vars["rule_w$i"],'h'=>$vars["rule_h$i"],'v'=>$vars["rule_v$i"]); - }elseif($vars["rule_v$i"]){ + }elseif($vars["rule_v$i"]) { $errors["rule_$i"]='Incomplete selection'; } } @@ -383,7 +405,7 @@ class Filter { if(!$id) return true; //When ID is 0 then assume it was just validation... //Clear existing rules...we're doing mass replace on each save!! - db_query('DELETE FROM '.EMAIL_FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id)); + db_query('DELETE FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id)); $num=0; foreach($rules as $rule) { $rule['filter_id']=$id; @@ -394,39 +416,52 @@ class Filter { return $num; } - function save($id,$vars,&$errors){ + function save($id,$vars,&$errors) { if(!$vars['execorder']) - $errors['execorder']='Order required'; + $errors['execorder'] = 'Order required'; elseif(!is_numeric($vars['execorder'])) - $errors['execorder']='Must be numeric value'; + $errors['execorder'] = 'Must be numeric value'; if(!$vars['name']) - $errors['name']='Name required'; + $errors['name'] = 'Name required'; elseif(($sid=self::getIdByName($vars['name'])) && $sid!=$id) - $errors['name']='Name already in-use'; + $errors['name'] = 'Name already in-use'; if(!$errors && !self::validate_rules($vars,$errors) && !$errors['rules']) - $errors['rules']='Unable to validate rules as entered'; + $errors['rules'] = 'Unable to validate rules as entered'; + + $targets = self::getTargets(); + if(!$vars['target']) + $errors['target'] = 'Target required'; + else if(!is_numeric($vars['target']) && !$targets[$vars['target']]) + $errors['target'] = 'Unknown or invalid target'; if($errors) return false; - $sql=' updated=NOW() '. - ',isactive='.db_input($vars['isactive']). - ',name='.db_input($vars['name']). - ',execorder='.db_input($vars['execorder']). - ',email_id='.db_input($vars['email_id']). - ',dept_id='.db_input($vars['dept_id']). - ',priority_id='.db_input($vars['priority_id']). - ',sla_id='.db_input($vars['sla_id']). - ',match_all_rules='.db_input($vars['match_all_rules']). - ',stop_onmatch='.db_input(isset($vars['stop_onmatch'])?1:0). - ',reject_email='.db_input(isset($vars['reject_email'])?1:0). - ',use_replyto_email='.db_input(isset($vars['use_replyto_email'])?1:0). - ',disable_autoresponder='.db_input(isset($vars['disable_autoresponder'])?1:0). - ',canned_response_id='.db_input($vars['canned_response_id']). - ',notes='.db_input($vars['notes']); + $emailId = 0; + if(is_numeric($vars['target'])) { + $emailId = $vars['target']; + $vars['target'] = 'Email'; + } + + $sql=' updated=NOW() ' + .',isactive='.db_input($vars['isactive']) + .',target='.db_input($vars['target']) + .',name='.db_input($vars['name']) + .',execorder='.db_input($vars['execorder']) + .',email_id='.db_input($emailId) + .',dept_id='.db_input($vars['dept_id']) + .',priority_id='.db_input($vars['priority_id']) + .',sla_id='.db_input($vars['sla_id']) + .',match_all_rules='.db_input($vars['match_all_rules']) + .',stop_onmatch='.db_input(isset($vars['stop_onmatch'])?1:0) + .',reject_ticket='.db_input(isset($vars['reject_ticket'])?1:0) + .',use_replyto_email='.db_input(isset($vars['use_replyto_email'])?1:0) + .',disable_autoresponder='.db_input(isset($vars['disable_autoresponder'])?1:0) + .',canned_response_id='.db_input($vars['canned_response_id']) + .',notes='.db_input($vars['notes']); //Auto assign ID is overloaded... @@ -438,11 +473,11 @@ class Filter { $sql.=',staff_id=0,team_id=0 '; //no auto-assignment! if($id) { - $sql='UPDATE '.EMAIL_FILTER_TABLE.' SET '.$sql.' WHERE id='.db_input($id); + $sql='UPDATE '.FILTER_TABLE.' SET '.$sql.' WHERE id='.db_input($id); if(!db_query($sql)) $errors['err']='Unable to update the filter. Internal error occurred'; }else{ - $sql='INSERT INTO '.EMAIL_FILTER_TABLE.' SET '.$sql.',created=NOW() '; + $sql='INSERT INTO '.FILTER_TABLE.' SET '.$sql.',created=NOW() '; if(!db_query($sql) || !($id=db_insert_id())) $errors['err']='Unable to add filter. Internal error'; } @@ -464,14 +499,14 @@ class FilterRule { var $filter; - function FilterRule($id,$filterId=0){ + function FilterRule($id,$filterId=0) { $this->id=0; $this->load($id,$filterId); } function load($id,$filterId=0) { - $sql='SELECT rule.* FROM '.EMAIL_FILTER_RULE_TABLE.' rule ' + $sql='SELECT rule.* FROM '.FILTER_RULE_TABLE.' rule ' .' WHERE rule.id='.db_input($id); if($filterId) $sql.=' AND rule.filter_id='.db_input($filterId); @@ -529,9 +564,9 @@ class FilterRule { return true; } - function delete(){ + function delete() { - $sql='DELETE FROM '.EMAIL_FILTER_RULE_TABLE.' WHERE id='.db_input($this->getId()).' AND filter_id='.db_input($this->getFilterId()); + $sql='DELETE FROM '.FILTER_RULE_TABLE.' WHERE id='.db_input($this->getId()).' AND filter_id='.db_input($this->getFilterId()); return (db_query($sql) && db_affected_rows()); } @@ -559,12 +594,12 @@ class FilterRule { $sql.=',notes='.db_input($vars['notes']); if($id) { - $sql='UPDATE '.EMAIL_FILTER_RULE_TABLE.' SET '.$sql.' WHERE id='.db_input($id).' AND filter_id='.db_input($vars['filter_id']); + $sql='UPDATE '.FILTER_RULE_TABLE.' SET '.$sql.' WHERE id='.db_input($id).' AND filter_id='.db_input($vars['filter_id']); if(db_query($sql)) return true; } else { - $sql='INSERT INTO '.EMAIL_FILTER_RULE_TABLE.' SET created=NOW(), filter_id='.db_input($vars['filter_id']).', '.$sql; + $sql='INSERT INTO '.FILTER_RULE_TABLE.' SET created=NOW(), filter_id='.db_input($vars['filter_id']).', '.$sql; if(db_query($sql) && ($id=db_insert_id())) return $id; } @@ -579,50 +614,65 @@ class FilterRule { } /** - * Applies rules defined in the staff control panel "Email Filters". Each + * Applies rules defined in the admin control panel > Settings tab > "Ticket Filters". Each * filter can have up to 25 rules (*currently). This will attempt to match - * the incoming email against the defined rules, and, if the email matches, - * the ticket will be modified as described in the filter + * the incoming tickets against the defined rules, and, if the email matches, + * the ticket will be modified as described in the filter actions. */ -class EmailFilter { +class TicketFilter { + + var $target; + var $vars; + /** - * Construct a list of filters to handle a new ticket generated from an - * email or something with information common to email (such as API - * calls, etc). + * Construct a list of filters to handle a new ticket + * taking into account the source/origin of the ticket. * - * $email is an ARRAY, which has valid keys - * *email - email address of sender - * name - name of sender - * subject - subject line of the email - * email-id - id of osTicket email recipient address + * $vars is an ARRAY, which has valid keys + * *email - email address of user + * name - name of user + * subject - subject of the ticket + * emailId - id of osTicket's system email (for emailed tickets) * --------------- * @see Filter::matches() for a complete list of supported keys * - * $slow - if TRUE, every (active) filter will be fetched from the - * database and matched against the email. Otherwise, a subset - * of filters from the database that appear to have rules that - * deal with the data in the email will be considered. @see - * ::quickList() for more information. + * IF $vars is not provided, every (active) filter will be fetched from the + * database and matched against the incoming ticket. Otherwise, a subset + * of filters from the database that appear to have rules that + * deal with the data in the incoming ticket (based on $vars) will be considered. + * @see ::quickList() for more information. */ - function EmailFilter($email, $slow=false) { - $this->email = $email; - if ($slow) { - $this->build($this->getAllActive()); - } else { - $this->build( - $this->quickList($email['email'], $email['name'], - $email['subject'], $email['emailId'])); - } + function TicketFilter($origin, $vars=null) { + + $this->target = self::origin2target($origin); + $this->vars = ($vars && is_array($vars))?array_filter(array_map('trim', $vars)):null; + + //Init filters. + $this->build(); } - - function build($res) { + + function build() { + + //Clear any memoized filters $this->filters = array(); - while (list($id) = db_fetch_row($res)) - array_push($this->filters, new Filter($id)); + $this->short_list = array(); + + //Query DB for "possibly" matching filters. + $res = $this->vars?$this->quickList():$this->getAllActive(); + if($res) { + while (list($id) = db_fetch_row($res)) + array_push($this->filters, new Filter($id)); + } + return $this->filters; } + + function getTarget() { + return $this->target; + } + /** - * Fetches the short list of filters that match the email received in the + * Fetches the short list of filters that match the ticket vars received in the * constructor. This function is memoized so subsequent calls will * return immediately. */ @@ -630,17 +680,18 @@ class EmailFilter { if (!isset($this->short_list)) { $this->short_list = array(); foreach ($this->filters as $filter) - if ($filter->matches($this->email)) + if ($filter->matches($this->vars)) $this->short_list[] = $filter; } + return $this->short_list; } /** - * Determine if the filters that match the received email indicate that + * Determine if the filters that match the received vars indicate that * the email should be rejected * * Returns FALSE if the email should be acceptable. If the email should - * be rejected, the first filter that matches and has rejectEmail set is + * be rejected, the first filter that matches and has reject ticket set is * returned. */ function shouldReject() { @@ -649,7 +700,7 @@ class EmailFilter { # be blocked; however, don't unset $reject, because if it # was set by another rule that did not set stopOnMatch(), we # should still honor its configuration - if ($filter->rejectEmail()) return $filter; + if ($filter->rejectOnMatch()) return $filter; } return false; } @@ -659,17 +710,21 @@ class EmailFilter { */ function apply(&$ticket) { foreach ($this->getMatchingFilterList() as $filter) { - $filter->apply($ticket, $this->email); + $filter->apply($ticket, $this->vars); if ($filter->stopOnMatch()) break; } } /* static */ function getAllActive() { - $sql="SELECT id FROM ".EMAIL_FILTER_TABLE." WHERE isactive" - ." ORDER BY execorder"; + + $sql='SELECT id FROM '.FILTER_TABLE + .' WHERE isactive=1 ' + .' AND target IN ("Any", '.db_input($this->getTarget()).') ' + .' ORDER BY execorder'; return db_query($sql); } + /** * Fast lookup function to all filters that have at least one rule that * matches the received address or name or is not defined to match based @@ -693,45 +748,55 @@ class EmailFilter { * information from the database. Whether the filter will completely * match or not is determined in the Filter::matches() method. */ - /* static */ function quickList($addr, $name=false, $subj=false, - $emailid=0) { - $sql="SELECT DISTINCT filter_id FROM ".EMAIL_FILTER_RULE_TABLE." rule" - ." INNER JOIN ".EMAIL_FILTER_TABLE." filter" - ." ON (filter.id=rule.filter_id)" - ." WHERE filter.isactive"; - # Filter by recipient email-id if specified - if ($emailid) #TODO: Fix the logic here... - $sql.=" AND filter.email_id=".db_input($emailid); + function quickList() { + + if(!$this->vars || !$this->vars['email']) + return $this->getAllActive(); + + $sql='SELECT DISTINCT filter_id FROM '.FILTER_RULE_TABLE.' rule ' + .' INNER JOIN '.FILTER_TABLE.' filter ' + .' ON (filter.id=rule.filter_id) ' + .' WHERE filter.isactive ' + ." AND filter.target IN ('Any', ".db_input($this->getTarget()).') '; + + # Filter by system's email-id if specified + if($this->vars['emailId']) + $sql.=' AND (filter.email_id=0 OR filter.email_id='.db_input($this->vars['emailId']).')'; + # Include rules for sender-email, sender-name and subject as # requested - $sql.=" AND ((what='email' AND LOCATE(val,".db_input($addr)."))"; - if ($name) - $sql.=" OR (what='name' AND LOCATE(val,".db_input($name)."))"; - if ($subj) - $sql.=" OR (what='subject' AND LOCATE(val,".db_input($subj)."))"; + $sql.=" AND ((what='email' AND LOCATE(val, ".db_input($this->vars['email']).'))'; + if($this->vars['name']) + $sql.=" OR (what='name' AND LOCATE(val, ".db_input($this->vars['name']).'))'; + if($this->vars['subject']) + $sql.=" OR (what='subject' AND LOCATE(val, ".db_input($this->vars['subject']).'))'; # Also include filters that do not have any rules concerning either # sender-email-addresses or sender-names or subjects $sql.=") OR filter.id IN (" ." SELECT filter_id " - ." FROM ".EMAIL_FILTER_RULE_TABLE." rule" - ." INNER JOIN ".EMAIL_FILTER_TABLE." filter" + ." FROM ".FILTER_RULE_TABLE." rule" + ." INNER JOIN ".FILTER_TABLE." filter" ." ON (rule.filter_id=filter.id)" + ." WHERE filter.isactive" + ." AND filter.target IN('Any', ".db_input($this->getTarget()).")" ." GROUP BY filter_id" ." HAVING COUNT(*)-COUNT(NULLIF(what,'email'))=0"; - if ($name!==false) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'name'))=0"; - if ($subj!==false) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'subject'))=0"; + if (!$this->vars['name']) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'name'))=0"; + if (!$this->vars['subject']) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'subject'))=0"; # Also include filters that do not have match_all_rules set to and # have at least one rule 'what' type that wasn't considered $sql.=") OR filter.id IN (" ." SELECT filter_id" - ." FROM ".EMAIL_FILTER_RULE_TABLE." rule" - ." INNER JOIN ".EMAIL_FILTER_TABLE." filter" + ." FROM ".FILTER_RULE_TABLE." rule" + ." INNER JOIN ".FILTER_TABLE." filter" ." ON (rule.filter_id=filter.id)" - ." WHERE what NOT IN ('email'" + ." WHERE filter.isactive" + ." AND filter.target IN ('Any', ".db_input($this->getTarget()).")" + ." AND what NOT IN ('email'" # Handle sender-name and subject if specified - .(($name!==false)?",'name'":"") - .(($subj!==false)?",'subject'":"") - .") AND filter.match_all_rules = false" + .((!$this->vars['name'])?",'name'":"") + .((!$this->vars['subject'])?",'subject'":"") + .") AND filter.match_all_rules = 0 " # Return filters in declared execution order .") ORDER BY filter.execorder"; @@ -751,10 +816,10 @@ class EmailFilter { /* static */ function isBanned($addr) { $sql='SELECT filter.id, what, how, UPPER(val) ' - .' FROM '.EMAIL_FILTER_TABLE.' filter' - .' INNER JOIN '.EMAIL_FILTER_RULE_TABLE.' rule' + .' FROM '.FILTER_TABLE.' filter' + .' INNER JOIN '.FILTER_RULE_TABLE.' rule' .' ON (filter.id=rule.filter_id)' - .' WHERE filter.reject_email' + .' WHERE filter.reject_ticket' .' AND filter.match_all_rules=0' .' AND filter.email_id=0' .' AND filter.isactive' @@ -762,20 +827,22 @@ class EmailFilter { .' AND rule.what="email"' .' AND LOCATE(rule.val,'.db_input($addr).')'; + if(!($res=db_query($sql)) || !db_num_rows($res)) + return false; + # XXX: Use MB_xxx function for proper unicode support $addr = strtoupper($addr); $how=array('equal' => array('strcmp', 0), 'contains' => array('strpos', null, false)); - - if ($res=db_query($sql)) { - while ($row=db_fetch_array($res)) { - list($func, $pos, $neg) = $how[$row['how']]; - if (!$func) continue; - $res = call_user_func($func, $addr, $row['val']); - if (($neg === null && $res === $pos) || $res !== $neg) - return $row['id']; - } + + while ($row=db_fetch_array($res)) { + list($func, $pos, $neg) = $how[$row['how']]; + if (!$func) continue; + $result = call_user_func($func, $addr, $row['val']); + if (($neg === null && $result === $pos) || $result !== $neg) + return $row['id']; } + return false; } @@ -814,5 +881,15 @@ class EmailFilter { } return false; } + + /** + * Normalize ticket source to supported filter target + * + */ + function origin2target($origin) { + $sources=array('web' => 'Web', 'email' => 'Email', 'phone' => 'Web', 'staff' => 'Web', 'api' => 'API'); + + return $sources[strtolower($origin)]; + } } ?> diff --git a/include/class.format.php b/include/class.format.php index 9de1197edbb3d4272193f67af48826851f72296e..aaa6667d32c2fce14da2cb9f5e15387ec9608b0c 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -180,7 +180,7 @@ class Format { } /* elapsed time */ - function elapsedTime($sec){ + function elapsedTime($sec) { if(!$sec || !is_numeric($sec)) return ""; @@ -197,32 +197,32 @@ class Format { /* Dates helpers...most of this crap will change once we move to PHP 5*/ function db_date($time) { global $cfg; - return Format::userdate($cfg->getDateFormat(),Misc::db2gmtime($time)); + return Format::userdate($cfg->getDateFormat(), Misc::db2gmtime($time)); } function db_datetime($time) { global $cfg; - return Format::userdate($cfg->getDateTimeFormat(),Misc::db2gmtime($time)); + return Format::userdate($cfg->getDateTimeFormat(), Misc::db2gmtime($time)); } function db_daydatetime($time) { global $cfg; - return Format::userdate($cfg->getDayDateTimeFormat(),Misc::db2gmtime($time)); + return Format::userdate($cfg->getDayDateTimeFormat(), Misc::db2gmtime($time)); } - function userdate($format,$gmtime) { - return Format::date($format,$gmtime,$_SESSION['TZ_OFFSET'],$_SESSION['TZ_DST']); + function userdate($format, $gmtime) { + return Format::date($format, $gmtime, $_SESSION['TZ_OFFSET'], $_SESSION['TZ_DST']); } - function date($format,$gmtimestamp,$offset=0,$daylight=false){ - if(!$gmtimestamp || !is_numeric($gmtimestamp)) return ""; - - $offset+=$daylight?date('I',$gmtimestamp):0; //Daylight savings crap. - return date($format,($gmtimestamp+($offset*3600))); - } - + function date($format, $gmtimestamp, $offset=0, $daylight=false){ - - + if(!$gmtimestamp || !is_numeric($gmtimestamp)) + return ""; + + $offset+=$daylight?date('I', $gmtimestamp):0; //Daylight savings crap. + + return date($format, ($gmtimestamp+ ($offset*3600))); + } + } ?> diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index fb8402169e83b39c258118329215d2459d7547a2..aa43ce89e960231bdf2e25bf0f622a24d2b44741 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -376,7 +376,7 @@ class MailFetcher { return true; //Reporting success so the email can be moved or deleted. //Is the email address banned? - if($mailinfo['email'] && EmailFilter::isBanned($mailinfo['email'])) { + if($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) { //We need to let admin know... $ost->logWarning('Ticket denied', 'Banned email - '.$mailinfo['email']); return true; //Report success (moved or delete) @@ -442,7 +442,7 @@ class MailFetcher { //This should be really a comment on message - NoT an internal note. //TODO: support comments on Messages and Responses. $error = sprintf('Attachment %s [%s] rejected because of file type', $a['name'], $a['mime']); - $ticket->postNote('Email Attachment Rejected', $error, false); + $ticket->postNote('Email Attachment Rejected', $error, 'SYSTEM', false); $ost->logDebug('Email Attachment Rejected (Ticket #'.$ticket->getExtId().')', $error); } } diff --git a/include/class.misc.php b/include/class.misc.php index 9fd6744ba8913061a5e34c2b10bf705216d286fa..8149a1fce37ea3a26106f6bb217d1c92cc17395e 100644 --- a/include/class.misc.php +++ b/include/class.misc.php @@ -35,7 +35,7 @@ class Misc { if(!$var) return; $dbtime=is_int($var)?$var:strtotime($var); - return $dbtime-($cfg->getMysqlTZoffset()*3600); + return $dbtime-($cfg->getDBTZoffset()*3600); } //Take user time or gmtime and return db (mysql) time. @@ -50,7 +50,7 @@ class Misc { $time=$time-($offset*3600); } //gm to db time - return $time+($cfg->getMysqlTZoffset()*3600); + return $time+($cfg->getDBTZoffset()*3600); } /*Helper get GM time based on timezone offset*/ diff --git a/include/class.nav.php b/include/class.nav.php index 0d5fddcbf3a48772db052a71363bfe0fb3b64ad5..eb66293770888faa572cce786b2d2b22c868ed67 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -43,7 +43,7 @@ class StaffNav { return (!$this->isAdminPanel()); } - function setTabActive($tab){ + function setTabActive($tab, $menu=''){ if($this->tabs[$tab]){ $this->tabs[$tab]['active']=true; @@ -51,6 +51,7 @@ class StaffNav { $this->tabs[$this->activetab]['active']=false; $this->activetab=$tab; + if($menu) $this->setActiveSubMenu($menu, $tab); return true; } @@ -58,16 +59,25 @@ class StaffNav { return false; } - function setActiveTab($tab){ - return $this->setTabActive($tab); + function setActiveTab($tab, $menu=''){ + return $this->setTabActive($tab, $menu); } function getActiveTab(){ return $this->activetab; } - function setActiveSubMenu($mid) { - $this->activeMenu = $mid; + function setActiveSubMenu($mid, $tab='') { + if(is_numeric($mid)) + $this->activeMenu = $mid; + elseif($mid && $tab && ($subNav=$this->getSubNav($tab))) { + foreach($subNav as $k => $menu) { + if(strcasecmp($mid, $menu['href'])) continue; + + $this->activeMenu = $k+1; + break; + } + } } function getActiveMenu() { @@ -162,12 +172,11 @@ class AdminNav extends StaffNav{ if(!$this->tabs){ $tabs=array(); - $tabs['dashboard']=array('desc'=>'Dashboard','href'=>'admin.php','title'=>'Admin Dashboard'); + $tabs['dashboard']=array('desc'=>'Dashboard','href'=>'logs.php','title'=>'Admin Dashboard'); $tabs['settings']=array('desc'=>'Settings','href'=>'settings.php','title'=>'System Settings'); + $tabs['manage']=array('desc'=>'Manage','href'=>'helptopics.php','title'=>'Manage Options'); $tabs['emails']=array('desc'=>'Emails','href'=>'emails.php','title'=>'Email Settings'); - $tabs['topics']=array('desc'=>'Help Topics','href'=>'helptopics.php','title'=>'Help Topics'); - $tabs['staff']=array('desc'=>'Staff','href'=>'staff.php','title'=>'Staff Members'); - $tabs['depts']=array('desc'=>'Departments','href'=>'departments.php','title'=>'Departments'); + $tabs['staff']=array('desc'=>'Staff','href'=>'staff.php','title'=>'Manage Staff'); $this->tabs=$tabs; } @@ -184,37 +193,32 @@ class AdminNav extends StaffNav{ $subnav[]=array('desc'=>'System Logs','href'=>'logs.php','iconclass'=>'logs'); break; case 'settings': - $subnav[]=array('desc'=>'Settings & Preferences','href'=>'settings.php','iconclass'=>'preferences'); + $subnav[]=array('desc'=>'System Preferences','href'=>'settings.php?t=system','iconclass'=>'preferences'); + $subnav[]=array('desc'=>'Tickets','href'=>'settings.php?t=tickets','iconclass'=>'ticket-settings'); + $subnav[]=array('desc'=>'Emails','href'=>'settings.php?t=emails','iconclass'=>'email-settings'); + $subnav[]=array('desc'=>'Knowledgebase','href'=>'settings.php?t=kb','iconclass'=>'kb-settings'); + $subnav[]=array('desc'=>'Autoresponder','href'=>'settings.php?t=autoresp','iconclass'=>'email-autoresponders'); + $subnav[]=array('desc'=>'Alerts & Notices','href'=>'settings.php?t=alerts','iconclass'=>'alert-settings'); + break; + case 'manage': + $subnav[]=array('desc'=>'Help Topics','href'=>'helptopics.php','iconclass'=>'helpTopics'); + $subnav[]=array('desc'=>'Ticket Filters','href'=>'filters.php', + 'title'=>'Ticket Filters','iconclass'=>'ticketFilters'); $subnav[]=array('desc'=>'SLA Plans','href'=>'slas.php','iconclass'=>'sla'); $subnav[]=array('desc'=>'API Keys','href'=>'apikeys.php','iconclass'=>'api'); break; case 'emails': - $subnav[]=array('desc'=>'Email Addresses','href'=>'emails.php','iconclass'=>'emailSettings'); - $subnav[]=array('desc'=>'Email Filters','href'=>'filters.php', - 'title'=>'Email Filters','iconclass'=>'emailFilters'); - $subnav[]=array('desc'=>'Email Banlist','href'=>'banlist.php', + $subnav[]=array('desc'=>'Emails','href'=>'emails.php', 'title'=>'Email Addresses', 'iconclass'=>'emailSettings'); + $subnav[]=array('desc'=>'Banlist','href'=>'banlist.php', 'title'=>'Banned Emails','iconclass'=>'emailDiagnostic'); - $subnav[]=array('desc'=>'Email Templates','href'=>'templates.php','title'=>'Email Templates','iconclass'=>'emailTemplates'); - $subnav[]=array('desc'=>'Email Diagnostic','href'=>'emailtest.php','iconclass'=>'emailDiagnostic'); - break; - case 'topics': - $subnav[]=array('desc'=>'Help Topics','href'=>'helptopics.php','iconclass'=>'helpTopics'); - $subnav[]=array('desc'=>'Add New Help Topics', - 'href'=>'helptopics.php?a=add', - 'iconclass'=>'newHelpTopic', - 'droponly'=>true); + $subnav[]=array('desc'=>'Templates','href'=>'templates.php','title'=>'Email Templates','iconclass'=>'emailTemplates'); + $subnav[]=array('desc'=>'Diagnostic','href'=>'emailtest.php', 'title'=>'Email Diagnostic', 'iconclass'=>'emailDiagnostic'); break; case 'staff': $subnav[]=array('desc'=>'Staff Members','href'=>'staff.php','iconclass'=>'users'); $subnav[]=array('desc'=>'Teams','href'=>'teams.php','iconclass'=>'teams'); $subnav[]=array('desc'=>'Groups','href'=>'groups.php','iconclass'=>'groups'); - break; - case 'depts': $subnav[]=array('desc'=>'Departments','href'=>'departments.php','iconclass'=>'departments'); - $subnav[]=array('desc'=>'Add New Department', - 'href'=>'departments.php?a=add', - 'iconclass'=>'newDepartment', - 'droponly'=>true); break; } if($subnav) diff --git a/include/class.osticket.php b/include/class.osticket.php index 033ec0f82accf22a97b855b39c12c4166fe580af..44f4143672b9e4b427fbace7e3ce4d1e71867f91 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -149,6 +149,17 @@ class osTicket { return (!$errors); } + /* Replace Template Variables */ + function replaceTemplateVariables($input, $vars=array()) { + + $replacer = new VariableReplacer(); + $replacer->assign(array_merge($vars, + array('url' => $this->getConfig()->getBaseUrl()) + )); + + return $replacer->replaceVars($input); + } + function addExtraHeader($header) { $this->headers[md5($header)] = $header; } diff --git a/include/class.pdf.php b/include/class.pdf.php index 2257dcc17aa6641fabeeac32a3fcecc28bd7b2c0..17c2e05886538028468952af9518e2695e51793f 100644 --- a/include/class.pdf.php +++ b/include/class.pdf.php @@ -82,12 +82,24 @@ class Ticket2PDF extends FPDF $this->Cell(0, 7, 'Page ' . ($this->PageNo() - $this->pageOffset), 0, 0, 'R'); } + function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') { + parent::Cell($w, $h, $this->_utf8($txt), $border, $ln, $align, $fill, $link); + } + function WriteText($w, $text, $border) { $this->SetFont('Times','',11); $this->MultiCell($w, 5, $text, $border, 'L'); } + + function _utf8($text) { + + if(function_exists('iconv')) + return iconv('UTF-8', 'windows-1252', $text); + + return utf8_encode($text); + } function _print() { @@ -95,7 +107,7 @@ class Ticket2PDF extends FPDF return; $w =(($this->w/2)-$this->lMargin); - $l = 40; + $l = 35; $c = $w-$l; $this->SetDrawColor(220, 220, 220); $this->SetFillColor(244, 250, 255); diff --git a/include/class.staff.php b/include/class.staff.php index 83c9ce2e58da2905c4716a28ff159dfb870074b1..778041e7ca6fd707d16b91b5f3a9a4669fe3076a 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -73,6 +73,10 @@ class Staff { return $this->load(); } + function asVar() { + return $this->getName(); + } + function getHastable() { return $this->ht; } @@ -503,7 +507,6 @@ class Staff { } /**** Static functions ********/ - function getStaffMembers($availableonly=false) { $sql='SELECT s.staff_id,CONCAT_WS(", ",s.lastname, s.firstname) as name ' @@ -555,15 +558,21 @@ class Staff { if($_SESSION['_staff']['laststrike']) { if((time()-$_SESSION['_staff']['laststrike'])<$cfg->getStaffLoginTimeout()) { - $errors['err']='You\'ve reached maximum failed login attempts allowed.'; + $errors['err']='Max. failed login attempts reached'; + $_SESSION['_staff']['laststrike'] = time(); //reset timer. } else { //Timeout is over. //Reset the counter for next round of attempts after the timeout. $_SESSION['_staff']['laststrike']=null; $_SESSION['_staff']['strikes']=0; } } + + if(!$username || !$passwd) + $errors['err'] = 'Username and password required'; + + if($errors) return false; - if(!$errors && ($user=new StaffSession($username)) && $user->getId() && $user->check_passwd($passwd)) { + if(($user=new StaffSession(trim($username))) && $user->getId() && $user->check_passwd($passwd)) { //update last login && password reset stuff. $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; if($user->isPasswdResetDue() && !$user->isAdmin()) @@ -571,15 +580,18 @@ class Staff { $sql.=' WHERE staff_id='.db_input($user->getId()); db_query($sql); //Now set session crap and lets roll baby! - $_SESSION['_staff']=array(); //clear. - $_SESSION['_staff']['userID']=$username; + $_SESSION['_staff'] = array(); //clear. + $_SESSION['_staff']['userID'] = $username; $user->refreshSession(); //set the hash. - $_SESSION['TZ_OFFSET']=$user->getTZoffset(); - $_SESSION['TZ_DST']=$user->observeDaylight(); + $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); + $_SESSION['TZ_DST'] = $user->observeDaylight(); + //Log debug info. $ost->logDebug('Staff login', sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. - $sid=session_id(); //Current ID + + //Regenerate session id. + $sid=session_id(); //Current id session_regenerate_id(TRUE); //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. if(($session=$ost->getSession()) && is_object($session) && $sid) @@ -596,14 +608,14 @@ class Staff { $errors['err']='Forgot your login info? Contact Admin.'; $_SESSION['_staff']['laststrike']=time(); $alert='Excessive login attempts by a staff member?'."\n". - 'Username: '.$_POST['username']."\n".'IP: '.$_SERVER['REMOTE_ADDR']."\n".'TIME: '.date('M j, Y, g:i a T')."\n\n". + 'Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']."\n".'TIME: '.date('M j, Y, g:i a T')."\n\n". 'Attempts #'.$_SESSION['_staff']['strikes']."\n".'Timeout: '.($cfg->getStaffLoginTimeout()/60)." minutes \n\n"; - $ost->logWarning('Excessive login attempts ('.$_POST['username'].')', $alert, ($cfg->alertONLoginError())); + $ost->logWarning('Excessive login attempts ('.$username.')', $alert, ($cfg->alertONLoginError())); } elseif($_SESSION['_staff']['strikes']%2==0) { //Log every other failed login attempt as a warning. - $alert='Username: '.$_POST['username']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. + $alert='Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']. "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_staff']['strikes']; - $ost->logWarning('Failed staff login attempt ('.$_POST['username'].')', $alert, false); + $ost->logWarning('Failed staff login attempt ('.$username.')', $alert, false); } return false; diff --git a/include/class.team.php b/include/class.team.php index cdf4cffcd3148fab082b2dd69f24733481aaeca0..367815b9637d7ab24d9e295379d11a1f6601df74 100644 --- a/include/class.team.php +++ b/include/class.team.php @@ -21,7 +21,7 @@ class Team { var $members; - function Team($id){ + function Team($id) { return $this->load($id); } @@ -47,30 +47,34 @@ class Team { return $this->id; } - function reload(){ + function reload() { return $this->load($this->getId()); } - function getId(){ + function asVar() { + return $this->getName(); + } + + function getId() { return $this->id; } - function getName(){ + function getName() { return $this->ht['name']; } - function getNumMembers(){ + function getNumMembers() { return $this->ht['members']; } - function getMembers(){ + function getMembers() { - if(!$this->members && $this->getNumMembers()){ + if(!$this->members && $this->getNumMembers()) { $sql='SELECT m.staff_id FROM '.TEAM_MEMBER_TABLE.' m ' .'LEFT JOIN '.STAFF_TABLE.' s USING(staff_id) ' .'WHERE m.team_id='.db_input($this->getId()).' AND s.staff_id IS NOT NULL ' .'ORDER BY s.lastname, s.firstname'; - if(($res=db_query($sql)) && db_num_rows($res)){ + if(($res=db_query($sql)) && db_num_rows($res)) { while(list($id)=db_fetch_row($res)) if(($staff= Staff::lookup($id))) $this->members[]= $staff; @@ -87,18 +91,18 @@ class Team { .' AND staff_id='.db_input($staff->getId())) !== 0; } - function getLeadId(){ + function getLeadId() { return $this->ht['lead_id']; } - function getTeamLead(){ + function getTeamLead() { if(!$this->lead && $this->getLeadId()) $this->lead=Staff::lookup($this->getLeadId()); return $this->lead; } - function getLead(){ + function getLead() { return $this->getTeamLead(); } @@ -106,27 +110,27 @@ class Team { return $this->ht; } - function getInfo(){ + function getInfo() { return $this->getHashtable(); } - function isEnabled(){ + function isEnabled() { return ($this->ht['isenabled']); } - function isActive(){ + function isActive() { return $this->isEnabled(); } - function update($vars,&$errors) { + function update($vars, &$errors) { //reset team lead if they're being deleted if($this->getLeadId()==$vars['lead_id'] - && $vars['remove'] && in_array($this->getLeadId(),$vars['remove'])) + && $vars['remove'] && in_array($this->getLeadId(), $vars['remove'])) $vars['lead_id']=0; //Save the changes... - if(!Team::save($this->getId(),$vars,$errors)) + if(!Team::save($this->getId(), $vars, $errors)) return false; //Delete staff marked for removal... @@ -170,12 +174,12 @@ class Team { } /* ----------- Static function ------------------*/ - function lookup($id){ + function lookup($id) { return ($id && is_numeric($id) && ($team= new Team($id)) && $team->getId()==$id)?$team:null; } - function getIdbyName($name){ + function getIdbyName($name) { $sql='SELECT team_id FROM '.TEAM_TABLE.' WHERE name='.db_input($name); if(($res=db_query($sql)) && db_num_rows($res)) @@ -201,7 +205,7 @@ class Team { .' ORDER by t.name '; } if(($res=db_query($sql)) && db_num_rows($res)) { - while(list($id,$name)=db_fetch_row($res)) + while(list($id, $name)=db_fetch_row($res)) $teams[$id] = $name; } @@ -212,20 +216,20 @@ class Team { return self::getTeams(true); } - function create($vars,&$errors) { - return self::save(0,$vars,$errors); + function create($vars, &$errors) { + return self::save(0, $vars, $errors); } - function save($id,$vars,&$errors) { + function save($id, $vars, &$errors) { if($id && $vars['id']!=$id) $errors['err']='Missing or invalid team'; if(!$vars['name']) { $errors['name']='Team name required'; - }elseif(strlen($vars['name'])<3) { + } elseif(strlen($vars['name'])<3) { $errors['name']='Team name must be at least 3 chars.'; - }elseif(($tid=Team::getIdByName($vars['name'])) && $tid!=$id){ + } elseif(($tid=Team::getIdByName($vars['name'])) && $tid!=$id) { $errors['name']='Team name already exists'; } @@ -242,7 +246,7 @@ class Team { return true; $errors['err']='Unable to update the team. Internal error'; - }else{ + } else { $sql='INSERT INTO '.TEAM_TABLE.' '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; diff --git a/include/class.template.php b/include/class.template.php index fa8d070d544c4bf5eda0da758fed814f106c529d..92cb61b54af382fad2fe5412f6d0feeb97bc66b1 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -375,6 +375,8 @@ class Template { .' ,message_alert_body='.db_input($info['message_alert_body']) .' ,note_alert_subj='.db_input($info['note_alert_subj']) .' ,note_alert_body='.db_input($info['note_alert_body']) + .' ,transfer_alert_subj='.db_input($info['transfer_alert_subj']) + .' ,transfer_alert_body='.db_input($info['transfer_alert_body']) .' ,assigned_alert_subj='.db_input($info['assigned_alert_subj']) .' ,assigned_alert_body='.db_input($info['assigned_alert_body']) .' ,ticket_overdue_subj='.db_input($info['ticket_overdue_subj']) diff --git a/include/class.thread.php b/include/class.thread.php new file mode 100644 index 0000000000000000000000000000000000000000..f830cd98ead00f6a553d8a0d5b77126b2ccfa80e --- /dev/null +++ b/include/class.thread.php @@ -0,0 +1,222 @@ +<?php +/********************************************************************* + class.thread.php + + Ticket thread + TODO: move thread related logic here from class.ticket.php + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2012 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +include_once(INCLUDE_DIR.'class.ticket.php'); + +Class ThreadEntry { + + var $id; + var $ht; + + var $staff; + var $ticket; + + function ThreadEntry($id, $type='', $ticketId=0) { + $this->load($id, $type, $ticketId); + } + + function load($id=0, $type='', $ticketId=0) { + + if(!$id && !($id=$this->getId())) + return false; + + $sql='SELECT thread.* ' + .' ,count(DISTINCT attach.attach_id) as attachments ' + .' FROM '.TICKET_THREAD_TABLE.' thread ' + .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach + ON (thread.ticket_id=attach.ticket_id + AND thread.id=attach.ref_id + AND thread.thread_type=attach.ref_type) ' + .' WHERE thread.id='.db_input($id); + + if($type) + $sql.=' AND thread.thread_type='.db_input($type); + + if($ticketId) + $sql.=' AND thread.ticket_id='.db_input($ticketId); + + $sql.=' GROUP BY thread.id '; + + if(!($res=db_query($sql)) || !db_num_rows($res)) + return false; + + $this->ht = db_fetch_array($res); + $this->id = $this->ht['id']; + + $this->staff = $this->ticket = null; + + return true; + } + + function reload() { + return $this->load(); + } + + function getId() { + return $this->id; + } + + function getPid() { + return $this->ht['pid']; + } + + function getType() { + return $this->ht['thread_type']; + } + + function getSource() { + return $this->ht['source']; + } + + function getPoster() { + return $this->ht['poster']; + } + + function getTitle() { + return $this->ht['title']; + } + + function getBody() { + return $this->ht['body']; + } + + function getCreateDate() { + return $this->ht['created']; + } + + function getUpdateDate() { + return $this->ht['updated']; + } + + function getNumAttachments() { + return $this->ht['attachments']; + } + + function getTicketId() { + return $this->ht['ticket_id']; + } + + function getTicket() { + + if(!$this->ticket && $this->getTicketId()) + $this->ticket = Ticket::lookup($this->getTicketId()); + + return $this->ticket; + } + + function getStaffId() { + return $this->ht['staff_id']; + } + + function getStaff() { + + if(!$this->staff && $this->getStaffId()) + $this->staff = Staff::lookup($this->getStaffId()); + + return $this->staff; + } + + /* variables */ + + function asVar() { + return $this->getBody(); + } + + function getVar($tag) { + global $cfg; + + if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) + return call_user_func(array($this, 'get'.ucfirst($tag))); + + switch(strtolower($tag)) { + case 'create_date': + return Format::date( + $cfg->getDateTimeFormat(), + Misc::db2gmtime($this->getCreateDate()), + $cfg->getTZOffset(), + $cfg->observeDaylightSaving()); + break; + case 'update_date': + return Format::date( + $cfg->getDateTimeFormat(), + Misc::db2gmtime($this->getUpdateDate()), + $cfg->getTZOffset(), + $cfg->observeDaylightSaving()); + break; + } + + return false; + } + + /* static calls */ + + function _lookup($id, $type='', $tid=0) { + return ($id && is_numeric($id) + && ($e = new ThreadEntry($id, $type, $tid)) && $e->getId()==$id)?$e:null; + } +} + +/* Message - Ticket thread entry of type message */ +class Message extends ThreadEntry { + + function Message($id, $ticketId=0) { + parent::ThreadEntry($id, 'M', $ticketId); + } + + function getSubject() { + return $this->getTitle(); + } + + function lookup($id, $tid) { + return parent::_lookup($id, 'M', $tid); + } +} + +/* Response - Ticket thread entry of type response */ +class Response extends ThreadEntry { + + function Response($id, $ticketId=0) { + parent::ThreadEntry($id, 'R', $ticketId); + } + + function getSubject() { + return $this->getTitle(); + } + + function getRespondent() { + return $this->getStaff(); + } + + function lookup($id, $tid) { + return parent::_lookup($id, 'R', $tid); + } +} + +/* Note - Ticket thread entry of type note (Internal Note) */ +class Note extends ThreadEntry { + + function Note($id, $ticketId=0) { + parent::ThreadEntry($id, 'N', $ticketId); + } + + function getMessage() { + return $this->getBody(); + } + + function lookup($id, $tid) { + return parent::_lookup($id, 'N', $tid); + } +} diff --git a/include/class.ticket.php b/include/class.ticket.php index 8b9581d532a1e94c8076d3eca0b82bcfe88d7d0e..9f8ce96d5649c75c9a9035aad404e9aef39fdeb8 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -13,6 +13,7 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +include_once(INCLUDE_DIR.'class.thread.php'); include_once(INCLUDE_DIR.'class.staff.php'); include_once(INCLUDE_DIR.'class.client.php'); include_once(INCLUDE_DIR.'class.team.php'); @@ -25,10 +26,11 @@ include_once(INCLUDE_DIR.'class.attachment.php'); include_once(INCLUDE_DIR.'class.pdf.php'); include_once(INCLUDE_DIR.'class.banlist.php'); include_once(INCLUDE_DIR.'class.template.php'); +include_once(INCLUDE_DIR.'class.variable.php'); include_once(INCLUDE_DIR.'class.priority.php'); include_once(INCLUDE_DIR.'class.sla.php'); -class Ticket{ +class Ticket { var $id; var $extid; @@ -198,13 +200,17 @@ class Ticket{ } //Getters - function getId(){ + function getId() { return $this->id; } - function getExtId(){ + function getExtId() { return $this->extid; } + + function getNumber() { + return $this->getExtId(); + } function getEmail(){ return $this->email; @@ -419,6 +425,11 @@ class Ticket{ return $assignees; } + function getAssigned($glue='/') { + $assignees = $this->getAssignees(); + return $assignees?implode($glue, $assignees):''; + } + function getTopicId() { return $this->topic_id; } @@ -535,7 +546,7 @@ class Ticket{ } function getResponses($msgId=0) { - return $this->getThreadByType('R', $msgID); + return $this->getThreadByType('R', $msgId); } function getNotes() { @@ -656,9 +667,9 @@ class Ticket{ //DeptId can NOT be 0. No orphans please! function setDeptId($deptId){ - + //Make sure it's a valid department// - if(!($dept=Dept::lookup($deptId))) + if(!($dept=Dept::lookup($deptId)) || $dept->getId()==$this->getDeptId()) return false; @@ -832,17 +843,17 @@ class Ticket{ if($autorespond && $email && $cfg->autoRespONNewTicket() && $dept->autoRespONNewTicket() && ($msg=$tpl->getAutoRespMsgTemplate())) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%message', $message, $body); - $body = str_replace('%signature',($dept && $dept->isPublic())?$dept->getSignature():'',$body); + $msg = $this->replaceVars($msg, + array('message' => $message, + 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'') + ); + if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $body ="\n$tag\n\n".$body; + $msg['body'] ="\n$tag\n\n".$msg['body']; //TODO: add auto flags....be nice to mail servers and sysadmins!! - $email->send($this->getEmail(),$subj,$body); + $email->send($this->getEmail(), $msg['subj'], $msg['body']); } if(!($email=$cfg->getAlertEmail())) @@ -852,17 +863,14 @@ class Ticket{ if($alertstaff && $email && $cfg->alertONNewTicket() && ($msg=$tpl->getNewTicketAlertMsgTemplate())) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%message', $message, $body); - + + $msg = $this->replaceVars($msg, array('message' => $message)); + $recipients=$sentlist=array(); - //Alert admin?? if($cfg->alertAdminONNewTicket()) { - $alert = str_replace("%staff",'Admin',$body); - $email->send($cfg->getAdminEmail(),$subj,$alert); + $alert = str_replace('%{recipient}', 'Admin', $msg['body']); + $email->send($cfg->getAdminEmail(), $msg['subj'], $alert); $sentlist[]=$cfg->getAdminEmail(); } @@ -877,8 +885,8 @@ class Ticket{ foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; - $alert = str_replace("%staff",$staff->getFirstName(),$body); - $email->send($staff->getEmail(),$subj,$alert); + $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } @@ -907,20 +915,21 @@ class Ticket{ $email=$cfg->getDefaultEmail(); if($tpl && ($msg=$tpl->getOverlimitMsgTemplate()) && $email) { - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%signature',($dept && $dept->isPublic())?$dept->getSignature():'',$body); - $email->send($this->getEmail(), $subj, $body); + + $msg = $this->replaceVars($msg, + array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); + + $email->send($this->getEmail(), $msg['subj'], $msg['body']); } $client= $this->getClient(); //Alert admin...this might be spammy (no option to disable)...but it is helpful..I think. - $msg='Max. open tickets reached for '.$this->getEmail()."\n" - .'Open ticket: '.$client->getNumOpenTickets()."\n" - .'Max Allowed: '.$cfg->getMaxOpenTickets()."\n\nNotice sent to the user."; + $alert='Max. open tickets reached for '.$this->getEmail()."\n" + .'Open ticket: '.$client->getNumOpenTickets()."\n" + .'Max Allowed: '.$cfg->getMaxOpenTickets()."\n\nNotice sent to the user."; - $ost->alertAdmin('Overlimit Notice', $msg); + $ost->alertAdmin('Overlimit Notice', $alert); return true; } @@ -958,38 +967,40 @@ class Ticket{ if(!$dept || !($tpl = $dept->getTemplate())) - $tpl= $cfg->getDefaultTemplate(); - + $tpl = $cfg->getDefaultTemplate(); + + if(!$dept || !($email = $dept->getAutoRespEmail())) + $email = $cfg->getDefaultEmail(); + //If enabled...send confirmation to user. ( New Message AutoResponse) - if($tpl && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%signature',($dept && $dept->isPublic())?$dept->getSignature():'',$body); + if($email && $tpl && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) { + + $msg = $this->replaceVars($msg, + array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); //Reply separator tag. if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $body ="\n$tag\n\n".$body; - - if(!$dept || !($email=$dept->getAutoRespEmail())) - $email=$cfg->getDefaultEmail(); - - if($email) { - $email->send($this->getEmail(),$subj,$body); - } + $msg['body'] ="\n$tag\n\n".$msg['body']; + + $email->send($this->getEmail(), $msg['subj'], $msg['body']); } } - function onAssign($note, $alert=true) { + function onAssign($assignee, $comments, $alert=true) { global $cfg, $thisstaff; if($this->isClosed()) $this->reopen(); //Assigned tickets must be open - otherwise why assign? + //Assignee must be an object of type Staff or Team + if(!$assignee || !is_object($assignee)) return false; + $this->reload(); - //Log an internal note - no alerts on the internal note. $note=$note?$note:'Ticket assignment'; - $this->postNote('Ticket Assigned to '.$this->getAssignee(),$note,false); + $assigner =$thisstaff?$thisstaff:'SYSTEM (Auto Assignment)'; + + //Log an internal note - no alerts on the internal note. + $this->postNote('Ticket Assigned to '.$assignee->getName(), $comments, $assigner, false); //See if we need to send alerts if(!$alert || !$cfg->alertONAssignment()) return true; //No alerts! @@ -998,39 +1009,39 @@ class Ticket{ //Get template. if(!$dept || !($tpl = $dept->getTemplate())) - $tpl= $cfg->getDefaultTemplate(); + $tpl = $cfg->getDefaultTemplate(); //Email to use! if(!($email=$cfg->getAlertEmail())) - $email =$cfg->getDefaultEmail(); + $email = $cfg->getDefaultEmail(); + + //recipients + $recipients=array(); + if(!strcasecmp(get_class($assignee), 'Staff')) { + if($cfg->alertStaffONAssignment()) + $recipients[] = $assignee; + } elseif(!strcasecmp(get_class($assignee), 'Team')) { + if($cfg->alertTeamMembersONAssignment() && ($members=$assignee->getMembers())) + $recipients+=$members; + elseif($cfg->alertTeamLeadONAssignment() && ($lead=$assignee->getTeamLead())) + $recipients[] = $lead; + } //Get the message template - if($tpl && ($msg=$tpl->getAssignedAlertMsgTemplate()) && $email) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%note', $note, $body); - $body = str_replace('%message', $note, $body); //Previous versions used message. - $body = str_replace('%assignee', $this->getAssignee(), $body); - $body = str_replace('%assigner', ($thisstaff)?$thisstaff->getName():'System',$body); - //recipients - $recipients=array(); - //Assigned staff or team... if any - // Assigning a ticket to a team when already assigned to staff disables alerts to the team (!)) - if($cfg->alertStaffONAssignment() && $this->getStaffId()) - $recipients[]=$this->getStaff(); - elseif($this->getTeamId() && ($team=$this->getTeam())) { - if($cfg->alertTeamMembersONAssignment() && ($members=$team->getMembers())) - $recipients+=$members; - elseif($cfg->alertTeamLeadONAssignment() && ($lead=$team->getTeamLead())) - $recipients[]=$lead; - } + if($email && $recipients && $tpl && ($msg=$tpl->getAssignedAlertMsgTemplate())) { + + $msg = $this->replaceVars($msg, + array('comments' => $comments, + 'assignee' => $assignee, + 'assigner' => $assigner + )); + //Send the alerts. $sentlist=array(); - foreach( $recipients as $k=>$staff){ + foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; - $alert = str_replace('%staff', $staff->getFirstName(), $body); - $email->send($staff->getEmail(), $subj, $alert); + $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1059,10 +1070,8 @@ class Ticket{ //Get the message template if($tpl && ($msg=$tpl->getOverdueAlertMsgTemplate()) && $email) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%comments', $comments, $body); //Planned support. + + $msg = $this->replaceVars($msg, array('comments' => $comments)); //recipients $recipients=array(); @@ -1084,51 +1093,83 @@ class Ticket{ $sentlist=array(); foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; - $alert = str_replace("%staff",$staff->getFirstName(),$body); - $email->send($staff->getEmail(),$subj,$alert); + $alert = str_replace("%{recipient}", $staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } return true; + + } + + //ticket obj as variable = ticket number. + function asVar() { + return $this->getNumber(); } - //Replace base variables. - function replaceTemplateVars($text){ + function getVar($tag) { global $cfg; - $dept = $this->getDept(); - $staff= $this->getStaff(); - $team = $this->getTeam(); - - //TODO: add new vars (team, sla...etc) - - - $search = array('/%id/','/%ticket/','/%email/','/%name/','/%subject/','/%topic/','/%phone/','/%status/','/%priority/', - '/%dept/','/%assigned_staff/','/%createdate/','/%duedate/','/%closedate/','/%url/', - '/%auth/', '/%clientlink/'); - $replace = array($this->getId(), - $this->getExtId(), - $this->getEmail(), - $this->getName(), - $this->getSubject(), - $this->getHelpTopic(), - $this->getPhoneNumber(), - $this->getStatus(), - $this->getPriority(), - ($dept?$dept->getName():''), - ($staff?$staff->getName():''), - Format::db_daydatetime($this->getCreateDate()), - Format::db_daydatetime($this->getDueDate()), - Format::db_daydatetime($this->getCloseDate()), - $cfg->getBaseUrl(), - $this->getAuthToken(), - '%url/view.php?t=%ticket&e=%email&a=%auth'); - while ($text != ($T = preg_replace($search,$replace,$text))) { - $text = $T; + if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) + return call_user_func(array($this, 'get'.ucfirst($tag))); + + switch(strtolower($tag)) { + case 'phone_number': + return $this->getPhoneNumber(); + break; + case 'auth_token': + return $this->getAuthToken(); + break; + case 'client_link': + return sprintf('%s/view.php?t=%s&e=%s&a=%s', + $cfg->getBaseUrl(), $this->getNumber(), $this->getEmail(), $this->getAuthToken()); + break; + case 'staff_link': + return sprintf('%s/scp/tickets.php?id=%d', $cfg->getBaseUrl(), $this->getId()); + break; + case 'create_date': + return Format::date( + $cfg->getDateTimeFormat(), + Misc::db2gmtime($this->getCreateDate()), + $cfg->getTZOffset(), + $cfg->observeDaylightSaving()); + break; + case 'due_date': + $duedate =''; + if($this->getDueDate()) + $duedate = Format::date( + $cfg->getDateTimeFormat(), + Misc::db2gmtime($this->getDueDate()), + $cfg->getTZOffset(), + $cfg->observeDaylightSaving()); + + return $duedate; + break; + case 'close_date'; + $closedate =''; + if($this->isClosed()) + $duedate = Format::date( + $cfg->getDateTimeFormat(), + Misc::db2gmtime($this->getCloseDate()), + $cfg->getTZOffset(), + $cfg->observeDaylightSaving()); + + return $closedate; + break; } - return $text; + + return false; + } + + //Replace base variables. + function replaceVars($input, $vars = array()) { + global $ost; + + $vars = array_merge($vars, array('ticket' => $this)); + + return $ost->replaceTemplateVariables($input, $vars); } function markUnAnswered() { @@ -1160,24 +1201,33 @@ class Ticket{ //Dept Tranfer...with alert.. done by staff function transfer($deptId, $comments, $alert = true) { + global $cfg, $thisstaff; - if(!$this->setDeptId($deptId)) + if(!$thisstaff || !$thisstaff->canTransferTickets()) return false; - + + $currentDept = $this->getDeptName(); //Current department + + if(!$deptId || !$this->setDeptId($deptId)) + return false; + + // Reopen ticket if closed + if($this->isClosed()) $this->reopen(); + + $this->reload(); // Change to SLA of the new department $this->selectSLAId(); - $currentDept = $this->getDeptName(); //XXX: add to olddept to tpl vars?? + + /*** log the transfer comments as internal note - with alerts disabled - ***/ + $title='Dept. Transfer from '.$currentDept.' to '.$this->getDeptName(); + $comments=$comments?$comments:$title; + $this->postNote($title, $comments, $thisstaff, false); - // Reopen ticket if closed - if($this->isClosed()) - $this->reopen(); + $this->logEvent('transferred'); - $this->reload(); //reload - new dept!! - $this->logEvent('transferred'); - - //Send out alerts if enabled AND requested - if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!! + //Send out alerts if enabled AND requested + if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!! //Get template. @@ -1191,10 +1241,7 @@ class Ticket{ //Get the message template if($tpl && ($msg=$tpl->getTransferAlertMsgTemplate()) && $email) { - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%note', $comments, $body); - + $msg = $this->replaceVars($msg, array('comments' => $comments, 'staff' => $thisstaff)); //recipients $recipients=array(); //Assigned staff or team... if any @@ -1216,8 +1263,8 @@ class Ticket{ $sentlist=array(); foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; - $alert = str_replace("%staff",$staff->getFirstName(),$body); - $email->send($staff->getEmail(),$subj,$alert); + $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1233,7 +1280,7 @@ class Ticket{ if(!$this->setStaffId($staff->getId())) return false; - $this->onAssign($note, $alert); + $this->onAssign($staff, $note, $alert); $this->logEvent('assigned'); return true; @@ -1252,7 +1299,7 @@ class Ticket{ if($this->isClosed()) $this->setStaffId(0); - $this->onAssign($note, $alert); + $this->onAssign($team, $note, $alert); $this->logEvent('assigned'); return true; @@ -1331,7 +1378,7 @@ class Ticket{ if($newticket) return $msgid; //Our work is done... $autorespond = true; - if ($autorespond && $headers && EmailFilter::isAutoResponse(Mail_Parse::splitHeaders($headers))) + if ($autorespond && $headers && TicketFilter::isAutoResponse(Mail_Parse::splitHeaders($headers))) $autorespond=false; $this->onMessage($autorespond); //must be called b4 sending alerts to staff. @@ -1347,9 +1394,7 @@ class Ticket{ //If enabled...send alert to staff (New Message Alert) if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) { - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace("%message", $message,$body); + $msg = $this->replaceVars($msg, array('message' => $message)); //Build list of recipients and fire the alerts. $recipients=array(); @@ -1369,8 +1414,8 @@ class Ticket{ $sentlist=array(); //I know it sucks...but..it works. foreach( $recipients as $k=>$staff){ if(!$staff || !$staff->getEmail() || !$staff->isAvailable() && in_array($staff->getEmail(),$sentlist)) continue; - $alert = str_replace("%staff",$staff->getFirstName(),$body); - $email->send($staff->getEmail(),$subj,$alert); + $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1380,9 +1425,7 @@ class Ticket{ /* public */ function postReply($vars, $errors, $alert = true) { - global $thisstaff,$cfg; - - if(!$thisstaff || !$thisstaff->isStaff() || !$cfg) return 0; + global $thisstaff, $cfg; if(!$vars['msgId']) $errors['msgId'] ='Missing messageId - internal error'; @@ -1391,14 +1434,16 @@ class Ticket{ if($errors) return 0; + $poster = $thisstaff?$thisstaff->getName():'SYSTEM (Canned Response)'; + $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() ' .' ,thread_type="R"' .' ,ticket_id='.db_input($this->getId()) .' ,pid='.db_input($vars['msgId']) .' ,body='.db_input(Format::striptags($vars['response'])) - .' ,staff_id='.db_input($thisstaff->getId()) - .' ,poster='.db_input($thisstaff->getName()) - .' ,ip_address='.db_input($thisstaff->getIP()); + .' ,staff_id='.db_input($thisstaff?$thisstaff->getId():0) + .' ,poster='.db_input($poster) + .' ,ip_address='.db_input($thisstaff?$thisstaff->getIP():''); if(!db_query($sql) || !($respId=db_insert_id())) return false; @@ -1435,26 +1480,24 @@ class Ticket{ $email = $cfg->getDefaultEmail(); if($tpl && ($msg=$tpl->getReplyMsgTemplate()) && $email) { - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%response',$vars['response'],$body); - if($vars['signature']=='mine') + if($thisstaff && $vars['signature']=='mine') $signature=$thisstaff->getSignature(); elseif($vars['signature']=='dept' && $dept && $dept->isPublic()) $signature=$dept->getSignature(); else $signature=''; - - $body = str_replace("%signature",$signature,$body); + + $msg = $this->replaceVars($msg, + array('response' => $vars['response'], 'signature' => $signature, 'staff' => $thisstaff)); if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) - $body ="\n$tag\n\n".$body; + $msg['body'] ="\n$tag\n\n".$msg['body']; //Set attachments if emailing. $attachments =($cfg->emailAttachments() && $attachments)?$this->getAttachments($respId,'R'):array(); //TODO: setup 5 param (options... e.g mid trackable on replies) - $email->send($this->getEmail(), $subj, $body, $attachments); + $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments); } return $respId; @@ -1467,7 +1510,7 @@ class Ticket{ if(!$cfg || !$cfg->logTicketActivity()) return 0; - return $this->postNote($title,$note,false,'System'); + return $this->postNote($title, $note, 'SYSTEM', false); } // History log -- used for statistics generation (pretty reports) @@ -1499,22 +1542,30 @@ class Ticket{ } //Insert Internal Notes - function postNote($title,$note,$alert=true,$poster='') { - global $thisstaff,$cfg; - - $poster=($poster || !$thisstaff)?$poster:$thisstaff->getName(); + function postNote($title, $body, $poster, $alert=true) { + global $cfg; + $staffId = 0; + if($poster && is_object($poster)) { + $staffId = $poster->getId(); + $poster = $poster->getName(); + } + + //TODO: move to class.thread.php + $sql= 'INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '. ',thread_type="N"'. ',ticket_id='.db_input($this->getId()). ',title='.db_input(Format::striptags($title)). - ',body='.db_input(Format::striptags($note)). - ',staff_id='.db_input($thisstaff?$thisstaff->getId():0). + ',body='.db_input(Format::striptags($body)). + ',staff_id='.db_input($staffId). ',poster='.db_input($poster); //echo $sql; if(!db_query($sql) || !($id=db_insert_id())) return false; + $note = Note::lookup($id, $this->getId()); + // If alerts are not enabled then return a success. if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept())) return $id; @@ -1527,12 +1578,8 @@ class Ticket{ if($tpl && ($msg=$tpl->getNoteAlertMsgTemplate()) && $email) { - - $body=$this->replaceTemplateVars($msg['body']); - $subj=$this->replaceTemplateVars($msg['subj']); - $body = str_replace('%note',"$title\n\n$note",$body); - # TODO: Support a variable replacement of the staff writing the - # note + + $msg = $this->replaceVars($msg, array('note' => $note)); // Alert recipients $recipients=array(); @@ -1552,9 +1599,9 @@ class Ticket{ $sentlist=array(); foreach( $recipients as $k=>$staff) { if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue; - if(in_array($staff->getEmail(),$sentlist) || ($thisstaff && $thisstaff->getId()==$staff->getId())) continue; - $alert = str_replace('%staff',$staff->getFirstName(),$body); - $email->send($staff->getEmail(),$subj,$alert); + if(in_array($staff->getEmail(),$sentlist) || ($staffId && $staffId==$staff->getId())) continue; + $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']); + $email->send($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1590,7 +1637,7 @@ class Ticket{ else $error ='Error #'.$file['error']; - $this->postNote('File Upload Error', $error, false); + $this->postNote('File Upload Error', $error, 'SYSTEM', false); $ost->logDebug('File Upload Error (Ticket #'.$this->getExtId().')', $error); } @@ -1711,7 +1758,7 @@ class Ticket{ if(!$vars['note']) $vars['note']=sprintf('Ticket Updated by %s', $thisstaff->getName()); - $this->postNote('Ticket Updated', $vars['note']); + $this->postNote('Ticket Updated', $vars['note'], $thisstaff); $this->reload(); return true; @@ -1719,8 +1766,17 @@ class Ticket{ /*============== Static functions. Use Ticket::function(params); ==================*/ - function getIdByExtId($extid) { - $sql ='SELECT ticket_id FROM '.TICKET_TABLE.' ticket WHERE ticketID='.db_input($extid); + function getIdByExtId($extId, $email=null) { + + if(!$extId || !is_numeric($extId)) + return 0; + + $sql ='SELECT ticket_id FROM '.TICKET_TABLE.' ticket ' + .' WHERE ticketID='.db_input($extId); + + if($email) + $sql.=' AND email='.db_input($email); + if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); @@ -1733,8 +1789,8 @@ class Ticket{ return ($id && is_numeric($id) && ($ticket= new Ticket($id)) && $ticket->getId()==$id)?$ticket:null; } - function lookupByExtId($id) { - return self::lookup(self:: getIdByExtId($id)); + function lookupByExtId($id, $email=null) { + return self::lookup(self:: getIdByExtId($id, $email)); } function genExtRandID() { @@ -1845,7 +1901,7 @@ class Ticket{ if ($vars['email'] && Validator::is_email($vars['email'])) { //Make sure the email address is not banned - if(EmailFilter::isBanned($vars['email'])) { + if(TicketFilter::isBanned($vars['email'])) { $errors['err']='Ticket denied. Error #403'; $ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']); return 0; @@ -1865,12 +1921,15 @@ class Ticket{ return 0; } } + + //Init ticket filters... + $ticket_filter = new TicketFilter($origin, $vars); // Make sure email contents should not be rejected - if (($email_filter=new EmailFilter($vars)) - && ($filter=$email_filter->shouldReject())) { + if($ticket_filter + && ($filter=$ticket_filter->shouldReject())) { $errors['err']='Ticket denied. Error #403'; $ost->logWarning('Ticket denied', - sprintf('Banned email - %s by filter "%s"', + sprintf('Ticket rejected ( %s) by filter "%s"', $vars['email'], $filter->getName())); return 0; @@ -1915,7 +1974,7 @@ class Ticket{ } //Make sure the due date is valid - if($vars['duedate']){ + if($vars['duedate']) { if(!$vars['time'] || strpos($vars['time'],':')===false) $errors['time']='Select time'; elseif(strtotime($vars['duedate'].' '.$vars['time'])===false) @@ -1924,16 +1983,16 @@ class Ticket{ $errors['duedate']='Due date must be in the future'; } - # Perform email filter actions on the new ticket arguments XXX: Move filter to the top and check for reject... - if (!$errors && $email_filter) $email_filter->apply($vars); + //Any error above is fatal. + if($errors) return 0; + + # Perform ticket filter actions on the new ticket arguments + if ($ticket_filter) $ticket_filter->apply($vars); # Some things will need to be unpacked back into the scope of this # function if (isset($vars['autorespond'])) $autorespond=$vars['autorespond']; - //Any error above is fatal. - if($errors) return 0; - // OK...just do it. $deptId=$vars['deptId']; //pre-selected Dept if any. $priorityId=$vars['priorityId']; @@ -1945,17 +2004,17 @@ class Ticket{ $priorityId=$priorityId?$priorityId:$topic->getPriorityId(); if($autorespond) $autorespond=$topic->autoRespond(); $source=$vars['source']?$vars['source']:'Web'; + if (!isset($vars['staffId']) && $topic->getStaffId()) + $vars['staffId'] = $topic->getStaffId(); + elseif (!isset($vars['teamId']) && $topic->getTeamId()) + $vars['teamId'] = $topic->getTeamId(); }elseif($vars['emailId'] && !$vars['deptId'] && ($email=Email::lookup($vars['emailId']))) { //Emailed Tickets $deptId=$email->getDeptId(); $priorityId=$priorityId?$priorityId:$email->getPriorityId(); if($autorespond) $autorespond=$email->autoRespond(); $email=null; $source='Email'; - }elseif($vars['deptId']){ //Opened by staff. - $deptId=$vars['deptId']; - $source=ucfirst($vars['source']); } - //Last minute checks $priorityId=$priorityId?$priorityId:$cfg->getDefaultPriorityId(); $deptId=$deptId?$deptId:$cfg->getDefaultDeptId(); @@ -2019,7 +2078,7 @@ class Ticket{ # Messages that are clearly auto-responses from email systems should # not have a return 'ping' message if ($autorespond && $vars['header'] && - EmailFilter::isAutoResponse(Mail_Parse::splitHeaders($vars['header']))) { + TicketFilter::isAutoResponse(Mail_Parse::splitHeaders($vars['header']))) { $autorespond=false; } @@ -2040,7 +2099,7 @@ class Ticket{ array( 'msgId' => $msgid, 'response' => - $ticket->replaceTemplateVars($canned->getResponse()), + $ticket->replaceVars($canned->getResponse()), 'cannedattachments' => $files ),$errors, true); @@ -2088,7 +2147,7 @@ class Ticket{ // post response - if any if($vars['response']) { - $vars['response']=$ticket->replaceTemplateVars($vars['response']); + $vars['response'] = $ticket->replaceVars($vars['response']); if(($respId=$ticket->postReply($vars, $errors, false))) { //Only state supported is closed on response if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets()) @@ -2097,16 +2156,16 @@ class Ticket{ } //Post Internal note if($vars['assignId'] && $thisstaff->canAssignTickets()) { //Assign ticket to staff or team. - $ticket->assign($vars['assignId'],$vars['note']); + $ticket->assign($vars['assignId'], $vars['note']); } elseif($vars['note']) { //Not assigned...save optional note if any - $ticket->postNote('New Ticket',$vars['note'],false); + $ticket->postNote('New Ticket', $vars['note'], $thisstaff, false); } else { //Not assignment and no internal note - log activity $ticket->logActivity('New Ticket by Staff','Ticket created by staff -'.$thisstaff->getName()); } $ticket->reload(); - if(!$cfg->notifyONNewStaffTicket() || !isset($var['alertuser'])) + if(!$cfg->notifyONNewStaffTicket() || !isset($vars['alertuser'])) return $ticket; //No alerts. //Send Notice to user --- if requested AND enabled!! @@ -2120,10 +2179,9 @@ class Ticket{ if($tpl && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) && $email) { - $message =$vars['issue']."\n\n".$vars['response']; - $body=$ticket->replaceTemplateVars($msg['body']); - $subj=$ticket->replaceTemplateVars($msg['subj']); - $body = str_replace('%message',$message,$body); + $message = $vars['issue']; + if($vars['response']) + $message.="\n\n".$vars['response']; if($vars['signature']=='mine') $signature=$thisstaff->getSignature(); @@ -2131,14 +2189,15 @@ class Ticket{ $signature=$dept->getSignature(); else $signature=''; - - $body = str_replace('%signature',$signature,$body); + + $msg = $ticket->replaceVars($msg, + array('message' => $message, 'signature' => $signature)); if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator()))) - $body ="\n$tag\n\n".$body; + $msg['body'] ="\n$tag\n\n".$msg['body']; - $attachments =($cfg->emailAttachments() && $respId)?$this->getAttachments($respId,'R'):array(); - $email->send($ticket->getEmail(), $subj, $body, $attachments); + $attachments =($cfg->emailAttachments() && $respId)?$ticket->getAttachments($respId,'R'):array(); + $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments); } return $ticket; diff --git a/include/class.topic.php b/include/class.topic.php index c8e6a9e72b9a8d26da82e97f48cfc9f25e97d51c..900ff76168363c47e9b4763fee6cf2f00334a0fd 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -48,6 +48,10 @@ class Topic { function reload() { return $this->load(); } + + function asVar() { + return $this->getName(); + } function getId() { return $this->id; diff --git a/include/class.upgrader.php b/include/class.upgrader.php index 7fa6c331ff542c44e7f4329c4e164f0848cf837e..2c898f646d7dd392790a55007774be1ae54c92a3 100644 --- a/include/class.upgrader.php +++ b/include/class.upgrader.php @@ -269,8 +269,6 @@ class Upgrader extends SetupWizard { $tasks=array(); switch($phash) { //Add patch specific scripted tasks. case 'c00511c7-7be60a84': //V1.6 ST- 1.7 * {{MD5('1.6 ST') -> c00511c7c1db65c0cfad04b4842afc57}} - $tasks[] = array('func' => 'migrateAttachments2DB', - 'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.'); $tasks[] = array('func' => 'migrateSessionFile2DB', 'desc' => 'Transitioning to db-backed sessions'); break; @@ -282,6 +280,10 @@ class Upgrader extends SetupWizard { $tasks[] = array('func' => 'migrateGroupDeptAccess', 'desc' => 'Migrating group\'s department access to a new table'); break; + case '15b30765-dd0022fb': + $tasks[] = array('func' => 'migrateAttachments2DB', + 'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.'); + break; } //Check IF SQL cleanup exists. diff --git a/include/class.variable.php b/include/class.variable.php new file mode 100644 index 0000000000000000000000000000000000000000..f83ba61be9063d385943b4fe4f4fdc751a58f422 --- /dev/null +++ b/include/class.variable.php @@ -0,0 +1,147 @@ +<?php +/********************************************************************* + class.variable.php + + Variable replacer + + Used to parse, resolve and replace variables. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2012 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +class VariableReplacer { + + var $start_delim; + var $end_delim; + + var $objects; + var $variables; + + var $errors; + + function VariableReplacer($start_delim='%{', $end_delim='}') { + + $this->start_delim = $start_delim; + $this->end_delim = $end_delim; + + $this->objects = array(); + $this->variables = array(); + } + + function setError($error) { + $this->errors[] = $error; + } + + function getErrors() { + return $this->errors; + } + + function getObj($tag) { + return @$this->objects[$tag]; + } + + function assign($var, $val='') { + + if($val && is_object($val)) { + $this->objects[$var] = $val; + } elseif($var && is_array($var)) { + foreach($var as $k => $v) + $this->assign($k, $v); + } elseif($var) { + $this->variables[$var] = $val; + } + } + + function getVar($obj, $var) { + + if(!$obj) return ""; + + if(!$var && is_callable(array($obj, 'asVar'))) + return call_user_func(array($obj, 'asVar')); + + list($v, $part) = explode('.', $var, 2); + if($v && is_callable(array($obj, 'get'.ucfirst($v)))) { + $rv = call_user_func(array($obj, 'get'.ucfirst($v))); + if(!$rv || !is_object($rv)) + return $rv; + + return $this->getVar($rv, $part); + } + + if(!$var || !is_callable(array($obj, 'getVar'))) + return ""; + + $parts = explode('.', $var); + if(($rv = call_user_func(array($obj, 'getVar'), $parts[0]))===false) + return ""; + + if(!is_object($rv)) + return $rv; + + list(, $part) = explode('.', $var, 2); + + return $this->getVar($rv, $part); + } + + function replaceVars($input) { + + if($input && is_array($input)) + return array_map(array($this, 'replaceVars'), $input); + + if(!($vars=$this->_parse($input))) + return $input; + + return preg_replace($this->_delimit(array_keys($vars)), array_values($vars), $input); + } + + function _resolveVar($var) { + + //Variable already memoized? + if($var && @isset($this->variables[$var])) + return $this->variables[$var]; + + $parts = explode('.', $var, 2); + if($parts && ($obj=$this->getObj($parts[0]))) + return $this->getVar($obj, $parts[1]); + elseif($parts[0] && @isset($this->variables[$parts[0]])) //root overwrite + return $this->variables[$parts[0]]; + + //Unknown object or variable - leavig it alone. + $this->setError('Unknown obj for "'.$var.'" tag '); + return false; + } + + function _parse($text) { + + $input = $text; + if(!preg_match_all('/'.$this->start_delim.'([A-Za-z_][\w._]+)'.$this->end_delim.'/', $input, $result)) + return null; + + $vars = array(); + foreach($result[0] as $k => $v) { + if(isset($vars[$v])) continue; + $val=$this->_resolveVar($result[1][$k]); + if($val!==false) + $vars[$v] = $val; + } + + return $vars; + } + + //Helper function - will be replaced by a lambda function (PHP 5.3+) + function _delimit($val, $d='/') { + + if($val && is_array($val)) + return array_map(array($this, '_delimit'), $val); + + return $d.$val.$d; + } +} +?> diff --git a/include/client/header.inc.php b/include/client/header.inc.php index da763a7bbf7b1e5b9481b8d26a0dfca2c79a9997..81191f98b7dacefb7bc34ba03d6b53920d9d16a7 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -10,7 +10,7 @@ header("Content-Type: text/html; charset=UTF-8\r\n"); <meta name="description" content="customer support platform"> <meta name="keywords" content="osTicket, Customer support system, support ticket system"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> - <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/theme.css" media="screen"> + <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/theme.min.css" media="screen"> <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/print.css" media="print"> <script src="<?php echo ROOT_PATH; ?>js/jquery-1.7.2.min.js"></script> <script src="<?php echo ROOT_PATH; ?>js/jquery.multifile.js"></script> diff --git a/include/client/login.inc.php b/include/client/login.inc.php index e1e52e9d454e7a1a4f62fdfec39c5625e496c5d2..7e46c2eb30598e2220128654b0232cd08d81ac62 100644 --- a/include/client/login.inc.php +++ b/include/client/login.inc.php @@ -1,5 +1,5 @@ <?php -if(!defined('OSTCLIENTINC')) die('Kwaheri'); +if(!defined('OSTCLIENTINC')) die('Access Denied'); $email=Format::input($_POST['lemail']?$_POST['lemail']:$_GET['e']); $ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']); @@ -8,7 +8,8 @@ $ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']); <p>To view the status of a ticket, provide us with the login details below.</p> <form action="login.php" method="post" id="clientLogin"> <?php csrf_token(); ?> - <strong>Authentication Required</strong> + <strong><?php echo Format::htmlchars($errors['login']); ?></strong> + <br> <div> <label for="email">E-Mail Address:</label> <input id="email" type="text" name="lemail" size="30" value="<?php echo $email; ?>"> diff --git a/include/staff/banlist.inc.php b/include/staff/banlist.inc.php index 0b61d1e8b779edaf48c448c82eb38c3d1ce3deed..b2b37b284b0ae4de0750650b2feb9fae2135cf5d 100644 --- a/include/staff/banlist.inc.php +++ b/include/staff/banlist.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$filter) $qstr=''; $select='SELECT rule.* '; -$from='FROM '.EMAIL_FILTER_RULE_TABLE.' rule '; +$from='FROM '.FILTER_RULE_TABLE.' rule '; $where='WHERE rule.filter_id='.db_input($filter->getId()); $search=false; if($_REQUEST['q'] && strlen($_REQUEST['q'])>3) { diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index c2aad0bcf4456d5bade4fa323a4ba3c991c39929..36186838bd93cdba6977add500645becba4d673e 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -33,7 +33,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em>Filters are executed based on execution order.</em> + <em>Filters are executed based on execution order. Filter can target specific ticket source.</em> </th> </tr> </thead> @@ -71,31 +71,37 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> </tr> <tr> - <td width="180"> - To Email Address: + <td width="180" class="required"> + Target: </td> <td> - <select name="email_id"> - <option value="0">— Filter applies to ALL incoming emails ‐</option> - <?php + <select name="target"> + <option value="">— Select a Target ‐</option> + <?php + foreach(Filter::getTargets() as $k => $v) { + echo sprintf('<option value="%s" %s>%s</option>', + $k, (($k==$info['target'])?'selected="selected"':''), $v); + } $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE.' email ORDER by name'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$email,$name)=db_fetch_row($res)){ + if(($res=db_query($sql)) && db_num_rows($res)) { + echo '<OPTGROUP label="Specific System Email">'; + while(list($id,$email,$name)=db_fetch_row($res)) { $selected=($info['email_id'] && $id==$info['email_id'])?'selected="selected"':''; if($name) $email=Format::htmlchars("$name <$email>"); echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$email); } + echo '</OPTGROUP>'; } ?> </select> - <br><em>(Highly recommended if the filter is specific to one incoming email address)</em> + + <span class="error">* <?php echo $errors['target']; ?></span> </td> </tr> <tr> <th colspan="2"> - <em><strong>Filter Rules</strong>: Rules are applied based on the criteria. - <span class="error">* <?php echo $errors['rules']; ?></span></em> + <em><strong>Filter Rules</strong>: Rules are applied based on the criteria. <span class="error">* <?php echo $errors['rules']; ?></span></em> </th> </tr> <tr> @@ -156,11 +162,11 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <td width="180"> - Ban Email: + Reject Ticket: </td> <td> - <input type="checkbox" name="reject_email" value="1" <?php echo $info['reject_email']?'checked="checked"':''; ?> > - <strong><font class="error">Reject email</font></strong> <em>(All other actions, rules and filters are ignored)</em> + <input type="checkbox" name="reject_ticket" value="1" <?php echo $info['reject_ticket']?'checked="checked"':''; ?> > + <strong><font class="error">Reject Ticket</font></strong> <em>(All other actions and filters are ignored)</em> </td> </tr> <tr> diff --git a/include/staff/filters.inc.php b/include/staff/filters.inc.php index 7f3aab393f56cb3b1eb3f8dc3da0113204d35e41..7573eceec7d3a82950a52e9e04ce2fce213754e8 100644 --- a/include/staff/filters.inc.php +++ b/include/staff/filters.inc.php @@ -1,13 +1,13 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); - +$targets = Filter::getTargets(); $qstr=''; $sql='SELECT filter.*,count(rule.id) as rules '. - 'FROM '.EMAIL_FILTER_TABLE.' filter '. - 'LEFT JOIN '.EMAIL_FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) '. + 'FROM '.FILTER_TABLE.' filter '. + 'LEFT JOIN '.FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) '. 'GROUP BY filter.id'; $sortOptions=array('name'=>'filter.name','status'=>'filter.isactive','order'=>'filter.execorder','rules'=>'rules', - 'created'=>'filter.created','updated'=>'filter.updated'); + 'target'=>'filter.target', 'created'=>'filter.created','updated'=>'filter.updated'); $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); $sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name'; //Sorting options... @@ -28,7 +28,7 @@ $x=$sort.'_sort'; $$x=' class="'.strtolower($order).'" '; $order_by="$order_column $order "; -$total=db_count('SELECT count(*) FROM '.EMAIL_FILTER_TABLE.' filter '); +$total=db_count('SELECT count(*) FROM '.FILTER_TABLE.' filter '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); $pageNav->setURL('filters.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); @@ -44,7 +44,7 @@ else ?> <div style="width:700;padding-top:5px; float:left;"> - <h2>Email Filters</h2> + <h2>Ticket Filters</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> <b><a href="filters.php?a=add" class="Icon newEmailFilter">Add New Filter</a></b></div> @@ -58,9 +58,10 @@ else <tr> <th width="7"> </th> <th width="320"><a <?php echo $name_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=name">Name</a></th> - <th width="100"><a <?php echo $status_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=status">Status</a></th> + <th width="80"><a <?php echo $status_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=status">Status</a></th> <th width="80" style="text-align:center;"><a <?php echo $order_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=order">Order</a></th> <th width="80" style="text-align:center;"><a <?php echo $rules_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=rules">Rules</a></th> + <th width="100"><a <?php echo $target_sort; ?> href="filters.php?<?php echo $qstr; ?>&sort=target">Target</a></th> <th width="120" nowrap><a <?php echo $created_sort; ?>href="filters.php?<?php echo $qstr; ?>&sort=created">Date Added</a></th> <th width="150" nowrap><a <?php echo $updated_sort; ?>href="filters.php?<?php echo $qstr; ?>&sort=updated">Last Updated</a></th> </tr> @@ -85,6 +86,7 @@ else <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td> <td style="text-align:right;padding-right:25px;"><?php echo $row['execorder']; ?> </td> <td style="text-align:right;padding-right:25px;"><?php echo $row['rules']; ?> </td> + <td> <?php echo Format::htmlchars($targets[$row['target']]); ?></td> <td> <?php echo Format::db_date($row['created']); ?></td> <td> <?php echo Format::db_datetime($row['updated']); ?></td> </tr> @@ -93,7 +95,7 @@ else endif; ?> <tfoot> <tr> - <td colspan="7"> + <td colspan="8"> <?php if($res && $num){ ?> Select: <a href="#" onclick="return select_all(document.forms['filters'],true)">All</a> diff --git a/include/staff/login.tpl.php b/include/staff/login.tpl.php index 2d8a41f650601c2ec8396ecb89a8ea5ac90a4790..b8b136eb56d01282e8b15dbd4c0f11085e437eaa 100644 --- a/include/staff/login.tpl.php +++ b/include/staff/login.tpl.php @@ -1,4 +1,8 @@ -<?php defined('OSTSCPINC') or die('Invalid path'); ?> +<?php +defined('OSTSCPINC') or die('Invalid path'); + +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> @@ -9,6 +13,12 @@ <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> + <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + $("input:not(.dp):visible:enabled:first").focus(); + }); + </script> </head> <body id="loginBody"> <div id="loginBox"> @@ -16,9 +26,9 @@ <h3><?php echo Format::htmlchars($msg); ?></h3> <form action="login.php" method="post"> <?php csrf_token(); ?> - <input type="hidden" name="d"o value="scplogin"> + <input type="hidden" name="do" value="scplogin"> <fieldset> - <input type="text" name="username" id="name" value="" placeholder="username" autocorrect="off" autocapitalize="off"> + <input type="text" name="username" id="name" value="<?php echo $info['username']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> <input type="password" name="passwd" id="pass" placeholder="password" autocorrect="off" autocapitalize="off"> </fieldset> <input class="submit" type="submit" name="submit" value="Log In"> diff --git a/include/staff/settings-alerts.inc.php b/include/staff/settings-alerts.inc.php index 5bb5d393246ede7266440c30a7e9f5e41df8cc8e..cc91ffdf804fce3dad988a6e11139a1b259d60c8 100644 --- a/include/staff/settings-alerts.inc.php +++ b/include/staff/settings-alerts.inc.php @@ -1,102 +1,177 @@ +<h2>Alerts and Notices</h2> <form action="settings.php?t=alerts" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="alerts" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> - <th colspan="2"> - <h4>Alerts and Notices Setting</h4> - <em>Alerts sent to staff on ticket "events". Staff assignment takes precedence over team assignment.</em> + <th> + <h4>Alerts and Notices sent to staff on ticket "events"</h4> </th> </tr> </thead> <tbody> + <tr><th><em><b>New Ticket Alert</b>: Alert sent out on new tickets</em></th></tr> <tr> - <td width="160">New Ticket Alert:</td> - <td> + <td><em><b>Status:</b></em> <input type="radio" name="ticket_alert_active" value="1" <?php echo $config['ticket_alert_active']?'checked':''; ?> />Enable <input type="radio" name="ticket_alert_active" value="0" <?php echo !$config['ticket_alert_active']?'checked':''; ?> />Disable - <em>Alert sent out on new tickets <font class="error"> <?php echo $errors['ticket_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: - <input type="checkbox" name="ticket_alert_admin" <?php echo $config['ticket_alert_admin']?'checked':''; ?>> Admin Email + <font class="error"> <?php echo $errors['ticket_alert_active']; ?></font></em> + </td> + </tr> + <tr> + <td> + <input type="checkbox" name="ticket_alert_admin" <?php echo $config['ticket_alert_admin']?'checked':''; ?>> Admin Email <em>(<?php echo $cfg->getAdminEmail(); ?>)</em> + </td> + </tr> + <tr> + <td> <input type="checkbox" name="ticket_alert_dept_manager" <?php echo $config['ticket_alert_dept_manager']?'checked':''; ?>> Department Manager - <input type="checkbox" name="ticket_alert_dept_members" <?php echo $config['ticket_alert_dept_members']?'checked':''; ?>> Department Members (spammy) </td> </tr> <tr> - <td width="160">New Message Alert:</td> <td> + <input type="checkbox" name="ticket_alert_dept_members" <?php echo $config['ticket_alert_dept_members']?'checked':''; ?>> Department Members <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>New Message Alert</b>: Alert sent out when a new message, from the user, is appended to an existing ticket</em></th></tr> + <tr> + <td><em><b>Status:</b></em> <input type="radio" name="message_alert_active" value="1" <?php echo $config['message_alert_active']?'checked':''; ?> />Enable + <input type="radio" name="message_alert_active" value="0" <?php echo !$config['message_alert_active']?'checked':''; ?> />Disable - <em>Alert sent out when a new message is appended to an existing ticket <font class="error"> <?php echo $errors['message_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: + </td> + </tr> + <tr> + <td> <input type="checkbox" name="message_alert_laststaff" <?php echo $config['message_alert_laststaff']?'checked':''; ?>> Last Respondent + </td> + </tr> + <tr> + <td> <input type="checkbox" name="message_alert_assigned" <?php echo $config['message_alert_assigned']?'checked':''; ?>> Assigned Staff - <input type="checkbox" name="message_alert_dept_manager" <?php echo $config['message_alert_dept_manager']?'checked':''; ?>> Department Manager (spammy) </td> </tr> <tr> - <td width="160">New Internal Note Alert:</td> <td> + <input type="checkbox" name="message_alert_dept_manager" <?php echo $config['message_alert_dept_manager']?'checked':''; ?>> Department Manager <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>New Internal Note Alert</b>: Alert sent out when a new internal note is posted.</em></th></tr> + <tr> + <td><em><b>Status:</b></em> <input type="radio" name="note_alert_active" value="1" <?php echo $config['note_alert_active']?'checked':''; ?> />Enable + <input type="radio" name="note_alert_active" value="0" <?php echo !$config['note_alert_active']?'checked':''; ?> />Disable - <em>Alert sent out when a new internal note is posted <font class="error"> <?php echo $errors['note_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: + <font class="error"> <?php echo $errors['note_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> <input type="checkbox" name="note_alert_laststaff" <?php echo $config['note_alert_laststaff']?'checked':''; ?>> Last Respondent + </td> + </tr> + <tr> + <td> <input type="checkbox" name="note_alert_assigned" <?php echo $config['note_alert_assigned']?'checked':''; ?>> Assigned Staff - <input type="checkbox" name="note_alert_dept_manager" <?php echo $config['note_alert_dept_manager']?'checked':''; ?>> Department Manager (spammy) </td> </tr> <tr> - <td width="160">Ticket Assignment Alert:</td> <td> + <input type="checkbox" name="note_alert_dept_manager" <?php echo $config['note_alert_dept_manager']?'checked':''; ?>> Department Manager <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>Ticket Assignment Alert</b>: Alert sent out to staff on ticket assignment.</em></th></tr> + <tr> + <td><em><b>Status: </b></em> <input name="assigned_alert_active" value="1" checked="checked" type="radio">Enable + <input name="assigned_alert_active" value="0" type="radio">Disable - <em>Alert sent out to staff on ticket assignment <font class="error"> <?php echo $errors['assigned_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: + <font class="error"> <?php echo $errors['assigned_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> <input type="checkbox" name="assigned_alert_staff" <?php echo $config['assigned_alert_staff']?'checked':''; ?>> Assigned Staff - <input type="checkbox"name="assigned_alert_team_lead" <?php echo $config['assigned_alert_team_lead']?'checked':''; ?>>Team Lead (Team assignment) - <input type="checkbox"name="assigned_alert_team_members" <?php echo $config['assigned_alert_team_members']?'checked':''; ?>> - Team Members (spammy) </td> </tr> <tr> - <td width="160">Ticket Transfer Alert:</td> <td> + <input type="checkbox"name="assigned_alert_team_lead" <?php echo $config['assigned_alert_team_lead']?'checked':''; ?>>Team Lead <em>(On team assignment)</em> + </td> + </tr> + <tr> + <td> + <input type="checkbox"name="assigned_alert_team_members" <?php echo $config['assigned_alert_team_members']?'checked':''; ?>> + Team Members <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>Ticket Transfer Alert</b>: Alert sent out to staff of the target department on ticket transfer.</em></th></tr> + <tr> + <td><em><b>Status:</b></em> <input type="radio" name="transfer_alert_active" value="1" <?php echo $config['transfer_alert_active']?'checked':''; ?> />Enable <input type="radio" name="transfer_alert_active" value="0" <?php echo !$config['transfer_alert_active']?'checked':''; ?> />Disable - <em>Alert sent out to staff on ticket transfer <font class="error"> -<?php echo $errors['alert_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: + <font class="error"> <?php echo $errors['alert_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> <input type="checkbox" name="transfer_alert_assigned" <?php echo $config['transfer_alert_assigned']?'checked':''; ?>> Assigned Staff/Team + </td> + </tr> + <tr> + <td> <input type="checkbox" name="transfer_alert_dept_manager" <?php echo $config['transfer_alert_dept_manager']?'checked':''; ?>> Department Manager - <input type="checkbox" name="transfer_alert_dept_members" <?php echo $config['transfer_alert_dept_members']?'checked':''; ?>> Department Members - (spammy) </td> </tr> <tr> - <td width="160">Overdue Ticket Alert:</td> <td> + <input type="checkbox" name="transfer_alert_dept_members" <?php echo $config['transfer_alert_dept_members']?'checked':''; ?>> + Department Members <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>Overdue Ticket Alert</b>: Alert sent out when a ticket becomes overdue - admin email gets an alert by default.</em></th></tr> + <tr> + <td><em><b>Status:</b></em> <input type="radio" name="overdue_alert_active" value="1" <?php echo $config['overdue_alert_active']?'checked':''; ?> />Enable <input type="radio" name="overdue_alert_active" value="0" <?php echo !$config['overdue_alert_active']?'checked':''; ?> />Disable - <em>Alert sent out when a ticket becomes overdue - admin email gets an alert by default. <font class="error"> <?php echo $errors['overdue_alert_active']; ?></font></em><br> - <strong>Recipients</strong>: + <font class="error"> <?php echo $errors['overdue_alert_active']; ?></font> + </td> + </tr> + <tr> + <td> <input type="checkbox" name="overdue_alert_assigned" <?php echo $config['overdue_alert_assigned']?'checked':''; ?>> Assigned Staff/Team + </td> + </tr> + <tr> + <td> <input type="checkbox" name="overdue_alert_dept_manager" <?php echo $config['overdue_alert_dept_manager']?'checked':''; ?>> Department Manager - <input type="checkbox" name="overdue_alert_dept_members" <?php echo $config['overdue_alert_dept_members']?'checked':''; ?>> Department Members (spammy) </td> </tr> <tr> - <td width="160">System Alerts:</td> - <td><em><b>Enabled</b>: Errors are sent to system admin email (<?php echo $cfg->getAdminEmail(); ?>)</em><br> + <td> + <input type="checkbox" name="overdue_alert_dept_members" <?php echo $config['overdue_alert_dept_members']?'checked':''; ?>> Department Members <em>(spammy)</em> + </td> + </tr> + <tr><th><em><b>System Alerts</b>: Enabled by default. Errors are sent to system admin email (<?php echo $cfg->getAdminEmail(); ?>)</em></th></tr> + <tr> + <td> <input type="checkbox" name="send_sys_errors" checked="checked" disabled="disabled">System Errors + </td> + </tr> + <tr> + <td> <input type="checkbox" name="send_sql_errors" <?php echo $config['send_sql_errors']?'checked':''; ?>>SQL errors + </td> + </tr> + <tr> + <td> <input type="checkbox" name="send_login_errors" <?php echo $config['send_login_errors']?'checked':''; ?>>Excessive Login attempts </td> </tr> </tbody> </table> -<p style="padding-left:200px;"> +<p style="padding-left:350px;"> <input class="button" type="submit" name="submit" value="Save Changes"> <input class="button" type="reset" name="reset" value="Reset Changes"> </p> diff --git a/include/staff/settings-attachments.inc.php b/include/staff/settings-attachments.inc.php deleted file mode 100644 index b381fa40d334f9f2c9077f91698c6ec90aa31491..0000000000000000000000000000000000000000 --- a/include/staff/settings-attachments.inc.php +++ /dev/null @@ -1,108 +0,0 @@ -<?php -if(!($maxfileuploads=ini_get('max_file_uploads'))) - $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; - -?> -<form action="settings.php?t=attachments" method="post" id="save"> -<?php csrf_token(); ?> -<input type="hidden" name="t" value="attachments" > -<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th colspan="2"> - <h4>Attachments Settings</h4> - <em> Before enabling attachments make sure you understand PHP file upload settings and security issues related to file upload.</em> - </th> - </tr> - </thead> - <tbody> - <tr> - <td width="180">Allow Attachments:</td> - <td> - <input type="checkbox" name="allow_attachments" <?php echo $config['allow_attachments']?'checked="checked"':''; ?>><b>Allow Attachments</b> - <em>(Global Setting)</em> - <font class="error"> <?php echo $errors['allow_attachments']; ?></font> - </td> - </tr> - <tr> - <td width="180">Emailed Attachments:</td> - <td> - <input type="checkbox" name="allow_email_attachments" <?php echo $config['allow_email_attachments']?'checked="checked"':''; ?>> Accept emailed files - <font class="error"> <?php echo $errors['allow_email_attachments']; ?></font> - </td> - </tr> - <tr> - <td width="180">Online Attachments:</td> - <td> - <input type="checkbox" name="allow_online_attachments" <?php echo $config['allow_online_attachments']?'checked="checked"':''; ?> > - Allow web upload - <input type="checkbox" name="allow_online_attachments_onlogin" <?php echo $config['allow_online_attachments_onlogin'] ?'checked="checked"':''; ?> > - Limit to authenticated users only. <em>(User must be logged in to upload files)</em> - <font class="error"> <?php echo $errors['allow_online_attachments']; ?></font> - </td> - </tr> - <tr> - <td>Max. User File Uploads:</td> - <td> - <select name="max_user_file_uploads"> - <?php - for($i = 1; $i <=$maxfileuploads; $i++) { - ?> - <option <?php echo $config['max_user_file_uploads']==$i?'selected="selected"':''; ?> value="<?php echo $i; ?>"> - <?php echo $i; ?> <?php echo ($i>1)?'files':'file'; ?></option> - <?php - } ?> - </select> - <em>(Number of files the user is allowed to upload simultaneously)</em> - <font class="error"> <?php echo $errors['max_user_file_uploads']; ?></font> - </td> - </tr> - <tr> - <td>Max. Staff File Uploads:</td> - <td> - <select name="max_staff_file_uploads"> - <?php - for($i = 1; $i <=$maxfileuploads; $i++) { - ?> - <option <?php echo $config['max_staff_file_uploads']==$i?'selected="selected"':''; ?> value="<?php echo $i; ?>"> - <?php echo $i; ?> <?php echo ($i>1)?'files':'file'; ?></option> - <?php - } ?> - </select> - <em>(Number of files the staff is allowed to upload simultaneously)</em> - <font class="error"> <?php echo $errors['max_staff_file_uploads']; ?></font> - </td> - </tr> - <tr> - <td width="180">Maximum File Size:</td> - <td> - <input type="text" name="max_file_size" value="<?php echo $config['max_file_size']; ?>"> in bytes. - <em>(Max <?php echo Format::file_size(ini_get('upload_max_filesize')); ?>)</em> - <font class="error"> <?php echo $errors['max_file_size']; ?></font> - </td> - </tr> - <tr> - <td width="180">Ticket Response Files:</td> - <td> - <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?> >Email attachments to the user - </td> - </tr> - <tr> - <th colspan="2"> - <em><strong>Accepted File Types</strong>: Limit the type of files users are allowed to upload. - <font class="error"> <?php echo $errors['allowed_filetypes']; ?></font></em> - </th> - </tr> - <tr> - <td colspan="2"> - <em>Enter allowed file extensions separated by a comma. e.g .doc, .pdf. To accept all files enter wildcard <b><i>.*</i></b> i.e dotStar (NOT Recommended).</em><br> - <textarea name="allowed_filetypes" cols="21" rows="4" style="width: 65%;" wrap="hard" ><?php echo $config['allowed_filetypes']; ?></textarea> - </td> - </tr> - </tbody> -</table> -<p style="padding-left:210px;"> - <input class="button" type="submit" name="submit" value="Save Changes"> - <input class="button" type="reset" name="reset" value="Reset Changes"> -</p> -</form> diff --git a/include/staff/settings-autoresponders.inc.php b/include/staff/settings-autoresp.inc.php similarity index 93% rename from include/staff/settings-autoresponders.inc.php rename to include/staff/settings-autoresp.inc.php index 106e7f3f4d7063a6bcb4835cdbd07006b6f16b5c..b5815915fc346bb97fbfa318db9416bda6bfccce 100644 --- a/include/staff/settings-autoresponders.inc.php +++ b/include/staff/settings-autoresp.inc.php @@ -1,6 +1,7 @@ -<form action="settings.php?t=autoresponders" method="post" id="save"> +<h2>Autoresponder Settings</h2> +<form action="settings.php?t=autoresp" method="post" id="save"> <?php csrf_token(); ?> -<input type="hidden" name="t" value="autoresponders" > +<input type="hidden" name="t" value="autoresp" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> diff --git a/include/staff/settings-dates.inc.php b/include/staff/settings-dates.inc.php deleted file mode 100644 index f8085cfc3193342bf1bd6c66df87b48d9dcf5edd..0000000000000000000000000000000000000000 --- a/include/staff/settings-dates.inc.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -$gmtime=Misc::gmtime(); -?> -<form action="settings.php?t=dates" method="post" id="save"> -<?php csrf_token(); ?> -<input type="hidden" name="t" value="dates" > -<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th colspan="2"> - <h4>Date and Time Options</h4> - <em>Please refer to <a href="http://php.net/date" target="_blank">PHP Manual</a> for supported parameters.</em> - </th> - </tr> - </thead> - <tbody> - <tr><td width="220" class="required">Time Format:</td> - <td> - <input type="text" name="time_format" value="<?php echo $config['time_format']; ?>"> - <font class="error">* <?php echo $errors['time_format']; ?></font> - <em><?php echo Format::date($config['time_format'],$gmtime,$config['timezone_offset'],$config['enable_daylight_saving']); ?></em></td> - </tr> - <tr><td width="220" class="required">Date Format:</td> - <td><input type="text" name="date_format" value="<?php echo $config['date_format']; ?>"> - <font class="error">* <?php echo $errors['date_format']; ?></font> - <em><?php echo Format::date($config['date_format'],$gmtime,$config['timezone_offset'],$config['enable_daylight_saving']); ?></em> - </td> - </tr> - <tr><td width="220" class="required">Date & Time Format:</td> - <td><input type="text" name="datetime_format" value="<?php echo $config['datetime_format']; ?>"> - <font class="error">* <?php echo $errors['datetime_format']; ?></font> - <em><?php echo Format::date($config['datetime_format'],$gmtime,$config['timezone_offset'],$config['enable_daylight_saving']); ?></em> - </td> - </tr> - <tr><td width="220" class="required">Day, Date & Time Format:</td> - <td><input type="text" name="daydatetime_format" value="<?php echo $config['daydatetime_format']; ?>"> - <font class="error">* <?php echo $errors['daydatetime_format']; ?></font> - <em><?php echo Format::date($config['daydatetime_format'],$gmtime,$config['timezone_offset'],$config['enable_daylight_saving']); ?></em> - </td> - </tr> - <tr><td width="220" class="required">Default Time Zone:</td> - <td> - <select name="default_timezone_id"> - <option value="">— Select Default Time Zone —</option> - <?php - $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$offset, $tz)=db_fetch_row($res)){ - $sel=($config['default_timezone_id']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz); - } - } - ?> - </select> - <font class="error">* <?php echo $errors['default_timezone_id']; ?></font> - </td> - </tr> - <tr> - <td width="220">Daylight Saving:</td> - <td> - <input type="checkbox" name="enable_daylight_saving" <?php echo $config['enable_daylight_saving'] ? 'checked="checked"': ''; ?>>Observe daylight savings - </td> - </tr> - </tbody> -</table> -<p style="padding-left:250px;"> - <input class="button" type="submit" name="submit" value="Save Changes"> - <input class="button" type="reset" name="reset" value="Reset Changes"> -</p> -</form> diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index e4ccaf3a4e69329c15d474bfd3f136f9a8fe17f9..f8674095d49cfe25102ef682e64bb9df59e1614a 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -1,110 +1,118 @@ -<form action="settings.php?t=emails" method="post" id="save"> +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); +?> +<h2>Email Settings and Options</h2> +<form action="settings.php?t=emails" method="post" id="save"> <?php csrf_token(); ?> -<input type="hidden" name="t" value="emails" > -<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th colspan="2"> - <h4>Email Settings</h4> - <em>Note that some of the global settings can be overwritten at department/email level.</em> - </th> - </tr> - </thead> - <tbody> - <tr> - <td width="180" class="required">Default System Email:</td> - <td> - <select name="default_email_id"> - <option value=0 disabled>Select One</option> +<input type="hidden" name="t" value="emails" > +<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th colspan="2"> + <h4>Email Settings</h4> + <em>Note that some of the global settings can be overwritten at department/email level.</em> + </th> + </tr> + </thead> + <tbody> + <tr> + <td width="180" class="required">Default System Email:</td> + <td> + <select name="default_email_id"> + <option value=0 disabled>Select One</option> <?php - $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE; - if(($res=db_query($sql)) && db_num_rows($res)){ - while (list($id,$email,$name) = db_fetch_row($res)){ - $email=$name?"$name <$email>":$email; - ?> - <option value="<?php echo $id; ?>"<?php echo ($config['default_email_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> + $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE; + if(($res=db_query($sql)) && db_num_rows($res)){ + while (list($id,$email,$name) = db_fetch_row($res)){ + $email=$name?"$name <$email>":$email; + ?> + <option value="<?php echo $id; ?>"<?php echo ($config['default_email_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> <?php - } - } ?> - </select> - <font class="error">* <?php echo $errors['default_email_id']; ?></font> - </td> - </tr> - <tr> - <td width="180" class="required">Default Alert Email:</td> - <td> - <select name="alert_email_id"> - <option value="0" selected="selected">Use Default System Email (above)</option> + } + } ?> + </select> + <font class="error">* <?php echo $errors['default_email_id']; ?></font> + </td> + </tr> + <tr> + <td width="180" class="required">Default Alert Email:</td> + <td> + <select name="alert_email_id"> + <option value="0" selected="selected">Use Default System Email (above)</option> <?php - $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE.' WHERE email_id != '.db_input($config['default_email_id']); - if(($res=db_query($sql)) && db_num_rows($res)){ - while (list($id,$email,$name) = db_fetch_row($res)){ - $email=$name?"$name <$email>":$email; - ?> - <option value="<?php echo $id; ?>"<?php echo ($config['alert_email_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> + $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE.' WHERE email_id != '.db_input($config['default_email_id']); + if(($res=db_query($sql)) && db_num_rows($res)){ + while (list($id,$email,$name) = db_fetch_row($res)){ + $email=$name?"$name <$email>":$email; + ?> + <option value="<?php echo $id; ?>"<?php echo ($config['alert_email_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> <?php - } - } ?> - </select> - <font class="error">* <?php echo $errors['alert_email_id']; ?></font> - </td> - </tr> - <tr> - <td width="180" class="required">Admin Email Address:</td> - <td> - <input type="text" size=40 name="admin_email" value="<?php echo $config['admin_email']; ?>"> - <font class="error">* <?php echo $errors['admin_email']; ?></font></td> - </tr> - <tr><th colspan=2><em><strong>Incoming Emails</strong>: For mail fetcher (POP/IMAP) to work you must set a cron job or enable auto-cron</em></th> - <tr> - <td width="180">Email Polling:</td> - <td><input type="checkbox" name="enable_mail_polling" value=1 <?php echo $config['enable_mail_polling']? 'checked="checked"': ''; ?> > Enable POP/IMAP - <em>(Global setting which can be disabled at email level)</em> - </td> - </tr> - <tr> - <td width="180">Email Piping:</td> - <td><input type="checkbox" name="enable_email_piping" value=1 <?php echo $config['enable_email_piping']? 'checked="checked"': ''; ?>> Enable email piping - <em>(You pipe we accept policy)</em> - </td> - </tr> - <tr> - <td width="180">Strip Quoted Reply:</td> - <td> - <input type="checkbox" name="strip_quoted_reply" <?php echo $config['strip_quoted_reply'] ? 'checked="checked"':''; ?>> - <em>(depends on the reply separator tag set below)</em> - <font class="error"> <?php echo $errors['strip_quoted_reply']; ?></font> - </td> - </tr> - <tr> - <td width="180">Reply Separator Tag:</td> - <td><input type="text" name="reply_separator" value="<?php echo $config['reply_separator']; ?>"> - <font class="error"> <?php echo $errors['reply_separator']; ?></font> - </td> - </tr> - <tr><th colspan=2><em><strong>Outgoing Emails</strong>: Default email only applies to outgoing emails without SMTP setting.</em></th></tr> - <tr><td width="180">Default Outgoing Email:</td> - <td> - <select name="default_smtp_id"> - <option value=0 selected="selected">None: Use PHP mail function</option> + } + } ?> + </select> + <font class="error">* <?php echo $errors['alert_email_id']; ?></font> + </td> + </tr> + <tr> + <td width="180" class="required">Admin's Email Address:</td> + <td> + <input type="text" size=40 name="admin_email" value="<?php echo $config['admin_email']; ?>"> + <font class="error">* <?php echo $errors['admin_email']; ?></font> + <em>(System administrator's email)</em> + </td> + </tr> + <tr><th colspan=2><em><strong>Incoming Emails</strong>: For mail fetcher (polling) to work you must set an external cron job or enable auto-cron</em></th> + <tr> + <td width="180">Email Polling:</td> + <td><input type="checkbox" name="enable_mail_polling" value=1 <?php echo $config['enable_mail_polling']? 'checked="checked"': ''; ?> > Enable POP/IMAP polling + + <input type="checkbox" name="enable_auto_cron" <?php echo $config['enable_auto_cron']?'checked="checked"':''; ?>> + Enable Auto-Cron <em>(Poll based on staff activity - NOT recommended)</em> + </td> + </tr> + <tr> + <td width="180">Email Piping:</td> + <td><input type="checkbox" name="enable_email_piping" value=1 <?php echo $config['enable_email_piping']? 'checked="checked"': ''; ?>> Enable email piping + <em>(You pipe we accept policy)</em> + </td> + </tr> + <tr> + <td width="180">Strip Quoted Reply:</td> + <td> + <input type="checkbox" name="strip_quoted_reply" <?php echo $config['strip_quoted_reply'] ? 'checked="checked"':''; ?>> + <em>(depends on the reply separator tag set below)</em> + <font class="error"> <?php echo $errors['strip_quoted_reply']; ?></font> + </td> + </tr> + <tr> + <td width="180">Reply Separator Tag:</td> + <td><input type="text" name="reply_separator" value="<?php echo $config['reply_separator']; ?>"> + <font class="error"> <?php echo $errors['reply_separator']; ?></font> + </td> + </tr> + <tr><th colspan=2><em><strong>Outgoing Emails</strong>: Default email only applies to outgoing emails without SMTP setting.</em></th></tr> + <tr><td width="180">Default Outgoing Email:</td> + <td> + <select name="default_smtp_id"> + <option value=0 selected="selected">None: Use PHP mail function</option> <?php - $sql='SELECT email_id,email,name,smtp_host FROM '.EMAIL_TABLE.' WHERE smtp_active=1'; - - if(($res=db_query($sql)) && db_num_rows($res)) { - while (list($id,$email,$name,$host) = db_fetch_row($res)){ - $email=$name?"$name <$email>":$email; - ?> - <option value="<?php echo $id; ?>"<?php echo ($config['default_smtp_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> + $sql='SELECT email_id,email,name,smtp_host FROM '.EMAIL_TABLE.' WHERE smtp_active=1'; + + if(($res=db_query($sql)) && db_num_rows($res)) { + while (list($id,$email,$name,$host) = db_fetch_row($res)){ + $email=$name?"$name <$email>":$email; + ?> + <option value="<?php echo $id; ?>"<?php echo ($config['default_smtp_id']==$id)?'selected="selected"':''; ?>><?php echo $email; ?></option> <?php - } - } ?> - </select> <font class="error"> <?php echo $errors['default_smtp_id']; ?></font> - </td> - </tr> - </tbody> -</table> -<p style="padding-left:250px;"> - <input class="button" type="submit" name="submit" value="Save Changes"> - <input class="button" type="reset" name="reset" value="Reset Changes"> -</p> -</form> + } + } ?> + </select> <font class="error"> <?php echo $errors['default_smtp_id']; ?></font> + </td> + </tr> + </tbody> +</table> +<p style="padding-left:250px;"> + <input class="button" type="submit" name="submit" value="Save Changes"> + <input class="button" type="reset" name="reset" value="Reset Changes"> +</p> +</form> diff --git a/include/staff/settings-kb.inc.php b/include/staff/settings-kb.inc.php index 6fe8433f5ee580c5a201a42518652106af63d313..0ab2ec09449a07f8f7efa78f8602683534b7666a 100644 --- a/include/staff/settings-kb.inc.php +++ b/include/staff/settings-kb.inc.php @@ -1,3 +1,7 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); +?> +<h2>Knowledge Base Settings and Options</h2> <form action="settings.php?t=kb" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="kb" > @@ -5,25 +9,25 @@ <thead> <tr> <th colspan="2"> - <h4>Knowledgebase Settings</h4> - <em>Disabling knowledgebase disables user's knowledgebase interface.</em> + <h4>Knowledge Base Settings</h4> + <em>Disabling knowledge base disables clients'interface.</em> </th> </tr> </thead> <tbody> <tr> - <td width="180">Knowledgebase Status:</td> + <td width="180">Knowledge base status:</td> <td> <input type="checkbox" name="enable_kb" value="1" <?php echo $config['enable_kb']?'checked="checked"':''; ?>> - Enable Knowledgebase <em>(Client Interface)</em> + Enable Knowledge base <em>(Client interface)</em> <font class="error"> <?php echo $errors['enable_kb']; ?></font> </td> </tr> <tr> - <td width="180">Premade Responses:</td> + <td width="180">Canned Responses:</td> <td> <input type="checkbox" name="enable_premade" value="1" <?php echo $config['enable_premade']?'checked="checked"':''; ?> > - Enable premade/canned responses <em>(Available on ticket reply)</em> + Enable canned responses <em>(Available on ticket reply)</em> <font class="error"> <?php echo $errors['enable_premade']; ?></font> </td> </tr> diff --git a/include/staff/settings-general.inc.php b/include/staff/settings-system.inc.php similarity index 66% rename from include/staff/settings-general.inc.php rename to include/staff/settings-system.inc.php index 20bbd94866960d2e06ae4d3451825d5752c853ac..3fba7f15977390d6882f00894523b90d92d4aced 100644 --- a/include/staff/settings-general.inc.php +++ b/include/staff/settings-system.inc.php @@ -1,12 +1,18 @@ -<form action="settings.php?t=general" method="post" id="save"> +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); + +$gmtime = Misc::gmtime(); +?> +<h2>System Settings and Preferences - <span>osTicket (v<?php echo $cfg->getVersion(); ?>)</span></h2> +<form action="settings.php?t=system" method="post" id="save"> <?php csrf_token(); ?> -<input type="hidden" name="t" value="general" > +<input type="hidden" name="t" value="system" > <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> <th colspan="2"> - <h4>General Settings</h4> - <em>Offline mode will disable client interface and only allow admins to login to Staff Control Panel</em> + <h4>System Settings & Preferences</h4> + <em><b>General Settings</b>: Offline mode will disable client interface and only allow admins to login to Staff Control Panel</em> </th> </tr> </thead> @@ -39,7 +45,7 @@ <?php $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' WHERE ispublic=1'; if(($res=db_query($sql)) && db_num_rows($res)){ - while (list($id,$name) = db_fetch_row($res)){ + while (list($id, $name) = db_fetch_row($res)){ $selected = ($config['default_dept_id']==$id)?'selected="selected"':''; ?> <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?> Dept</option> <?php @@ -56,7 +62,7 @@ <?php $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_TABLE.' WHERE isactive=1 AND cfg_id='.db_input($cfg->getId()).' ORDER BY name'; if(($res=db_query($sql)) && db_num_rows($res)){ - while (list($id,$name) = db_fetch_row($res)){ + while (list($id, $name) = db_fetch_row($res)){ $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?> <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> <?php @@ -113,26 +119,32 @@ <?php for ($i = 1; $i <= 12; $i++) { echo sprintf('<option value="%d" %s>%s%s</option>', - $i,(($config['passwd_reset_period']==$i)?'selected="selected"':''),$i>1?"Every $i ":'',$i>1?' Months':'Monthly'); + $i,(($config['passwd_reset_period']==$i)?'selected="selected"':''), $i>1?"Every $i ":'', $i>1?' Months':'Monthly'); } ?> </select> <font class="error"> <?php echo $errors['passwd_reset_period']; ?></font> </td> </tr> + <tr><td>Bind Staff Session to IP:</td> + <td> + <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> + <em>(binds staff session to originating IP address upon login)</em> + </td> + </tr> <tr><td>Staff Excessive Logins:</td> <td> <select name="staff_max_logins"> <?php for ($i = 1; $i <= 10; $i++) { - echo sprintf('<option value="%d" %s>%d</option>',$i,(($config['staff_max_logins']==$i)?'selected="selected"':''),$i); + echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_max_logins']==$i)?'selected="selected"':''), $i); } ?> </select> failed login attempt(s) allowed before a <select name="staff_login_timeout"> <?php for ($i = 1; $i <= 10; $i++) { - echo sprintf('<option value="%d" %s>%d</option>',$i,(($config['staff_login_timeout']==$i)?'selected="selected"':''),$i); + echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_login_timeout']==$i)?'selected="selected"':''), $i); } ?> </select> minute lock-out is enforced. @@ -144,18 +156,12 @@ Maximum idle time in minutes before a staff member must log in again (enter 0 to disable). </td> </tr> - <tr><td>Bind Staff Session to IP:</td> - <td> - <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> - <em>(binds staff session to originating IP address upon login)</em> - </td> - </tr> <tr><td>Client Excessive Logins:</td> <td> <select name="client_max_logins"> <?php for ($i = 1; $i <= 10; $i++) { - echo sprintf('<option value="%d" %s>%d</option>',$i,(($config['client_max_logins']==$i)?'selected="selected"':''),$i); + echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_max_logins']==$i)?'selected="selected"':''), $i); } ?> @@ -163,7 +169,7 @@ <select name="client_login_timeout"> <?php for ($i = 1; $i <= 10; $i++) { - echo sprintf('<option value="%d" %s>%d</option>',$i,(($config['client_login_timeout']==$i)?'selected="selected"':''),$i); + echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_login_timeout']==$i)?'selected="selected"':''), $i); } ?> </select> minute lock-out is enforced. @@ -176,16 +182,56 @@ Maximum idle time in minutes before a client must log in again (enter 0 to disable). </td> </tr> - <tr><td>Clickable URLs:</td> + <tr> + <th colspan="2"> + <em><b>Date and Time Options</b>: Please refer to <a href="http://php.net/date" target="_blank">PHP Manual</a> for supported parameters.</em> + </th> + </tr> + <tr><td width="220" class="required">Time Format:</td> + <td> + <input type="text" name="time_format" value="<?php echo $config['time_format']; ?>"> + <font class="error">* <?php echo $errors['time_format']; ?></font> + <em><?php echo Format::date($config['time_format'], $gmtime, $config['tz_offset'], $config['enable_daylight_saving']); ?></em></td> + </tr> + <tr><td width="220" class="required">Date Format:</td> + <td><input type="text" name="date_format" value="<?php echo $config['date_format']; ?>"> + <font class="error">* <?php echo $errors['date_format']; ?></font> + <em><?php echo Format::date($config['date_format'], $gmtime, $config['tz_offset'], $config['enable_daylight_saving']); ?></em> + </td> + </tr> + <tr><td width="220" class="required">Date & Time Format:</td> + <td><input type="text" name="datetime_format" value="<?php echo $config['datetime_format']; ?>"> + <font class="error">* <?php echo $errors['datetime_format']; ?></font> + <em><?php echo Format::date($config['datetime_format'], $gmtime, $config['tz_offset'], $config['enable_daylight_saving']); ?></em> + </td> + </tr> + <tr><td width="220" class="required">Day, Date & Time Format:</td> + <td><input type="text" name="daydatetime_format" value="<?php echo $config['daydatetime_format']; ?>"> + <font class="error">* <?php echo $errors['daydatetime_format']; ?></font> + <em><?php echo Format::date($config['daydatetime_format'], $gmtime, $config['tz_offset'], $config['enable_daylight_saving']); ?></em> + </td> + </tr> + <tr><td width="220" class="required">Default Time Zone:</td> <td> - <input type="checkbox" name="clickable_urls" <?php echo $config['clickable_urls']?'checked="checked"':''; ?>> - <em>(converts URLs in messages to clickable links)</em> + <select name="default_timezone_id"> + <option value="">— Select Default Time Zone —</option> + <?php + $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id, $offset, $tz)=db_fetch_row($res)){ + $sel=($config['default_timezone_id']==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>GMT %s - %s</option>', $id, $sel, $offset, $tz); + } + } + ?> + </select> + <font class="error">* <?php echo $errors['default_timezone_id']; ?></font> </td> </tr> - <tr><td>Enable Auto Cron:</td> + <tr> + <td width="220">Daylight Saving:</td> <td> - <input type="checkbox" name="enable_auto_cron" <?php echo $config['enable_auto_cron']?'checked="checked"':''; ?>> - <em>(executes cron jobs based on staff activity - not recommended)</em> + <input type="checkbox" name="enable_daylight_saving" <?php echo $config['enable_daylight_saving'] ? 'checked="checked"': ''; ?>>Observe daylight savings </td> </tr> </tbody> @@ -195,4 +241,3 @@ <input class="button" type="reset" name="reset" value="Reset Changes"> </p> </form> - diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index 280abca08ea44d698e0a41b56b1d8d0a62cd1693..992e9900425c19d708f7cb35d75a59f7b18c36bf 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -1,3 +1,9 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); +if(!($maxfileuploads=ini_get('max_file_uploads'))) + $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; +?> +<h2>Ticket Settings and Options</h2> <form action="settings.php?t=tickets" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="tickets" > @@ -5,7 +11,7 @@ <thead> <tr> <th colspan="2"> - <h4>Ticket Settings and Options</h4> + <h4>Ticket Settings</h4> <em>Global ticket settings and options.</em> </th> </tr> @@ -55,53 +61,59 @@ </td> </tr> <tr> - <td width="180">Web Tickets Priority</td> + <td>Maximum <b>Open</b> Tickets:</td> <td> - <input type="checkbox" name="allow_priority_change" value="1" <?php echo $config['allow_priority_change'] ?'checked="checked"':''; ?>> - <em>(Allow user to overwrite/set priority)</em> + <input type="text" name="max_open_tickets" size=4 value="<?php echo $config['max_open_tickets']; ?>"> + per email/user. <em>(Helps with spam and email flood control - enter 0 for unlimited)</em> </td> </tr> <tr> - <td width="180">Emailed Tickets Priority</td> + <td>Ticket Auto-lock Time:</td> <td> - <input type="checkbox" name="use_email_priority" value="1" <?php echo $config['use_email_priority'] ?'checked="checked"':''; ?> > - <em>(Use email priority when available)</em> + <input type="text" name="autolock_minutes" size=4 value="<?php echo $config['autolock_minutes']; ?>"> + <font class="error"><?php echo $errors['autolock_minutes']; ?></font> + <em>(Minutes to lock a ticket on activity - enter 0 to disable locking)</em> + </td> + </tr> + <tr> + <td width="180">Web Tickets Priority:</td> + <td> + <input type="checkbox" name="allow_priority_change" value="1" <?php echo $config['allow_priority_change'] ?'checked="checked"':''; ?>> + <em>(Allow user to overwrite/set priority)</em> + </td> + </tr> + <tr> + <td width="180">Emailed Tickets Priority:</td> + <td> + <input type="checkbox" name="use_email_priority" value="1" <?php echo $config['use_email_priority'] ?'checked="checked"':''; ?> > + <em>(Use email priority when available)</em> </td> </tr> <tr> - <td width="180">Show Related Tickets</td> + <td width="180">Show Related Tickets:</td> <td> <input type="checkbox" name="show_related_tickets" value="1" <?php echo $config['show_related_tickets'] ?'checked="checked"':''; ?> > <em>(Show all related tickets on user login - otherwise access is restricted to one ticket view per login)</em> </td> </tr> <tr> - <td width="180">Show Notes Inline</td> + <td width="180">Show Notes Inline:</td> <td> <input type="checkbox" name="show_notes_inline" value="1" <?php echo $config['show_notes_inline'] ?'checked="checked"':''; ?> > <em>(Show internal notes inline)</em> </td> - </tr> - <tr> - <td>Human Verification:</td> - <td> - <input type="checkbox" name="enable_captcha" <?php echo $config['enable_captcha']?'checked="checked"':''; ?>> - Enable CAPTCHA on new web tickets.<em>(requires GDLib)</em> <font class="error"> <?php echo $errors['enable_captcha']; ?></font><br/> - </td> </tr> - <tr> - <td>Maximum <b>Open</b> Tickets:</td> + <tr><td>Clickable URLs:</td> <td> - <input type="text" name="max_open_tickets" size=4 value="<?php echo $config['max_open_tickets']; ?>"> - per email/user. <em>(Helps with spam and email flood control - enter 0 for unlimited)</em> + <input type="checkbox" name="clickable_urls" <?php echo $config['clickable_urls']?'checked="checked"':''; ?>> + <em>(converts URLs in ticket thread to clickable links)</em> </td> </tr> <tr> - <td>Ticket Auto-lock Time:</td> + <td>Human Verification:</td> <td> - <input type="text" name="autolock_minutes" size=4 value="<?php echo $config['autolock_minutes']; ?>"> - <font class="error"><?php echo $errors['autolock_minutes']; ?></font> - <em>(Minutes to lock a ticket on activity - enter 0 to disable locking)</em> + <input type="checkbox" name="enable_captcha" <?php echo $config['enable_captcha']?'checked="checked"':''; ?>> + Enable CAPTCHA on new web tickets.<em>(requires GDLib)</em> <font class="error"> <?php echo $errors['enable_captcha']; ?></font><br/> </td> </tr> <tr> @@ -139,6 +151,94 @@ Hide staff's name on responses. </td> </tr> + <tr> + <th colspan="2"> + <em><b>Attachments</b>: Size setting mainly apply to web tickets.</em> + </th> + </tr> + <tr> + <td width="180">Allow Attachments:</td> + <td> + <input type="checkbox" name="allow_attachments" <?php echo $config['allow_attachments']?'checked="checked"':''; ?>><b>Allow Attachments</b> + <em>(Global Setting)</em> + <font class="error"> <?php echo $errors['allow_attachments']; ?></font> + </td> + </tr> + <tr> + <td width="180">Emailed Attachments:</td> + <td> + <input type="checkbox" name="allow_email_attachments" <?php echo $config['allow_email_attachments']?'checked="checked"':''; ?>> Accept emailed files + <font class="error"> <?php echo $errors['allow_email_attachments']; ?></font> + </td> + </tr> + <tr> + <td width="180">Online Attachments:</td> + <td> + <input type="checkbox" name="allow_online_attachments" <?php echo $config['allow_online_attachments']?'checked="checked"':''; ?> > + Allow web upload + <input type="checkbox" name="allow_online_attachments_onlogin" <?php echo $config['allow_online_attachments_onlogin'] ?'checked="checked"':''; ?> > + Limit to authenticated users only. <em>(User must be logged in to upload files)</em> + <font class="error"> <?php echo $errors['allow_online_attachments']; ?></font> + </td> + </tr> + <tr> + <td>Max. User File Uploads:</td> + <td> + <select name="max_user_file_uploads"> + <?php + for($i = 1; $i <=$maxfileuploads; $i++) { + ?> + <option <?php echo $config['max_user_file_uploads']==$i?'selected="selected"':''; ?> value="<?php echo $i; ?>"> + <?php echo $i; ?> <?php echo ($i>1)?'files':'file'; ?></option> + <?php + } ?> + </select> + <em>(Number of files the user is allowed to upload simultaneously)</em> + <font class="error"> <?php echo $errors['max_user_file_uploads']; ?></font> + </td> + </tr> + <tr> + <td>Max. Staff File Uploads:</td> + <td> + <select name="max_staff_file_uploads"> + <?php + for($i = 1; $i <=$maxfileuploads; $i++) { + ?> + <option <?php echo $config['max_staff_file_uploads']==$i?'selected="selected"':''; ?> value="<?php echo $i; ?>"> + <?php echo $i; ?> <?php echo ($i>1)?'files':'file'; ?></option> + <?php + } ?> + </select> + <em>(Number of files the staff is allowed to upload simultaneously)</em> + <font class="error"> <?php echo $errors['max_staff_file_uploads']; ?></font> + </td> + </tr> + <tr> + <td width="180">Maximum File Size:</td> + <td> + <input type="text" name="max_file_size" value="<?php echo $config['max_file_size']; ?>"> in bytes. + <em>(System Max. <?php echo Format::file_size(ini_get('upload_max_filesize')); ?>)</em> + <font class="error"> <?php echo $errors['max_file_size']; ?></font> + </td> + </tr> + <tr> + <td width="180">Ticket Response Files:</td> + <td> + <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?> >Email attachments to the user + </td> + </tr> + <tr> + <th colspan="2"> + <em><strong>Accepted File Types</strong>: Limit the type of files users are allowed to submit. + <font class="error"> <?php echo $errors['allowed_filetypes']; ?></font></em> + </th> + </tr> + <tr> + <td colspan="2"> + <em>Enter allowed file extensions separated by a comma. e.g .doc, .pdf. To accept all files enter wildcard <b><i>.*</i></b> i.e dotStar (NOT Recommended).</em><br> + <textarea name="allowed_filetypes" cols="21" rows="4" style="width: 65%;" wrap="hard" ><?php echo $config['allowed_filetypes']; ?></textarea> + </td> + </tr> </tbody> </table> <p style="padding-left:250px;"> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a58206963acf4deb36fd48bf17d80c2ea63020aa..ddd2839b24a720341841edd2c9b81e4efd218164 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -27,7 +27,7 @@ if($ticket->isAssigned() && ( $warn.=' <span class="Icon assignedTicket">Ticket is assigned to '.implode('/', $ticket->getAssignees()).'</span>'; if(!$errors['err'] && ($lock && $lock->getStaffId()!=$thisstaff->getId())) $errors['err']='This ticket is currently locked by '.$lock->getStaffName(); -if(!$errors['err'] && ($emailBanned=EmailFilter::isBanned($ticket->getEmail()))) +if(!$errors['err'] && ($emailBanned=TicketFilter::isBanned($ticket->getEmail()))) $errors['err']='Email is in banlist! Must be removed before any reply/response'; $unbannable=($emailBanned) ? BanList::includes($ticket->getEmail()) : false; diff --git a/include/upgrader/sql/15719536-dd0022fb.patch.sql b/include/upgrader/sql/15719536-dd0022fb.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..5a44aa898bb9e88ca5c6cda017910f05bbb0e206 --- /dev/null +++ b/include/upgrader/sql/15719536-dd0022fb.patch.sql @@ -0,0 +1,11 @@ + +/** + * @version v1.7 RC2+ + * @signature dd0022fb14892c0bb6a9700392df2de7 + * + * Transitional patch - dev branch installations + * + */ + +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='dd0022fb14892c0bb6a9700392df2de7'; diff --git a/include/upgrader/sql/15b30765-dd0022fb.cleanup.sql b/include/upgrader/sql/15b30765-dd0022fb.cleanup.sql new file mode 100644 index 0000000000000000000000000000000000000000..fdbd27d1f799d5da54b12d60522aa451f177798c --- /dev/null +++ b/include/upgrader/sql/15b30765-dd0022fb.cleanup.sql @@ -0,0 +1,10 @@ + +-- Drop fields we no longer need in the reference table. +-- NOTE: This was moved from the 1.6* major upgrade script because the +-- handling of attachments changed with dd0022fb +ALTER TABLE `%TABLE_PREFIX%ticket_attachment` + DROP `file_size`, + DROP `file_name`, + DROP `file_key`, + DROP `updated`, + DROP `deleted`; diff --git a/include/upgrader/sql/15b30765-dd0022fb.patch.sql b/include/upgrader/sql/15b30765-dd0022fb.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..0006139d679fdf9af8924b639b488e3edc0f2c34 --- /dev/null +++ b/include/upgrader/sql/15b30765-dd0022fb.patch.sql @@ -0,0 +1,26 @@ +/** + * @version v1.7 RC2+ + * @signature dd0022fb14892c0bb6a9700392df2de7 + * + * Migrate file attachment data from %file to %file_chunk + * + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`; +CREATE TABLE `%TABLE_PREFIX%file_chunk` ( + `file_id` int(11) NOT NULL, + `chunk_id` int(11) NOT NULL, + `filedata` longblob NOT NULL, + PRIMARY KEY (`file_id`, `chunk_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%file_chunk` (`file_id`, `chunk_id`, `filedata`) + SELECT `id`, 0, `filedata` + FROM `%TABLE_PREFIX%file`; + +ALTER TABLE `%TABLE_PREFIX%file` DROP COLUMN `filedata`; +OPTIMIZE TABLE `%TABLE_PREFIX%file`; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='dd0022fb14892c0bb6a9700392df2de7'; diff --git a/include/upgrader/sql/1da1bcba-15b30765.patch.sql b/include/upgrader/sql/1da1bcba-15b30765.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..cb37a02f4af2e3c86f0367674454b22554973c3b --- /dev/null +++ b/include/upgrader/sql/1da1bcba-15b30765.patch.sql @@ -0,0 +1,11 @@ +/** + * @version v1.7 RC2+ + * @signature 15b3076533123ff617801d89861136c8 + * + * Transitional patch. + * + */ + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='15b3076533123ff617801d89861136c8'; diff --git a/include/upgrader/sql/c00511c7-7be60a84.cleanup.sql b/include/upgrader/sql/c00511c7-7be60a84.cleanup.sql index 01d69e2d72b732b2d7c3f618fd6e595ccb7c67f7..eabc72c9e22d724978f4f5449901ea5157b2b6a1 100644 --- a/include/upgrader/sql/c00511c7-7be60a84.cleanup.sql +++ b/include/upgrader/sql/c00511c7-7be60a84.cleanup.sql @@ -4,14 +4,6 @@ ALTER TABLE `%TABLE_PREFIX%config` DROP COLUMN `timezone_offset`, DROP COLUMN `api_passphrase`; --- Drop fields we no longer need in the reference table. -ALTER TABLE `%TABLE_PREFIX%ticket_attachment` - DROP `file_size`, - DROP `file_name`, - DROP `file_key`, - DROP `updated`, - DROP `isdeleted`; - -- Drop fields we no longer need in staff table. ALTER TABLE `%TABLE_PREFIX%staff` DROP `append_signature`, diff --git a/include/upgrader/sql/c00511c7-7be60a84.patch.sql b/include/upgrader/sql/c00511c7-7be60a84.patch.sql index 638248b976019a670c773578b564d99a3daacc6a..fdefd09bea960de619fc5a1feac8bd69d3c77b8e 100644 --- a/include/upgrader/sql/c00511c7-7be60a84.patch.sql +++ b/include/upgrader/sql/c00511c7-7be60a84.patch.sql @@ -63,7 +63,7 @@ ALTER TABLE `%TABLE_PREFIX%config` ADD `transfer_alert_dept_manager` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '1' AFTER `transfer_alert_assigned` , ADD `transfer_alert_dept_members` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `transfer_alert_dept_manager`, ADD `send_sys_errors` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `enable_email_piping`, - ADD `enable_kb` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `use_email_priority`, + ADD `enable_kb` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `use_email_priority`, ADD `enable_premade` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `enable_kb`, ADD `show_related_tickets` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `auto_assign_reopened_tickets`, ADD `schema_signature` CHAR( 32 ) NOT NULL AFTER `ostversion`; diff --git a/include/upgrader/sql/d0e37dca-1da1bcba.patch.sql b/include/upgrader/sql/d0e37dca-1da1bcba.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..3d3bda68e2fd49293e1a73545506416c7a1cc126 --- /dev/null +++ b/include/upgrader/sql/d0e37dca-1da1bcba.patch.sql @@ -0,0 +1,23 @@ +/** + * @version v1.7 RC3 + * @signature 1da1bcbafcedc65efef58f142a48ac91 + * + * Upgrade from 1.6 RC3 + filters + * + */ + +RENAME TABLE `%TABLE_PREFIX%email_filter` TO `%TABLE_PREFIX%filter`; + +RENAME TABLE `%TABLE_PREFIX%email_filter_rule` TO `%TABLE_PREFIX%filter_rule`; + +ALTER TABLE `%TABLE_PREFIX%filter` CHANGE `reject_email` `reject_ticket` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0'; + +ALTER TABLE `%TABLE_PREFIX%filter` + ADD `target` ENUM( 'Any', 'Web', 'Email', 'API' ) NOT NULL DEFAULT 'Any' AFTER `sla_id` , + ADD INDEX ( `target` ); + +UPDATE `%TABLE_PREFIX%filter` SET `target` = 'Email' WHERE `email_id` != 0; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='1da1bcbafcedc65efef58f142a48ac91'; diff --git a/include/upgrader/sql/dd0022fb-f4da0c9b.patch.sql b/include/upgrader/sql/dd0022fb-f4da0c9b.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..c0f10cfa52ec2a8767109d12cfcdec38d9d13136 --- /dev/null +++ b/include/upgrader/sql/dd0022fb-f4da0c9b.patch.sql @@ -0,0 +1,445 @@ +/* + * @version=1.7RC2+ + * + * change variable names + */ + +-- Canned Responses (with variables) +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%id', '%{ticket.id}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%ticket', '%{ticket.number}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%name', '%{ticket.name}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%email', '%{ticket.email}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%subject', '%{ticket.subject}'); + +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%status', '%{ticket.status}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%priority', '%{ticket.priority}'); + +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%auth', '%{ticket.auth_token}'); + +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%phone', '%{ticket.phone_number}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%createdate', '%{ticket.create_date}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%duedate', '%{ticket.due_date}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%closedate', '%{ticket.close_date}'); + +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%topic', '%{ticket.topic.name}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%dept', '%{ticket.dept.name}'); +UPDATE `%TABLE_PREFIX%canned_response` SET `response` = REPLACE(`response`, '%team', '%{ticket.team.name}'); + +-- %id +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%id', '%{ticket.id}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%id', '%{ticket.id}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%id', '%{ticket.id}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%id', '%{ticket.id}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%id', '%{ticket.id}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%id', '%{ticket.id}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%id', '%{ticket.id}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%id', '%{ticket.id}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%id', '%{ticket.id}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%id', '%{ticket.id}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%id', '%{ticket.id}'); + +-- %ticket +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%ticket', '%{ticket.number}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%ticket', '%{ticket.number}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%ticket', '%{ticket.number}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%ticket', '%{ticket.number}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%ticket', '%{ticket.number}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%ticket', '%{ticket.number}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%ticket', '%{ticket.number}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%ticket', '%{ticket.number}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%ticket', '%{ticket.number}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%ticket', '%{ticket.number}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%ticket', '%{ticket.number}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%ticket', '%{ticket.number}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%ticket', '%{ticket.number}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%ticket', '%{ticket.number}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%ticket', '%{ticket.number}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%ticket', '%{ticket.number}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%ticket', '%{ticket.number}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%ticket', '%{ticket.number}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%ticket', '%{ticket.number}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%ticket', '%{ticket.number}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%ticket', '%{ticket.number}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%ticket', '%{ticket.number}'); + +-- %subject +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%subject', '%{ticket.subject}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%subject', '%{ticket.subject}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%subject', '%{ticket.subject}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%subject', '%{ticket.subject}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%subject', '%{ticket.subject}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%subject', '%{ticket.subject}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%subject', '%{ticket.subject}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%subject', '%{ticket.subject}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%subject', '%{ticket.subject}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%subject', '%{ticket.subject}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%subject', '%{ticket.subject}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%subject', '%{ticket.subject}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%subject', '%{ticket.subject}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%subject', '%{ticket.subject}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%subject', '%{ticket.subject}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%subject', '%{ticket.subject}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%subject', '%{ticket.subject}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%subject', '%{ticket.subject}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%subject', '%{ticket.subject}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%subject', '%{ticket.subject}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%subject', '%{ticket.subject}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%subject', '%{ticket.subject}'); + +-- %name +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%name', '%{ticket.name}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%name', '%{ticket.name}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%name', '%{ticket.name}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%name', '%{ticket.name}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%name', '%{ticket.name}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%name', '%{ticket.name}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%name', '%{ticket.name}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%name', '%{ticket.name}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%name', '%{ticket.name}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%name', '%{ticket.name}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%name', '%{ticket.name}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%name', '%{ticket.name}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%name', '%{ticket.name}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%name', '%{ticket.name}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%name', '%{ticket.name}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%name', '%{ticket.name}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%name', '%{ticket.name}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%name', '%{ticket.name}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%name', '%{ticket.name}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%name', '%{ticket.name}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%name', '%{ticket.name}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%name', '%{ticket.name}'); + +-- %email +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%email', '%{ticket.email}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%email', '%{ticket.email}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%email', '%{ticket.email}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%email', '%{ticket.email}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%email', '%{ticket.email}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%email', '%{ticket.email}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%email', '%{ticket.email}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%email', '%{ticket.email}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%email', '%{ticket.email}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%email', '%{ticket.email}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%email', '%{ticket.email}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%email', '%{ticket.email}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%email', '%{ticket.email}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%email', '%{ticket.email}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%email', '%{ticket.email}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%email', '%{ticket.email}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%email', '%{ticket.email}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%email', '%{ticket.email}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%email', '%{ticket.email}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%email', '%{ticket.email}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%email', '%{ticket.email}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%email', '%{ticket.email}'); + +-- %status +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%status', '%{ticket.status}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%status', '%{ticket.status}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%status', '%{ticket.status}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%status', '%{ticket.status}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%status', '%{ticket.status}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%status', '%{ticket.status}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%status', '%{ticket.status}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%status', '%{ticket.status}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%status', '%{ticket.status}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%status', '%{ticket.status}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%status', '%{ticket.status}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%status', '%{ticket.status}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%status', '%{ticket.status}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%status', '%{ticket.status}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%status', '%{ticket.status}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%status', '%{ticket.status}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%status', '%{ticket.status}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%status', '%{ticket.status}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%status', '%{ticket.status}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%status', '%{ticket.status}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%status', '%{ticket.status}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%status', '%{ticket.status}'); + +-- %priority +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_subj` = REPLACE(`ticket_autoresp_subj`, '%priority', '%{ticket.priority}'), + `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%priority', '%{ticket.priority}'), + `message_autoresp_subj` = REPLACE(`message_autoresp_subj`, '%priority', '%{ticket.priority}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%priority', '%{ticket.priority}'), + `ticket_notice_subj` = REPLACE(`ticket_notice_subj`, '%priority', '%{ticket.priority}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%priority', '%{ticket.priority}'), + `ticket_overlimit_subj` = REPLACE(`ticket_overlimit_subj`, '%priority', '%{ticket.priority}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%priority', '%{ticket.priority}'), + `ticket_reply_subj` = REPLACE(`ticket_reply_subj`, '%priority', '%{ticket.priority}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%priority', '%{ticket.priority}'), + `ticket_alert_subj` = REPLACE(`ticket_alert_subj`, '%priority', '%{ticket.priority}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%priority', '%{ticket.priority}'), + `message_alert_subj` = REPLACE(`message_alert_subj`, '%priority', '%{ticket.priority}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%priority', '%{ticket.priority}'), + `note_alert_subj` = REPLACE(`note_alert_subj`, '%priority', '%{ticket.priority}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%priority', '%{ticket.priority}'), + `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%priority', '%{ticket.priority}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%priority', '%{ticket.priority}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%priority', '%{ticket.priority}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%priority', '%{ticket.priority}'), + `ticket_overdue_subj` = REPLACE(`ticket_overdue_subj`, '%priority', '%{ticket.priority}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%priority', '%{ticket.priority}'); + +-- %auth +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%auth', '%{ticket.auth_code}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%auth', '%{ticket.auth_code}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%auth', '%{ticket.auth_code}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%auth', '%{ticket.auth_code}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%auth', '%{ticket.auth_code}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%auth', '%{ticket.auth_code}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%auth', '%{ticket.auth_code}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%auth', '%{ticket.auth_code}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%auth', '%{ticket.auth_code}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%auth', '%{ticket.auth_code}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%auth', '%{ticket.auth_code}'); + +-- %phone +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%phone', '%{ticket.phone_number}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%phone', '%{ticket.phone_number}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%phone', '%{ticket.phone_number}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%phone', '%{ticket.phone_number}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%phone', '%{ticket.phone_number}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%phone', '%{ticket.phone_number}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%phone', '%{ticket.phone_number}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%phone', '%{ticket.phone_number}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%phone', '%{ticket.phone_number}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%phone', '%{ticket.phone_number}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%phone', '%{ticket.phone_number}'); + +-- %createdate +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%createdate', '%{ticket.create_date}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%createdate', '%{ticket.create_date}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%createdate', '%{ticket.create_date}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%createdate', '%{ticket.create_date}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%createdate', '%{ticket.create_date}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%createdate', '%{ticket.create_date}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%createdate', '%{ticket.create_date}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%createdate', '%{ticket.create_date}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%createdate', '%{ticket.create_date}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%createdate', '%{ticket.create_date}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%createdate', '%{ticket.create_date}'); + +-- %duedate +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%duedate', '%{ticket.due_date}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%duedate', '%{ticket.due_date}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%duedate', '%{ticket.due_date}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%duedate', '%{ticket.due_date}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%duedate', '%{ticket.due_date}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%duedate', '%{ticket.due_date}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%duedate', '%{ticket.due_date}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%duedate', '%{ticket.due_date}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%duedate', '%{ticket.due_date}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%duedate', '%{ticket.due_date}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%duedate', '%{ticket.due_date}'); + +-- %closedate +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%closedate', '%{ticket.close_date}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%closedate', '%{ticket.close_date}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%closedate', '%{ticket.close_date}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%closedate', '%{ticket.close_date}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%closedate', '%{ticket.close_date}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%closedate', '%{ticket.close_date}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%closedate', '%{ticket.close_date}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%closedate', '%{ticket.close_date}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%closedate', '%{ticket.close_date}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%closedate', '%{ticket.close_date}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%closedate', '%{ticket.close_date}'); + +-- %topic +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%topic', '%{ticket.topic.name}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%topic', '%{ticket.topic.name}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%topic', '%{ticket.topic.name}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%topic', '%{ticket.topic.name}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%topic', '%{ticket.topic.name}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%topic', '%{ticket.topic.name}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%topic', '%{ticket.topic.name}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%topic', '%{ticket.topic.name}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%topic', '%{ticket.topic.name}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%topic', '%{ticket.topic.name}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%topic', '%{ticket.topic.name}'); + +-- %dept +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%dept', '%{ticket.dept.name}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%dept', '%{ticket.dept.name}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%dept', '%{ticket.dept.name}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%dept', '%{ticket.dept.name}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%dept', '%{ticket.dept.name}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%dept', '%{ticket.dept.name}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%dept', '%{ticket.dept.name}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%dept', '%{ticket.dept.name}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%dept', '%{ticket.dept.name}'), + `transfer_alert_subj` = REPLACE(`transfer_alert_subj`, '%dept', '%{ticket.dept.name}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%dept', '%{ticket.dept.name}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%dept', '%{ticket.dept.name}'); + +-- %team +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%team', '%{ticket.team.name}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%team', '%{ticket.team.name}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%team', '%{ticket.team.name}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%team', '%{ticket.team.name}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%team', '%{ticket.team.name}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%team', '%{ticket.team.name}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%team', '%{ticket.team.name}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%team', '%{ticket.team.name}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%team', '%{ticket.team.name}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%team', '%{ticket.team.name}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%team', '%{ticket.team.name}'); + +-- %clientlink +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%clientlink', '%{ticket.client_link}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%clientlink', '%{ticket.client_link}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%clientlink', '%{ticket.client_link}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%clientlink', '%{ticket.client_link}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%clientlink', '%{ticket.client_link}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%clientlink', '%{ticket.client_link}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%clientlink', '%{ticket.client_link}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%clientlink', '%{ticket.client_link}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%clientlink', '%{ticket.client_link}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%clientlink', '%{ticket.client_link}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%clientlink', '%{ticket.client_link}'); + +-- %staff (recipient of the alert) +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%staff', '%{recipient}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%staff', '%{recipient}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%staff', '%{recipient}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%staff', '%{recipient}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%staff', '%{recipient}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%staff', '%{recipient}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%staff', '%{recipient}'); + +-- %message +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%message', '%{message}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%message', '%{message}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%message', '%{message}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%message', '%{message}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%message', '%{message}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%message', '%{message}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%message', '%{message}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%message', '%{message}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%message', '%{message}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%message', '%{message}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%message', '%{message}'); + +-- %response +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%response', '%{response}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%response', '%{response}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%response', '%{response}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%response', '%{response}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%response', '%{response}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%response', '%{response}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%response', '%{response}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%response', '%{response}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%response', '%{response}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%response', '%{response}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%response', '%{response}'); + +-- %note +UPDATE `%TABLE_PREFIX%email_template` + SET `note_alert_body` = REPLACE(`note_alert_body`, '%note', '* %{note.title} *\r\n\r\n%{note.message}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%note', '%{comments}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%note', '%{comments}'); + +-- %{note} (dev branch installations) +UPDATE `%TABLE_PREFIX%email_template` + SET `note_alert_body` = REPLACE(`note_alert_body`, '%{note}', '%{note.message}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%{note}', '%{comments}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%{note}', '%{comments}'); + +-- %{title} (dev branch installations) +UPDATE `%TABLE_PREFIX%email_template` + SET `note_alert_body` = REPLACE(`note_alert_body`, '%{title}', '* %{note.title} *\r\n'); + +-- %url +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%url', '%{url}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%url', '%{url}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%url', '%{url}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%url', '%{url}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%url', '%{url}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%url', '%{url}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%url', '%{url}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%url', '%{url}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%url', '%{url}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%url', '%{url}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%url', '%{url}'); + +-- %signature +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%signature', '%{signature}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%signature', '%{signature}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%signature', '%{signature}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%signature', '%{signature}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%signature', '%{signature}'), + `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%signature', '%{signature}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%signature', '%{signature}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%signature', '%{signature}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%signature', '%{signature}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%signature', '%{signature}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%signature', '%{signature}'); + +-- %assignee +UPDATE `%TABLE_PREFIX%email_template` + SET `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%assignee', '%{assignee}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%assignee', '%{assignee}'); + +-- %assigner +UPDATE `%TABLE_PREFIX%email_template` + SET `assigned_alert_subj` = REPLACE(`assigned_alert_subj`, '%assigner', '%{assigner}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%assigner', '%{assigner}'); + +/* Change links */ + +-- Client URL -> %{url}/view.php?e=%{ticket.email}&t=%{ticket.number} +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_autoresp_body` = REPLACE(`ticket_autoresp_body`, '%{url}/view.php?e=%{ticket.email}&t=%{ticket.number}', '%{ticket.client_link}'), + `message_autoresp_body` = REPLACE(`message_autoresp_body`, '%{url}/view.php?e=%{ticket.email}&t=%{ticket.number}', '%{ticket.client_link}'), + `ticket_notice_body` = REPLACE(`ticket_notice_body`, '%{url}/view.php?e=%{ticket.email}&t=%{ticket.number}', '%{ticket.client_link}'), + `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%{url}/view.php?e=%{ticket.email}&t=%{ticket.number}', '%{ticket.client_link}'), + `ticket_reply_body` = REPLACE(`ticket_reply_body`, '%{url}/view.php?e=%{ticket.email}&t=%{ticket.number}', '%{ticket.client_link}'); + +-- Client URL -> %{url}/view.php?e=%{ticket.email} (overlimit template) +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_overlimit_body` = REPLACE(`ticket_overlimit_body`, '%{url}/view.php?e=%{ticket.email}', '%{url}/tickets.php?e=%{ticket.email}'); + +-- Staff URL -> %{url}/scp/ticket.php?id=%{ticket.id} (should be tickets.php) +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%{url}/scp/ticket.php?id=%{ticket.id}', '%{ticket.staff_link}'); + +-- Staff URL 2 -> %{url}/scp/tickets.php?id=%{ticket.id} +UPDATE `%TABLE_PREFIX%email_template` + SET `ticket_alert_body` = REPLACE(`ticket_alert_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `message_alert_body` = REPLACE(`message_alert_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `note_alert_body` = REPLACE(`note_alert_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `transfer_alert_body` = REPLACE(`transfer_alert_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'), + `ticket_overdue_body` = REPLACE(`ticket_overdue_body`, '%{url}/scp/tickets.php?id=%{ticket.id}', '%{ticket.staff_link}'); + + -- update schema signature +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='f4da0c9befa257b5a20a923d4e9c0e91'; diff --git a/login.php b/login.php index f35693fd4083401a1aea72125e8cb1cc116710c7..834f00ddf72410de113271462f272ef38a4b9737 100644 --- a/login.php +++ b/login.php @@ -21,8 +21,17 @@ define('OSTCLIENTINC',TRUE); //make includes happy require_once(INCLUDE_DIR.'class.client.php'); require_once(INCLUDE_DIR.'class.ticket.php'); -if ($_POST) ClientSession::tryLogin($_POST['lticket'], $_POST['lemail']); -else ClientSession::tryLogin($_GET['t'], $_GET['e'], $_GET['a']); +if($_POST) { + + if(($user=Client::login(trim($_POST['lticket']), trim($_POST['lemail']), null, $errors))) { + //XXX: Ticket owner is assumed. + @header('Location: tickets.php?id='.$user->getTicketID()); + require_once('tickets.php'); //Just in case of 'header already sent' error. + exit; + } elseif(!$errors['err']) { + $errors['err'] = 'Authentication error - try again!'; + } +} $nav = new UserNav(); $nav->setActiveNav('status'); diff --git a/main.inc.php b/main.inc.php index abcbfeabdaee005bdd83abb7719ac163e0e531af..b007a5a4b53fbe43cb20967c323da94721311a4b 100644 --- a/main.inc.php +++ b/main.inc.php @@ -62,9 +62,8 @@ /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/ #Current version && schema signature (Changes from version to version) - define('THIS_VERSION','1.7-RC2'); //Shown on admin panel - define('SCHEMA_SIGNATURE','d0e37dca324648f1ce2d10528a6026d4'); //MD5 signature of the db schema. (used to trigger upgrades) - + define('THIS_VERSION','1.7-RC2+'); //Shown on admin panel + define('SCHEMA_SIGNATURE','f4da0c9befa257b5a20a923d4e9c0e91'); //MD5 signature of the db schema. (used to trigger upgrades) #load config info $configfile=''; if(file_exists(ROOT_DIR.'ostconfig.php')) //Old installs prior to v 1.6 RC5 @@ -113,7 +112,8 @@ require(INCLUDE_DIR.'mysql.php'); #CURRENT EXECUTING SCRIPT. - define('THISPAGE',Misc::currentURL()); + define('THISPAGE', Misc::currentURL()); + define('THISURI', $_SERVER['REQUEST_URI']); # This is to support old installations. with no secret salt. if(!defined('SECRET_SALT')) define('SECRET_SALT',md5(TABLE_PREFIX.ADMIN_EMAIL)); @@ -132,6 +132,7 @@ define('SYSLOG_TABLE',TABLE_PREFIX.'syslog'); define('SESSION_TABLE',TABLE_PREFIX.'session'); define('FILE_TABLE',TABLE_PREFIX.'file'); + define('FILE_CHUNK_TABLE',TABLE_PREFIX.'file_chunk'); define('STAFF_TABLE',TABLE_PREFIX.'staff'); define('DEPT_TABLE',TABLE_PREFIX.'department'); @@ -159,8 +160,10 @@ define('EMAIL_TABLE',TABLE_PREFIX.'email'); define('EMAIL_TEMPLATE_TABLE',TABLE_PREFIX.'email_template'); - define('EMAIL_FILTER_TABLE',TABLE_PREFIX.'email_filter'); - define('EMAIL_FILTER_RULE_TABLE',TABLE_PREFIX.'email_filter_rule'); + + define('FILTER_TABLE',TABLE_PREFIX.'filter'); + define('FILTER_RULE_TABLE',TABLE_PREFIX.'filter_rule'); + define('BANLIST_TABLE',TABLE_PREFIX.'email_banlist'); //Not in use anymore....as of v 1.7 define('SLA_TABLE',TABLE_PREFIX.'sla'); diff --git a/scp/apikeys.php b/scp/apikeys.php index e393a31c5f2bea0647a238240ba5e9021ad3c9ab..1e17675442066356a077041f03357fa0c3913414 100644 --- a/scp/apikeys.php +++ b/scp/apikeys.php @@ -96,7 +96,7 @@ $page='apikeys.inc.php'; if($api || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) $page='apikey.inc.php'; -$nav->setTabActive('settings'); +$nav->setTabActive('manage'); require(STAFFINC_DIR.'header.inc.php'); require(STAFFINC_DIR.$page); include(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/autocron.php b/scp/autocron.php index 08b65ba3481e65f99e6bb06567c3be46efb60631..4a3b00678539f628908743800177efc4097e54e1 100644 --- a/scp/autocron.php +++ b/scp/autocron.php @@ -30,13 +30,17 @@ ob_start(); //Keep the image output clean. Hide our dirt. //TODO: Make cron DB based to allow for better time limits. Direct calls for now sucks big time. //We DON'T want to spawn cron on every page load...we record the lastcroncall on the session per user $sec=time()-$_SESSION['lastcroncall']; +$caller = $thisstaff->getUserName(); + if($sec>180): //user can call cron once every 3 minutes. -require_once(INCLUDE_DIR.'class.cron.php'); +require_once(INCLUDE_DIR.'class.cron.php'); + +$thisstaff = null; //Clear staff obj to avoid false credit internal notes & auto-assignment Cron::TicketMonitor(); //Age tickets: We're going to age tickets regardless of cron settings. if($cfg && $cfg->isAutoCronEnabled()) { //ONLY fetch tickets if autocron is enabled! Cron::MailFetcher(); //Fetch mail. - $ost->logDebug('Auto Cron', 'Mail fetcher cron call ['.$thisstaff->getUserName().']'); -} + $ost->logDebug('Auto Cron', 'Mail fetcher cron call ['.$caller.']'); +} $_SESSION['lastcroncall']=time(); endif; diff --git a/scp/banlist.php b/scp/banlist.php index 081fde9b4ed0cd18713cab78e795c2e0eaf1df1b..a31d91d7e8d2392c2d7e23dba570a07257532ec8 100644 --- a/scp/banlist.php +++ b/scp/banlist.php @@ -68,7 +68,7 @@ if($_POST && !$errors && $filter){ }else{ $count=count($_POST['ids']); if($_POST['enable']){ - $sql='UPDATE '.EMAIL_FILTER_RULE_TABLE.' SET isactive=1 WHERE filter_id='. + $sql='UPDATE '.FILTER_RULE_TABLE.' SET isactive=1 WHERE filter_id='. db_input($filter->getId()). ' AND id IN ('. implode(',', db_input($_POST['ids'])).')'; @@ -81,7 +81,7 @@ if($_POST && !$errors && $filter){ $errors['err']='Unable to enable selected emails'; } }elseif($_POST['disable']){ - $sql='UPDATE '.EMAIL_FILTER_RULE_TABLE.' SET isactive=0 WHERE filter_id='. + $sql='UPDATE '.FILTER_RULE_TABLE.' SET isactive=0 WHERE filter_id='. db_input($filter->getId()). ' AND id IN ('. implode(',', db_input($_POST['ids'])).')'; diff --git a/scp/css/scp.css b/scp/css/scp.css index 0042ed4306a406337f3a3deec2811467c06a325d..fd4faed6dc77cb4e1ebee9ca0c1a8d71b661dab6 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -247,6 +247,13 @@ a.attachment { background:url(../images/icons/attachment.gif ) } a.api { background:url(../images/icons/api.png) } a.newapi { background:url(../images/icons/new_api.png) } +a.ticket-settings { background:url(../images/icons/ticket-settings.gif) } +a.email-settings { background:url(../images/icons/email-settings.gif) } +a.kb-settings { background:url(../images/icons/kb-settings.gif) } +a.alert-settings { background:url(../images/icons/alert-settings.gif) } +a.email-autoresponders { background:url(../images/icons/email-autoresponders.gif) } + + a.sla { background:url(../images/icons/slas.png) } a.newsla { background:url(../images/icons/new_sla.png) } @@ -259,7 +266,8 @@ a.emailTemplates { background:url(../images/icons/email_templates.png) } a.newEmailTemplate { background:url(../images/icons/new_email_template.png) } a.emailFilters { background:url(../images/icons/email_filters.png) } -a.newEmailFilter { background:url(../images/icons/new_email_filter.png) } +a.ticketFilters { background:url(../images/icons/ticket_filters.png) } +a.newTicketFilter { background:url(../images/icons/new_ticket_filter.png) } a.emailSettings { background:url(../images/icons/emails.png) } a.emailDiagnostic { background:url(../images/icons/email_diagnostic.gif) } @@ -1292,7 +1300,7 @@ time { #advanced-search input[type="reset"], #advanced-search input[type="button"], #print-options input[type="reset"], -#print-options input[type="button"] { +#print-options input[type="button"] { opacity:0.7; } diff --git a/scp/departments.php b/scp/departments.php index ef1acb12d62adcaa3c6285a210e7aa9d3f57fc29..31fc4c6df5602f5959d9f4585517a8ebf04fc008 100644 --- a/scp/departments.php +++ b/scp/departments.php @@ -104,7 +104,7 @@ $page='departments.inc.php'; if($dept || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) $page='department.inc.php'; -$nav->setTabActive('depts'); +$nav->setTabActive('staff'); require(STAFFINC_DIR.'header.inc.php'); require(STAFFINC_DIR.$page); include(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/filters.php b/scp/filters.php index 4ce0f30765f5192617f22345c00571f26d206af2..29045410fe685f10a86a362942f34259a6401edd 100644 --- a/scp/filters.php +++ b/scp/filters.php @@ -49,7 +49,7 @@ if($_POST){ }else{ $count=count($_POST['ids']); if($_POST['enable']){ - $sql='UPDATE '.EMAIL_FILTER_TABLE.' SET isactive=1 WHERE id IN ('. + $sql='UPDATE '.FILTER_TABLE.' SET isactive=1 WHERE id IN ('. implode(',', db_input($_POST['ids'])).')'; if(db_query($sql) && ($num=db_affected_rows())){ if($num==$count) @@ -60,7 +60,7 @@ if($_POST){ $errors['err']='Unable to enable selected filters'; } }elseif($_POST['disable']){ - $sql='UPDATE '.EMAIL_FILTER_TABLE.' SET isactive=0 WHERE id IN ('. + $sql='UPDATE '.FILTER_TABLE.' SET isactive=0 WHERE id IN ('. implode(',', db_input($_POST['ids'])).')'; if(db_query($sql) && ($num=db_affected_rows())) { if($num==$count) @@ -100,7 +100,7 @@ $page='filters.inc.php'; if($filter || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) $page='filter.inc.php'; -$nav->setTabActive('emails'); +$nav->setTabActive('manage'); require(STAFFINC_DIR.'header.inc.php'); require(STAFFINC_DIR.$page); include(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/helptopics.php b/scp/helptopics.php index 5bd1ded7048f26307f2cd18d09d6b3a946f13f65..37e865b22057483bc87595023ad92ce9be1bb306 100644 --- a/scp/helptopics.php +++ b/scp/helptopics.php @@ -96,7 +96,7 @@ $page='helptopics.inc.php'; if($topic || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) $page='helptopic.inc.php'; -$nav->setTabActive('topics'); +$nav->setTabActive('manage'); require(STAFFINC_DIR.'header.inc.php'); require(STAFFINC_DIR.$page); include(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/images/icons/alert-settings.gif b/scp/images/icons/alert-settings.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c8a0b1debd05f87bb53b0d43fb10f153738df1d Binary files /dev/null and b/scp/images/icons/alert-settings.gif differ diff --git a/scp/images/icons/email-autoresponders.gif b/scp/images/icons/email-autoresponders.gif new file mode 100644 index 0000000000000000000000000000000000000000..5c118b46b3daf67261a92bd252a97ae4a34b4153 Binary files /dev/null and b/scp/images/icons/email-autoresponders.gif differ diff --git a/scp/images/icons/email-settings.gif b/scp/images/icons/email-settings.gif new file mode 100644 index 0000000000000000000000000000000000000000..6dc8aa0023e6d69355200e53ac492d61346f83d3 Binary files /dev/null and b/scp/images/icons/email-settings.gif differ diff --git a/scp/images/icons/kb-settings.gif b/scp/images/icons/kb-settings.gif new file mode 100644 index 0000000000000000000000000000000000000000..991f3c812f0216eb0957d619a92da2e55d22131c Binary files /dev/null and b/scp/images/icons/kb-settings.gif differ diff --git a/scp/images/icons/new_ticket_filter.png b/scp/images/icons/new_ticket_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..00c091ebff2b5c2f20729f4f0b16a6405022b5ea Binary files /dev/null and b/scp/images/icons/new_ticket_filter.png differ diff --git a/scp/images/icons/ticket-settings.gif b/scp/images/icons/ticket-settings.gif new file mode 100644 index 0000000000000000000000000000000000000000..f5d5234a95f4b5d26e7a8eed41054a9377077d90 Binary files /dev/null and b/scp/images/icons/ticket-settings.gif differ diff --git a/scp/images/icons/ticket_filters.png b/scp/images/icons/ticket_filters.png new file mode 100644 index 0000000000000000000000000000000000000000..4f2065212f29d70d1f17f316de5f6b8a77b3f950 Binary files /dev/null and b/scp/images/icons/ticket_filters.png differ diff --git a/scp/images/icons/ticket_source_other.png b/scp/images/icons/ticket_source_other.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a78f27412669beb75c2371d035d5aa104bdce4 Binary files /dev/null and b/scp/images/icons/ticket_source_other.png differ diff --git a/scp/login.php b/scp/login.php index 6a28e0f45130f26f733f8eca82066565f5e2d9dc..5ba5e28e0568afb7626001cc00d76d35b02f2f93 100644 --- a/scp/login.php +++ b/scp/login.php @@ -19,20 +19,19 @@ if(!defined('INCLUDE_DIR')) die('Fatal Error. Kwaheri!'); require_once(INCLUDE_DIR.'class.staff.php'); require_once(INCLUDE_DIR.'class.csrf.php'); -$msg=$_SESSION['_staff']['auth']['msg']; -$msg=$msg?$msg:'Authentication Required'; -if($_POST && (!empty($_POST['username']) && !empty($_POST['passwd']))){ +$dest = $_SESSION['_staff']['auth']['dest']; +$msg = $_SESSION['_staff']['auth']['msg']; +$msg = $msg?$msg:'Authentication Required'; +if($_POST) { //$_SESSION['_staff']=array(); #Uncomment to disable login strikes. - $msg='Invalid login'; - if(($user=Staff::login($_POST['username'],$_POST['passwd'],$errors))){ - $dest=$_SESSION['_staff']['auth']['dest']; + if(($user=Staff::login($_POST['username'], $_POST['passwd'], $errors))){ $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; @header("Location: $dest"); require_once('index.php'); //Just incase header is messed up. exit; - }elseif(!$errors['err']){ - $errors['err']='Login error - try again'; } + + $msg = $errors['err']?$errors['err']:'Invalid login'; } define("OSTSCPINC",TRUE); //Make includes happy! include_once(INCLUDE_DIR.'staff/login.tpl.php'); diff --git a/scp/settings.php b/scp/settings.php index 9058fe42ca52c4f9b428bbc52f2ed2cb61f80a8a..0a3f97a912d8fdf6d92dc42ddf035f36bc61b12f 100644 --- a/scp/settings.php +++ b/scp/settings.php @@ -15,49 +15,28 @@ **********************************************************************/ require('admin.inc.php'); $errors=array(); -$SettingOptions=array('general'=>'General Settings', - 'dates'=>'Date and Time Options', - 'tickets'=>'Ticket Settings and Options', - 'emails'=>'Email Settings', - 'attachments'=>'Attachments Settings', - 'kb'=>'Knowledgebase Settings', - 'autoresponders'=>'Autoresponder Settings', - 'alerts'=>'Alerts and Notices Settings'); - +$settingOptions=array( + 'system' => 'System Settings', + 'tickets' => 'Ticket Settings and Options', + 'emails' => 'Email Settings', + 'kb' => 'Knowledgebase Settings', + 'autoresp' => 'Autoresponder Settings', + 'alerts' => 'Alerts and Notices Settings'); //Handle a POST. -if($_POST && !$errors){ - $errors=array(); - if($cfg && $cfg->updateSettings($_POST,$errors)){ - $msg=Format::htmlchars($SettingOptions[$_POST['t']]).' Updated Successfully'; +if($_POST && !$errors) { + if($cfg && $cfg->updateSettings($_POST,$errors)) { + $msg=Format::htmlchars($settingOptions[$_POST['t']]).' Updated Successfully'; $cfg->reload(); - }elseif(!$errors['err']){ - $errors['err']='Unable to update system settings - correct any errors below and try again'; + } elseif(!$errors['err']) { + $errors['err']='Unable to update settings - correct errors below and try again'; } } -$target=($_REQUEST['t'] && $SettingOptions[$_REQUEST['t']])?$_REQUEST['t']:'general'; - -$nav->setTabActive('settings'); -require(STAFFINC_DIR.'header.inc.php'); -?> -<h2>System Preferences and Settings - <span>osTicket (v<?php echo $cfg->getVersion(); ?>)</span></h2> -<div style="padding-top:10px;padding-bottom:5px;"> - <form method="get" action="settings.php"> - Setting Option: - <select id="setting_options" name="t" style="width:300px;"> - <option value="">— Select Setting Group —</option> - <?php - foreach($SettingOptions as $k=>$v) { - $sel=($target==$k)?'selected="selected"':''; - echo sprintf('<option value="%s" %s>%s</option>',$k,$sel,$v); - } - ?> - </select> - <input type="submit" value="Go"> - </form> -</div> -<?php +$target=($_REQUEST['t'] && $settingOptions[$_REQUEST['t']])?$_REQUEST['t']:'system'; $config=($errors && $_POST)?Format::input($_POST):Format::htmlchars($cfg->getConfigInfo()); + +$nav->setTabActive('settings', ('settings.php?t='.$target)); +require_once(STAFFINC_DIR.'header.inc.php'); include_once(STAFFINC_DIR."settings-$target.inc.php"); include_once(STAFFINC_DIR.'footer.inc.php'); ?> diff --git a/scp/slas.php b/scp/slas.php index 8f3b0f75ca331381beb6a348f46c2a703853b229..8dd5b86c90b33b6cf2e88558f9ac679f63af77be 100644 --- a/scp/slas.php +++ b/scp/slas.php @@ -96,7 +96,7 @@ $page='slaplans.inc.php'; if($sla || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) $page='slaplan.inc.php'; -$nav->setTabActive('settings'); +$nav->setTabActive('manage'); require(STAFFINC_DIR.'header.inc.php'); require(STAFFINC_DIR.$page); include(STAFFINC_DIR.'footer.inc.php'); diff --git a/scp/staff.inc.php b/scp/staff.inc.php index 5dcf6045042397c1ee806a8df9f0f2b0f28725a3..decf2c5cd624a719f718d5e53e92770fc502ae4c 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -49,7 +49,7 @@ require_once(INCLUDE_DIR.'class.csrf.php'); if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the function to trap expired sessions. function staffLoginPage($msg) { - $_SESSION['_staff']['auth']['dest']=THISPAGE; + $_SESSION['_staff']['auth']['dest']=THISURI; $_SESSION['_staff']['auth']['msg']=$msg; require(SCP_DIR.'login.php'); exit; diff --git a/scp/tickets.php b/scp/tickets.php index 8c799a2a6a02a26c4185afece329ffc5e57cdcf4..9cc247cc68cf8aeed4530dfff6333ce5bf105921 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -52,7 +52,7 @@ if($_POST && !$errors): $errors['err']='Action Denied. Ticket is locked by someone else!'; //Make sure the email is not banned - if(!$errors['err'] && EmailFilter::isBanned($ticket->getEmail())) + if(!$errors['err'] && TicketFilter::isBanned($ticket->getEmail())) $errors['err']='Email is in banlist. Must be removed to reply.'; $wasOpen =($ticket->isOpen()); @@ -81,13 +81,8 @@ if($_POST && !$errors): elseif(strlen($_POST['transfer_message'])<5) $errors['transfer_message'] = 'Transfer comments too short!'; - $currentDept = $ticket->getDeptName(); //save current dept name. if(!$errors && $ticket->transfer($_POST['deptId'], $_POST['transfer_message'])) { $msg = 'Ticket transferred successfully to '.$ticket->getDeptName(); - //ticket->transfer does a reload...new dept at this point. - $title='Dept. Transfer from '.$currentDept.' to '.$ticket->getDeptName(); - /*** log the message as internal note - with alerts disabled - ***/ - $ticket->postNote($title, $_POST['transfer_message'], false); //Check to make sure the staff still has access to the ticket if(!$ticket->checkStaffAccess($thisstaff)) $ticket=null; @@ -140,7 +135,7 @@ if($_POST && !$errors): if(!Validator::process($fields, $_POST, $errors) && !$errors['err']) $errors['err']=$errors['note']='Missing or invalid data. Correct the error(s) below and try again!'; - if(!$errors && ($noteId=$ticket->postNote($_POST['title'], $_POST['internal_note']))) { + if(!$errors && ($noteId=$ticket->postNote($_POST['title'], $_POST['internal_note'], $thisstaff))) { $msg='Internal note posted successfully'; //Upload attachments IF ANY - TODO: validate attachment types?? if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/sql/osTicket-mysql.sql index 5cfba00d6538506e29e136f188610df4508551cb..ec886a23169af1a12164e6c78e58d8bde6f6af10 100644 --- a/setup/inc/sql/osTicket-mysql.sql +++ b/setup/inc/sql/osTicket-mysql.sql @@ -91,7 +91,7 @@ CREATE TABLE `%TABLE_PREFIX%config` ( `clickable_urls` tinyint(1) unsigned NOT NULL default '1', `allow_priority_change` tinyint(1) unsigned NOT NULL default '0', `use_email_priority` tinyint(1) unsigned NOT NULL default '0', - `enable_kb` tinyint(1) unsigned NOT NULL default '1', + `enable_kb` tinyint(1) unsigned NOT NULL default '0', `enable_premade` tinyint(1) unsigned NOT NULL default '1', `enable_captcha` tinyint(1) unsigned NOT NULL default '0', `enable_auto_cron` tinyint(1) unsigned NOT NULL default '0', @@ -228,14 +228,14 @@ CREATE TABLE `%TABLE_PREFIX%email` ( KEY `dept_id` (`dept_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -DROP TABLE IF EXISTS `%TABLE_PREFIX%email_filter`; -CREATE TABLE `%TABLE_PREFIX%email_filter` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter`; +CREATE TABLE `%TABLE_PREFIX%filter` ( `id` int(11) unsigned NOT NULL auto_increment, `execorder` int(10) unsigned NOT NULL default '99', `isactive` tinyint(1) unsigned NOT NULL default '1', `match_all_rules` tinyint(1) unsigned NOT NULL default '0', `stop_onmatch` tinyint(1) unsigned NOT NULL default '0', - `reject_email` tinyint(1) unsigned NOT NULL default '0', + `reject_ticket` tinyint(1) unsigned NOT NULL default '0', `use_replyto_email` tinyint(1) unsigned NOT NULL default '0', `disable_autoresponder` tinyint(1) unsigned NOT NULL default '0', `canned_response_id` int(11) unsigned NOT NULL default '0', @@ -245,21 +245,23 @@ CREATE TABLE `%TABLE_PREFIX%email_filter` ( `staff_id` int(10) unsigned NOT NULL default '0', `team_id` int(10) unsigned NOT NULL default '0', `sla_id` int(10) unsigned NOT NULL default '0', + `target` ENUM( 'Any', 'Web', 'Email', 'API' ) NOT NULL DEFAULT 'Any', `name` varchar(32) NOT NULL default '', `notes` text, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), + KEY `target` (`target`), KEY `email_id` (`email_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%email_filter` ( - `id`,`isactive`,`execorder`,`reject_email`,`name`,`notes`,`created`) +INSERT INTO `%TABLE_PREFIX%filter` ( + `id`,`isactive`,`execorder`,`reject_ticket`,`name`,`notes`,`created`) VALUES (1, 1, 99, 1, 'SYSTEM BAN LIST', 'Internal list for email banning. Do not remove', NOW()); -DROP TABLE IF EXISTS `%TABLE_PREFIX%email_filter_rule`; -CREATE TABLE `%TABLE_PREFIX%email_filter_rule` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_rule`; +CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `id` int(11) unsigned NOT NULL auto_increment, `filter_id` int(10) unsigned NOT NULL default '0', `what` enum('name','email','subject','body','header') NOT NULL, @@ -274,7 +276,7 @@ CREATE TABLE `%TABLE_PREFIX%email_filter_rule` ( UNIQUE `filter` (`filter_id`, `what`, `how`, `val`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%email_filter_rule` ( +INSERT INTO `%TABLE_PREFIX%filter_rule` ( `id`, `filter_id`, `isactive`, `what`,`how`,`val`,`created`) VALUES (1, 1, 1, 'email', 'equal', 'test@example.com',NOW()); @@ -316,7 +318,7 @@ CREATE TABLE `%TABLE_PREFIX%email_template` ( -- TODO: Dump revised copy before release!!! INSERT INTO `%TABLE_PREFIX%email_template` (`tpl_id`, `cfg_id`, `isactive`, `name`, `notes`, `ticket_autoresp_subj`, `ticket_autoresp_body`, `ticket_notice_subj`, `ticket_notice_body`, `ticket_alert_subj`, `ticket_alert_body`, `message_autoresp_subj`, `message_autoresp_body`, `message_alert_subj`, `message_alert_body`, `note_alert_subj`, `note_alert_body`, `assigned_alert_subj`, `assigned_alert_body`, `transfer_alert_subj`, `transfer_alert_body`, `ticket_overdue_subj`, `ticket_overdue_body`, `ticket_overlimit_subj`, `ticket_overlimit_body`, `ticket_reply_subj`, `ticket_reply_body`, `created`, `updated`) VALUES -(1, 1, 1, 'osTicket Default Template', 'Default osTicket templates', 'Support Ticket Opened [#%ticket]', '%name,\r\n\r\nA request for support has been created and assigned ticket #%ticket. A representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %url/view.php?e=%email&t=%ticket.\r\n\r\nIf you wish to send additional comments or information regarding this issue, please don''t open a new ticket. Simply login using the link above and update the ticket.\r\n\r\n%signature', '[#%ticket] %subject', '%name,\r\n\r\nOur customer care team has created a ticket, #%ticket on your behalf, with the following message.\r\n\r\n%message\r\n\r\nIf you wish to provide additional comments or information regarding this issue, please don''t open a new ticket. You can update or view this ticket''s progress online here: %url/view.php?e=%email&t=%ticket.\r\n\r\n%signature', 'New Ticket Alert', '%staff,\r\n\r\nNew ticket #%ticket created.\r\n-------------------\r\nName: %name\r\nEmail: %email\r\nDept: %dept\r\n\r\n%message\r\n-------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', '[#%ticket] Message Added', '%name,\r\n\r\nYour reply to support request #%ticket has been noted.\r\n\r\nYou can view this support request progress online here: %url/view.php?e=%email&t=%ticket.\r\n\r\n%signature', 'New Message Alert', '%staff,\r\n\r\nNew message appended to ticket #%ticket\r\n\r\n----------------------\r\nName: %name\r\nEmail: %email\r\nDept: %dept\r\n\r\n%message\r\n-------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'New Internal Note Alert', '%staff,\r\n\r\nInternal note appended to ticket #%ticket\r\n\r\n----------------------\r\nName: %name\r\n\r\n%note\r\n-------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Ticket #%ticket Assigned to you', '%assignee,\r\n\r\n%assigner has assigned ticket #%ticket to you or one of your teams!\r\n\r\n%note\r\n\r\nTo view complete details, simply login to the support system.\r\n\r\n%url/scp/tickets.php?id=%id\r\n\r\n- Your friendly Support Ticket System - powered by osTicket.', 'Ticket Transfer #%ticket - %dept', '%staff,\r\n\r\nTicket #%ticket has been transferred to %dept department\r\n\r\n----------------------\r\n\r\n%note\r\n\r\n-------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%url/scp/ticket.php?id=%id\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Stale Ticket Alert', '%staff,\r\n\r\nA ticket, #%ticket assigned to you or in your department is seriously overdue.\r\n\r\n%url/scp/tickets.php?id=%id\r\n\r\nWe should all work hard to guarantee that all tickets are being addressed in a timely manner. Enough baby talk...please address the issue or you will hear from me again.\r\n\r\n\r\n- Your friendly (although with limited patience) Support Ticket System - powered by osTicket.', 'Open Tickets Limit Reached', '%name\r\n\r\nYou have reached the maximum number of open tickets allowed.\r\n\r\nTo be able to open another ticket, one of your pending tickets must be closed. To update or add comments to an open ticket simply login using the link below.\r\n\r\n%url/view.php?e=%email\r\n\r\nThank you.\r\n\r\nSupport Ticket System', '[#%ticket] %subject', '%name,\r\n\r\nA customer support staff member has replied to your support request, #%ticket with the following response:\r\n\r\n%response\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not send another email. Instead, reply to this email or login to your account for a complete archive of all your support requests and responses.\r\n\r\n%url/view.php?e=%email&t=%ticket\r\n\r\n%signature', '2011-08-05 17:00:03', '2012-03-19 01:44:54'); +(1, 1, 1, 'osTicket Default Template', 'Default osTicket templates', 'Support Ticket Opened [#%{ticket.number}]', '%{ticket.name},\r\n\r\nA request for support has been created and assigned ticket #%{ticket.number}. A representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\nIf you wish to send additional comments or information regarding this issue, please don''t open a new ticket. Simply login using the link above and update the ticket.\r\n\r\n%{signature}', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name},\r\n\r\nOur customer care team has created a ticket, #%{ticket.number} on your behalf, with the following message.\r\n\r\n%{message}\r\n\r\nIf you wish to provide additional comments or information regarding this issue, please don''t open a new ticket. You can update or view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\n%{signature}', 'New Ticket Alert', '%{recipient},\r\n\r\nNew ticket #%{ticket.number} created.\r\n\r\n-----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n-----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', '[#%{ticket.number}] Message Added', '%{ticket.name},\r\n\r\nYour reply to support request #%{ticket.number} has been noted.\r\n\r\nYou can view this support request progress online here: %{ticket.client_link}.\r\n\r\n%{signature}', 'New Message Alert', '%{recipient},\r\n\r\nNew message appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'New Internal Note Alert', '%{recipient},\r\n\r\nInternal note appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\n* %{note.title} *\r\n\r\n%{note.message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Ticket #%{ticket.number} Assigned to you', '%{assignee},\r\n\r\nTicket #%{ticket.number} has been assigned to you by %{assigner}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view complete details, simply login to the support system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Support Ticket System - powered by osTicket.', 'Ticket Transfer #%{ticket.number} - %{ticket.dept.name}', '%{recipient},\r\n\r\nTicket #%{ticket.number} has been transferred to %{ticket.dept.name} department by %{staff.name}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Stale Ticket Alert', '%{recipient},\r\n\r\nA ticket, #%{ticket.number} assigned to you or in your department is seriously overdue.\r\n\r\n%{ticket.staff_link}\r\n\r\nWe should all work hard to guarantee that all tickets are being addressed in a timely manner.\r\n\r\n- Your friendly (although with limited patience) Support Ticket System - powered by osTicket.', 'Open Tickets Limit Reached', '%{ticket.name}\r\n\r\nYou have reached the maximum number of open tickets allowed.\r\n\r\nTo be able to open another ticket, one of your pending tickets must be closed. To update or add comments to an open ticket simply login using the link below.\r\n\r\n%{url}/tickets.php?e=%{ticket.email}\r\n\r\nThank you.\r\n\r\nSupport Ticket System', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name},\r\n\r\nA customer support staff member has replied to your support request, #%{ticket.number} with the following response:\r\n\r\n%{response}\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not send another email. Instead, reply to this email or login to your account for a complete archive of all your support requests and responses.\r\n\r\n%{ticket.client_link}\r\n\r\n%{signature}', NOW(), NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%file`; CREATE TABLE `%TABLE_PREFIX%file` ( @@ -325,15 +327,24 @@ CREATE TABLE `%TABLE_PREFIX%file` ( `size` varchar(25) NOT NULL default '', `hash` varchar(125) NOT NULL, `name` varchar(255) NOT NULL default '', - `filedata` longblob NOT NULL, `created` datetime NOT NULL, PRIMARY KEY (`id`), KEY `hash` (`hash`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%file` (`id`, `type`, `size`, `hash`, `name`, `filedata`, `created`) VALUES -(1, 'text/plain', '25', '670c6cc1d1dfc97fad20e5470251b255', 'osTicket.txt', 0x43616e6e6564206174746163686d656e747320726f636b210a, NOW()); +INSERT INTO `%TABLE_PREFIX%file` (`id`, `type`, `size`, `hash`, `name`, `created`) VALUES +(1, 'text/plain', '25', '670c6cc1d1dfc97fad20e5470251b255', 'osTicket.txt', NOW()); +DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`; +CREATE TABLE `%TABLE_PREFIX%file_chunk` ( + `file_id` int(11) NOT NULL, + `chunk_id` int(11) NOT NULL, + `filedata` longblob NOT NULL, + PRIMARY KEY (`file_id`, `chunk_id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%file_chunk` (`file_id`, `chunk_id`, `filedata`) +VALUES (1, 0, 0x43616e6e6564206174746163686d656e747320726f636b210a); DROP TABLE IF EXISTS `%TABLE_PREFIX%groups`; CREATE TABLE `%TABLE_PREFIX%groups` ( @@ -420,7 +431,7 @@ CREATE TABLE `%TABLE_PREFIX%canned_response` ( INSERT INTO `%TABLE_PREFIX%canned_response` (`canned_id`, `dept_id`, `isenabled`, `title`, `response`) VALUES (1, 0, 1, 'What is osTicket (sample)?', '\r\nosTicket is a widely-used open source support ticket system, an attractive alternative to higher-cost and complex customer support systems - simple, lightweight, reliable, open source, web-based and easy to setup and use.'), - (2, 0, 1, 'Sample (with variables)', '\r\n%name,\r\n\r\nYour ticket #%ticket created on %createdate is in %dept department.\r\n\r\n'); + (2, 0, 1, 'Sample (with variables)', '\r\n%{ticket.name},\r\n\r\nYour ticket #%{ticket.number} created on %{ticket.create_date} is in %{ticket.dept.name} department.\r\n\r\n'); DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_attachment`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%canned_attachment` ( diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5 index 63d2ce443b0448ca776ebc37b298a3c4ff93c387..d6d0340e6558e27fc296c43407e88191f3430e99 100644 --- a/setup/inc/sql/osTicket-mysql.sql.md5 +++ b/setup/inc/sql/osTicket-mysql.sql.md5 @@ -1 +1 @@ -d0e37dca324648f1ce2d10528a6026d4 +f4da0c9befa257b5a20a923d4e9c0e91 diff --git a/setup/setup.inc.php b/setup/setup.inc.php index 07b86c6ffa43e13338f3b1f6c176e0cee2f53316..f2d0bb5e1095ca82d1a148b11b25f73e9193edf1 100644 --- a/setup/setup.inc.php +++ b/setup/setup.inc.php @@ -15,10 +15,16 @@ **********************************************************************/ #This version - changed on every release -define('THIS_VERSION', '1.7-RC2'); +define('THIS_VERSION', '1.7-RC2+'); -#inits -error_reporting(E_ALL ^ E_NOTICE); //turn on errors?? +#inits - error reporting. +$error_reporting = E_ALL & ~E_NOTICE; +if (defined('E_STRICT')) # 5.4.0 + $error_reporting &= ~E_STRICT; +if (defined('E_DEPRECATED')) # 5.3.0 + $error_reporting &= ~(E_DEPRECATED | E_USER_DEPRECATED); + +error_reporting($error_reporting); ini_set('magic_quotes_gpc', 0); ini_set('session.use_trans_sid', 0); ini_set('session.cache_limiter', 'nocache'); diff --git a/view.php b/view.php index 984b04c3019645c6ca1d075bbab0aac8460e4094..10e5374fe71b8dcf3551c0ee922ac5b4800e961a 100644 --- a/view.php +++ b/view.php @@ -3,6 +3,7 @@ view.php Ticket View. + TODO: Support different views based on auth_token - e.g for BCC'ed users vs. Ticket owner. Peter Rotich <peter@osticket.com> Copyright (c) 2006-2010 osTicket @@ -14,8 +15,22 @@ vim: expandtab sw=4 ts=4 sts=4: $Id: $ **********************************************************************/ -require('secure.inc.php'); -if(!is_object($thisclient) || !$thisclient->isValid()) die('Access denied'); //Double check again. -//We are now using tickets.php but we need to keep view.php for backward compatibility +require_once('client.inc.php'); + +//If the user is NOT logged in - try auto-login (if params exists). +if(!$thisclient || !$thisclient->isValid()) { + // * On login Client::login will redirect the user to tickets.php view. + // * See TODO above for planned multi-view. + $user = null; + if($_GET['t'] && $_GET['e'] && $_GET['a']) + $user = Client::login($_GET['t'], $_GET['e'], $_GET['a'], $errors); + + //XXX: For now we're assuming the user is the ticket owner + // (multi-view based on auth token will come later). + if($user && $user->getTicketID()==trim($_GET['t'])) + @header('Location: tickets.php?id='.$user->getTicketID()); +} + +//Simply redirecting to tickets.php until multiview is implemented. require('tickets.php'); ?>