diff --git a/.gitignore b/.gitignore index 32828f4a..bdfa288b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ admin/archive.php admin/assign_owner.php admin/generate_spam_question.php admin/move_category.php -admin/options.php admin/priority.php admin/test_connection.php attachments/index.htm diff --git a/README.md b/README.md index c6e37e35..7298a3c9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Stories in Ready](https://badge.waffle.io/mkoch227/Mods-For-Hesk.png?label=waffle:ready&title=Ready)](https://waffle.io/mkoch227/Mods-For-Hesk) -

Mods for HESK v1.6.1

+

Mods for HESK v1.7.0

Mods for HESK is a set of modifications for HESK v2.5.5, a free and popular helpdesk solution. @@ -26,16 +26,7 @@ You can download Mods for HESK via two ways:

Installation

- -
    -
  1. Download HESK from http://www.hesk.com/download.php.
  2. -
  3. Extract the contents of HESK to a directory of your choice.
  4. -
  5. Download Mods for HESK from one of the two methods described above.
  6. -
  7. Copy and paste the contents of the zip/tar.gz bundle and overwrite any files in the original HESK 2.x folder.
  8. -
  9. Upload the resulting folder to your webserver.
  10. -
  11. Go to the /install directory in your web browser and click on "Install/Update Mods for HESK Installation"
  12. -
-

Please consult the official HESK Documentation on how to install HESK, as it is the same for both HESK and Mods for HESK.

+

Visit http://mods-for-hesk.mkochcs.com/download.php for installation instructions.

Languages

As of current, only English is a supported language, as there have been several language items that have been edited/created. If you want to translate Mods for HESK to your own language, it is recommended to download the original HESK language file for your language, and then add/edit the lines listed under //Added or modified in Mods for HESK X.X.X (where X.X.X is a version number) for your language.

diff --git a/admin/admin_settings.php b/admin/admin_settings.php index 4ac24a06..36f69e87 100644 --- a/admin/admin_settings.php +++ b/admin/admin_settings.php @@ -100,7 +100,7 @@ if ( defined('HESK_DEMO') )
-
+
- - @@ -153,6 +153,11 @@ if ( defined('HESK_DEMO') ) + - + @@ -1238,6 +1245,8 @@ if ( defined('HESK_DEMO') ) + + @@ -1670,7 +1679,24 @@ if ( defined('HESK_DEMO') )
- + + + + + +
@@ -1790,7 +1816,7 @@ if ( defined('HESK_DEMO') )
@@ -1805,7 +1831,7 @@ if ( defined('HESK_DEMO') )
@@ -1817,6 +1843,38 @@ if ( defined('HESK_DEMO') )
+
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+
diff --git a/admin/admin_settings_save.php b/admin/admin_settings_save.php index bb29ae1c..07228650 100644 --- a/admin/admin_settings_save.php +++ b/admin/admin_settings_save.php @@ -390,7 +390,7 @@ for ($i=1;$i<=20;$i++) $set['custom_fields'][$this_field]['maxlen'] = intval( hesk_POST('s_custom'.$i.'_maxlen', 255) ); $set['custom_fields'][$this_field]['value'] = hesk_input( hesk_POST('s_custom'.$i.'_val') ); - if (!in_array($set['custom_fields'][$this_field]['type'],array('text','textarea','select','radio','checkbox'))) + if (!in_array($set['custom_fields'][$this_field]['type'],array('text','textarea','select','radio','checkbox','date','multiselect'))) { $set['custom_fields'][$this_field]['type'] = 'text'; } @@ -537,6 +537,14 @@ $set['hesk_version'] = $hesk_settings['hesk_version']; // Save the modsForHesk_settings.inc.php file $set['rtl'] = empty($_POST['rtl']) ? 0 : 1; $set['show-icons'] = empty($_POST['show-icons']) ? 0 : 1; +$set['custom-field-setting'] = empty($_POST['custom-field-setting']) ? 0 : 1; +$set['customer-email-verification-required'] = empty($_POST['email-verification']) ? 0 : 1; + +if ($set['customer-email-verification-required']) +{ + //-- Don't allow multiple emails if verification is required + $set['multi_eml'] = 0; +} $set['navbarBackgroundColor'] = hesk_input(hesk_POST('navbarBackgroundColor')); $set['navbarBrandColor'] = hesk_input(hesk_POST('navbarBrandColor')); $set['navbarBrandHoverColor'] = hesk_input(hesk_POST('navbarBrandHoverColor')); @@ -567,7 +575,13 @@ $modsForHesk_settings[\'questionMarkColor\'] = \''.$set['questionMarkColor'].'\' $modsForHesk_settings[\'rtl\'] = '.$set['rtl'].'; //-- Set this to 1 to show icons next to navigation menu items -$modsForHesk_settings[\'show_icons\'] = '.$set['show-icons'].';'; +$modsForHesk_settings[\'show_icons\'] = '.$set['show-icons'].'; + +//-- Set this to 1 to enable custom field names as keys +$modsForHesk_settings[\'custom_field_setting\'] = '.$set['custom-field-setting'].'; + +//-- Set this to 1 to enable email verification for new customers +$modsForHesk_settings[\'customer_email_verification_required\'] = '.$set['customer-email-verification-required'].';'; // Write the file if ( ! file_put_contents(HESK_PATH . 'modsForHesk_settings.inc.php', $modsForHesk_file_content) ) diff --git a/admin/admin_submit_ticket.php b/admin/admin_submit_ticket.php index 8b229636..a0d347af 100644 --- a/admin/admin_submit_ticket.php +++ b/admin/admin_submit_ticket.php @@ -92,7 +92,11 @@ foreach ($hesk_settings['custom_fields'] as $k=>$v) { if ($v['use'] && isset($_POST[$k])) { - if (is_array($_POST[$k])) + // Date will be handled by the jQuery datepicker + if( $v['type'] == 'date' && $_POST[$k] != '') + { + $tmpvar[$k] = strtotime($_POST[$k]); + } else if (is_array($_POST[$k])) { $tmpvar[$k]=''; foreach ($_POST[$k] as $myCB) diff --git a/admin/admin_ticket.php b/admin/admin_ticket.php index a55e8985..c7a74042 100644 --- a/admin/admin_ticket.php +++ b/admin/admin_ticket.php @@ -1036,8 +1036,21 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); { if ($v['use'] && $v['place']==0) { - echo ' -

'.$v['name'].': '.$ticket[$k].'

'; + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + + echo '

'.$v['name'].': '; + if ($v['type'] == 'date' && !empty($ticket[$k])) + { + $dt = date('Y-m-d h:i:s', $ticket[$k]); + echo hesk_dateToString($dt, 0); + } else + { + echo $ticket[$k]; + } + echo '

'; } } ?> @@ -1054,8 +1067,21 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); { if ($v['use'] && $v['place']) { - echo ' -

'.$v['name'].': '.$ticket[$k].'

'; + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + + echo '

'.$v['name'].': '; + if ($v['type'] == 'date' && !empty($ticket[$k])) + { + $dt = date('Y-m-d h:i:s', $ticket[$k]); + echo hesk_dateToString($dt, 0); + } else + { + echo $ticket[$k]; + } + echo '

'; } } /* Attachments */ diff --git a/admin/change_status.php b/admin/change_status.php index 890e61c2..8f0448ee 100644 --- a/admin/change_status.php +++ b/admin/change_status.php @@ -94,7 +94,11 @@ if ($statusRow['IsClosed']) // Closed // Notify customer require(HESK_PATH . 'inc/email_functions.inc.php'); - hesk_notifyCustomer('ticket_closed'); + + if (!empty($ticket['email'])) + { + hesk_notifyCustomer('ticket_closed'); + } } elseif ($statusRow['ID'] != 0) //Ticket is still open, but not new { diff --git a/admin/edit_post.php b/admin/edit_post.php index 730cd9d4..f75c02b9 100644 --- a/admin/edit_post.php +++ b/admin/edit_post.php @@ -144,7 +144,10 @@ if (isset($_POST['save'])) { if ($v['use'] && isset($_POST[$k])) { - if (is_array($_POST[$k])) + if( $v['type'] == 'date' && $_POST[$k] != '') + { + $tmpvar[$k] = strtotime($_POST[$k]); + } elseif (is_array($_POST[$k])) { $tmpvar[$k]=''; foreach ($_POST[$k] as $myCB) @@ -246,6 +249,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); { if ($v['use']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + $k_value = $ticket[$k]; if ($v['type'] == 'checkbox') @@ -360,6 +368,44 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
'; break; + case 'date': + if (strlen($k_value) != 0) + { + $v['value'] = $k_value; + } + echo ' +
+ +
+ +
+
'; + break; + case 'multiselect': + echo '
+
+
+ + +
'; + break; + /* Default text input */ default: if (strlen($k_value) != 0) diff --git a/admin/export.php b/admin/export.php index 4be220ef..abf577f6 100644 --- a/admin/export.php +++ b/admin/export.php @@ -481,6 +481,11 @@ if (isset($_GET['w'])) { if ($v['use']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + $tmp .= ''.$v['name'].'' . "\n"; } } diff --git a/admin/manage_canned.php b/admin/manage_canned.php index f3a61a18..cb1a8958 100644 --- a/admin/manage_canned.php +++ b/admin/manage_canned.php @@ -285,6 +285,11 @@ myField.value += myValue; { if ($v['use']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + echo '| '.$v['name'].' '; } } diff --git a/admin/new_ticket.php b/admin/new_ticket.php index 84a0a323..33f51c9d 100644 --- a/admin/new_ticket.php +++ b/admin/new_ticket.php @@ -184,6 +184,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); { if ($v['use'] && $v['place']==0) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + // $v['req'] = $v['req'] ? '*' : ''; // Staff doesn't need to fill in required custom fields $v['req'] = ''; @@ -275,6 +280,37 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); echo ''; break; + case 'multiselect': + $cls = in_array($k,$_SESSION['iserror']) ? ' class="isError" ' : ''; + + echo '
+
+
+ + +
'; + break; + /* Checkbox */ case 'checkbox': echo '
'; @@ -312,6 +348,25 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
'; break; + case 'date': + if (strlen($k_value) != 0) + { + $v['value'] = $k_value; + } + + $cls = in_array($k,$_SESSION['iserror']) ? ' isError ' : ''; + + echo ' +
+ +
+ + '.$hesklang['date_format'].' +
+
'; + break; + /* Default text input */ default: if (strlen($k_value) != 0) @@ -352,6 +407,11 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php'); { if ($v['use'] && $v['place']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + // $v['req'] = $v['req'] ? '*' : ''; // Staff doesn't need to fill in required custom fields $v['req'] = ''; @@ -471,6 +531,56 @@ require_once(HESK_PATH . 'inc/show_admin_nav.inc.php');
'; break; + case 'date': + if (strlen($k_value) != 0) + { + $v['value'] = $k_value; + } + + $cls = in_array($k,$_SESSION['iserror']) ? ' isError ' : ''; + + echo ' +
+ +
+ + '.$hesklang['date_format'].' +
+
'; + break; + + case 'multiselect': + $cls = in_array($k,$_SESSION['iserror']) ? ' class="isError" ' : ''; + + echo '
+
+
+ + +
'; + break; + /* Default text input */ default: if (strlen($k_value) != 0) diff --git a/admin/options.php b/admin/options.php new file mode 100644 index 00000000..95d05481 --- /dev/null +++ b/admin/options.php @@ -0,0 +1,323 @@ + + + + +<?php echo $hesklang['opt']; ?> + + + + + +

+ +

+ + + function hesk_saveOptions() + { + window.opener.document.getElementById(\'s_'.$id.'_val\').value = document.getElementById(\'o2\').value; + window.opener.document.getElementById(\'s_'.$id.'_maxlen\').value = document.getElementById(\'o1\').value; + window.close(); + } + +
@@ -141,10 +141,10 @@ if ( defined('HESK_DEMO') ) ?>
+ : +
+ : + + +
/hesk_settings.inc.php @@ -1215,7 +1220,9 @@ if ( defined('HESK_DEMO') ) + +
+ + + + + + +
'.$hesklang['custom_l'].': +
'.$hesklang['defw'].': +
+

+ '; + break; + case 'textarea': + if (strpos($query,'#') !== false) + { + list($rows,$cols)=explode('#',$query); + } + else + { + $rows = ''; + $cols = ''; + } + echo ' + + + + + + + + +
'.$hesklang['rows'].': +
'.$hesklang['cols'].': +
+

+ '; + break; + case 'radio': + $options=str_replace('#HESK#',"\n",$query); + echo ' + + +

'.$hesklang['opt2'].'

+ +

+ '; + break; + case 'select': + $options=str_replace('#HESK#',"\n",$query); + echo ' + + +

'.$hesklang['opt3'].'

+ +

+ '; + break; + case 'checkbox': + $options=str_replace('#HESK#',"\n",$query); + echo ' + + +

'.$hesklang['opt4'].'

+ +

+ '; + break; + case 'date': + echo '

'.$hesklang['date_custom_field_text'].'

'; + break; + case 'multiselect': + $options=str_replace('#HESK#',"\n",$query); + echo ' + + +

'.$hesklang['multiple_select_custom_field_text'].'

+ +

+ '; + break; + default: + die('Invalid type'); +} +?> + +

+ +

 

+ + + + + diff --git a/css/datepicker.css b/css/datepicker.css new file mode 100755 index 00000000..6d37209e --- /dev/null +++ b/css/datepicker.css @@ -0,0 +1,510 @@ +/*! + * Datepicker for Bootstrap + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-top:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-top:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker.days div.datepicker-days { + display: block; +} +.datepicker.months div.datepicker-months { + display: block; +} +.datepicker.years div.datepicker-years { + display: block; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); + background-image: linear-gradient(top, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(top, #f3c17a, #f3e97a); + background-image: linear-gradient(top, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(top, #b3b3b3, #808080); + background-image: -ms-linear-gradient(top, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(top, #b3b3b3, #808080); + background-image: -o-linear-gradient(top, #b3b3b3, #808080); + background-image: linear-gradient(top, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -ms-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(top, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker th.datepicker-switch { + width: 145px; +} +.datepicker thead tr:first-child th, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker thead tr:first-child th:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.datepicker thead tr:first-child th.cw { + cursor: default; + background-color: transparent; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + cursor: pointer; + width: 16px; + height: 16px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 20px; + padding: 4px 5px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 20px; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 4px 5px; +} diff --git a/css/hesk_newStyle.php b/css/hesk_newStyle.php index f77a5f98..6f65ba1b 100644 --- a/css/hesk_newStyle.php +++ b/css/hesk_newStyle.php @@ -364,3 +364,8 @@ div.setupButtons { float: right; } } + +.white-readonly { + cursor: text !important; + background-color: #fff !important; +} diff --git a/img/ui-bg_flat_0_aaaaaa_40x100.png b/img/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 00000000..9869d1fa Binary files /dev/null and b/img/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/img/ui-bg_flat_75_ffffff_40x100.png b/img/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 00000000..94a8e562 Binary files /dev/null and b/img/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/img/ui-bg_glass_55_fbf9ee_1x400.png b/img/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 00000000..f9e28706 Binary files /dev/null and b/img/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/img/ui-bg_glass_65_ffffff_1x400.png b/img/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 00000000..02bcf0ad Binary files /dev/null and b/img/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/img/ui-bg_glass_75_dadada_1x400.png b/img/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 00000000..a2160772 Binary files /dev/null and b/img/ui-bg_glass_75_dadada_1x400.png differ diff --git a/img/ui-bg_glass_75_e6e6e6_1x400.png b/img/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 00000000..4c544ef4 Binary files /dev/null and b/img/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/img/ui-bg_glass_95_fef1ec_1x400.png b/img/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 00000000..32ab7d10 Binary files /dev/null and b/img/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/img/ui-bg_highlight-soft_75_cccccc_1x100.png b/img/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 00000000..bdf5d2c6 Binary files /dev/null and b/img/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/img/ui-icons_222222_256x240.png b/img/ui-icons_222222_256x240.png new file mode 100644 index 00000000..c1cb1170 Binary files /dev/null and b/img/ui-icons_222222_256x240.png differ diff --git a/img/ui-icons_2e83ff_256x240.png b/img/ui-icons_2e83ff_256x240.png new file mode 100644 index 00000000..84b601bf Binary files /dev/null and b/img/ui-icons_2e83ff_256x240.png differ diff --git a/img/ui-icons_454545_256x240.png b/img/ui-icons_454545_256x240.png new file mode 100644 index 00000000..b6db1acd Binary files /dev/null and b/img/ui-icons_454545_256x240.png differ diff --git a/img/ui-icons_888888_256x240.png b/img/ui-icons_888888_256x240.png new file mode 100644 index 00000000..feea0e20 Binary files /dev/null and b/img/ui-icons_888888_256x240.png differ diff --git a/img/ui-icons_cd0a0a_256x240.png b/img/ui-icons_cd0a0a_256x240.png new file mode 100644 index 00000000..ed5b6b09 Binary files /dev/null and b/img/ui-icons_cd0a0a_256x240.png differ diff --git a/inc/email_functions.inc.php b/inc/email_functions.inc.php index d8dc60ac..a347a0a1 100644 --- a/inc/email_functions.inc.php +++ b/inc/email_functions.inc.php @@ -46,6 +46,25 @@ if ($hesk_settings['smtp']) } } +function hesk_notifyCustomerForVerifyEmail($email_template = 'verify_email', $activationKey) +{ + global $hesk_settings, $ticket; + + if (defined('HESK_DEMO')) + { + return true; + } + + // Format email subject and message + $subject = hesk_getEmailSubject($email_template, $ticket); + $message = hesk_getEmailMessage($email_template, $ticket); + $activationUrl = $hesk_settings['hesk_url'] . '/verifyemail.php?key=%%ACTIVATIONKEY%%'; + $message = str_replace('%%VERIFYURL%%', $activationUrl, $message); + $message = str_replace('%%ACTIVATIONKEY%%', $activationKey, $message); + + hesk_mail($ticket['email'], $subject, $message); +} + function hesk_notifyCustomer($email_template = 'new_ticket') { @@ -225,6 +244,9 @@ function hesk_validEmails() // --> Ticket closed 'ticket_closed' => $hesklang['ticket_closed'], + // --> Verify email + 'verify_email' => $hesklang['verify_email'], + /*** Emails sent to STAFF ***/ @@ -246,6 +268,9 @@ function hesk_validEmails() // --> New note by someone to a ticket assigned to you 'new_note' => $hesklang['new_note'], + // --> Assigned ticket reopened + 'ticket_reopen_assigned' => $hesklang['ticket_reopen_assigned'], + ); } // END hesk_validEmails() diff --git a/inc/header.inc.php b/inc/header.inc.php index 70375a1a..6f440217 100644 --- a/inc/header.inc.php +++ b/inc/header.inc.php @@ -47,6 +47,7 @@ require(HESK_PATH . 'modsForHesk_settings.inc.php'); + @@ -60,6 +61,8 @@ require(HESK_PATH . 'modsForHesk_settings.inc.php'); + + >  + + + +
diff --git a/inc/headerAdmin.inc.php b/inc/headerAdmin.inc.php index 63e6bfe9..1088af73 100644 --- a/inc/headerAdmin.inc.php +++ b/inc/headerAdmin.inc.php @@ -47,6 +47,7 @@ require(HESK_PATH . 'modsForHesk_settings.inc.php'); + @@ -61,6 +62,7 @@ require(HESK_PATH . 'modsForHesk_settings.inc.php'); + 'CRITICAL', - 1 => 'HIGH', - 2 => 'MEDIUM', - 3 => 'LOW', - ); + $priority = array( + 0 => 'CRITICAL', + 1 => 'HIGH', + 2 => 'MEDIUM', + 3 => 'LOW', + ); } if ( ! isset($what) ) { - $what = 'trackid'; + $what = 'trackid'; } if ( ! isset($date_input) ) { - $date_input = ''; + $date_input = ''; } /* Can view tickets that are unassigned or assigned to others? */ @@ -73,22 +73,22 @@ $can_view_unassigned = hesk_checkPermission('can_view_unassigned',0); $category_options = ''; if ( isset($hesk_settings['categories']) && count($hesk_settings['categories']) ) { - foreach ($hesk_settings['categories'] as $row['id'] => $row['name']) - { - $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'],0,30) . '...' : $row['name']; - $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; - $category_options .= ''; - } + foreach ($hesk_settings['categories'] as $row['id'] => $row['name']) + { + $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'],0,30) . '...' : $row['name']; + $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; + $category_options .= ''; + } } else { - $res2 = hesk_dbQuery('SELECT `id`, `name` FROM `'.hesk_dbEscape($hesk_settings['db_pfix']).'categories` WHERE ' . hesk_myCategories('id') . ' ORDER BY `cat_order` ASC'); - while ($row=hesk_dbFetchAssoc($res2)) - { - $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'],0,30) . '...' : $row['name']; - $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; - $category_options .= ''; - } + $res2 = hesk_dbQuery('SELECT `id`, `name` FROM `'.hesk_dbEscape($hesk_settings['db_pfix']).'categories` WHERE ' . hesk_myCategories('id') . ' ORDER BY `cat_order` ASC'); + while ($row=hesk_dbFetchAssoc($res2)) + { + $row['name'] = (strlen($row['name']) > 30) ? substr($row['name'],0,30) . '...' : $row['name']; + $selected = ($row['id'] == $category) ? 'selected="selected"' : ''; + $category_options .= ''; + } } $more = empty($_GET['more']) ? 0 : 1; @@ -98,337 +98,342 @@ $more2 = empty($_GET['more2']) ? 0 : 1; ?> +
+
+

+
+
+ + + + +
+
+ + + + + +
:   + + + fetch_assoc()) + { + if ($rowCounter > 3) + { + echo ''; + $rowCounter = 1; + } + echo ''; + + $rowCounter++; + } + ?> + +
'.$hesklang[$row['ShortNameContentKey']].'
+
+ +
+  
+ + +
 
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
:   + + + + + + + + + + + + +
 
 
+ +
:   + + + + + + + +
+ + +
+ + +
+ + +
+ +
 
+ +
:   + + + + + + + + + + + +
 
+ +
:   + + + + + + + + + + + +
+ + +  
 
+ +
:   +
+
:  
:   + + | +
:   + +
+ () + +
+ +

+

+ +
+ +
+ +
+
+
+ + +
+ +
-

+

+
-
- + + + +
- -
:   - - - fetch_assoc()) + + '; - $rowCounter = 1; + $v['name'] = $hesklang[$v['name']]; } - echo ''; - $rowCounter++; + $v['name'] = (strlen($v['name']) > 30) ? substr($v['name'],0,30) . '...' : $v['name']; + echo ''; } - ?> - -
+
+ /> +
+
+
'.$hesklang[$row['ShortNameContentKey']].'
+ } + ?> + +
-
-  
- - -
 
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:   - - - - - - - - - - - - -
 
 
- -
:   - - - - - - - -
- - -
- - -
- - -
- -
 
- -
:   - - - - - - - - - - - -
 
- -
:   - - - - - - - - - - - -
- - -  
 
- -
:   -
-
:  
:   - - | -
:   - -
- () - -
- -

-

- -
- -
+
+  
+ + +
 
+
+ +
+ +  
+ + + + + + + + + + + + + + if ($can_view_unassigned) + { + ?> +
+ + +
+ + + + + + + +
:   + +
:   +
/>
+
:   + + +
+ + -
:  
+ +

+

+ +
+ + +
- -
- - -
-
-

-
-
- - - - -
- -
- - - - - - -
-
- /> -
-
- -
- -

- - -
 
-
- -
- - 
- - - - - - - - - - - - - - - - - - -
:   - -
:   -
/>
-
:   - - -
- - -
- - -
- -
:  
- -

-

- -
- -
-
-
-
- diff --git a/index.php b/index.php index 7e559085..27d6349c 100644 --- a/index.php +++ b/index.php @@ -66,7 +66,7 @@ exit(); function print_add_ticket() { - global $hesk_settings, $hesklang; + global $hesk_settings, $hesklang, $modsForHesk_settings; // Auto-focus first empty or error field define('AUTOFOCUS', true); @@ -106,16 +106,6 @@ function print_add_ticket()
- -
-
- -
-
- -
-
-

@@ -232,8 +222,14 @@ function print_add_ticket() foreach ($hesk_settings['custom_fields'] as $k=>$v) { + if ($v['use'] && $v['place']==0) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + $v['req'] = $v['req'] ? '*' : ''; if ($v['type'] == 'checkbox') @@ -351,6 +347,57 @@ function print_add_ticket()
'; break; + case 'multiselect': + $cls = in_array($k,$_SESSION['iserror']) ? ' class="isError" ' : ''; + + echo '
+
+
+ + +
+
'; + break; + + case 'date': + if (strlen($k_value) != 0) + { + $v['value'] = $k_value; + } + + $cls = in_array($k,$_SESSION['iserror']) ? ' isError ' : ''; + + echo ' +
+ +
+ + '.$hesklang['date_format'].' +
+
'; + break; + /* Default text input */ default: if (strlen($k_value) != 0) @@ -411,12 +458,18 @@ function print_add_ticket() $v) { + if ($v['use'] && $v['place']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + $v['req'] = $v['req'] ? '*' : ''; if ($v['type'] == 'checkbox') @@ -534,6 +587,56 @@ function print_add_ticket()
'; break; + case 'multiselect': + $cls = in_array($k,$_SESSION['iserror']) ? ' class="isError" ' : ''; + + echo '
+
+
+ + +
'; + break; + + case 'date': + if (strlen($k_value) != 0) + { + $v['value'] = $k_value; + } + + $cls = in_array($k,$_SESSION['iserror']) ? ' isError ' : ''; + + echo ' +
+ +
+ + '.$hesklang['date_format'].' +
+
'; + break; + /* Default text input */ default: if (strlen($k_value) != 0) diff --git a/install/freshInstall.php b/install/freshInstall.php index d7d9fef1..871de091 100644 --- a/install/freshInstall.php +++ b/install/freshInstall.php @@ -5,7 +5,14 @@ require(HESK_PATH . 'install/install_functions.inc.php'); require(HESK_PATH . 'hesk_settings.inc.php'); hesk_dbConnect(); //-- Need to do this since we are no longer restricted on IDs and we want an INT for proper INNER JOINs -hesk_dbQuery("ALTER TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` CHANGE COLUMN `status` `status` INT NOT NULL DEFAULT '0'"); +hesk_dbQuery("ALTER TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` ADD COLUMN `status_int` INT NOT NULL DEFAULT 0 AFTER `status`"); +$ticketsRS = hesk_dbQuery("SELECT `id`, `status` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets`"); +while ($currentResult = $ticketsRS->fetch_assoc()) +{ + hesk_dbQuery("UPDATE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` SET `status_int` = ".$currentResult['status']." WHERE `id` = ".$currentResult['id']); +} +hesk_dbQuery("ALTER TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` DROP COLUMN `status`"); +hesk_dbQuery("ALTER TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."tickets` CHANGE COLUMN `status_int` `status` INT NOT NULL"); hesk_dbQuery("CREATE TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."statuses` ( `ID` INT NOT NULL, diff --git a/install/install_functions.inc.php b/install/install_functions.inc.php index 2deee5ec..98704d30 100644 --- a/install/install_functions.inc.php +++ b/install/install_functions.inc.php @@ -37,7 +37,7 @@ if (!defined('IN_SCRIPT')) {die('Invalid attempt');} // We will be installing this HESK version: define('HESK_NEW_VERSION','2.5.5'); -define('MODS_FOR_HESK_NEW_VERSION','1.6.0'); +define('MODS_FOR_HESK_NEW_VERSION','1.7.0'); // Other required files and settings define('INSTALL',1); @@ -347,35 +347,7 @@ function hesk_iDatabase($problem=0)

Summary

-
Double-check all the information below. Contact your hosting company for the correct information to use!

MySQL said: '.$mysql_log.'

', 'Database connection failed'; - } - elseif ($problem == 2) - { - echo 'Database tables already exist!

- HESK database tables with '.$hesk_settings['db_pfix'].' prefix already exist in this database!

- To upgrade an existing HESK installation select Update existing install instead.

- To install a new copy of HESK in use a unique table prefix.'; - } - elseif ($problem == 3) - { - echo 'Old database tables not found!

- HESK database tables have not been found in this database!

- To install HESK use the New install option instead.'; - } - elseif ($problem == 4) - { - echo 'Version '.HESK_NEW_VERSION.' tables already exist!

- Your database seems to be compatible with HESK version '.HESK_NEW_VERSION.'

- To install a new copy of HESK use the New install option instead.'; - } - else - { - echo '

To complete setup HESK needs to connect to your database. You can get this information from your hosting control panel.

'; - } - ?> +

To complete setup HESK needs to connect to your database. You can get this information from your hosting control panel.

@@ -384,6 +356,39 @@ function hesk_iDatabase($problem=0)
Database Settings
+ '; + echo '

Double-check all the information below. Contact your hosting company for the correct information to use!

MySQL said: '.$mysql_log.'

', 'Database connection failed'; + echo ''; + } + elseif ($problem == 2) + { + echo '
'; + echo 'Database tables already exist!

+ HESK database tables with '.$hesk_settings['db_pfix'].' prefix already exist in this database!

+ To upgrade an existing HESK installation select Update existing install instead.

+ To install a new copy of HESK in use a unique table prefix.'; + echo '
'; + } + elseif ($problem == 3) + { + echo '
'; + echo 'Old database tables not found!

+ HESK database tables have not been found in this database!

+ To install HESK use the New install option instead.'; + echo '
'; + } + elseif ($problem == 4) + { + echo '
'; + echo 'Version '.HESK_NEW_VERSION.' tables already exist!

+ Your database seems to be compatible with HESK version '.HESK_NEW_VERSION.'

+ To install a new copy of HESK use the New install option instead.'; + echo '
'; + } + ?>
diff --git a/install/updateModsForHesk.php b/install/updateModsForHesk.php index 3503a673..7c05ad18 100644 --- a/install/updateModsForHesk.php +++ b/install/updateModsForHesk.php @@ -6,12 +6,13 @@ require(HESK_PATH . 'hesk_settings.inc.php'); ?> - Mods For HESK 1.6.0 Install / Upgrade + Mods For HESK 1.7.0 Install / Upgrade -

Mods for HESK 1.6.0 Install / Upgrade

+

Mods for HESK 1.7.0 Install / Upgrade

Select your current Mods for HESK version number to upgrade.

-

Please verify the database information below. Additionally, ensure that the database user has CREATE and ALTER permissions.

+

Please verify the database information below. Additionally, ensure that the database user has CREATE and ALTER permissions.

Database Host:

Database Name:

Database User:

diff --git a/install/updateTo1-6-0.php b/install/updateTo1-6-0.php index bf1d0184..616ebb48 100644 --- a/install/updateTo1-6-0.php +++ b/install/updateTo1-6-0.php @@ -13,4 +13,4 @@ hesk_dbQuery("ALTER TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."attachmen hesk_dbQuery("CREATE TABLE `".hesk_dbEscape($hesk_settings['db_pfix'])."settings` (`Key` NVARCHAR(200) NOT NULL, `Value` NVARCHAR(200) NOT NULL)"); hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."settings` (`Key`, `Value`) VALUES ('modsForHeskVersion', '1.6.0')"); -header('Location: update-to1-6-1.php'); \ No newline at end of file +header('Location: updateTo1-6-1.php'); \ No newline at end of file diff --git a/install/updateTo1-6-1.php b/install/updateTo1-6-1.php index 359edddf..977540ba 100644 --- a/install/updateTo1-6-1.php +++ b/install/updateTo1-6-1.php @@ -5,7 +5,5 @@ require(HESK_PATH . 'install/install_functions.inc.php'); require(HESK_PATH . 'hesk_settings.inc.php'); hesk_dbConnect(); hesk_dbQuery("UPDATE `".hesk_dbEscape($hesk_settings['db_pfix'])."settings` SET `Value` = '1.6.1' WHERE `Key` = 'modsForHeskVersion'"); -?> -

Installation / Update complete!

-

Please delete the install folder for security reasons, and then proceed back to the Help Desk

\ No newline at end of file +header('Location: updateTo1-7-0.php'); \ No newline at end of file diff --git a/install/updateTo1-7-0.php b/install/updateTo1-7-0.php new file mode 100644 index 00000000..1808270f --- /dev/null +++ b/install/updateTo1-7-0.php @@ -0,0 +1,105 @@ +Failure! +

An issue occurred when trying to update the modsForHesk_settings.inc.php file.

+
+

Add the following lines to your modsForHesk_settings.inc.php file:

+
+ //-- Set this to 1 to enable custom field names as keys + $modsForHesk_settings[\'custom_field_setting\'] = 0;

+ + //-- Set this to 1 to enable email verification for new customers + $modsForHesk_settings[\'customer_email_verification_required\'] = 0; + +

+

Now you can delete the install folder for security reasons, and then proceed back to the Help Desk

'; + +} + +if ($updateSuccess) { +?> + +

Installation / Update complete!

+

Please delete the install folder for security reasons, and then proceed back to the Help Desk

+ + \ No newline at end of file diff --git a/js/bootstrap-datepicker.js b/js/bootstrap-datepicker.js new file mode 100755 index 00000000..5db96af2 --- /dev/null +++ b/js/bootstrap-datepicker.js @@ -0,0 +1,1681 @@ +/* ========================================================= + * bootstrap-datepicker.js + * Repo: https://github.com/eternicode/bootstrap-datepicker/ + * Demo: http://eternicode.github.io/bootstrap-datepicker/ + * Docs: http://bootstrap-datepicker.readthedocs.org/ + * Forked from http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Started by Stefan Petre; improvements by Andrew Rowls + contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + +(function($, undefined){ + + var $window = $(window); + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + this.dates = new DateArray(); + this.viewDate = UTCToday(); + this.focusDate = null; + + this._process_options(options); + + this.element = $(element); + this.isInline = false; + this.isInput = this.element.is('input'); + this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; + this.hasInput = this.component && this.element.find('input').length; + if (this.component && this.component.length === 0) + this.component = false; + + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + this.viewMode = this.o.startView; + + if (this.o.calendarWeeks) + this.picker.find('tfoot th.today, tfoot th.clear') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); + + this.fillDow(); + this.fillMonths(); + + this._allow_update = true; + + this.update(); + this.showMode(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + else + o.multidate = 1; + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = ((o.weekStart + 6) % 7); + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return (/^auto|left|right|top|bottom$/).test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return (/^left|right$/).test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return (/^top|bottom$/).test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + if (this.isInput){ // single input + this._events = [ + [this.element, { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }] + ]; + } + else if (this.component && this.hasInput){ // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + 'mousedown touchstart': $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + if (!this.isInline) + this.picker.appendTo('body'); + this.picker.show(); + this.place(); + this._attachSecondaryEvents(); + this._trigger('show'); + }, + + hide: function(){ + if (this.isInline) + return; + if (!this.picker.is(':visible')) + return; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + this._trigger('hide'); + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + }, + + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + return new Date(this.dates.get(-1)); + }, + + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted).change(); + } + } + else { + this.element.val(formatted).change(); + } + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + if (this.isInline) + return; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + windowWidth = $window.width(), + windowHeight = $window.height(), + scrollTop = $window.scrollTop(); + + var parentsZindex = []; + this.element.parents().each(function() { + var itemZIndex = $(this).css('z-index'); + if ( itemZIndex !== 'auto' && itemZIndex !== 0 ) parentsZindex.push( parseInt( itemZIndex ) ); + }); + var zIndex = Math.max.apply( Math, parentsZindex ) + 10; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left, + top = offset.top; + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + if (offset.left < 0) + left -= offset.left - visualPadding; + else if (offset.left + calendarWidth > windowWidth) + left = windowWidth - calendarWidth - visualPadding; + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow, bottom_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + offset.top - calendarHeight; + bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight); + if (Math.max(top_overflow, bottom_overflow) === bottom_overflow) + yorient = 'top'; + else + yorient = 'bottom'; + } + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top += height; + else + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + date < this.o.startDate || + date > this.o.endDate || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + + this.fill(); + }, + + fillDow: function(){ + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + var cell = ' '; + html += cell; + this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+dates[this.o.language].daysMin[(dowCnt++)%7]+''; + } + html += ''; + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = '', + i = 0; + while (i < 12){ + html += ''+dates[this.o.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); + }, + + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = new Date(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } + else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with local today, not UTC today + if (this.o.todayHighlight && + date.getUTCFullYear() === today.getFullYear() && + date.getUTCMonth() === today.getMonth() && + date.getUTCDate() === today.getDate()){ + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || + $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ + cls.push('disabled'); + } + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + } + return cls; + }, + + fill: function(){ + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = dates[this.o.language].today || dates['en'].today || '', + cleartxt = dates[this.o.language].clear || dates['en'].clear || '', + tooltip; + if (isNaN(year) || isNaN(month)) return; + this.picker.find('.datepicker-days thead th.datepicker-switch') + .text(dates[this.o.language].months[month]+' '+year); + this.picker.find('tfoot th.today') + .text(todaytxt) + .toggle(this.o.todayBtn !== false); + this.picker.find('tfoot th.clear') + .text(cleartxt) + .toggle(this.o.clearBtn !== false); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth){ + if (prevMonth.getUTCDay() === this.o.weekStart){ + html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + + } + } + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + if (this.o.beforeShowDay !== $.noop){ + var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof(before) === 'boolean') + before = {enabled: before}; + else if (typeof(before) === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; + } + + clsName = $.unique(clsName); + html.push(''+prevMonth.getUTCDate() + ''); + tooltip = null; + if (prevMonth.getUTCDay() === this.o.weekEnd){ + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + + var months = this.picker.find('.datepicker-months') + .find('th:eq(1)') + .text(year) + .end() + .find('span').removeClass('active'); + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ + months.addClass('disabled'); + } + if (year === startYear){ + months.slice(0, startMonth).addClass('disabled'); + } + if (year === endYear){ + months.slice(endMonth+1).addClass('disabled'); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('th:eq(1)') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + var years = $.map(this.dates, function(d){ + return d.getUTCFullYear(); + }), + classes; + for (var i = -1; i < 11; i++){ + classes = ['year']; + if (i === -1) + classes.push('old'); + else if (i === 10) + classes.push('new'); + if ($.inArray(year, years) !== -1) + classes.push('active'); + if (year < startYear || year > endYear) + classes.push('disabled'); + html += ''+year+''; + year += 1; + } + yearCont.html(html); + }, + + updateNavArrows: function(){ + if (!this._allow_update) + return; + + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode){ + case 0: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){ + this.picker.find('.prev').css({visibility: 'hidden'}); + } + else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){ + this.picker.find('.next').css({visibility: 'hidden'}); + } + else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } + }, + + click: function(e){ + e.preventDefault(); + var target = $(e.target).closest('span, td, th'), + year, month, day; + if (target.length === 1){ + switch (target[0].nodeName.toLowerCase()){ + case 'th': + switch (target[0].className){ + case 'datepicker-switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); + switch (this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger('changeMonth', this.viewDate); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + if (this.viewMode === 1) + this._trigger('changeYear', this.viewDate); + break; + } + this.fill(); + break; + case 'today': + var date = new Date(); + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + + this.showMode(-2); + var which = this.o.todayBtn === 'linked' ? null : 'view'; + this._setDate(date, which); + break; + case 'clear': + var element; + if (this.isInput) + element = this.element; + else if (this.component) + element = this.element.find('input'); + if (element) + element.val("").change(); + this.update(); + this._trigger('changeDate'); + if (this.o.autoclose) + this.hide(); + break; + } + break; + case 'span': + if (!target.is('.disabled')){ + this.viewDate.setUTCDate(1); + if (target.is('.month')){ + day = 1; + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); + this.viewDate.setUTCMonth(month); + this._trigger('changeMonth', this.viewDate); + if (this.o.minViewMode === 1){ + this._setDate(UTCDate(year, month, day)); + } + } + else { + day = 1; + month = 0; + year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this._trigger('changeYear', this.viewDate); + if (this.o.minViewMode === 2){ + this._setDate(UTCDate(year, month, day)); + } + } + this.showMode(-1); + this.fill(); + } + break; + case 'td': + if (target.is('.day') && !target.is('.disabled')){ + day = parseInt(target.text(), 10)||1; + year = this.viewDate.getUTCFullYear(); + month = this.viewDate.getUTCMonth(); + if (target.is('.old')){ + if (month === 0){ + month = 11; + year -= 1; + } + else { + month -= 1; + } + } + else if (target.is('.new')){ + if (month === 11){ + month = 0; + year += 1; + } + else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day)); + } + break; + } + } + if (this.picker.is(':visible') && this._focused_from){ + $(this._focused_from).focus(); + } + delete this._focused_from; + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + if (this.o.multidate === 1 && ix === 0){ + // single datepicker, don't remove selected date + } + else if (ix !== -1){ + this.dates.remove(ix); + } + else { + this.dates.push(date); + } + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if (!which || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + this._trigger('changeDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } + }, + + moveMonth: function(date, dir){ + if (!date) + return undefined; + if (!dir) + return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag === 1){ + test = dir === -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ + return new_date.getUTCMonth() === month; + } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ + return new_date.getUTCMonth() !== new_month; + }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } + else { + // For magnitudes >1, move one month at a time... + for (var i=0; i < mag; i++) + // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... + new_date = this.moveMonth(new_date, dir); + // ...then reset the day, keeping it in the new month + new_month = new_date.getUTCMonth(); + new_date.setUTCDate(day); + test = function(){ + return new_month !== new_date.getUTCMonth(); + }; + } + // Common date-resetting loop -- if date is beyond end of month, make it + // end of month + while (test()){ + new_date.setUTCDate(--day); + new_date.setUTCMonth(new_month); + } + return new_date; + }, + + moveYear: function(date, dir){ + return this.moveMonth(date, dir*12); + }, + + dateWithinRange: function(date){ + return date >= this.o.startDate && date <= this.o.endDate; + }, + + keydown: function(e){ + if (this.picker.is(':not(:visible)')){ + if (e.keyCode === 27) // allow escape to hide and re-show picker + this.show(); + return; + } + var dateChanged = false, + dir, newDate, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ + case 27: // escape + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); + e.preventDefault(); + break; + case 37: // left + case 39: // right + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 37 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 38: // up + case 40: // down + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 38 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir * 7); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newDate)){ + this.focusDate = this.viewDate = newViewDate; + this.setValue(); + this.fill(); + e.preventDefault(); + } + break; + case 32: // spacebar + // Spacebar is used in manually typing dates in some formats. + // As such, its behavior should not be hijacked. + break; + case 13: // enter + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + if (this.o.keyboardNavigation) { + this._toggle_multidate(focusDate); + dateChanged = true; + } + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + if (this.o.autoclose) + this.hide(); + } + break; + case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + this.hide(); + break; + } + if (dateChanged){ + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + } + }, + + showMode: function(dir){ + if (dir){ + this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); + } + this.picker + .find('>div') + .hide() + .filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName) + .css('display', 'block'); + this.updateNavArrows(); + } + }; + + var DateRangePicker = function(element, options){ + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; + }); + delete options.inputs; + + $(this.inputs) + .datepicker(options) + .bind('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $(i).data('datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $(e.target).data('datepicker'), + new_date = dp.getUTCDate(), + i = $.inArray(e.target, this.inputs), + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate()) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[i]){ + // Date being moved earlier/left + while (i >= 0 && new_date < this.dates[i]){ + this.pickers[i--].setUTCDate(new_date); + } + } + else if (new_date > this.dates[i]){ + // Date being moved later/right + while (i < l && new_date > this.dates[i]){ + this.pickers[i++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + remove: function(){ + $.map(this.pickers, function(p){ p.remove(); }); + delete this.element.data().datepicker; + } + }; + + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + $.fn.datepicker = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.is('.input-daterange') || opts.inputs){ + var ropts = { + inputs: opts.inputs || $this.find('input').toArray() + }; + $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); + } + else { + $this.data('datepicker', (data = new Datepicker(this, opts))); + } + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + if (internal_return !== undefined) + return false; + } + }); + if (internal_return !== undefined) + return internal_return; + else + return this; + }; + + var defaults = $.fn.datepicker.defaults = { + autoclose: false, + beforeShowDay: $.noop, + calendarWeeks: false, + clearBtn: false, + daysOfWeekDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + weekStart: 0 + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; + $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear" + } + }; + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], + isLeapYear: function(year){ + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); + }, + getDaysInMonth: function(year, month){ + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, + parseFormat: function(format){ + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length === 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + part, dir, i; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ + date = new Date(); + for (i=0; i < parts.length; i++){ + part = part_re.exec(parts[i]); + dir = parseInt(part[1]); + switch (part[2]){ + case 'd': + date.setUTCDate(date.getUTCDate() + dir); + break; + case 'm': + date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); + break; + case 'w': + date.setUTCDate(date.getUTCDate() + dir * 7); + break; + case 'y': + date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); + break; + } + } + return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); + } + parts = date && date.match(this.nonpunctuation) || []; + date = new Date(); + var parsed = {}, + setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], + setters_map = { + yyyy: function(d,v){ + return d.setUTCFullYear(v); + }, + yy: function(d,v){ + return d.setUTCFullYear(2000+v); + }, + m: function(d,v){ + if (isNaN(d)) + return d; + v -= 1; + while (v < 0) v += 12; + v %= 12; + d.setUTCMonth(v); + while (d.getUTCMonth() !== v) + d.setUTCDate(d.getUTCDate()-1); + return d; + }, + d: function(d,v){ + return d.setUTCDate(v); + } + }, + val, filtered; + setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; + setters_map['dd'] = setters_map['d']; + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + var fparts = format.parts.slice(); + // Remove noop parts + if (parts.length !== fparts.length){ + fparts = $(fparts).filter(function(i,p){ + return $.inArray(p, setters_order) !== -1; + }).toArray(); + } + // Process remainder + function match_part(){ + var m = this.slice(0, parts[i].length), + p = parts[i].slice(0, m.length); + return m === p; + } + if (parts.length === fparts.length){ + var cnt; + for (i=0, cnt = fparts.length; i < cnt; i++){ + val = parseInt(parts[i], 10); + part = fparts[i]; + if (isNaN(val)){ + switch (part){ + case 'MM': + filtered = $(dates[language].months).filter(match_part); + val = $.inArray(filtered[0], dates[language].months) + 1; + break; + case 'M': + filtered = $(dates[language].monthsShort).filter(match_part); + val = $.inArray(filtered[0], dates[language].monthsShort) + 1; + break; + } + } + parsed[part] = val; + } + var _date, s; + for (i=0; i < setters_order.length; i++){ + s = setters_order[i]; + if (s in parsed && !isNaN(parsed[s])){ + _date = new Date(date); + setters_map[s](_date, parsed[s]); + if (!isNaN(_date)) + date = _date; + } + } + } + return date; + }, + formatDate: function(date, format, language){ + if (!date) + return ''; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var val = { + d: date.getUTCDate(), + D: dates[language].daysShort[date.getUTCDay()], + DD: dates[language].days[date.getUTCDay()], + m: date.getUTCMonth() + 1, + M: dates[language].monthsShort[date.getUTCMonth()], + MM: dates[language].months[date.getUTCMonth()], + yy: date.getUTCFullYear().toString().substring(2), + yyyy: date.getUTCFullYear() + }; + val.dd = (val.d < 10 ? '0' : '') + val.d; + val.mm = (val.m < 10 ? '0' : '') + val.m; + date = []; + var seps = $.extend([], format.separators); + for (var i=0, cnt = format.parts.length; i <= cnt; i++){ + if (seps.length) + date.push(seps.shift()); + date.push(val[format.parts[i]]); + } + return date.join(''); + }, + headTemplate: ''+ + ''+ + '«'+ + ''+ + '»'+ + ''+ + '', + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + }; + DPGlobal.template = '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + ''+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + DPGlobal.footTemplate+ + '
'+ + '
'+ + '
'; + + $.fn.datepicker.DPGlobal = DPGlobal; + + + /* DATEPICKER NO CONFLICT + * =================== */ + + $.fn.datepicker.noConflict = function(){ + $.fn.datepicker = old; + return this; + }; + + + /* DATEPICKER DATA-API + * ================== */ + + $(document).on( + 'focus.datepicker.data-api click.datepicker.data-api', + '[data-provide="datepicker"]', + function(e){ + var $this = $(this); + if ($this.data('datepicker')) + return; + e.preventDefault(); + // component click requires us to explicitly show it + $this.datepicker('show'); + } + ); + $(function(){ + $('[data-provide="datepicker-inline"]').datepicker(); + }); + +}(window.jQuery)); diff --git a/js/modsForHesk-javascript.js b/js/modsForHesk-javascript.js index f8100465..5383ad72 100644 --- a/js/modsForHesk-javascript.js +++ b/js/modsForHesk-javascript.js @@ -4,12 +4,39 @@ var loadJquery = function() //-- Activate tooltips $('[data-toggle="tooltip"]').tooltip(); - //-- Active popovers + //-- Activate popovers $('[data-toggle="popover"]').popover({ - trigger: 'hover' - }) + trigger: 'hover', + container: 'body' + }); + + //-- Activate HTML popovers + $('[data-toggle="htmlpopover"]').popover({ + trigger: 'hover', + container: 'body', + html: 'true' + }); + + //-- Activate jQuery's date picker + $(function() { + $('.datepicker').datepicker({ + todayBtn: "linked", + clearBtn: true, + autoclose: true, + todayHighlight: true, + format: "yyyy-mm-dd" + }); + }); }; +function selectAll(id) { + $('#' + id + ' option').prop('selected', true); +} + +function deselectAll(id) { + $('#' + id + ' option').prop('selected', false); +} + function toggleRow(id) { if ($('#' + id).hasClass('danger')) { diff --git a/language/en/emails/ticket_reopen_assigned.txt b/language/en/emails/ticket_reopen_assigned.txt new file mode 100644 index 00000000..ace2d404 --- /dev/null +++ b/language/en/emails/ticket_reopen_assigned.txt @@ -0,0 +1,16 @@ +Hello, + +A support ticket assigned to you has been re-opened. + +%%NAME%% has just re-opened the ticket "%%SUBJECT%%". + +Tracking ID: %%TRACK_ID%% + +You can manage this ticket here: +%%TRACK_URL%% + + +Regards, + +%%SITE_TITLE%% +%%SITE_URL%% \ No newline at end of file diff --git a/language/en/emails/verify_email.txt b/language/en/emails/verify_email.txt new file mode 100644 index 00000000..fa573f6f --- /dev/null +++ b/language/en/emails/verify_email.txt @@ -0,0 +1,10 @@ +Dear %%NAME%%, + +Your email needs to be verified before your ticket can be submitted. Please click the link below to verify your email. + +%%VERIFYURL%% + +Sincerely, + +%%SITE_TITLE%% +%%SITE_URL%% \ No newline at end of file diff --git a/language/en/text.php b/language/en/text.php index f4c4174a..ba17d865 100644 --- a/language/en/text.php +++ b/language/en/text.php @@ -21,6 +21,34 @@ $hesklang['_COLLATE']='utf8_unicode_ci'; // This is the email break line that will be used in email piping $hesklang['EMAIL_HR']='------ Reply above this line ------'; +// ADDED OR MODIFIED IN Mods For HESK 1.7.0 +$hesklang['date_custom_field'] = 'Date'; +$hesklang['date_custom_field_text'] = 'No options for this custom field type.'; +$hesklang['multiple_select_custom_field'] = 'Multiple Select box'; +$hesklang['multiple_select_custom_field_text'] = 'Options for this multi-select box, enter one option per line (each line will be a choice your customers can choose from). You need to enter at least two options!'; +$hesklang['date_format'] = 'Date must be in YYYY-MM-DD format.'; +$hesklang['custom_field_setting'] = 'Multilanguage support'; +$hesklang['custom_field_setting_help'] = 'Enabling this setting will use the name of the custom field as the language + file\'s key, rather than the direct name itself. This allows the custom field to be translated into different languages.'; +$hesklang['enable_custom_field_language'] = 'Enable multilanguage support'; +$hesklang['custom_language_key'] = 'Field language file key'; +$hesklang['ticket_reopen_assigned'] = '[#%%TRACK_ID%%] Assigned ticket reopened'; +$hesklang['verify_email'] = 'Verify Email'; +$hesklang['email_verified'] = 'The email address %s has been verified. Additionally, the following tickets have been created:'; //%s: email address +$hesklang['verify_no_records'] = 'No records were found for this activation key. Has this activation key already been used?'; +$hesklang['activation_key'] = 'Activation Key'; +$hesklang['no_tickets_created'] = 'No tickets created'; +$hesklang['customer_email_verification'] = 'Customer Email Verifications'; +$hesklang['customer_email_verification_help'] = 'Require customers to verify their email address via email. Once their + address has been verified, it does not need to be re-verified in the future.

NOTE: Enabling this will disable the + ability for the customer to provide multiple emails, as HESK will be unable to determine which email needs to be verified.'; +$hesklang['require_customer_validate_email'] = 'Require customers to verify email'; +$hesklang['multi_eml_disabled'] = 'This feature has been disabled because this help desk has been configured to require + customers to verify their email address'; +$hesklang['feature_disabled'] = 'Feature Disabled'; +$hesklang['verify_your_email'] = 'Your ticket has been created; however your email needs to be verified before your ticket can be addressed. An email has been sent to the email provided for verification.'; +$hesklang['installation_information'] = 'Installation Information'; + // ADDED OR MODIFIED IN Mods For HESK 1.6.0 $hesklang['ticket_closed'] = '[#%%TRACK_ID%%] Ticket closed/resolved'; $hesklang['ticket_reopen'] = '[#%%TRACK_ID%%] Ticket reopened'; diff --git a/modsForHesk_settings.inc.php b/modsForHesk_settings.inc.php index 2d9a5780..3335dc4e 100644 --- a/modsForHesk_settings.inc.php +++ b/modsForHesk_settings.inc.php @@ -17,4 +17,10 @@ $modsForHesk_settings['questionMarkColor'] = '#000000'; $modsForHesk_settings['rtl'] = 0; //-- Set this to 1 to show icons next to navigation menu items -$modsForHesk_settings['show_icons'] = 0; \ No newline at end of file +$modsForHesk_settings['show_icons'] = 0; + +//-- Set this to 1 to enable custom field names as keys +$modsForHesk_settings['custom_field_setting'] = 0; + +//-- Set this to 1 to enable email verification for new customers +$modsForHesk_settings['customer_email_verification_required'] = 0;$modsForHesk_settings['show_icons'] = 0; \ No newline at end of file diff --git a/print.php b/print.php index 5ae2ebf7..4b0fb8f8 100644 --- a/print.php +++ b/print.php @@ -206,8 +206,13 @@ $num_cols = 0; echo ''; foreach ($hesk_settings['custom_fields'] as $k=>$v) { - if ($v['use']) + if ($v['use']) { + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + if ($num_cols == 3) { echo ''; diff --git a/submit_ticket.php b/submit_ticket.php index f2e436eb..dd1bb5f0 100644 --- a/submit_ticket.php +++ b/submit_ticket.php @@ -37,6 +37,7 @@ define('HESK_PATH','./'); // Get all the required files and functions require(HESK_PATH . 'hesk_settings.inc.php'); +require(HESK_PATH . 'modsForHesk_settings.inc.php'); require(HESK_PATH . 'inc/common.inc.php'); hesk_load_database_functions(); require(HESK_PATH . 'inc/email_functions.inc.php'); @@ -207,7 +208,12 @@ foreach ($hesk_settings['custom_fields'] as $k=>$v) { if ($v['use']) { - if ($v['type'] == 'checkbox') + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + + if ($v['type'] == 'checkbox' || $v['type'] == 'multiselect') { $tmpvar[$k]=''; @@ -238,10 +244,21 @@ foreach ($hesk_settings['custom_fields'] as $k=>$v) { $hesk_error_buffer[$k]=$hesklang['fill_all'].': '.$v['name']; } + + if ($v['type'] == 'date') + { + $tmpvar[$k] = strtotime($_POST[$k]); + } } else { - $tmpvar[$k]=hesk_makeURL(nl2br(hesk_input( hesk_POST($k) ))); + if ($v['type'] == 'date' && $_POST[$k] != '') + { + $tmpvar[$k] = strtotime($_POST[$k]); + } else + { + $tmpvar[$k] = hesk_makeURL(nl2br(hesk_input(hesk_POST($k)))); + } } $_SESSION["c_$k"]=hesk_POST($k); } @@ -344,22 +361,49 @@ if ($hesk_settings['attachments']['use'] && ! empty($attachments) ) } } -// Insert ticket to database -$ticket = hesk_newTicket($tmpvar); +// Should the helpdesk validate emails? +$createTicket = true; +if ($modsForHesk_settings['customer_email_verification_required']) +{ + $verifiedEmailSql = "SELECT `Email` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."verified_emails` WHERE `Email` = '".hesk_dbEscape($tmpvar['email'])."'"; + $verifiedEmailRS = hesk_dbQuery($verifiedEmailSql); + if ($verifiedEmailRS->num_rows == 0) + { + //-- email has not yet been verified. + $ticket = hesk_newTicket($tmpvar, false); + + //-- generate the activation key, which is a hash of their email address along with the current time. + $unhashedKey = $tmpvar['email'].time(); + $key = hash('sha512', $unhashedKey); -// Notify the customer -hesk_notifyCustomer(); + $escapedEmail = hesk_dbEscape($tmpvar['email']); + $escapedKey = hesk_dbEscape($key); + hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."pending_verification_emails` (`Email`, `ActivationKey`) + VALUES ('".$escapedEmail."', '".$escapedKey."')"); -// Need to notify staff? -// --> From autoassign? -if ($tmpvar['owner'] && $autoassign_owner['notify_assigned']) -{ - hesk_notifyAssignedStaff($autoassign_owner, 'ticket_assigned_to_you'); + hesk_notifyCustomerForVerifyEmail('verify_email', $key); + $createTicket = false; + } } -// --> No autoassign, find and notify appropriate staff -elseif ( ! $tmpvar['owner'] ) +if ($createTicket) { - hesk_notifyStaff('new_ticket_staff', " `notify_new_unassigned` = '1' "); + //-- email has been verified, and a ticket can be created + $ticket = hesk_newTicket($tmpvar); + + // Notify the customer + hesk_notifyCustomer(); + + // Need to notify staff? + // --> From autoassign? + if ($tmpvar['owner'] && $autoassign_owner['notify_assigned']) + { + hesk_notifyAssignedStaff($autoassign_owner, 'ticket_assigned_to_you'); + } + // --> No autoassign, find and notify appropriate staff + elseif ( ! $tmpvar['owner'] ) + { + hesk_notifyStaff('new_ticket_staff', " `notify_new_unassigned` = '1' "); + } } // Next ticket show suggested articles again @@ -395,14 +439,19 @@ require_once(HESK_PATH . 'inc/header.inc.php');

' . - $hesklang['ticket_submitted_success'] . ': ' . $ticket['trackid'] . '

+ $hesklang['ticket_submitted'] . '

' . + $hesklang['ticket_submitted_success'] . ': ' . $ticket['trackid'] . '

' . $hesklang['view_your_ticket'] . '' - ); + ); + } else + { + hesk_show_notice($hesklang['verify_your_email'].'

'.$hesklang['check_spambox']); + } // Any other messages to display? hesk_handle_messages(); diff --git a/ticket.php b/ticket.php index fe6cc0a5..18c4c008 100644 --- a/ticket.php +++ b/ticket.php @@ -212,16 +212,6 @@ require_once(HESK_PATH . 'inc/header.inc.php');
- -
-
- -
-
- -
-
-

@@ -340,8 +330,21 @@ require_once(HESK_PATH . 'inc/header.inc.php'); { if ($v['use'] && $v['place']==0) { - echo ' -

'.$v['name'].': '.$ticket[$k].'

'; + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + + echo '

'.$v['name'].': '; + if ($v['type'] == 'date' && !empty($ticket[$k])) + { + $dt = date('Y-m-d h:i:s', $ticket[$k]); + echo hesk_dateToString($dt, 0); + } else + { + echo $ticket[$k]; + } + echo '

'; } } ?> @@ -358,8 +361,21 @@ require_once(HESK_PATH . 'inc/header.inc.php'); { if ($v['use'] && $v['place']) { - echo ' -

'.$v['name'].': '.$ticket[$k].'

'; + if ($modsForHesk_settings['custom_field_setting']) + { + $v['name'] = $hesklang[$v['name']]; + } + + echo '

'.$v['name'].': '; + if ($v['type'] == 'date' && !empty($ticket[$k])) + { + $dt = date('Y-m-d h:i:s', $ticket[$k]); + echo hesk_dateToString($dt, 0); + } else + { + echo $ticket[$k]; + } + echo '

'; } } /* Attachments */ diff --git a/verifyemail.php b/verifyemail.php new file mode 100644 index 00000000..a77999e7 --- /dev/null +++ b/verifyemail.php @@ -0,0 +1,128 @@ + + +
+
+

+
+ + fetch_assoc()) + { + $email = $result['Email']; + $ticketRs = hesk_dbQuery("SELECT * FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."stage_tickets` + WHERE `email` = '".hesk_dbEscape($result['Email'])."'"); + while ($innerResult = $ticketRs->fetch_assoc()) + { + $ticket = hesk_newTicket($innerResult); + // Notify the customer + hesk_notifyCustomer(); + + // Need to notify staff? + // --> From autoassign? + $getOwnerRs = hesk_dbQuery("SELECT * FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."users` WHERE ID = ".hesk_dbEscape($ticket['owner'])); + $autoassign_owner = $getOwnerRs->fetch_assoc(); + if ($ticket['owner'] && $autoassign_owner['notify_assigned']) + { + hesk_notifyAssignedStaff($autoassign_owner, 'ticket_assigned_to_you'); + } + // --> No autoassign, find and notify appropriate staff + elseif ( ! $ticket['owner'] ) + { + hesk_notifyStaff('new_ticket_staff', " `notify_new_unassigned` = '1' "); + } + + array_push($submittedTickets, $innerResult['trackid']); + hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."stage_tickets` + WHERE `id` = ".$innerResult['id']); + } + + //Add email address to the verified emails table + hesk_dbQuery('INSERT INTO `'.hesk_dbEscape($hesk_settings['db_pfix']).'verified_emails` (`Email`) VALUES (\''.hesk_dbEscape($email).'\')'); + } + hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."pending_verification_emails` + WHERE `ActivationKey` = '".hesk_dbEscape($key)."'"); + + //-- was there an email recorded for the key? + if (!empty($email)) + { + $showForm = false; + ?> +
+

+
    + '.$ticket.''; + } + if (count($submittedTickets) == 0) + { + echo '
  • '.$hesklang['no_tickets_created'].'
  • '; + } + ?> +
+
+ +
+

+
+ + +
+ +
+ +
+
+
+
+ +
+
+ + +
+
+ \ No newline at end of file