diff --git a/alpine/.gitignore b/alpine/.gitignore new file mode 100644 index 0000000..77d7892 --- /dev/null +++ b/alpine/.gitignore @@ -0,0 +1,6 @@ +db/ +etc/ +files/ +log/ +backup/ +mysql_settings.ini diff --git a/alpine/Dockerfile b/alpine/Dockerfile deleted file mode 100644 index 12fa1a9..0000000 --- a/alpine/Dockerfile +++ /dev/null @@ -1,59 +0,0 @@ -FROM alpine -MAINTAINER Benoit LORAND - -WORKDIR /root -ENV GLPI_CONFIG_DIR=/etc/glpi -ENV GLPI_VAR_DIR=/var/lib/glpi -ENV GLPI_LOG_DIR=/var/log/glpi -ENV GLPI_VERSION=9.5.1 -ENV FUSIONINVENTORY_VERSION=9.5.0+1.0 - -RUN \ -apk add \ - runit \ - php7-apache2 \ - php7 \ - mariadb-client \ - php7-pecl-apcu \ - php7-mysqli \ - php7-gd \ - php7-intl \ - php7-ldap \ - php7-xmlrpc \ - php7-exif \ - php7-zip \ - php7-bz2 \ - php7-opcache \ - php7-pear \ - php7-curl \ - php7-dom \ - php7-pdo \ - php7-json \ - php7-session \ - php7-ctype \ - php7-fileinfo \ - php7-mbstring \ - php7-simplexml \ - php7-iconv \ - php7-sodium \ - php7-phar - -COPY CAS-1.3.8.tgz /root/ -RUN pear install /root/CAS-1.3.8.tgz && \ -pear install Archive_Tar -COPY httpd.conf /etc/apache2 -COPY service/ /etc/service/ -COPY glpi_init.sh /root/glpi_init.sh -COPY glpi.cron /var/spool/cron/crontabs/apache -COPY initrc /etc/ -COPY glpi_ticket.class.php.patch /root/ -ADD https://github.com/glpi-project/glpi/releases/download/${GLPI_VERSION}/glpi-${GLPI_VERSION}.tgz /root/glpi-${GLPI_VERSION}.tgz -ADD https://github.com/fusioninventory/fusioninventory-for-glpi/releases/download/glpi${FUSIONINVENTORY_VERSION}/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 /root/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 - -RUN \ -chmod a+x /root/glpi_init.sh /etc/initrc && \ -chmod 600 /etc/crontabs/apache && \ -rm -f /var/www/html/* /root/CAS-1.3.8.tgz && \ -rm -rf /tmp/* /var/tmp/* - -ENTRYPOINT ["/etc/initrc"] diff --git a/alpine/mysql_settings.ini b/alpine/README.md similarity index 53% rename from alpine/mysql_settings.ini rename to alpine/README.md index b66e7ca..1d1c23f 100644 --- a/alpine/mysql_settings.ini +++ b/alpine/README.md @@ -1,4 +1,8 @@ +Ajouter un fichier mysql_settings.ini qui contient + +``` MYSQL_DATABASE= MYSQL_USER= MYSQL_PASSWORD='' MYSQL_ROOT_PASSWORD='' +``` diff --git a/alpine/docker-compose.yml b/alpine/docker-compose.yml index ffd67f9..239be86 100644 --- a/alpine/docker-compose.yml +++ b/alpine/docker-compose.yml @@ -2,21 +2,24 @@ version: '3.5' services: web: container_name: glpi-web - build: . + build: web-builder restart: always - ports: - - 8089:80 volumes: - ./etc/:/etc/glpi/ - ./files/:/var/lib/glpi/ - ./log/:/var/log/glpi/ env_file: - ./mysql_settings.ini + environment: + - PHP_MEMORY_LIMIT=256M + - PHP_UPLOAD_MAX_FILESIZE=10M + - PHP_POST_MAX_SIZE=20M + - PHP_DATE_TIMEZONE=Europe/Paris depends_on: - db db: - image: mysql:5.7 + image: mariadb container_name: glpi-db command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci restart: always diff --git a/alpine/CAS-1.3.8.tgz b/alpine/web-builder/CAS-1.3.8.tgz similarity index 100% rename from alpine/CAS-1.3.8.tgz rename to alpine/web-builder/CAS-1.3.8.tgz diff --git a/alpine/web-builder/Dockerfile b/alpine/web-builder/Dockerfile new file mode 100644 index 0000000..fa9dc9f --- /dev/null +++ b/alpine/web-builder/Dockerfile @@ -0,0 +1,83 @@ +FROM alpine +MAINTAINER Benoit LORAND + +WORKDIR /root +ENV GLPI_CONFIG_DIR=/etc/glpi +ENV GLPI_VAR_DIR=/var/lib/glpi +ENV GLPI_LOG_DIR=/var/log/glpi +ENV GLPI_VERSION=9.5.5 +ENV FUSIONINVENTORY_VERSION=9.5+3.0 +ENV FIELDS_VERSION=1.12.4 +ENV DATAINJECTION_VERSION=2.9.0 + +RUN \ +apk add --no-cache \ + runit \ + php7-apache2 \ + php7 \ + mariadb-client \ + php7-pecl-apcu \ + php7-mysqli \ + php7-gd \ + php7-intl \ + php7-ldap \ + php7-xmlrpc \ + php7-xml \ + php7-exif \ + php7-zip \ + php7-bz2 \ + php7-opcache \ + php7-pear \ + php7-curl \ + php7-dom \ + php7-pdo \ + php7-json \ + php7-session \ + php7-ctype \ + php7-fileinfo \ + php7-mbstring \ + php7-simplexml \ + php7-iconv \ + php7-sodium \ + php7-imap \ + php7-pdo \ + php7-pdo_mysql \ + php7-pspell \ + php7-phar \ + patch + +COPY CAS-1.3.8.tgz /root/ +RUN pear install /root/CAS-1.3.8.tgz && \ +pear install Archive_Tar +COPY httpd.conf /etc/apache2 +COPY service/ /etc/service/ +COPY glpi_init.sh /root/glpi_init.sh +COPY glpi.cron /var/spool/cron/crontabs/apache +COPY initrc /etc/ +COPY glpi_ticket.class.php.patch /root/glpi_ticket.class.php.patch +ADD https://github.com/glpi-project/glpi/releases/download/${GLPI_VERSION}/glpi-${GLPI_VERSION}.tgz /root/glpi-${GLPI_VERSION}.tgz +ADD https://github.com/fusioninventory/fusioninventory-for-glpi/releases/download/glpi${FUSIONINVENTORY_VERSION}/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 /root/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 +ADD https://github.com/pluginsGLPI/fields/releases/download/${FIELDS_VERSION}/glpi-fields-${FIELDS_VERSION}.tar.bz2 /root/glpi-fields-${FIELDS_VERSION}.tar.bz2 +ADD https://github.com/pluginsGLPI/datainjection/releases/download/${DATAINJECTION_VERSION}/glpi-datainjection-${DATAINJECTION_VERSION}.tar.bz2 /root/glpi-datainjection-${DATAINJECTION_VERSION}.tar.bz2 + +RUN \ +mkdir -p /root/glpi_template/etc /root/glpi_template/files && \ +tar x -f /root/glpi-${GLPI_VERSION}.tgz && \ +cp -r /root/glpi/config/. /root/glpi_template/etc/. && \ +cp -r /root/glpi/files/. /root/glpi_template/files/. && \ +rm -r /root/glpi/config /root/glpi/files && \ +mv /root/glpi /var/www/glpi && \ +cd /var/www/glpi && \ +patch -Np0 -i /root/glpi_ticket.class.php.patch && \ +cd /var/www/glpi/plugins && \ +tar x -f /root/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 && \ +cd /var/www/glpi/marketplace && \ +tar x -f /root/glpi-fields-${FIELDS_VERSION}.tar.bz2 && \ +tar x -f /root/glpi-datainjection-${DATAINJECTION_VERSION}.tar.bz2 && \ +chmod a+x /root/glpi_init.sh /etc/initrc && \ +chmod 600 /etc/crontabs/apache && \ +rm -f /var/www/html/* /root/CAS-1.3.8.tgz /root/glpi-${GLPI_VERSION}.tgz /root/fusioninventory-${FUSIONINVENTORY_VERSION}.tar.bz2 /root/glpi-fields-${FIELDS_VERSION}.tar.bz2 /root/glpi-datainjection-${DATAINJECTION_VERSION}.tar.bz2 && \ +rm -f /root/glpi_ticket.class.php.patch && \ +rm -rf /tmp/* /var/tmp/* + +ENTRYPOINT ["/etc/initrc"] diff --git a/alpine/glpi.cron b/alpine/web-builder/glpi.cron similarity index 63% rename from alpine/glpi.cron rename to alpine/web-builder/glpi.cron index f22a190..c92ce0d 100644 --- a/alpine/glpi.cron +++ b/alpine/web-builder/glpi.cron @@ -3,3 +3,4 @@ GLPI_VAR_DIR=/var/lib/glpi GLPI_LOG_DIR=/var/log/glpi */1 * * * * /usr/bin/php7 /var/www/glpi/front/cron.php +0 * * * * cd /var/www/glpi && php bin/console glpi:ldap:synchronize_users -n diff --git a/alpine/glpi_init.sh b/alpine/web-builder/glpi_init.sh similarity index 50% rename from alpine/glpi_init.sh rename to alpine/web-builder/glpi_init.sh index 572eba1..1a7fe5c 100644 --- a/alpine/glpi_init.sh +++ b/alpine/web-builder/glpi_init.sh @@ -1,6 +1,4 @@ #!/bin/sh -GLPI_TARBALL="/root/glpi-9.5.1.tgz" -FUSION_TARBALL="/root/fusioninventory-9.5.0+1.0.tar.bz2" NORMAL='' RED='' GREEN='' @@ -28,19 +26,33 @@ while ! mysqlshow -h db -uroot -p${MYSQL_ROOT_PASSWORD} 2>&1 | grep "^| ${MYSQL_ done } +updatephpini() { + variable=$1 + value=$2 + file="/etc/php7/php.ini" + + if [ ! -z "${value}" ] ; then + msglog green "Updating $variable to $value in $file" + if grep "^${variable}" "${file}" > /dev/null 2<&1 ; then + sed -i "s@^\(${variable}\s*=\).*@\1 ${value}@g" "${file}" + else + echo ${variable} = ${value} >> ${file} + fi + fi +} + +updatephpini memory_limit ${PHP_MEMORY_LIMIT} +updatephpini upload_max_filesize ${PHP_UPLOAD_MAX_FILESIZE} +updatephpini post_max_size ${PHP_POST_MAX_SIZE} +updatephpini date.timezone ${PHP_DATE_TIMEZONE} + mkdir -p /var/www/glpi -if [ -z "$(ls -A /var/www/glpi)" ] ; then +if [ ! -e "/etc/glpi/config_db.php" ] ; then waiting_for_db msglog red "Initialazing ${GLPI_TARBALL}..." cd /root - tar xf ${GLPI_TARBALL} - cp -r /root/glpi/config/. /etc/glpi/. - cp -r /root/glpi/files/. /var/lib/glpi/. - rm -r /root/glpi/config /root/glpi/files - cp -r /root/glpi/. /var/www/glpi/. - cd /var/www/glpi/plugins - tar xf ${FUSION_TARBALL} - rm -r /root/glpi + cp -r /root/glpi_template/etc/. /etc/glpi/. + cp -r /root/glpi_template/files/. /var/lib/glpi/. mysql --host=db --user=root --password=${MYSQL_ROOT_PASSWORD} << EOF use mysql; GRANT SELECT ON time_zone_name TO '${MYSQL_USER}'@'%'; @@ -52,31 +64,32 @@ EOF rm install/install.php chown -R apache:apache /var/www/glpi /etc/glpi /var/lib/glpi /var/log/glpi patch -Np0 -i /root/glpi_ticket.class.php.patch + echo "${GLPI_VERSION}" > /etc/glpi/glpi_actual_version + echo "${FUSIONINVENTORY_VERSION}" > /etc/glpi/fusioninventory_actual_version + echo "${FIELDS_VERSION}" > /etc/glpi/fields_actual_version + echo "${DATAINJECTION_VERSION}" > /etc/glpi/datainjection_version msglog green "Initialazing complete..." else msglog green "GLPI is already initialized" - cd /var/www/glpi - GLPI_ACTUAL_VERSION=$(awk -F", '" '/^define\(.GLPI_VERSION/ { print $2 }' inc/define.php | sed 's/\([0-9\.]*\).*/\1/') - FUSIONINVENTORY_ACTUAL_VERSION=$(awk -F', "' '/^define \(.PLUGIN_FUSIONINVENTORY_VERSION/ { print $2 }' plugins/fusioninventory/setup.php | sed 's/\([0-9\.+]*\).*/\1/') - if [ "${GLPI_ACTUAL_VERSION}" = "${GLPI_VERSION}" -a "${FUSIONINVENTORY_ACTUAL_VERSION}" = "${FUSIONINVENTORY_VERSION}" ] ; then + GLPI_ACTUAL_VERSION=$(cat /etc/glpi/glpi_actual_version) + FUSIONINVENTORY_ACTUAL_VERSION=$(cat /etc/glpi/fusioninventory_actual_version) + FIELDS_ACTUAL_VERSION=$(cat /etc/glpi/fields_actual_version) + DATAINJECTION_ACTUAL_VERSION=$(cat /etc/glpi/datainjection_version) + if [ "${GLPI_ACTUAL_VERSION}" = "${GLPI_VERSION}" -a "${FUSIONINVENTORY_ACTUAL_VERSION}" = "${FUSIONINVENTORY_VERSION}" -a "${FIELDS_ACTUAL_VERSION}" = "${FIELDS_VERSION}" -a "${DATAINJECTION_ACTUAL_VERSION}" = "${DATAINJECTION_VERSION}" ] ; then msglog green "GLPI already up2date" exit fi msglog red "Updating GLPI from ${GLPI_ACTUAL_VERSION} to ${GLPI_VERSION}" waiting_for_db - php bin/console glpi:maintenance:enable -n - php bin/console glpi:plugin:deactivate fusioninventory -n - cd /root - tar xf ${GLPI_TARBALL} - rm -r glpi/config glpi/files /var/www/glpi - mv glpi /var/www/glpi - cd /var/www/glpi/plugins - tar xf ${FUSION_TARBALL} - rm /var/www/glpi/install/install.php cd /var/www/glpi - php bin/console db:update --config-dir=${GLPI_CONFIG_DIR} -n + php bin/console glpi:maintenance:enable -n + php bin/console glpi:plugin:deactivate --all -n + rm /var/www/glpi/install/install.php + php bin/console db:update --config-dir=${GLPI_CONFIG_DIR} -n && \ + ( echo "${GLPI_VERSION}" > /etc/glpi/glpi_actual_version ; \ + echo "${FUSIONINVENTORY_VERSION}" > /etc/glpi/fusioninventory_actual_version ; \ + echo "${FIELDS_VERSION}" > /etc/glpi/fields_actual_version ; \ + echo "${DATAINJECTION_VERSION}" > /etc/glpi/datainjection_actual_version ) php bin/console glpi:maintenance:disable -n chown -R apache:apache /var/www/glpi /etc/glpi /var/lib/glpi /var/log/glpi - patch -Np0 -i /root/glpi_ticket.class.php.patch - fi diff --git a/alpine/glpi_ticket.class.php.patch b/alpine/web-builder/glpi_ticket.class.php.patch similarity index 100% rename from alpine/glpi_ticket.class.php.patch rename to alpine/web-builder/glpi_ticket.class.php.patch diff --git a/alpine/httpd.conf b/alpine/web-builder/httpd.conf similarity index 100% rename from alpine/httpd.conf rename to alpine/web-builder/httpd.conf diff --git a/alpine/initrc b/alpine/web-builder/initrc similarity index 100% rename from alpine/initrc rename to alpine/web-builder/initrc diff --git a/alpine/service/20-cron/run b/alpine/web-builder/service/20-cron/run old mode 100755 new mode 100644 similarity index 100% rename from alpine/service/20-cron/run rename to alpine/web-builder/service/20-cron/run diff --git a/alpine/service/30-apache2/run b/alpine/web-builder/service/30-apache2/run old mode 100755 new mode 100644 similarity index 100% rename from alpine/service/30-apache2/run rename to alpine/web-builder/service/30-apache2/run diff --git a/alpine/service/90-glpi_init/run b/alpine/web-builder/service/90-glpi_init/run old mode 100755 new mode 100644 similarity index 100% rename from alpine/service/90-glpi_init/run rename to alpine/web-builder/service/90-glpi_init/run diff --git a/alpine/service/template b/alpine/web-builder/service/template similarity index 100% rename from alpine/service/template rename to alpine/web-builder/service/template diff --git a/alpine/web-builder/ticket.class.php b/alpine/web-builder/ticket.class.php new file mode 100644 index 0000000..279aeb1 --- /dev/null +++ b/alpine/web-builder/ticket.class.php @@ -0,0 +1,7331 @@ +. + * --------------------------------------------------------------------- + */ + +use Glpi\Event; + +if (!defined('GLPI_ROOT')) { + die("Sorry. You can't access this file directly"); +} + +/** + * Ticket Class +**/ +class Ticket extends CommonITILObject { + + // From CommonDBTM + public $dohistory = true; + static protected $forward_entity_to = ['TicketValidation', 'TicketCost']; + + // From CommonITIL + public $userlinkclass = 'Ticket_User'; + public $grouplinkclass = 'Group_Ticket'; + public $supplierlinkclass = 'Supplier_Ticket'; + + static $rightname = 'ticket'; + + protected $userentity_oncreate = true; + + const MATRIX_FIELD = 'priority_matrix'; + const URGENCY_MASK_FIELD = 'urgency_mask'; + const IMPACT_MASK_FIELD = 'impact_mask'; + const STATUS_MATRIX_FIELD = 'ticket_status'; + + // HELPDESK LINK HARDWARE DEFINITION : CHECKSUM SYSTEM : BOTH=1*2^0+1*2^1=3 + const HELPDESK_MY_HARDWARE = 0; + const HELPDESK_ALL_HARDWARE = 1; + + // Specific ones + /// Hardware datas used by getFromDBwithData + public $hardwaredatas = []; + /// Is a hardware found in getHardwareData / getFromDBwithData : hardware link to the job + public $computerfound = 0; + + // Request type + const INCIDENT_TYPE = 1; + // Demand type + const DEMAND_TYPE = 2; + + const READMY = 1; + const READALL = 1024; + const READGROUP = 2048; + const READASSIGN = 4096; + const ASSIGN = 8192; + const STEAL = 16384; + const OWN = 32768; + const CHANGEPRIORITY = 65536; + const SURVEY = 131072; + + + function getForbiddenStandardMassiveAction() { + + $forbidden = parent::getForbiddenStandardMassiveAction(); + + if (!Session::haveRightsOr(self::$rightname, [DELETE, PURGE])) { + $forbidden[] = 'delete'; + $forbidden[] = 'purge'; + $forbidden[] = 'restore'; + } + + return $forbidden; + } + + + /** + * Name of the type + * + * @param $nb : number of item in the type (default 0) + **/ + static function getTypeName($nb = 0) { + return _n('Ticket', 'Tickets', $nb); + } + + + /** + * @see CommonGLPI::getMenuShorcut() + * + * @since 0.85 + **/ + static function getMenuShorcut() { + return 't'; + } + + + /** + * @see CommonGLPI::getAdditionalMenuContent() + * + * @since 0.85 + **/ + static function getAdditionalMenuContent() { + + if (static::canCreate()) { + $menu = [ + 'create_ticket' => [ + 'title' => __('Create ticket'), + 'page' => static::getFormURL(false), + 'icon' => 'fas fa-plus', + ], + ]; + return $menu; + } else { + return self::getAdditionalMenuOptions(); + } + } + + + /** + * @see CommonGLPI::getAdditionalMenuLinks() + * + * @since 0.85 + **/ + static function getAdditionalMenuLinks() { + global $CFG_GLPI; + + $links = parent::getAdditionalMenuLinks(); + if (Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())) { + $opt = []; + $opt['reset'] = 'reset'; + $opt['criteria'][0]['field'] = 55; // validation status + $opt['criteria'][0]['searchtype'] = 'equals'; + $opt['criteria'][0]['value'] = CommonITILValidation::WAITING; + $opt['criteria'][0]['link'] = 'AND'; + + $opt['criteria'][1]['field'] = 59; // validation aprobator + $opt['criteria'][1]['searchtype'] = 'equals'; + $opt['criteria'][1]['value'] = Session::getLoginUserID(); + $opt['criteria'][1]['link'] = 'AND'; + + $opt['criteria'][2]['field'] = 52; // global validation status + $opt['criteria'][2]['searchtype'] = 'equals'; + $opt['criteria'][2]['value'] = CommonITILValidation::WAITING; + $opt['criteria'][2]['link'] = 'AND'; + + $opt['criteria'][3]['field'] = 12; // ticket status + $opt['criteria'][3]['searchtype'] = 'equals'; + $opt['criteria'][3]['value'] = Ticket::CLOSED; + $opt['criteria'][3]['link'] = 'AND NOT'; + + $opt['criteria'][4]['field'] = 12; // ticket status + $opt['criteria'][4]['searchtype'] = 'equals'; + $opt['criteria'][4]['value'] = Ticket::SOLVED; + $opt['criteria'][4]['link'] = 'AND NOT'; + + $pic_validate = "\""."; + + $links[$pic_validate] = Ticket::getSearchURL(false) . '?'.Toolbox::append_params($opt, '&'); + } + + return $links; + } + + + function canAssign() { + if (isset($this->fields['is_deleted']) && ($this->fields['is_deleted'] == 1) + || isset($this->fields['status']) && in_array($this->fields['status'], $this->getClosedStatusArray()) + ) { + return false; + } + return Session::haveRight(static::$rightname, self::ASSIGN); + } + + + function canAssignToMe() { + + if (isset($this->fields['is_deleted']) && $this->fields['is_deleted'] == 1 + || isset($this->fields['status']) && in_array($this->fields['status'], $this->getClosedStatusArray()) + ) { + return false; + } + return (Session::haveRight(self::$rightname, self::STEAL) + || (Session::haveRight(self::$rightname, self::OWN) + && ($this->countUsers(CommonITILActor::ASSIGN) == 0))); + } + + + static function canUpdate() { + + // To allow update of urgency and category for post-only + if (Session::getCurrentInterface() == "helpdesk") { + return Session::haveRight(self::$rightname, CREATE); + } + + return Session::haveRightsOr(self::$rightname, + [UPDATE, + self::ASSIGN, + self::STEAL, + self::OWN, + self::CHANGEPRIORITY]); + } + + + static function canView() { + return (Session::haveRightsOr(self::$rightname, + [self::READALL, self::READMY, UPDATE, self::READASSIGN, + self::READGROUP, self::OWN]) + || Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())); + } + + + /** + * Is the current user have right to show the current ticket ? + * + * @return boolean + **/ + function canViewItem() { + + if (!Session::haveAccessToEntity($this->getEntityID())) { + return false; + } + return (Session::haveRight(self::$rightname, self::READALL) + || (Session::haveRight(self::$rightname, self::READMY) + && (($this->fields["users_id_recipient"] === Session::getLoginUserID()) + || $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) + || $this->isUser(CommonITILActor::OBSERVER, Session::getLoginUserID()))) + || (Session::haveRight(self::$rightname, self::READGROUP) + && isset($_SESSION["glpigroups"]) + && ($this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION["glpigroups"]) + || $this->haveAGroup(CommonITILActor::OBSERVER, $_SESSION["glpigroups"]))) + || (Session::haveRight(self::$rightname, self::READASSIGN) + && ($this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) + || (isset($_SESSION["glpigroups"]) + && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION["glpigroups"])) + || (Session::haveRight(self::$rightname, self::ASSIGN) + && ($this->fields["status"] == self::INCOMING)))) + || (Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights()) + && TicketValidation::canValidate($this->fields["id"]))); + } + + + /** + * Is the current user have right to approve solution of the current ticket ? + * + * @return boolean + **/ + function canApprove() { + + return ((($this->fields["users_id_recipient"] === Session::getLoginUserID()) + && Session::haveRight('ticket', Ticket::SURVEY)) + || $this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) + || (isset($_SESSION["glpigroups"]) + && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION["glpigroups"]))); + } + + + /** + * @see CommonDBTM::canMassiveAction() + **/ + function canMassiveAction($action, $field, $value) { + + switch ($action) { + case 'update' : + switch ($field) { + case 'status' : + if (!self::isAllowedStatus($this->fields['status'], $value)) { + return false; + } + break; + } + break; + } + return true; + } + + /** + * Check if current user can take into account the ticket. + * + * @return boolean + */ + public function canTakeIntoAccount() { + + // Can take into account if user is assigned user + if ($this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) + || (isset($_SESSION["glpigroups"]) + && $this->haveAGroup(CommonITILActor::ASSIGN, $_SESSION['glpigroups']))) { + return true; + } + + // Cannot take into account if user is a requester (and not assigned) + if ($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) + || (isset($_SESSION["glpigroups"]) + && $this->haveAGroup(CommonITILActor::REQUESTER, $_SESSION['glpigroups']))) { + return false; + } + + $canAddTask = Session::haveRight("task", CommonITILTask::ADDALLITEM); + $canAddFollowup = Session::haveRightsOr( + 'followup', + [ + ITILFollowup::ADDALLTICKET, + ITILFollowup::ADDMYTICKET, + ITILFollowup::ADDGROUPTICKET, + ] + ); + + // Can take into account if user has rights to add tasks or followups, + // assuming that users that does not have those rights cannot treat the ticket. + return $canAddTask || $canAddFollowup; + } + + /** + * Check if ticket has already been taken into account. + * + * @return boolean + */ + public function isAlreadyTakenIntoAccount() { + + return array_key_exists('takeintoaccount_delay_stat', $this->fields) + && $this->fields['takeintoaccount_delay_stat'] != 0; + } + + /** + * Get Datas to be added for SLA add + * + * @param $slas_id SLA id + * @param $entities_id entity ID of the ticket + * @param $date begin date of the ticket + * @param $type type of SLA + * + * @since 9.1 (before getDatasToAddSla without type parameter) + * + * @return array of datas to add in ticket + **/ + function getDatasToAddSLA($slas_id, $entities_id, $date, $type) { + + list($dateField, $slaField) = SLA::getFieldNames($type); + + $calendars_id = Entity::getUsedConfig('calendars_id', $entities_id); + $data = []; + + $sla = new SLA(); + if ($sla->getFromDB($slas_id)) { + $sla->setTicketCalendar($calendars_id); + if ($sla->fields['type'] == SLM::TTR) { + $data["slalevels_id_ttr"] = SlaLevel::getFirstSlaLevel($slas_id); + } + // Compute time_to_resolve + $data[$dateField] = $sla->computeDate($date); + $data['sla_waiting_duration'] = 0; + + } else { + $data["slalevels_id_ttr"] = 0; + $data[$slaField] = 0; + $data['sla_waiting_duration'] = 0; + } + return $data; + + } + + /** + * Get Datas to be added for OLA add + * + * @param $olas_id OLA id + * @param $entities_id entity ID of the ticket + * @param $date begin date of the ticket + * @param $type type of OLA + * + * @since 9.2 (before getDatasToAddOla without type parameter) + * + * @return array of datas to add in ticket + **/ + function getDatasToAddOLA($olas_id, $entities_id, $date, $type) { + + list($dateField, $olaField) = OLA::getFieldNames($type); + + $calendars_id = Entity::getUsedConfig('calendars_id', $entities_id); + $data = []; + + $ola = new OLA(); + if ($ola->getFromDB($olas_id)) { + $ola->setTicketCalendar($calendars_id); + if ($ola->fields['type'] == SLM::TTR) { + $data["olalevels_id_ttr"] = OlaLevel::getFirstOlaLevel($olas_id); + $data['ola_ttr_begin_date'] = $date; + } + // Compute time_to_resolve + $data[$dateField] = $ola->computeDate($date); + $data['ola_waiting_duration'] = 0; + + } else { + $data["olalevels_id_ttr"] = 0; + $data[$olaField] = 0; + $data['ola_waiting_duration'] = 0; + } + return $data; + + } + + + /** + * Delete Level Agreement for the ticket + * + * @since 9.2 + * + * @param string $laType (SLA | OLA) + * @param integer $id the sla/ola id + * @param integer $subtype (SLM::TTR | SLM::TTO) + * @param bool $delete_date (default false) + * + * @return bool + **/ + function deleteLevelAgreement($laType, $la_id, $subtype, $delete_date = false) { + switch ($laType) { + case "SLA": + $prefix = "sla"; + $prefix_ticket = ""; + $level_ticket = new SlaLevel_Ticket(); + break; + case "OLA": + $prefix = "ola"; + $prefix_ticket = "internal_"; + $level_ticket = new OlaLevel_Ticket(); + break; + } + + $input = []; + switch ($subtype) { + case SLM::TTR : + $input[$prefix.'s_id_ttr'] = 0; + if ($delete_date) { + $input[$prefix_ticket.'time_to_resolve'] = ''; + } + break; + + case SLM::TTO : + $input[$prefix.'s_id_tto'] = 0; + if ($delete_date) { + $input[$prefix_ticket.'time_to_own'] = ''; + } + break; + } + + $input[$prefix.'_waiting_duration'] = 0; + $input['id'] = $la_id; + $level_ticket->deleteForTicket($la_id, $subtype); + + return $this->update($input); + } + + + /** + * Is the current user have right to create the current ticket ? + * + * @return boolean + **/ + function canCreateItem() { + + if (!Session::haveAccessToEntity($this->getEntityID())) { + return false; + } + return self::canCreate(); + } + + + /** + * Is the current user have right to update the current ticket ? + * + * @return boolean + **/ + function canUpdateItem() { + if (!$this->checkEntity()) { + return false; + } + + // for all, if no modification in ticket return true + if ($can_requester = $this->canRequesterUpdateItem()) { + return true; + } + + // for self-service only, if modification in ticket, we can't update the ticket + if (Session::getCurrentInterface() == "helpdesk" + && !$can_requester) { + return false; + } + + // if we don't have global UPDATE right, maybe we can own the current ticket + if (!Session::haveRight(self::$rightname, UPDATE) + && !$this->ownItem()) { + //we always return false, as ownItem() = true is managed by below self::canUpdate + return false; + } + + return self::canupdate(); + } + + + /** + * Is the current user is a requester of the current ticket and have the right to update it ? + * + * @return boolean + */ + function canRequesterUpdateItem() { + return ($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) + || $this->fields["users_id_recipient"] === Session::getLoginUserID()) + && $this->fields['status'] != self::SOLVED + && $this->fields['status'] != self::CLOSED + && $this->numberOfFollowups() == 0 + && $this->numberOfTasks() == 0; + } + + /** + * Is the current user have OWN right and is the assigned to the ticket + * + * @return boolean + */ + function ownItem() { + return Session::haveRight(self::$rightname, self::OWN) + && $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()); + } + + + /** + * @since 0.85 + **/ + static function canDelete() { + + // to allow delete for self-service only if no action on the ticket + if (Session::getCurrentInterface() == "helpdesk") { + return Session::haveRight(self::$rightname, CREATE); + } + return Session::haveRight(self::$rightname, DELETE); + } + + /** + * is the current user could reopen the current ticket + * @since 9.2 + * @return boolean + */ + function canReopen() { + return Session::haveRight('followup', CREATE) + && in_array($this->fields["status"], $this->getClosedStatusArray()) + && ($this->isAllowedStatus($this->fields['status'], self::INCOMING) + || $this->isAllowedStatus($this->fields['status'], self::ASSIGNED)); + } + + + /** + * Is the current user have right to delete the current ticket ? + * + * @return boolean + **/ + function canDeleteItem() { + + if (!Session::haveAccessToEntity($this->getEntityID())) { + return false; + } + + // user can delete his ticket if no action on it + if (Session::getCurrentInterface() == "helpdesk" + && (!($this->isUser(CommonITILActor::REQUESTER, Session::getLoginUserID()) + || $this->fields["users_id_recipient"] === Session::getLoginUserID()) + || $this->numberOfFollowups() > 0 + || $this->numberOfTasks() > 0 + || $this->fields["date"] != $this->fields["date_mod"])) { + return false; + } + + return static::canDelete(); + } + + /** + * @see CommonITILObject::getDefaultActor() + **/ + function getDefaultActor($type) { + + if ($type == CommonITILActor::ASSIGN) { + if (Session::haveRight(self::$rightname, self::OWN) + && $_SESSION['glpiset_default_tech']) { + return Session::getLoginUserID(); + } + } + if ($type == CommonITILActor::REQUESTER) { + if (Session::haveRight(self::$rightname, CREATE) + && $_SESSION['glpiset_default_requester']) { + return Session::getLoginUserID(); + } + } + return 0; + } + + + /** + * @see CommonITILObject::getDefaultActorRightSearch() + **/ + function getDefaultActorRightSearch($type) { + + $right = "all"; + if ($type == CommonITILActor::ASSIGN) { + $right = "own_ticket"; + if (!Session::haveRight(self::$rightname, self::ASSIGN)) { + $right = 'id'; + } + } + return $right; + } + + + function pre_deleteItem() { + global $CFG_GLPI; + + if (!isset($this->input['_disablenotif']) && $CFG_GLPI['use_notifications']) { + NotificationEvent::raiseEvent('delete', $this); + } + return true; + } + + + function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) { + + if (static::canView()) { + $nb = 0; + $title = self::getTypeName(Session::getPluralNumber()); + if ($_SESSION['glpishow_count_on_tabs']) { + switch ($item->getType()) { + case 'User' : + $nb = countElementsInTable( + ['glpi_tickets', 'glpi_tickets_users'], [ + 'glpi_tickets_users.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), + 'glpi_tickets_users.users_id' => $item->getID(), + 'glpi_tickets_users.type' => CommonITILActor::REQUESTER + ] + getEntitiesRestrictCriteria(self::getTable()) + ); + $title = __('Created tickets'); + break; + + case 'Supplier' : + $nb = countElementsInTable( + ['glpi_tickets', 'glpi_suppliers_tickets'], [ + 'glpi_suppliers_tickets.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), + 'glpi_suppliers_tickets.suppliers_id' => $item->getID() + ] + getEntitiesRestrictCriteria(self::getTable()) + ); + break; + + case 'SLA' : + $nb = countElementsInTable( + 'glpi_tickets', [ + 'OR' => [ + 'slas_id_tto' => $item->getID(), + 'slas_id_ttr' => $item->getID() + ] + ] + ); + break; + case 'OLA' : + $nb = countElementsInTable( + 'glpi_tickets', [ + 'OR' => [ + 'olas_id_tto' => $item->getID(), + 'olas_id_ttr' => $item->getID() + ] + ] + ); + break; + + case 'Group' : + $nb = countElementsInTable( + ['glpi_tickets', 'glpi_groups_tickets'], [ + 'glpi_groups_tickets.tickets_id' => new \QueryExpression(DB::quoteName('glpi_tickets.id')), + 'glpi_groups_tickets.groups_id' => $item->getID(), + 'glpi_groups_tickets.type' => CommonITILActor::REQUESTER + ] + getEntitiesRestrictCriteria(self::getTable()) + ); + $title = __('Created tickets'); + break; + + default : + // Direct one + $nb = countElementsInTable( + 'glpi_items_tickets', + [ + 'INNER JOIN' => [ + 'glpi_tickets' => [ + 'FKEY' => [ + 'glpi_items_tickets' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ] + ], + 'WHERE' => [ + 'itemtype' => $item->getType(), + 'items_id' => $item->getID(), + 'is_deleted' => 0 + ] + ] + ); + + // Linked items + $linkeditems = $item->getLinkedItems(); + + if (count($linkeditems)) { + foreach ($linkeditems as $type => $tab) { + foreach ($tab as $ID) { + $nb += countElementsInTable( + 'glpi_items_tickets', + [ + 'INNER JOIN' => [ + 'glpi_tickets' => [ + 'FKEY' => [ + 'glpi_items_tickets' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ] + ], + 'WHERE' => [ + 'itemtype' => $type, + 'items_id' => $ID, + 'is_deleted' => 0 + ] + ] + ); + } + } + } + break; + } + + } // glpishow_count_on_tabs + // Not for Ticket class + if ($item->getType() != __CLASS__) { + return self::createTabEntry($title, $nb); + } + } // self::READALL right check + + // Not check self::READALL for Ticket itself + switch ($item->getType()) { + case __CLASS__ : + $ong = []; + + $timeline = $item->getTimelineItems(); + $nb_elements = count($timeline); + $ong[1] = __("Processing ticket")." $nb_elements"; + + // enquete si statut clos + $satisfaction = new TicketSatisfaction(); + if ($satisfaction->getFromDB($item->getID()) + && $item->fields['status'] == self::CLOSED) { + $ong[3] = __('Satisfaction'); + } + if ($item->canView()) { + $ong[4] = __('Statistics'); + } + return $ong; + + // default : + // return _n('Ticket','Tickets', Session::getPluralNumber()); + } + + return ''; + } + + + static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) { + + switch ($item->getType()) { + case __CLASS__ : + switch ($tabnum) { + + case 1 : + echo "
"; + $rand = mt_rand(); + $item->showTimelineForm($rand); + $item->showTimeline($rand); + echo "
"; + break; + + case 3 : + $satisfaction = new TicketSatisfaction(); + if (($item->fields['status'] == self::CLOSED) + && $satisfaction->getFromDB($_GET["id"])) { + + $duration = Entity::getUsedConfig('inquest_duration', $item->fields['entities_id']); + $date2 = strtotime($satisfaction->fields['date_begin']); + if (($duration == 0) + || (strtotime("now") - $date2) <= $duration*DAY_TIMESTAMP) { + $satisfaction->showForm($item); + } else { + echo "

".__('Satisfaction survey expired')."

"; + } + + } else { + echo "

".__('No generated survey')."

"; + } + break; + + case 4 : + $item->showStats(); + break; + } + break; + + case 'Group' : + case 'SLA' : + case 'OLA' : + default : + self::showListForItem($item, $withtemplate); + } + return true; + } + + + function defineTabs($options = []) { + $ong = []; + + $this->defineDefaultObjectTabs($ong, $options); + $this->addStandardTab('TicketValidation', $ong, $options); + $this->addStandardTab('KnowbaseItem_Item', $ong, $options); + $this->addStandardTab('Item_Ticket', $ong, $options); + + if ($this->hasImpactTab()) { + $this->addStandardTab('Impact', $ong, $options); + } + + $this->addStandardTab('TicketCost', $ong, $options); + $this->addStandardTab('Itil_Project', $ong, $options); + $this->addStandardTab('ProjectTask_Ticket', $ong, $options); + $this->addStandardTab('Problem_Ticket', $ong, $options); + $this->addStandardTab('Change_Ticket', $ong, $options); + + $entity = $this->getEntityID(); + if (!(Entity::getUsedConfig('anonymize_support_agents', $entity) + && Session::getCurrentInterface() == 'helpdesk') + ) { + $this->addStandardTab('Log', $ong, $options); + } + + return $ong; + } + + + /** + * Retrieve data of the hardware linked to the ticket if exists + * + * @return void + **/ + function getAdditionalDatas() { + + $this->hardwaredatas = []; + + if (!empty($this->fields["id"])) { + $item_ticket = new Item_Ticket(); + $data = $item_ticket->find(['tickets_id' => $this->fields["id"]]); + + foreach ($data as $val) { + if (!empty($val["itemtype"]) && ($item = getItemForItemtype($val["itemtype"]))) { + if ($item->getFromDB($val["items_id"])) { + $this->hardwaredatas[] = $item; + } + } + } + } + + } + + + function cleanDBonPurge() { + + // OlaLevel_Ticket does not extends CommonDBConnexity + $olaLevel_ticket = new OlaLevel_Ticket(); + $olaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTO); + $olaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTR); + + // SlaLevel_Ticket does not extends CommonDBConnexity + $slaLevel_ticket = new SlaLevel_Ticket(); + $slaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTO); + $slaLevel_ticket->deleteForTicket($this->fields['id'], SLM::TTR); + + // TicketSatisfaction does not extends CommonDBConnexity + $tf = new TicketSatisfaction(); + $tf->deleteByCriteria(['tickets_id' => $this->fields['id']]); + + // CommonITILTask does not extends CommonDBConnexity + $tt = new TicketTask(); + $tt->deleteByCriteria(['tickets_id' => $this->fields['id']]); + + $this->deleteChildrenAndRelationsFromDb( + [ + Change_Ticket::class, + Item_Ticket::class, + Problem_Ticket::class, + ProjectTask_Ticket::class, + TicketCost::class, + Ticket_Ticket::class, + TicketValidation::class, + ] + ); + + parent::cleanDBonPurge(); + + } + + + function prepareInputForUpdate($input) { + global $DB; + + // Get ticket : need for comparison + $this->getFromDB($input['id']); + + // Clean new lines before passing to rules + if (isset($input["content"])) { + $input["content"] = preg_replace('/\\\\r\\\\n/', "\n", $input['content']); + $input["content"] = preg_replace('/\\\\n/', "\n", $input['content']); + } + + // automatic recalculate if user changes urgence or technician change impact + $canpriority = Session::haveRight(self::$rightname, self::CHANGEPRIORITY); + if ((isset($input['urgency']) && $input['urgency'] != $this->fields['urgency']) + || (isset($input['impact']) && $input['impact'] != $this->fields['impact']) + && ($canpriority && !isset($input['priority']) || !$canpriority) + ) { + if (!isset($input['urgency'])) { + $input['urgency'] = $this->fields['urgency']; + } + if (!isset($input['impact'])) { + $input['impact'] = $this->fields['impact']; + } + $input['priority'] = self::computePriority($input['urgency'], $input['impact']); + } + + // Security checks + if (!Session::isCron() + && !Session::haveRight(self::$rightname, self::ASSIGN)) { + if (isset($input["_itil_assign"]) + && isset($input['_itil_assign']['_type']) + && ($input['_itil_assign']['_type'] == 'user')) { + + // must own_ticket to grab a non assign ticket + if ($this->countUsers(CommonITILActor::ASSIGN) == 0) { + if ((!Session::haveRightsOr(self::$rightname, [self::STEAL, self::OWN])) + || !isset($input["_itil_assign"]['users_id']) + || ($input["_itil_assign"]['users_id'] != Session::getLoginUserID())) { + unset($input["_itil_assign"]); + } + + } else { + // Can not steal or can steal and not assign to me + if (!Session::haveRight(self::$rightname, self::STEAL) + || !isset($input["_itil_assign"]['users_id']) + || ($input["_itil_assign"]['users_id'] != Session::getLoginUserID())) { + unset($input["_itil_assign"]); + } + } + } + + // No supplier assign + if (isset($input["_itil_assign"]) + && isset($input['_itil_assign']['_type']) + && ($input['_itil_assign']['_type'] == 'supplier')) { + unset($input["_itil_assign"]); + } + + // No group + if (isset($input["_itil_assign"]) + && isset($input['_itil_assign']['_type']) + && ($input['_itil_assign']['_type'] == 'group')) { + unset($input["_itil_assign"]); + } + } + + //must be handled here for tickets. @see CommonITILObject::prepareInputForUpdate() + $input = $this->handleTemplateFields($input); + if ($input === false) { + return false; + } + + if (isset($input['entities_id'])) { + $entid = $input['entities_id']; + } else { + $entid = $this->fields['entities_id']; + } + + // Process Business Rules + $this->fillInputForBusinessRules($input); + + // Add actors on standard input + $rules = new RuleTicketCollection($entid); + $rule = $rules->getRuleClass(); + $changes = []; + $post_added = []; + $tocleanafterrules = []; + $usertypes = [ + CommonITILActor::ASSIGN => 'assign', + CommonITILActor::REQUESTER => 'requester', + CommonITILActor::OBSERVER => 'observer' + ]; + foreach ($usertypes as $k => $t) { + //handle new input + if (isset($input['_itil_'.$t]) && isset($input['_itil_'.$t]['_type'])) { + $field = $input['_itil_'.$t]['_type'].'s_id'; + if (isset($input['_itil_'.$t][$field]) + && !isset($input[$field.'_'.$t])) { + $input['_'.$field.'_'.$t][] = $input['_itil_'.$t][$field]; + $tocleanafterrules['_'.$field.'_'.$t][] = $input['_itil_'.$t][$field]; + } + } + + //handle existing actors: load all existing actors from ticket + //to make sure business rules will receive all informations, and not just + //what have been entered in the html form. + // + //ref also this actor into $post_added to avoid the filling of $changes + //and triggering businness rules when not needed + $users = $this->getUsers($k); + if (count($users)) { + $field = 'users_id'; + foreach ($users as $user) { + if (!isset($input['_'.$field.'_'.$t]) || !in_array($user[$field], $input['_'.$field.'_'.$t])) { + if (!isset($input['_'.$field.'_'.$t])) { + $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; + } + $input['_'.$field.'_'.$t][] = $user[$field]; + $tocleanafterrules['_'.$field.'_'.$t][] = $user[$field]; + } + } + } + + $groups = $this->getGroups($k); + if (count($groups)) { + $field = 'groups_id'; + foreach ($groups as $group) { + if (!isset($input['_'.$field.'_'.$t]) || !in_array($group[$field], $input['_'.$field.'_'.$t])) { + if (!isset($input['_'.$field.'_'.$t])) { + $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; + } + $input['_'.$field.'_'.$t][] = $group[$field]; + $tocleanafterrules['_'.$field.'_'.$t][] = $group[$field]; + } + } + } + + $suppliers = $this->getSuppliers($k); + if (count($suppliers)) { + $field = 'suppliers_id'; + foreach ($suppliers as $supplier) { + if (!isset($input['_'.$field.'_'.$t]) || !in_array($supplier[$field], $input['_'.$field.'_'.$t])) { + if (!isset($input['_'.$field.'_'.$t])) { + $post_added['_'.$field.'_'.$t] = '_'.$field.'_'.$t; + } + $input['_'.$field.'_'.$t][] = $supplier[$field]; + $tocleanafterrules['_'.$field.'_'.$t][] = $supplier[$field]; + } + } + } + } + + foreach ($rule->getCriterias() as $key => $val) { + if (array_key_exists($key, $input) + && !array_key_exists($key, $post_added)) { + if (!isset($this->fields[$key]) + || ($DB->escape($this->fields[$key]) != $input[$key])) { + $changes[] = $key; + } + } + } + + // Business Rules do not override manual SLA and OLA + $manual_slas_id = []; + $manual_olas_id = []; + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + list($dateField, $slaField) = SLA::getFieldNames($slmType); + if (isset($input[$slaField]) && ($input[$slaField] > 0)) { + $manual_slas_id[$slmType] = $input[$slaField]; + } + + list($dateField, $olaField) = OLA::getFieldNames($slmType); + if (isset($input[$olaField]) && ($input[$olaField] > 0)) { + $manual_olas_id[$slmType] = $input[$olaField]; + } + } + + // Only process rules on changes + if (count($changes)) { + if (in_array('_users_id_requester', $changes)) { + // If _users_id_requester changed : set users_locations + $user = new User(); + if (isset($input["_itil_requester"]["users_id"]) + && $user->getFromDB($input["_itil_requester"]["users_id"])) { + $input['users_locations'] = $user->fields['locations_id']; + $changes[] = 'users_locations'; + } + // If _users_id_requester changed : add _groups_id_of_requester to changes + $changes[] = '_groups_id_of_requester'; + } + + $input = $rules->processAllRules($input, + $input, + ['recursive' => true, + 'entities_id' => $entid], + ['condition' => RuleTicket::ONUPDATE, + 'only_criteria' => $changes]); + $input = Toolbox::stripslashes_deep($input); + } + + // Clean actors fields added for rules + foreach ($tocleanafterrules as $key => $val) { + if ($input[$key] == $val) { + unset($input[$key]); + } + } + + // Manage fields from auto update or rules : map rule actions to standard additional ones + $usertypes = ['assign', 'requester', 'observer']; + $actortypes = ['user','group','supplier']; + foreach ($usertypes as $t) { + foreach ($actortypes as $a) { + if (isset($input['_'.$a.'s_id_'.$t])) { + switch ($a) { + case 'user' : + $additionalfield = '_additional_'.$t.'s'; + $input[$additionalfield][] = ['users_id' => $input['_'.$a.'s_id_'.$t]]; + break; + + default : + $additionalfield = '_additional_'.$a.'s_'.$t.'s'; + $input[$additionalfield][] = $input['_'.$a.'s_id_'.$t]; + break; + } + } + } + } + + if (isset($input['_link'])) { + $ticket_ticket = new Ticket_Ticket(); + if (!empty($input['_link']['tickets_id_2'])) { + if ($ticket_ticket->can(-1, CREATE, $input['_link'])) { + if ($ticket_ticket->add($input['_link'])) { + $input['_forcenotif'] = true; + } + } else { + Session::addMessageAfterRedirect(__('Unknown ticket'), false, ERROR); + } + } + } + + // SLA / OLA affect by rules : reset time_to_resolve / internal_time_to_resolve + // Manual SLA / OLA defined : reset time_to_resolve / internal_time_to_resolve + // No manual SLA / OLA and due date defined : reset auto SLA / OLA + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + $this->slaAffect($slmType, $input, $manual_slas_id); + $this->olaAffect($slmType, $input, $manual_olas_id); + } + + if (isset($input['content'])) { + if (isset($input['_filename']) || isset($input['_content'])) { + $input['_disablenotif'] = true; + } else { + $input['_donotadddocs'] = true; + } + } + + $input = parent::prepareInputForUpdate($input); + return $input; + } + + + /** + * SLA affect by rules : reset time_to_resolve and time_to_own + * Manual SLA defined : reset time_to_resolve and time_to_own + * No manual SLA and due date defined : reset auto SLA + * + * @since 9.1 + * + * @param $type + * @param $input + * @param $manual_slas_id + */ + function slaAffect($type, &$input, $manual_slas_id) { + + list($dateField, $slaField) = SLA::getFieldNames($type); + + // Restore slas + if (isset($manual_slas_id[$type]) + && !isset($input['_'.$slaField])) { + $input[$slaField] = $manual_slas_id[$type]; + } + + // Ticket update + if (isset($this->fields['id']) && $this->fields['id'] > 0) { + if (!isset($manual_slas_id[$type]) + && isset($input[$slaField]) && ($input[$slaField] > 0) + && ($input[$slaField] != $this->fields[$slaField])) { + + if (isset($input[$dateField])) { + // Unset due date + unset($input[$dateField]); + } + } + + if (isset($input[$slaField]) && ($input[$slaField] > 0) + && ($input[$slaField] != $this->fields[$slaField])) { + + $date = $this->fields['date']; + /// Use updated date if also done + if (isset($input["date"])) { + $date = $input["date"]; + } + // Get datas to initialize SLA and set it + $sla_data = $this->getDatasToAddSLA($input[$slaField], $this->fields['entities_id'], + $date, $type); + if (count($sla_data)) { + foreach ($sla_data as $key => $val) { + $input[$key] = $val; + } + } + } + } else { // Ticket add + if (!isset($manual_slas_id[$type]) + && isset($input[$dateField]) && ($input[$dateField] != 'NULL')) { + // Valid due date + if ($input[$dateField] >= $input['date']) { + if (isset($input[$slaField])) { + unset($input[$slaField]); + } + } else { + // Unset due date + unset($input[$dateField]); + } + } + + if (isset($input[$slaField]) && ($input[$slaField] > 0)) { + // Get datas to initialize SLA and set it + $sla_data = $this->getDatasToAddSLA($input[$slaField], $input['entities_id'], + $input['date'], $type); + if (count($sla_data)) { + foreach ($sla_data as $key => $val) { + $input[$key] = $val; + } + } + } + } + } + + /** + * OLA affect by rules : reset internal_time_to_resolve and internal_time_to_own + * Manual OLA defined : reset internal_time_to_resolve and internal_time_to_own + * No manual OLA and due date defined : reset auto OLA + * + * @since 9.1 + * + * @param $type + * @param $input + * @param $manual_olas_id + */ + function olaAffect($type, &$input, $manual_olas_id) { + + list($dateField, $olaField) = OLA::getFieldNames($type); + + // Restore olas + if (isset($manual_olas_id[$type]) + && !isset($input['_'.$olaField])) { + $input[$olaField] = $manual_olas_id[$type]; + } + + // Ticket update + if (isset($this->fields['id']) && $this->fields['id'] > 0) { + if (!isset($manual_olas_id[$type]) + && isset($input[$olaField]) && ($input[$olaField] > 0) + && ($input[$olaField] != $this->fields[$olaField])) { + + if (isset($input[$dateField])) { + // Unset due date + unset($input[$dateField]); + } + } + + if (isset($input[$olaField]) && ($input[$olaField] > 0) + && ($input[$olaField] != $this->fields[$olaField] + || isset($input['_'.$olaField]))) { + + $date = date('Y-m-d H:i:s'); + + // Get datas to initialize OLA and set it + $ola_data = $this->getDatasToAddOLA($input[$olaField], $this->fields['entities_id'], + $date, $type); + if (count($ola_data)) { + foreach ($ola_data as $key => $val) { + $input[$key] = $val; + } + } + } + } else { // Ticket add + if (!isset($manual_olas_id[$type]) + && isset($input[$dateField]) && ($input[$dateField] != 'NULL')) { + // Valid due date + if ($input[$dateField] >= $input['date']) { + if (isset($input[$olaField])) { + unset($input[$olaField]); + } + } else { + // Unset due date + unset($input[$dateField]); + } + } + + if (isset($input[$olaField]) && ($input[$olaField] > 0)) { + // Get datas to initialize OLA and set it + $ola_data = $this->getDatasToAddOLA($input[$olaField], $input['entities_id'], + $input['date'], $type); + if (count($ola_data)) { + foreach ($ola_data as $key => $val) { + $input[$key] = $val; + } + } + } + } + } + + + /** + * Manage SLA level escalation + * + * @since 9.1 + * + * @param $slas_id + **/ + function manageSlaLevel($slas_id) { + + $calendars_id = Entity::getUsedConfig('calendars_id', $this->fields['entities_id']); + // Add first level in working table + $slalevels_id = SlaLevel::getFirstSlaLevel($slas_id); + + $sla = new SLA(); + if ($sla->getFromDB($slas_id)) { + $sla->setTicketCalendar($calendars_id); + $sla->addLevelToDo($this, $slalevels_id); + } + SlaLevel_Ticket::replayForTicket($this->getID(), $sla->getField('type')); + } + + /** + * Manage OLA level escalation + * + * @since 9.1 + * + * @param $slas_id + **/ + function manageOlaLevel($slas_id) { + + $calendars_id = Entity::getUsedConfig('calendars_id', $this->fields['entities_id']); + // Add first level in working table + $olalevels_id = OlaLevel::getFirstOlaLevel($slas_id); + + $ola = new OLA(); + if ($ola->getFromDB($slas_id)) { + $ola->setTicketCalendar($calendars_id); + $ola->addLevelToDo($this, $olalevels_id); + } + OlaLevel_Ticket::replayForTicket($this->getID(), $ola->getField('type')); + } + + + function pre_updateInDB() { + + if (!$this->isTakeIntoAccountComputationBlocked($this->input) + && !$this->isAlreadyTakenIntoAccount() + && $this->canTakeIntoAccount() + && !$this->isNew() + ) { + $this->updates[] = "takeintoaccount_delay_stat"; + $this->fields['takeintoaccount_delay_stat'] = $this->computeTakeIntoAccountDelayStat(); + } + + parent::pre_updateInDB(); + + } + + + /** + * Compute take into account stat of the current ticket + **/ + function computeTakeIntoAccountDelayStat() { + + if (isset($this->fields['id']) + && !empty($this->fields['date'])) { + $calendars_id = $this->getCalendar(); + $calendar = new Calendar(); + + // Using calendar + if (($calendars_id > 0) && $calendar->getFromDB($calendars_id)) { + return max(1, $calendar->getActiveTimeBetween($this->fields['date'], + $_SESSION["glpi_currenttime"])); + } + // Not calendar defined + return max(1, strtotime($_SESSION["glpi_currenttime"])-strtotime($this->fields['date'])); + } + return 0; + } + + + function post_updateItem($history = 1) { + global $CFG_GLPI; + + parent::post_updateItem($history); + + //Action for send_validation rule : do validation before clean + $this->manageValidationAdd($this->input); + + // Put same status on duplicated tickets when solving or closing (autoclose on solve) + if (isset($this->input['status']) + && in_array('status', $this->updates) + && (in_array($this->input['status'], $this->getSolvedStatusArray()) + || in_array($this->input['status'], $this->getClosedStatusArray()))) { + Ticket_Ticket::manageLinkedTicketsOnSolved($this->getID()); + } + + $donotif = count($this->updates); + + if (isset($this->input['_forcenotif'])) { + $donotif = true; + } + + // Manage SLA / OLA Level : add actions + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + list($dateField, $slaField) = SLA::getFieldNames($slmType); + if (in_array($slaField, $this->updates) + && ($this->fields[$slaField] > 0)) { + $this->manageSlaLevel($this->fields[$slaField]); + } + + list($dateField, $olaField) = OLA::getFieldNames($slmType); + if (in_array($olaField, $this->updates) + && ($this->fields[$olaField] > 0)) { + $this->manageOlaLevel($this->fields[$olaField]); + } + } + + if (count($this->updates)) { + // Update Ticket Tco + if (in_array("actiontime", $this->updates) + || in_array("cost_time", $this->updates) + || in_array("cost_fixed", $this->updates) + || in_array("cost_material", $this->updates)) { + + if (!empty($this->input["items_id"])) { + foreach ($this->input["items_id"] as $itemtype => $items) { + foreach ($items as $items_id) { + if ($itemtype && ($item = getItemForItemtype($itemtype))) { + if ($item->getFromDB($items_id)) { + $newinput = []; + $newinput['id'] = $items_id; + $newinput['ticket_tco'] = self::computeTco($item); + $item->update($newinput); + } + } + } + } + } + } + + $donotif = true; + } + + if (isset($this->input['_disablenotif'])) { + $donotif = false; + } + + if ($donotif && $CFG_GLPI["use_notifications"]) { + $mailtype = "update"; + + if (isset($this->input["status"]) + && $this->input["status"] + && in_array("status", $this->updates) + && in_array($this->input["status"], $this->getSolvedStatusArray())) { + + $mailtype = "solved"; + } + + if (isset($this->input["status"]) + && $this->input["status"] + && in_array("status", $this->updates) + && in_array($this->input["status"], $this->getClosedStatusArray())) { + + $mailtype = "closed"; + } + // to know if a solution is approved or not + if ((isset($this->input['solvedate']) && ($this->input['solvedate'] == 'NULL') + && isset($this->oldvalues['solvedate']) && $this->oldvalues['solvedate']) + && (isset($this->input['status']) + && ($this->input['status'] != $this->oldvalues['status']) + && ($this->oldvalues['status'] == self::SOLVED))) { + + $mailtype = "rejectsolution"; + } + + // Read again ticket to be sure that all data are up to date + $this->getFromDB($this->fields['id']); + NotificationEvent::raiseEvent($mailtype, $this); + } + + // inquest created immediatly if delay = O + $inquest = new TicketSatisfaction(); + $rate = Entity::getUsedConfig('inquest_config', $this->fields['entities_id'], + 'inquest_rate'); + $delay = Entity::getUsedConfig('inquest_config', $this->fields['entities_id'], + 'inquest_delay'); + $type = Entity::getUsedConfig('inquest_config', $this->fields['entities_id']); + $max_closedate = $this->fields['closedate']; + + if (in_array("status", $this->updates) + && in_array($this->input["status"], $this->getClosedStatusArray()) + && ($delay == 0) + && ($rate > 0) + && (mt_rand(1, 100) <= $rate)) { + + // For reopened ticket + if ($inquest->getFromDB($this->fields['id'])) { + $resp = $inquest->fields; + $inquest->delete($resp); + } + + $inquest->add( + [ + 'tickets_id' => $this->fields['id'], + 'date_begin' => $_SESSION["glpi_currenttime"], + 'entities_id' => $this->fields['entities_id'], + 'type' => $type, + 'max_closedate' => $max_closedate, + ] + ); + } + } + + + function prepareInputForAdd($input) { + // Standard clean datas + $input = parent::prepareInputForAdd($input); + if ($input === false) { + return false; + } + + if (!isset($input["requesttypes_id"])) { + $input["requesttypes_id"] = RequestType::getDefault('helpdesk'); + } + + if (!isset($input['global_validation'])) { + $input['global_validation'] = CommonITILValidation::NONE; + } + + // Set additional default dropdown + $dropdown_fields = ['users_locations', 'items_locations']; + foreach ($dropdown_fields as $field) { + if (!isset($input[$field])) { + $input[$field] = 0; + } + } + if (!isset($input['itemtype']) || !isset($input['items_id']) || !($input['items_id'] > 0)) { + $input['itemtype'] = ''; + } + + // Get first item location + $item = null; + if (isset($input["items_id"]) + && is_array($input["items_id"]) + && (count($input["items_id"]) > 0)) { + $infocom = new Infocom(); + foreach ($input["items_id"] as $itemtype => $items) { + foreach ($items as $items_id) { + if ($item = getItemForItemtype($itemtype)) { + $item->getFromDB($items_id); + $input['items_states'] = $item->fields['states_id']; + $input['items_locations'] = $item->fields['locations_id']; + if ($infocom->getFromDBforDevice($itemtype, $items_id)) { + $input['items_businesscriticities'] + = Dropdown::getDropdownName('glpi_businesscriticities', + $infocom->fields['businesscriticities_id']); + } + if (isset($item->fields['groups_id'])) { + $input['items_groups'] = $item->fields['groups_id']; + + } + break(2); + } + } + } + } + + // Business Rules do not override manual SLA and OLA + $manual_slas_id = []; + $manual_olas_id = []; + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + list($dateField, $slaField) = SLA::getFieldNames($slmType); + if (isset($input[$slaField]) && ($input[$slaField] > 0)) { + $manual_slas_id[$slmType] = $input[$slaField]; + } + list($dateField, $olaField) = OLA::getFieldNames($slmType); + if (isset($input[$olaField]) && ($input[$olaField] > 0)) { + $manual_olas_id[$slmType] = $input[$olaField]; + } + } + + // fill auto-assign when no tech defined (only for tech) + if (!isset($input['_auto_import']) + && isset($_SESSION['glpiset_default_tech']) && $_SESSION['glpiset_default_tech'] + && Session::getCurrentInterface() == 'central' + && (!isset($input['_users_id_assign']) || $input['_users_id_assign'] == 0) + && Session::haveRight("ticket", Ticket::OWN) + ) { + $input['_users_id_assign'] = Session::getLoginUserID(); + } + + // Process Business Rules + $this->fillInputForBusinessRules($input); + + $rules = new RuleTicketCollection($input['entities_id']); + + // Set unset variables with are needed + $tmprequester = 0; + $user = new User(); + if (isset($input["_users_id_requester"])) { + if (!is_array($input["_users_id_requester"]) + && $user->getFromDB($input["_users_id_requester"])) { + $input['users_locations'] = $user->fields['locations_id']; + $input['users_default_groups'] = $user->fields['groups_id']; + $tmprequester = $input["_users_id_requester"]; + } else if (is_array($input["_users_id_requester"]) && ($user_id = reset($input["_users_id_requester"])) !== false) { + if ($user->getFromDB($user_id)) { + $input['users_locations'] = $user->fields['locations_id']; + $input['users_default_groups'] = $user->fields['groups_id']; + } + } + } + + // Clean new lines before passing to rules + if (isset($input["content"])) { + $input["content"] = preg_replace('/\\\\r\\\\n/', "\\n", $input['content']); + $input["content"] = preg_replace('/\\\\n/', "\\n", $input['content']); + } + + $input = $rules->processAllRules($input, + $input, + ['recursive' => true], + ['condition' => RuleTicket::ONADD]); + $input = Toolbox::stripslashes_deep($input); + + // Recompute default values based on values computed by rules + $input = $this->computeDefaultValuesForAdd($input); + + if (isset($input['_users_id_requester']) + && !is_array($input['_users_id_requester']) + && ($input['_users_id_requester'] != $tmprequester)) { + // if requester set by rule, clear address from mailcollector + unset($input['_users_id_requester_notif']); + } + if (isset($input['_users_id_requester_notif']) + && isset($input['_users_id_requester_notif']['alternative_email']) + && is_array($input['_users_id_requester_notif']['alternative_email'])) { + foreach ($input['_users_id_requester_notif']['alternative_email'] as $email) { + if ($email && !NotificationMailing::isUserAddressValid($email)) { + Session::addMessageAfterRedirect( + sprintf(__('Invalid email address %s'), $email), + false, + ERROR + ); + return false; + } + } + } + + // Manage auto assign + $auto_assign_mode = Entity::getUsedConfig('auto_assign_mode', $input['entities_id']); + + switch ($auto_assign_mode) { + case Entity::CONFIG_NEVER : + break; + + case Entity::AUTO_ASSIGN_HARDWARE_CATEGORY : + if ($item != null) { + // Auto assign tech from item + if ((!isset($input['_users_id_assign']) || ($input['_users_id_assign'] == 0)) + && $item->isField('users_id_tech')) { + $input['_users_id_assign'] = $item->getField('users_id_tech'); + } + // Auto assign group from item + if ((!isset($input['_groups_id_assign']) || ($input['_groups_id_assign'] == 0)) + && $item->isField('groups_id_tech')) { + $input['_groups_id_assign'] = $item->getField('groups_id_tech'); + } + } + // Auto assign tech/group from Category + if (($input['itilcategories_id'] > 0) + && ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) + || (!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']))) { + + $cat = new ITILCategory(); + $cat->getFromDB($input['itilcategories_id']); + if ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) + && $cat->isField('users_id')) { + $input['_users_id_assign'] = $cat->getField('users_id'); + } + if ((!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']) + && $cat->isField('groups_id')) { + $input['_groups_id_assign'] = $cat->getField('groups_id'); + } + } + break; + + case Entity::AUTO_ASSIGN_CATEGORY_HARDWARE : + // Auto assign tech/group from Category + if (($input['itilcategories_id'] > 0) + && ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) + || (!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']))) { + + $cat = new ITILCategory(); + $cat->getFromDB($input['itilcategories_id']); + if ((!isset($input['_users_id_assign']) || !$input['_users_id_assign']) + && $cat->isField('users_id')) { + $input['_users_id_assign'] = $cat->getField('users_id'); + } + if ((!isset($input['_groups_id_assign']) || !$input['_groups_id_assign']) + && $cat->isField('groups_id')) { + $input['_groups_id_assign'] = $cat->getField('groups_id'); + } + } + if ($item != null) { + // Auto assign tech from item + if ((!isset($input['_users_id_assign']) || ($input['_users_id_assign'] == 0)) + && $item->isField('users_id_tech')) { + $input['_users_id_assign'] = $item->getField('users_id_tech'); + } + // Auto assign group from item + if ((!isset($input['_groups_id_assign']) || ($input['_groups_id_assign'] == 0)) + && $item->isField('groups_id_tech')) { + $input['_groups_id_assign'] = $item->getField('groups_id_tech'); + } + } + break; + } + + // Replay setting auto assign if set in rules engine or by auto_assign_mode + // Do not force status if status has been set by rules + if (((isset($input["_users_id_assign"]) + && ((!is_array($input['_users_id_assign']) && $input["_users_id_assign"] > 0) + || is_array($input['_users_id_assign']) && count($input['_users_id_assign']) > 0)) + || (isset($input["_groups_id_assign"]) + && ((!is_array($input['_groups_id_assign']) && $input["_groups_id_assign"] > 0) + || is_array($input['_groups_id_assign']) && count($input['_groups_id_assign']) > 0)) + || (isset($input["_suppliers_id_assign"]) + && ((!is_array($input['_suppliers_id_assign']) && $input["_suppliers_id_assign"] > 0) + || is_array($input['_suppliers_id_assign']) && count($input['_suppliers_id_assign']) > 0))) + && (in_array($input['status'], $this->getNewStatusArray())) + && !$this->isStatusComputationBlocked($input)) { + $input["status"] = self::ASSIGNED; + } + + // Manage SLA / OLA asignment + // Manual SLA / OLA defined : reset due date + // No manual SLA / OLA and due date defined : reset auto SLA / OLA + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + $this->slaAffect($slmType, $input, $manual_slas_id); + $this->olaAffect($slmType, $input, $manual_olas_id); + } + + // auto set type if not set + if (!isset($input["type"])) { + $input['type'] = Entity::getUsedConfig('tickettype', $input['entities_id'], '', + Ticket::INCIDENT_TYPE); + } + + return $input; + } + + + function post_addItem() { + global $CFG_GLPI; + + $this->manageValidationAdd($this->input); + + // Log this event + $username = 'anonymous'; + if (isset($_SESSION["glpiname"])) { + $username = $_SESSION["glpiname"]; + } + Event::log($this->fields['id'], "ticket", 4, "tracking", + sprintf(__('%1$s adds the item %2$s'), $username, + $this->fields['id'])); + + if (isset($this->input["_followup"]) + && is_array($this->input["_followup"]) + && (strlen($this->input["_followup"]['content']) > 0)) { + + $fup = new ITILFollowup(); + $type = "new"; + if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { + $type = "solved"; + } + $toadd = ['type' => $type, + 'items_id' => $this->fields['id'], + 'itemtype' => 'Ticket']; + + if (isset($this->input["_followup"]['content']) + && (strlen($this->input["_followup"]['content']) > 0)) { + $toadd["content"] = $this->input["_followup"]['content']; + } + + if (isset($this->input["_followup"]['is_private'])) { + $toadd["is_private"] = $this->input["_followup"]['is_private']; + } + // $toadd['_no_notif'] = true; + + $fup->add($toadd); + } + + if ((isset($this->input["plan"]) && count($this->input["plan"])) + || (isset($this->input["actiontime"]) && ($this->input["actiontime"] > 0))) { + + $task = new TicketTask(); + $type = "new"; + if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { + $type = "solved"; + } + $toadd = ["type" => $type, + "tickets_id" => $this->fields['id'], + "actiontime" => $this->input["actiontime"]]; + + if (isset($this->input["plan"]) && count($this->input["plan"])) { + $toadd["plan"] = $this->input["plan"]; + } + + if (isset($_SESSION['glpitask_private'])) { + $toadd['is_private'] = $_SESSION['glpitask_private']; + } + + // $toadd['_no_notif'] = true; + + $task->add($toadd); + } + + $ticket_ticket = new Ticket_Ticket(); + + // From interface + if (isset($this->input['_link'])) { + $this->input['_link']['tickets_id_1'] = $this->fields['id']; + // message if ticket's ID doesn't exist + if (!empty($this->input['_link']['tickets_id_2'])) { + if ($ticket_ticket->can(-1, CREATE, $this->input['_link'])) { + $ticket_ticket->add($this->input['_link']); + } else { + Session::addMessageAfterRedirect(__('Unknown ticket'), false, ERROR); + } + } + } + + // From mailcollector : do not check rights + if (isset($this->input["_linkedto"])) { + $input2 = [ + 'tickets_id_1' => $this->fields['id'], + 'tickets_id_2' => $this->input["_linkedto"], + 'link' => Ticket_Ticket::LINK_TO, + ]; + $ticket_ticket->add($input2); + } + + // Manage SLA / OLA Level : add actions + foreach ([SLM::TTR, SLM::TTO] as $slmType) { + list($dateField, $slaField) = SLA::getFieldNames($slmType); + if (isset($this->input[$slaField]) && ($this->input[$slaField] > 0)) { + $this->manageSlaLevel($this->input[$slaField]); + } + list($dateField, $olaField) = OLA::getFieldNames($slmType); + if (isset($this->input[$olaField]) && ($this->input[$olaField] > 0)) { + $this->manageOlaLevel($this->input[$olaField]); + } + } + + // Add project task link if needed + if (isset($this->input['_projecttasks_id'])) { + $projecttask = new ProjectTask(); + if ($projecttask->getFromDB($this->input['_projecttasks_id'])) { + $pt = new ProjectTask_Ticket(); + $pt->add(['projecttasks_id' => $this->input['_projecttasks_id'], + 'tickets_id' => $this->fields['id'], + /*'_no_notif' => true*/]); + } + } + + if (isset($this->input['_promoted_fup_id']) && $this->input['_promoted_fup_id'] > 0) { + $fup = new ITILFollowup(); + $fup->getFromDB($this->input['_promoted_fup_id']); + $fup->update([ + 'id' => $this->input['_promoted_fup_id'], + 'sourceof_items_id' => $this->getID() + ]); + Event::log($this->getID(), "ticket", 4, "tracking", + sprintf(__('%s promotes a followup from ticket %s'), $_SESSION["glpiname"], $fup->fields['items_id'])); + } + + if (!empty($this->input['items_id'])) { + $item_ticket = new Item_Ticket(); + foreach ($this->input['items_id'] as $itemtype => $items) { + foreach ($items as $items_id) { + $item_ticket->add(['items_id' => $items_id, + 'itemtype' => $itemtype, + 'tickets_id' => $this->fields['id'], + '_disablenotif' => true]); + } + } + } + + parent::post_addItem(); + + // Processing Email + if (!isset($this->input['_disablenotif']) && $CFG_GLPI["use_notifications"]) { + // Clean reload of the ticket + $this->getFromDB($this->fields['id']); + + $type = "new"; + if (isset($this->fields["status"]) && ($this->fields["status"] == self::SOLVED)) { + $type = "solved"; + } + NotificationEvent::raiseEvent($type, $this); + } + + if (isset($_SESSION['glpiis_ids_visible']) && !$_SESSION['glpiis_ids_visible']) { + Session::addMessageAfterRedirect(sprintf(__('%1$s (%2$s)'), + __('Your ticket has been registered, its treatment is in progress.'), + sprintf(__('%1$s: %2$s'), __('Ticket'), + "". + $this->fields['id'].""))); + } + + } + + + /** + * Manage Validation add from input + * + * @since 0.85 + * + * @param $input array : input array + * + * @return boolean + **/ + function manageValidationAdd($input) { + + //Action for send_validation rule + if (isset($input["_add_validation"])) { + if (isset($input['entities_id'])) { + $entid = $input['entities_id']; + } else if (isset($this->fields['entities_id'])) { + $entid = $this->fields['entities_id']; + } else { + return false; + } + + $validations_to_send = []; + if (!is_array($input["_add_validation"])) { + $input["_add_validation"] = [$input["_add_validation"]]; + } + + foreach ($input["_add_validation"] as $key => $validation) { + switch ($validation) { + case 'requester_supervisor' : + if (isset($input['_groups_id_requester']) + && $input['_groups_id_requester']) { + $users = Group_User::getGroupUsers( + $input['_groups_id_requester'], + ['is_manager' => 1] + ); + foreach ($users as $data) { + $validations_to_send[] = $data['id']; + } + } + // Add to already set groups + foreach ($this->getGroups(CommonITILActor::REQUESTER) as $d) { + $users = Group_User::getGroupUsers( + $d['groups_id'], + ['is_manager' => 1] + ); + foreach ($users as $data) { + $validations_to_send[] = $data['id']; + } + } + break; + + case 'assign_supervisor' : + if (isset($input['_groups_id_assign']) + && $input['_groups_id_assign']) { + $users = Group_User::getGroupUsers( + $input['_groups_id_assign'], + ['is_manager' => 1] + ); + foreach ($users as $data) { + $validations_to_send[] = $data['id']; + } + } + foreach ($this->getGroups(CommonITILActor::ASSIGN) as $d) { + $users = Group_User::getGroupUsers( + $d['groups_id'], + ['is_manager' => 1] + ); + foreach ($users as $data) { + $validations_to_send[] = $data['id']; + } + } + break; + + case 'requester_responsible': + if (isset($input['_users_id_requester'])) { + if (is_array($input['_users_id_requester'])) { + foreach ($input['_users_id_requester'] as $users_id) { + $user = new User(); + if ($user->getFromDB($users_id)) { + $validations_to_send[] = $user->getField('users_id_supervisor'); + } + } + } else { + $user = new User(); + if ($user->getFromDB($input['_users_id_requester'])) { + $validations_to_send[] = $user->getField('users_id_supervisor'); + } + } + } + break; + + default : + // Group case from rules + if ($key === 'group') { + foreach ($validation as $groups_id) { + $validation_right = 'validate_incident'; + if (isset($input['type']) + && ($input['type'] == Ticket::DEMAND_TYPE)) { + $validation_right = 'validate_request'; + } + $opt = ['groups_id' => $groups_id, + 'right' => $validation_right, + 'entity' => $entid]; + + $data_users = TicketValidation::getGroupUserHaveRights($opt); + + foreach ($data_users as $user) { + $validations_to_send[] = $user['id']; + } + } + } else { + $validations_to_send[] = $validation; + } + } + + } + + // Validation user added on ticket form + if (isset($input['users_id_validate'])) { + if (array_key_exists('groups_id', $input['users_id_validate'])) { + foreach ($input['users_id_validate'] as $key => $validation_to_add) { + if (is_numeric($key)) { + $validations_to_send[] = $validation_to_add; + } + } + } else { + foreach ($input['users_id_validate'] as $key => $validation_to_add) { + if (is_numeric($key)) { + $validations_to_send[] = $validation_to_add; + } + } + } + } + + // Keep only one + $validations_to_send = array_unique($validations_to_send); + + $validation = new TicketValidation(); + + if (count($validations_to_send)) { + $values = []; + $values['tickets_id'] = $this->fields['id']; + if (isset($input['id']) && $input['id'] != $this->fields['id']) { + $values['_ticket_add'] = true; + } + + // to know update by rules + if (isset($input["_rule_process"])) { + $values['_rule_process'] = $input["_rule_process"]; + } + // if auto_import, tranfert it for validation + if (isset($input['_auto_import'])) { + $values['_auto_import'] = $input['_auto_import']; + } + + // Cron or rule process of hability to do + if (Session::isCron() + || isset($input["_auto_import"]) + || isset($input["_rule_process"]) + || $validation->can(-1, CREATE, $values)) { // cron or allowed user + + $add_done = false; + foreach ($validations_to_send as $user) { + // Do not auto add twice same validation + if (!TicketValidation::alreadyExists($values['tickets_id'], $user)) { + $values["users_id_validate"] = $user; + if ($validation->add($values)) { + $add_done = true; + } + } + } + if ($add_done) { + Event::log($this->fields['id'], "ticket", 4, "tracking", + sprintf(__('%1$s updates the item %2$s'), $_SESSION["glpiname"], + $this->fields['id'])); + } + } + } + } + return true; + } + + + /** + * Get active or solved tickets for an hardware last X days + * + * @since 0.83 + * + * @param $itemtype string Item type + * @param $items_id integer ID of the Item + * @param $days integer day number + * + * @return array + **/ + function getActiveOrSolvedLastDaysTicketsForItem($itemtype, $items_id, $days) { + global $DB; + + $result = []; + + $iterator = $DB->request([ + 'FROM' => $this->getTable(), + 'LEFT JOIN' => [ + 'glpi_items_tickets' => [ + 'ON' => [ + 'glpi_items_tickets' => 'tickets_id', + $this->getTable() => 'id' + ] + ] + ], + 'WHERE' => [ + 'glpi_items_tickets.items_id' => $items_id, + 'glpi_items_tickets.itemtype' => $itemtype, + 'OR' => [ + [ + 'NOT' => [ + $this->getTable() . '.status' => array_merge( + $this->getClosedStatusArray(), + $this->getSolvedStatusArray() + ) + ] + ], + [ + 'NOT' => [$this->getTable() . '.solvedate' => null], + new \QueryExpression( + "ADDDATE(" . $DB->quoteName($this->getTable()) . + ".".$DB->quoteName('solvedate').", INTERVAL $days DAY) > NOW()" + ) + ] + ] + ] + ]); + + while ($tick = $iterator->next()) { + $result[$tick['id']] = $tick['name']; + } + + return $result; + } + + + /** + * Count active tickets for an hardware + * + * @since 0.83 + * + * @param $itemtype string Item type + * @param $items_id integer ID of the Item + * + * @return integer + **/ + function countActiveTicketsForItem($itemtype, $items_id) { + global $DB; + + $result = $DB->request([ + 'COUNT' => 'cpt', + 'FROM' => $this->getTable(), + 'LEFT JOIN' => [ + 'glpi_items_tickets' => [ + 'ON' => [ + 'glpi_items_tickets' => 'tickets_id', + $this->getTable() => 'id' + ] + ] + ], + 'WHERE' => [ + 'glpi_items_tickets.itemtype' => $itemtype, + 'glpi_items_tickets.items_id' => $items_id, + 'NOT' => [ + $this->getTable() . '.status' => array_merge( + $this->getSolvedStatusArray(), + $this->getClosedStatusArray() + ) + ] + ] + ])->next(); + return $result['cpt']; + } + + /** + * Get active tickets for an item + * + * @since 9.5 + * + * @param string $itemtype Item type + * @param integer $items_id ID of the Item + * @param string $type Type of the tickets (incident or request) + * + * @return DBmysqlIterator + */ + public function getActiveTicketsForItem($itemtype, $items_id, $type) { + global $DB; + + return $DB->request([ + 'SELECT' => [ + $this->getTable() . '.id', + $this->getTable() . '.name', + $this->getTable() . '.priority', + ], + 'FROM' => $this->getTable(), + 'LEFT JOIN' => [ + 'glpi_items_tickets' => [ + 'ON' => [ + 'glpi_items_tickets' => 'tickets_id', + $this->getTable() => 'id' + ] + ] + ], + 'WHERE' => [ + 'glpi_items_tickets.itemtype' => $itemtype, + 'glpi_items_tickets.items_id' => $items_id, + $this->getTable() . '.is_deleted' => 0, + $this->getTable() . '.type' => $type, + 'NOT' => [ + $this->getTable() . '.status' => array_merge( + $this->getSolvedStatusArray(), + $this->getClosedStatusArray() + ) + ] + ] + ]); + } + + /** + * Count solved tickets for an hardware last X days + * + * @since 0.83 + * + * @param $itemtype string Item type + * @param $items_id integer ID of the Item + * @param $days integer day number + * + * @return integer + **/ + function countSolvedTicketsForItemLastDays($itemtype, $items_id, $days) { + global $DB; + + $result = $DB->request([ + 'COUNT' => 'cpt', + 'FROM' => $this->getTable(), + 'LEFT JOIN' => [ + 'glpi_items_tickets' => [ + 'ON' => [ + 'glpi_items_tickets' => 'tickets_id', + $this->getTable() => 'id' + ] + ] + ], + 'WHERE' => [ + 'glpi_items_tickets.itemtype' => $itemtype, + 'glpi_items_tickets.items_id' => $items_id, + $this->getTable() . '.status' => array_merge( + $this->getSolvedStatusArray(), + $this->getClosedStatusArray() + ), + new \QueryExpression( + "ADDDATE(".$DB->quoteName($this->getTable().".solvedate").", INTERVAL $days DAY) > NOW()" + ), + 'NOT' => [ + $this->getTable() . '.solvedate' => null + ] + ] + ])->next(); + return $result['cpt']; + } + + + /** + * Update date mod of the ticket + * + * @since 0.83.3 new proto + * + * @param $ID ID of the ticket + * @param $no_stat_computation boolean do not cumpute take into account stat (false by default) + * @param $users_id_lastupdater integer to force last_update id (default 0 = not used) + **/ + function updateDateMod($ID, $no_stat_computation = false, $users_id_lastupdater = 0) { + + if ($this->getFromDB($ID)) { + if (!$no_stat_computation + && !$this->isAlreadyTakenIntoAccount() + && ($this->canTakeIntoAccount() || isCommandLine())) { + return $this->update( + [ + 'id' => $ID, + 'takeintoaccount_delay_stat' => $this->computeTakeIntoAccountDelayStat(), + '_disablenotif' => true + ] + ); + } + + parent::updateDateMod($ID, $no_stat_computation, $users_id_lastupdater); + } + } + + + /** + * Overloaded from commonDBTM + * + * @since 0.83 + * + * @param $type itemtype of object to add + * + * @return rights + **/ + function canAddItem($type) { + + if ($type == 'Document') { + if ($this->getField('status') == self::CLOSED) { + return false; + } + + if ($this->canAddFollowups()) { + return true; + } + } + + // as self::canUpdate & $this->canUpdateItem checks more general rights + // (like STEAL or OWN), + // we specify only the rights needed for this action + return $this->checkEntity() + && (Session::haveRight(self::$rightname, UPDATE) + || $this->canRequesterUpdateItem()); + } + + + /** + * Check if user can add followups to the ticket. + * + * @param integer $user_id + * + * @return boolean + */ + public function canUserAddFollowups($user_id) { + + $entity_id = $this->fields['entities_id']; + + $group_user = new Group_User(); + $user_groups = $group_user->getUserGroups($user_id, ['entities_id' => $entity_id]); + $user_groups_ids = []; + foreach ($user_groups as $user_group) { + $user_groups_ids[] = $user_group['id']; + } + + $rightname = ITILFollowup::$rightname; + + return ( + Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDMYTICKET, $entity_id) + && ($this->isUser(CommonITILActor::REQUESTER, $user_id) + || ( + isset($this->fields['users_id_recipient']) + && ($this->fields['users_id_recipient'] === $user_id) + ) + ) + ) + || Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDALLTICKET, $entity_id) + || ( + Profile::haveUserRight($user_id, $rightname, ITILFollowup::ADDGROUPTICKET, $entity_id) + && $this->haveAGroup(CommonITILActor::REQUESTER, $user_groups_ids) + ) + || $this->isUser(CommonITILActor::ASSIGN, Session::getLoginUserID()) + || $this->haveAGroup(CommonITILActor::ASSIGN, $user_groups_ids); + } + + + /** + * Get default values to search engine to override + **/ + static function getDefaultSearchRequest() { + + $search = ['criteria' => [0 => ['field' => 12, + 'searchtype' => 'equals', + 'value' => 'notclosed']], + 'sort' => 19, + 'order' => 'DESC']; + + if (Session::haveRight(self::$rightname, self::READALL)) { + $search['criteria'][0]['value'] = 'notold'; + } + return $search; + } + + + /** + * @see CommonDBTM::getSpecificMassiveActions() + **/ + function getSpecificMassiveActions($checkitem = null) { + + $actions = []; + + if (Session::getCurrentInterface() == 'central') { + if (Ticket::canUpdate() && Ticket::canDelete()) { + $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'merge_as_followup'] + = "". + __('Merge as Followup'); + } + + if (Item_Ticket::canCreate()) { + $actions['Item_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'add_item'] + = "". + _x('button', 'Add an item'); + } + + if (ITILFollowup::canCreate()) { + $actions['ITILFollowup'.MassiveAction::CLASS_ACTION_SEPARATOR.'add_followup'] + = "". + __('Add a new followup'); + } + + if (TicketTask::canCreate()) { + $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'add_task'] + = "". + __('Add a new task'); + } + + if (TicketValidation::canCreate()) { + $actions['TicketValidation'.MassiveAction::CLASS_ACTION_SEPARATOR.'submit_validation'] + = "". + __('Approval request'); + } + + if (Item_Ticket::canDelete()) { + $actions['Item_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'delete_item'] + = _x('button', 'Remove an item'); + } + + if (Session::haveRight(self::$rightname, UPDATE)) { + $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'add_actor'] + = "". + __('Add an actor'); + $actions[__CLASS__.MassiveAction::CLASS_ACTION_SEPARATOR.'update_notif'] + = __('Set notifications for all actors'); + $actions['Ticket_Ticket'.MassiveAction::CLASS_ACTION_SEPARATOR.'add'] + = "". + _x('button', 'Link tickets'); + + KnowbaseItem_Item::getMassiveActionsForItemtype($actions, __CLASS__, 0, $checkitem); + } + } + + $actions += parent::getSpecificMassiveActions($checkitem); + + return $actions; + } + + + static function showMassiveActionsSubForm(MassiveAction $ma) { + switch ($ma->getAction()) { + case 'merge_as_followup' : + $rand = mt_rand(); + $mergeparam = [ + 'name' => "_mergeticket", + 'used' => $ma->items['Ticket'], + 'displaywith' => ['id'], + 'rand' => $rand + ]; + echo ""; + echo "
"; + Ticket::dropdown($mergeparam); + echo "
"; + Html::showCheckbox([ + 'name' => 'with_followups', + 'id' => 'with_followups', + 'checked' => true + ]); + echo ""; + Html::showCheckbox([ + 'name' => 'with_documents', + 'id' => 'with_documents', + 'checked' => true + ]); + echo "
"; + Html::showCheckbox([ + 'name' => 'with_tasks', + 'id' => 'with_tasks', + 'checked' => true + ]); + echo ""; + Html::showCheckbox([ + 'name' => 'with_actors', + 'id' => 'with_actors', + 'checked' => true + ]); + echo "
"; + Dropdown::showFromArray('link_type', [ + 0 => __('None'), + Ticket_Ticket::LINK_TO => __('Linked to'), + Ticket_Ticket::DUPLICATE_WITH => __('Duplicates'), + Ticket_Ticket::SON_OF => __('Son of'), + Ticket_Ticket::PARENT_OF => __('Parent of') + ], ['value' => Ticket_Ticket::SON_OF, 'rand' => $rand]); + echo "
"; + echo Html::submit(_x('button', 'Merge'), [ + 'name' => 'merge', + 'confirm' => __('Confirm the merge? This ticket will be deleted!') + ]); + echo "
"; + return true; + } + return parent::showMassiveActionsSubForm($ma); + } + + + static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, + array $ids) { + switch ($ma->getAction()) { + case 'merge_as_followup' : + $input = $ma->getInput(); + $status = []; + $mergeparams = [ + 'linktypes' => [], + 'link_type' => $input['link_type'] + ]; + + if ($input['with_followups']) { + $mergeparams['linktypes'][] = 'ITILFollowup'; + } + if ($input['with_tasks']) { + $mergeparams['linktypes'][] = 'TicketTask'; + } + if ($input['with_documents']) { + $mergeparams['linktypes'][] = 'Document'; + } + if ($input['with_actors']) { + $mergeparams['append_actors'] = [ + CommonITILActor::REQUESTER, + CommonITILActor::OBSERVER, + CommonITILActor::ASSIGN]; + } else { + $mergeparams['append_actors'] = []; + } + + Ticket::merge($input['_mergeticket'], $ids, $status, $mergeparams); + foreach ($status as $id => $status_code) { + if ($status_code == 0) { + $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK); + } else if ($status_code == 2) { + $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_NORIGHT); + $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION)); + } else { + $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO); + $ma->addMessage($item->getErrorMessage(ERROR_ON_ACTION)); + } + } + return; + } + parent::processMassiveActionsForOneItemtype($ma, $item, $ids); + } + + + function rawSearchOptions() { + global $DB; + + $tab = []; + + $tab = array_merge($tab, $this->getSearchOptionsMain()); + + $tab[] = [ + 'id' => '155', + 'table' => $this->getTable(), + 'field' => 'time_to_own', + 'name' => __('Time to own'), + 'datatype' => 'datetime', + 'maybefuture' => true, + 'massiveaction' => false, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '158', + 'table' => $this->getTable(), + 'field' => 'time_to_own', + 'name' => __('Time to own + Progress'), + 'massiveaction' => false, + 'nosearch' => true, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '159', + 'table' => 'glpi_tickets', + 'field' => 'is_late', + 'name' => __('Time to own exceedeed'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => 'IF('.$DB->quoteName('TABLE.time_to_own').' IS NOT NULL + AND '.$DB->quoteName('TABLE.status').' <> '.self::WAITING.' + AND ('.$DB->quoteName('TABLE.takeintoaccount_delay_stat').' + > TIME_TO_SEC(TIMEDIFF('.$DB->quoteName('TABLE.time_to_own').', + '.$DB->quoteName('TABLE.date').')) + OR ('.$DB->quoteName('TABLE.takeintoaccount_delay_stat').' = 0 + AND '.$DB->quoteName('TABLE.time_to_own').' < NOW())), + 1, 0)' + ]; + + $tab[] = [ + 'id' => '180', + 'table' => $this->getTable(), + 'field' => 'internal_time_to_resolve', + 'name' => __('Internal time to resolve'), + 'datatype' => 'datetime', + 'maybefuture' => true, + 'massiveaction' => false, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '181', + 'table' => $this->getTable(), + 'field' => 'internal_time_to_resolve', + 'name' => __('Internal time to resolve + Progress'), + 'massiveaction' => false, + 'nosearch' => true, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '182', + 'table' => $this->getTable(), + 'field' => 'is_late', + 'name' => __('Internal time to resolve exceedeed'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => 'IF('.$DB->quoteName('TABLE.internal_time_to_resolve').' IS NOT NULL + AND '.$DB->quoteName('TABLE.status').' <> 4 + AND ('.$DB->quoteName('TABLE.solvedate').' > '.$DB->quoteName('TABLE.internal_time_to_resolve').' + OR ('.$DB->quoteName('TABLE.solvedate').' IS NULL + AND '.$DB->quoteName('TABLE.internal_time_to_resolve').' < NOW())), + 1, 0)' + ]; + + $tab[] = [ + 'id' => '185', + 'table' => $this->getTable(), + 'field' => 'internal_time_to_own', + 'name' => __('Internal time to own'), + 'datatype' => 'datetime', + 'maybefuture' => true, + 'massiveaction' => false, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '186', + 'table' => $this->getTable(), + 'field' => 'internal_time_to_own', + 'name' => __('Internal time to own + Progress'), + 'massiveaction' => false, + 'nosearch' => true, + 'additionalfields' => ['status'] + ]; + + $tab[] = [ + 'id' => '187', + 'table' => 'glpi_tickets', + 'field' => 'is_late', + 'name' => __('Internal time to own exceedeed'), + 'datatype' => 'bool', + 'massiveaction' => false, + 'computation' => 'IF('.$DB->quoteName('TABLE.internal_time_to_own').' IS NOT NULL + AND '.$DB->quoteName('TABLE.status').' <> '.self::WAITING.' + AND ('.$DB->quoteName('TABLE.takeintoaccount_delay_stat').' + > TIME_TO_SEC(TIMEDIFF('.$DB->quoteName('TABLE.internal_time_to_own').', + '.$DB->quoteName('TABLE.date').')) + OR ('.$DB->quoteName('TABLE.takeintoaccount_delay_stat').' = 0 + AND '.$DB->quoteName('TABLE.internal_time_to_own').' < NOW())), + 1, 0)' + ]; + + $max_date = '99999999'; + $tab[] = [ + 'id' => '188', + 'table' => $this->getTable(), + 'field' => 'next_escalation_level', + 'name' => __('Next escalation level'), + 'datatype' => 'datetime', + 'usehaving' => true, + 'maybefuture' => true, + 'massiveaction' => false, + // Get least value from TTO/TTR fields: + // - use TTO fields only if ticket not already taken into account, + // - use TTR fields only if ticket not already solved, + // - replace NULL or not kept values with 99999999 to be sure that they will not be returned by the LEAST function, + // - replace 99999999 by empty string to keep only valid values. + 'computation' => "REPLACE( + LEAST( + IF(".$DB->quoteName('TABLE.takeintoaccount_delay_stat')." <= 0, + COALESCE(".$DB->quoteName('TABLE.time_to_own').", $max_date), + $max_date), + IF(".$DB->quoteName('TABLE.takeintoaccount_delay_stat')." <= 0, + COALESCE(".$DB->quoteName('TABLE.internal_time_to_own').", $max_date), + $max_date), + IF(".$DB->quoteName('TABLE.solvedate')." IS NULL, + COALESCE(".$DB->quoteName('TABLE.time_to_resolve').", $max_date), + $max_date), + IF(".$DB->quoteName('TABLE.solvedate')." IS NULL, + COALESCE(".$DB->quoteName('TABLE.internal_time_to_resolve').", $max_date), + $max_date) + ), $max_date, '')" + ]; + + $tab[] = [ + 'id' => '14', + 'table' => $this->getTable(), + 'field' => 'type', + 'name' => __('Type'), + 'searchtype' => 'equals', + 'datatype' => 'specific' + ]; + + $tab[] = [ + 'id' => '13', + 'table' => 'glpi_items_tickets', + 'field' => 'items_id', + 'name' => _n('Associated element', 'Associated elements', Session::getPluralNumber()), + 'datatype' => 'specific', + 'comments' => true, + 'nosort' => true, + 'nosearch' => true, + 'additionalfields' => ['itemtype'], + 'joinparams' => [ + 'jointype' => 'child' + ], + 'forcegroupby' => true, + 'massiveaction' => false + ]; + + $tab[] = [ + 'id' => '131', + 'table' => 'glpi_items_tickets', + 'field' => 'itemtype', + 'name' => _n('Associated item type', 'Associated item types', Session::getPluralNumber()), + 'datatype' => 'itemtypename', + 'itemtype_list' => 'ticket_types', + 'nosort' => true, + 'additionalfields' => ['itemtype'], + 'joinparams' => [ + 'jointype' => 'child' + ], + 'forcegroupby' => true, + 'massiveaction' => false + ]; + + $tab[] = [ + 'id' => '9', + 'table' => 'glpi_requesttypes', + 'field' => 'name', + 'name' => __('Request source'), + 'datatype' => 'dropdown' + ]; + + $location_so = Location::rawSearchOptionsToAdd(); + foreach ($location_so as &$so) { + //duplicated search options :( + switch ($so['id']) { + case 3: + $so['id'] = 83; + break; + case 91: + $so['id'] = 84; + break; + case 92: + $so['id'] = 85; + break; + case 93: + $so['id'] = 86; + break; + } + } + $tab = array_merge($tab, $location_so); + + $tab = array_merge($tab, $this->getSearchOptionsActors()); + + $tab[] = [ + 'id' => 'sla', + 'name' => __('SLA') + ]; + + $tab[] = [ + 'id' => '37', + 'table' => 'glpi_slas', + 'field' => 'name', + 'linkfield' => 'slas_id_tto', + 'name' => __('SLA')." ".__('Time to own'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'condition' => "AND NEWTABLE.`type` = '".SLM::TTO."'" + ], + 'condition' => "`glpi_slas`.`type` = '".SLM::TTO."'" + ]; + + $tab[] = [ + 'id' => '30', + 'table' => 'glpi_slas', + 'field' => 'name', + 'linkfield' => 'slas_id_ttr', + 'name' => __('SLA')." ".__('Time to resolve'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'condition' => "AND NEWTABLE.`type` = '".SLM::TTR."'" + ], + 'condition' => "`glpi_slas`.`type` = '".SLM::TTR."'" + ]; + + $tab[] = [ + 'id' => '32', + 'table' => 'glpi_slalevels', + 'field' => 'name', + 'name' => __('SLA')." ".__('Escalation level'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_slalevels_tickets', + 'joinparams' => [ + 'jointype' => 'child' + ] + ] + ], + 'forcegroupby' => true + ]; + + $tab[] = [ + 'id' => 'ola', + 'name' => __('OLA') + ]; + + $tab[] = [ + 'id' => '190', + 'table' => 'glpi_olas', + 'field' => 'name', + 'linkfield' => 'olas_id_tto', + 'name' => __('OLA')." ".__('Internal time to own'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'condition' => "AND NEWTABLE.`type` = '".SLM::TTO."'" + ], + 'condition' => "`glpi_olas`.`type` = '".SLM::TTO."'" + ]; + + $tab[] = [ + 'id' => '191', + 'table' => 'glpi_olas', + 'field' => 'name', + 'linkfield' => 'olas_id_ttr', + 'name' => __('OLA')." ".__('Internal time to resolve'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'condition' => "AND NEWTABLE.`type` = '".SLM::TTR."'" + ], + 'condition' => "`glpi_olas`.`type` = '".SLM::TTR."'" + ]; + + $tab[] = [ + 'id' => '192', + 'table' => 'glpi_olalevels', + 'field' => 'name', + 'name' => __('OLA')." ".__('Escalation level'), + 'massiveaction' => false, + 'datatype' => 'dropdown', + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_olalevels_tickets', + 'joinparams' => [ + 'jointype' => 'child' + ] + ] + ], + 'forcegroupby' => true + ]; + + $validation_options = TicketValidation::rawSearchOptionsToAdd(); + if (!Session::haveRightsOr( + 'ticketvalidation', + [ + TicketValidation::CREATEINCIDENT, + TicketValidation::CREATEREQUEST + ] + )) { + foreach ($validation_options as &$validation_option) { + if (isset($validation_option['table'])) { + $validation_option['massiveaction'] = false; + } + } + } + $tab = array_merge($tab, $validation_options); + + $tab[] = [ + 'id' => 'satisfaction', + 'name' => __('Satisfaction survey') + ]; + + $tab[] = [ + 'id' => '31', + 'table' => 'glpi_ticketsatisfactions', + 'field' => 'type', + 'name' => __('Type'), + 'massiveaction' => false, + 'searchtype' => ['equals', 'notequals'], + 'searchequalsonfield' => true, + 'joinparams' => [ + 'jointype' => 'child' + ], + 'datatype' => 'specific' + ]; + + $tab[] = [ + 'id' => '60', + 'table' => 'glpi_ticketsatisfactions', + 'field' => 'date_begin', + 'name' => __('Creation date'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => [ + 'jointype' => 'child' + ] + ]; + + $tab[] = [ + 'id' => '61', + 'table' => 'glpi_ticketsatisfactions', + 'field' => 'date_answered', + 'name' => __('Response date'), + 'datatype' => 'datetime', + 'massiveaction' => false, + 'joinparams' => [ + 'jointype' => 'child' + ] + ]; + + $tab[] = [ + 'id' => '62', + 'table' => 'glpi_ticketsatisfactions', + 'field' => 'satisfaction', + 'name' => __('Satisfaction'), + 'datatype' => 'number', + 'massiveaction' => false, + 'joinparams' => [ + 'jointype' => 'child' + ] + ]; + + $tab[] = [ + 'id' => '63', + 'table' => 'glpi_ticketsatisfactions', + 'field' => 'comment', + 'name' => __('Comments'), + 'datatype' => 'text', + 'massiveaction' => false, + 'joinparams' => [ + 'jointype' => 'child' + ] + ]; + + $tab = array_merge($tab, ITILFollowup::rawSearchOptionsToAdd()); + + $tab = array_merge($tab, TicketTask::rawSearchOptionsToAdd()); + + $tab = array_merge($tab, $this->getSearchOptionsStats()); + + $tab[] = [ + 'id' => '150', + 'table' => $this->getTable(), + 'field' => 'takeintoaccount_delay_stat', + 'name' => __('Take into account time'), + 'datatype' => 'timestamp', + 'forcegroupby' => true, + 'massiveaction' => false + ]; + + if (Session::haveRightsOr(self::$rightname, + [self::READALL, self::READASSIGN, self::OWN])) { + $tab[] = [ + 'id' => 'linktickets', + 'name' => _n('Linked ticket', 'Linked tickets', Session::getPluralNumber()) + ]; + + $tab[] = [ + 'id' => '40', + 'table' => 'glpi_tickets_tickets', + 'field' => 'tickets_id_1', + 'name' => __('All linked tickets'), + 'massiveaction' => false, + 'forcegroupby' => true, + 'searchtype' => 'equals', + 'joinparams' => [ + 'jointype' => 'item_item' + ], + 'additionalfields' => ['tickets_id_2'] + ]; + + $tab[] = [ + 'id' => '47', + 'table' => 'glpi_tickets_tickets', + 'field' => 'tickets_id_1', + 'name' => __('Duplicated tickets'), + 'massiveaction' => false, + 'searchtype' => 'equals', + 'joinparams' => [ + 'jointype' => 'item_item', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::DUPLICATE_WITH + ], + 'additionalfields' => ['tickets_id_2'], + 'forcegroupby' => true + ]; + + $tab[] = [ + 'id' => '41', + 'table' => 'glpi_tickets_tickets', + 'field' => 'id', + 'name' => __('Number of all linked tickets'), + 'massiveaction' => false, + 'datatype' => 'count', + 'usehaving' => true, + 'joinparams' => [ + 'jointype' => 'item_item' + ] + ]; + + $tab[] = [ + 'id' => '46', + 'table' => 'glpi_tickets_tickets', + 'field' => 'id', + 'name' => __('Number of duplicated tickets'), + 'massiveaction' => false, + 'datatype' => 'count', + 'usehaving' => true, + 'joinparams' => [ + 'jointype' => 'item_item', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::DUPLICATE_WITH + ] + ]; + + $tab[] = [ + 'id' => '50', + 'table' => 'glpi_tickets', + 'field' => 'id', + 'linkfield' => 'tickets_id_2', + 'name' => __('Parent tickets'), + 'massiveaction' => false, + 'searchtype' => 'equals', + 'datatype' => 'itemlink', + 'usehaving' => true, + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_tickets_tickets', + 'joinparams' => [ + 'jointype' => 'child', + 'linkfield' => 'tickets_id_1', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::SON_OF, + ] + ] + ], + 'forcegroupby' => true + ]; + + $tab[] = [ + 'id' => '67', + 'table' => 'glpi_tickets', + 'field' => 'id', + 'linkfield' => 'tickets_id_1', + 'name' => __('Child tickets'), + 'massiveaction' => false, + 'searchtype' => 'equals', + 'datatype' => 'itemlink', + 'usehaving' => true, + 'joinparams' => [ + 'beforejoin' => [ + 'table' => 'glpi_tickets_tickets', + 'joinparams' => [ + 'jointype' => 'child', + 'linkfield' => 'tickets_id_2', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::SON_OF, + ] + ] + ], + 'forcegroupby' => true + ]; + + $tab[] = [ + 'id' => '68', + 'table' => 'glpi_tickets_tickets', + 'field' => 'id', + 'name' => __('Number of sons tickets'), + 'massiveaction' => false, + 'datatype' => 'count', + 'usehaving' => true, + 'joinparams' => [ + 'linkfield' => 'tickets_id_2', + 'jointype' => 'child', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::SON_OF + ], + 'forcegroupby' => true + ]; + + $tab[] = [ + 'id' => '69', + 'table' => 'glpi_tickets_tickets', + 'field' => 'id', + 'name' => __('Number of parent tickets'), + 'massiveaction' => false, + 'datatype' => 'count', + 'usehaving' => true, + 'joinparams' => [ + 'linkfield' => 'tickets_id_1', + 'jointype' => 'child', + 'condition' => 'AND NEWTABLE.`link` = '.Ticket_Ticket::SON_OF + ], + 'additionalfields' => ['tickets_id_2'] + ]; + + $tab = array_merge($tab, $this->getSearchOptionsSolution()); + + if (Session::haveRight('ticketcost', READ)) { + $tab = array_merge($tab, TicketCost::rawSearchOptionsToAdd()); + } + } + + if (Session::haveRight('problem', READ)) { + $tab[] = [ + 'id' => 'problem', + 'name' => __('Problems') + ]; + + $tab[] = [ + 'id' => '141', + 'table' => 'glpi_problems_tickets', + 'field' => 'id', + 'name' => _x('quantity', 'Number of problems'), + 'forcegroupby' => true, + 'usehaving' => true, + 'datatype' => 'count', + 'massiveaction' => false, + 'joinparams' => [ + 'jointype' => 'child' + ] + ]; + } + + // Filter search fields for helpdesk + if (!Session::isCron() // no filter for cron + && (Session::getCurrentInterface() != 'central')) { + $tokeep = ['common', 'requester','satisfaction']; + if (Session::haveRightsOr('ticketvalidation', + array_merge(TicketValidation::getValidateRights(), + TicketValidation::getCreateRights()))) { + $tokeep[] = 'validation'; + } + $keep = false; + foreach ($tab as $key => &$val) { + if (!isset($val['table'])) { + $keep = in_array($val['id'], $tokeep); + } + if (!$keep) { + if (isset($val['table'])) { + $val['nosearch'] = true; + } + } + } + } + return $tab; + } + + + /** + * @since 0.84 + * + * @param $field + * @param $values + * @param $options array + **/ + static function getSpecificValueToDisplay($field, $values, array $options = []) { + + if (!is_array($values)) { + $values = [$field => $values]; + } + switch ($field) { + case 'content' : + $content = Toolbox::unclean_cross_side_scripting_deep(Html::entity_decode_deep($values[$field])); + $content = Html::clean($content); + if (empty($content)) { + $content = ' '; + } + return nl2br($content); + + case 'type': + return self::getTicketTypeName($values[$field]); + } + return parent::getSpecificValueToDisplay($field, $values, $options); + } + + + /** + * @since 0.84 + * + * @param $field + * @param $name (default '') + * @param $values (default '') + * @param $options array + * + * @return string + **/ + static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) { + + if (!is_array($values)) { + $values = [$field => $values]; + } + $options['display'] = false; + switch ($field) { + case 'content' : + return ""; + + case 'type': + $options['value'] = $values[$field]; + return self::dropdownType($name, $options); + } + return parent::getSpecificValueToSelect($field, $name, $values, $options); + } + + + /** + * Dropdown of ticket type + * + * @param $name select name + * @param $options array of options: + * - value : integer / preselected value (default 0) + * - toadd : array / array of specific values to add at the begining + * - on_change : string / value to transmit to "onChange" + * - display : boolean / display or get string (default true) + * + * @return string id of the select + **/ + static function dropdownType($name, $options = []) { + + $params = [ + 'value' => 0, + 'toadd' => [], + 'on_change' => '', + 'display' => true, + ]; + + if (is_array($options) && count($options)) { + foreach ($options as $key => $val) { + $params[$key] = $val; + } + } + + $items = []; + if (count($params['toadd']) > 0) { + $items = $params['toadd']; + } + + $items += self::getTypes(); + + return Dropdown::showFromArray($name, $items, $params); + } + + + /** + * Get ticket types + * + * @return array of types + **/ + static function getTypes() { + + $options = [ + self::INCIDENT_TYPE => __('Incident'), + self::DEMAND_TYPE => __('Request'), + ]; + + return $options; + } + + + /** + * Get ticket type Name + * + * @param $value type ID + **/ + static function getTicketTypeName($value) { + + switch ($value) { + case self::INCIDENT_TYPE : + return __('Incident'); + + case self::DEMAND_TYPE : + return __('Request'); + + default : + // Return $value if not defined + return $value; + } + } + + + /** + * get the Ticket status list + * + * @param $withmetaforsearch boolean (false by default) + * + * @return array + **/ + static function getAllStatusArray($withmetaforsearch = false) { + + // To be overridden by class + $tab = [self::INCOMING => _x('status', 'New'), + self::ASSIGNED => _x('status', 'Processing (assigned)'), + self::PLANNED => _x('status', 'Processing (planned)'), + self::WAITING => __('Pending'), + self::SOLVED => _x('status', 'Solved'), + self::CLOSED => _x('status', 'Closed')]; + + if ($withmetaforsearch) { + $tab['notold'] = _x('status', 'Not solved'); + $tab['notclosed'] = _x('status', 'Not closed'); + $tab['process'] = __('Processing'); + $tab['old'] = _x('status', 'Solved + Closed'); + $tab['all'] = __('All'); + } + return $tab; + } + + + /** + * Get the ITIL object closed status list + * + * @since 0.83 + * + * @return array + **/ + static function getClosedStatusArray() { + return [self::CLOSED]; + } + + + /** + * Get the ITIL object solved status list + * + * @since 0.83 + * + * @return array + **/ + static function getSolvedStatusArray() { + return [self::SOLVED]; + } + + /** + * Get the ITIL object new status list + * + * @since 0.83.8 + * + * @return array + **/ + static function getNewStatusArray() { + return [self::INCOMING]; + } + + /** + * Get the ITIL object assign or plan status list + * + * @since 0.83 + * + * @return array + **/ + static function getProcessStatusArray() { + return [self::ASSIGNED, self::PLANNED]; + } + + + /** + * Calculate Ticket TCO for an item + * + *@param $item CommonDBTM object of the item + * + *@return float + **/ + static function computeTco(CommonDBTM $item) { + global $DB; + + $totalcost = 0; + + $iterator = $DB->request([ + 'SELECT' => 'glpi_ticketcosts.*', + 'FROM' => 'glpi_ticketcosts', + 'LEFT JOIN' => [ + 'glpi_items_tickets' => [ + 'ON' => [ + 'glpi_items_tickets' => 'tickets_id', + 'glpi_ticketcosts' => 'tickets_id' + ] + ] + ], + 'WHERE' => [ + 'glpi_items_tickets.itemtype' => get_class($item), + 'glpi_items_tickets.items_id' => $item->getField('id'), + 'OR' => [ + 'glpi_ticketcosts.cost_time' => ['>', 0], + 'glpi_ticketcosts.cost_fixed' => ['>', 0], + 'glpi_ticketcosts.cost_material' => ['>', 0] + ] + ] + ]); + + while ($data = $iterator->next()) { + $totalcost += TicketCost::computeTotalCost( + $data["actiontime"], + $data["cost_time"], + $data["cost_fixed"], + $data["cost_material"] + ); + } + return $totalcost; + } + + + /** + * Print the helpdesk form + * + * @param $ID integer ID of the user who want to display the Helpdesk + * @param $ticket_template boolean ticket template for preview : false if not used for preview + * (false by default) + * + * @return void + **/ + function showFormHelpdesk($ID, $ticket_template = false) { + global $CFG_GLPI; + + if (!self::canCreate()) { + return false; + } + + if (!$ticket_template + && Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())) { + + $opt = []; + $opt['reset'] = 'reset'; + $opt['criteria'][0]['field'] = 55; // validation status + $opt['criteria'][0]['searchtype'] = 'equals'; + $opt['criteria'][0]['value'] = CommonITILValidation::WAITING; + $opt['criteria'][0]['link'] = 'AND'; + + $opt['criteria'][1]['field'] = 59; // validation aprobator + $opt['criteria'][1]['searchtype'] = 'equals'; + $opt['criteria'][1]['value'] = Session::getLoginUserID(); + $opt['criteria'][1]['link'] = 'AND'; + + $url_validate = Ticket::getSearchURL()."?".Toolbox::append_params($opt, + '&'); + + if (TicketValidation::getNumberToValidate(Session::getLoginUserID()) > 0) { + echo "". + __('Tickets awaiting approval')."

"; + } + } + + $email = UserEmail::getDefaultForUser($ID); + $default_use_notif = Entity::getUsedConfig('is_notif_enable_default', $_SESSION['glpiactive_entity'], '', 1); + + // Set default values... + $default_values = ['_users_id_requester_notif' + => ['use_notification' + => (($email == "")?0:$default_use_notif)], + 'nodelegate' => 1, + '_users_id_requester' => 0, + '_users_id_observer' => [0], + '_users_id_observer_notif' + => ['use_notification' => $default_use_notif], + 'name' => '', + 'content' => '', + 'itilcategories_id' => 0, + 'locations_id' => 0, + 'urgency' => 3, + 'items_id' => 0, + 'entities_id' => $_SESSION['glpiactive_entity'], + 'plan' => [], + 'global_validation' => CommonITILValidation::NONE, + '_add_validation' => 0, + 'type' => Entity::getUsedConfig('tickettype', + $_SESSION['glpiactive_entity'], + '', Ticket::INCIDENT_TYPE), + '_right' => "id", + '_content' => [], + '_tag_content' => [], + '_filename' => [], + '_tag_filename' => [], + '_tasktemplates_id' => []]; + + // Get default values from posted values on reload form + if (!$ticket_template) { + if (isset($_POST)) { + $options = $_POST; + } + } + + if (isset($options['name'])) { + $order = ["\\'", '\\"', "\\\\"]; + $replace = ["'", '"', "\\"]; + $options['name'] = str_replace($order, $replace, $options['name']); + } + + // Restore saved value or override with page parameter + $saved = $this->restoreInput(); + foreach ($default_values as $name => $value) { + if (!isset($options[$name])) { + if (isset($saved[$name])) { + $options[$name] = $saved[$name]; + } else { + $options[$name] = $value; + } + } + } + + // Check category / type validity + if ($options['itilcategories_id']) { + $cat = new ITILCategory(); + if ($cat->getFromDB($options['itilcategories_id'])) { + switch ($options['type']) { + case self::INCIDENT_TYPE : + if (!$cat->getField('is_incident')) { + $options['itilcategories_id'] = 0; + } + break; + + case self::DEMAND_TYPE : + if (!$cat->getField('is_request')) { + $options['itilcategories_id'] = 0; + } + break; + + default : + break; + } + } + } + + // Load ticket template if available : + $tt = $this->getITILTemplateToUse($ticket_template, $options['type'], + $options['itilcategories_id'], + $_SESSION["glpiactive_entity"]); + + // Put ticket template on $options for actors + $options['_tickettemplate'] = $tt; + + if (!$ticket_template) { + echo "
"; + } + + $delegating = User::getDelegateGroupsForUser($options['entities_id']); + + if (count($delegating) || $CFG_GLPI['use_check_pref']) { + echo "
"; + } + + if (count($delegating)) { + echo ""; + echo ""; + + echo "
".__('This ticket concerns me')." "; + + $rand = Dropdown::showYesNo("nodelegate", $options['nodelegate']); + + $params = ['nodelegate' => '__VALUE__', + 'rand' => $rand, + 'right' => "delegate", + '_users_id_requester' + => $options['_users_id_requester'], + '_users_id_requester_notif' + => $options['_users_id_requester_notif'], + 'use_notification' + => $options['_users_id_requester_notif']['use_notification'], + 'entity_restrict' + => $_SESSION["glpiactive_entity"]]; + + Ajax::UpdateItemOnSelectEvent("dropdown_nodelegate".$rand, "show_result".$rand, + $CFG_GLPI["root_doc"]."/ajax/dropdownDelegationUsers.php", + $params); + + $class = 'right'; + if ($CFG_GLPI['use_check_pref'] && $options['nodelegate']) { + echo "".__('Check your personnal information'); + $class = 'center'; + } + + echo "
"; + echo "
"; + + $self = new self(); + if ($options["_users_id_requester"] == 0) { + $options['_users_id_requester'] = Session::getLoginUserID(); + } else { + $options['_right'] = "delegate"; + } + $self->showActorAddFormOnCreate(CommonITILActor::REQUESTER, $options); + echo "
"; + if ($CFG_GLPI['use_check_pref'] && $options['nodelegate']) { + echo "
"; + User::showPersonalInformation(Session::getLoginUserID()); + } + echo "
"; + echo ""; + + } else { + // User as requester + $options['_users_id_requester'] = Session::getLoginUserID(); + + if ($CFG_GLPI['use_check_pref']) { + echo "".__('Check your personnal information').""; + echo ""; + User::showPersonalInformation(Session::getLoginUserID()); + echo ""; + echo ""; + } + } + + echo ""; + echo ""; + + // Predefined fields from template : reset them + if (isset($options['_predefined_fields'])) { + $options['_predefined_fields'] + = Toolbox::decodeArrayFromInput($options['_predefined_fields']); + } else { + $options['_predefined_fields'] = []; + } + + // Store predefined fields to be able not to take into account on change template + $predefined_fields = []; + $key = $this->getTemplateFormFieldName(); + + if (isset($tt->predefined) && count($tt->predefined)) { + foreach ($tt->predefined as $predeffield => $predefvalue) { + if (isset($options[$predeffield]) && isset($default_values[$predeffield])) { + // Is always default value : not set + // Set if already predefined field + // Set if ticket template change + if (((count($options['_predefined_fields']) == 0) + && ($options[$predeffield] == $default_values[$predeffield])) + || (isset($options['_predefined_fields'][$predeffield]) + && ($options[$predeffield] == $options['_predefined_fields'][$predeffield])) + || (isset($options[$key]) + && ($options[$key] != $tt->getID()))) { + $options[$predeffield] = $predefvalue; + $predefined_fields[$predeffield] = $predefvalue; + } + } else { // Not defined options set as hidden field + echo ""; + } + } + // All predefined override : add option to say predifined exists + if (count($predefined_fields) == 0) { + $predefined_fields['_all_predefined_override'] = 1; + } + } else { // No template load : reset predefined values + if (count($options['_predefined_fields'])) { + foreach ($options['_predefined_fields'] as $predeffield => $predefvalue) { + if ($options[$predeffield] == $predefvalue) { + $options[$predeffield] = $default_values[$predeffield]; + } + } + } + } + + if (isset($options['_tasktemplates_id'])) { + foreach ($options['_tasktemplates_id'] as $tasktemplates_id) { + echo ""; + } + } + + if (($CFG_GLPI['urgency_mask'] == (1<<3)) + || $tt->isHiddenField('urgency')) { + // Dont show dropdown if only 1 value enabled or field is hidden + echo ""; + } + + // Display predefined fields if hidden + if ($tt->isHiddenField('items_id')) { + if (!empty($options['items_id'])) { + foreach ($options['items_id'] as $itemtype => $items) { + foreach ($items as $items_id) { + echo ""; + } + } + } + } + if ($tt->isHiddenField('locations_id')) { + echo ""; + } + echo ""; + echo "
"; + + Plugin::doHook("pre_item_form", ['item' => $this, 'options' => &$options]); + + echo ""; + + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + if ($CFG_GLPI['urgency_mask'] != (1<<3)) { + if (!$tt->isHiddenField('urgency')) { + echo ""; + echo ""; + echo ""; + } + } + +/** if (empty($delegating) + && NotificationTargetTicket::isAuthorMailingActivatedForHelpdesk()) { + echo ""; + echo ""; + echo ""; + } +*/ + if (($_SESSION["glpiactiveprofile"]["helpdesk_hardware"] != 0) + && (count($_SESSION["glpiactiveprofile"]["helpdesk_item_type"]))) { + if (!$tt->isHiddenField('items_id')) { + echo ""; + echo ""; + echo ""; + } + } + + if (!$tt->isHiddenField('locations_id')) { + echo ""; + } + + if (!$tt->isHiddenField('_users_id_observer') + || $tt->isPredefinedField('_users_id_observer')) { + echo ""; + echo ""; + echo ""; + } + + if (!$tt->isHiddenField('name') + || $tt->isPredefinedField('name')) { + echo ""; + echo ""; + } + + if (!$tt->isHiddenField('content') + || $tt->isPredefinedField('content')) { + echo ""; + echo ""; + } + Plugin::doHook("post_item_form", ['item' => $this, 'options' => &$options]); + + if (!$ticket_template) { + echo ""; + echo ""; + } + + echo "
".__('Describe the incident or request').""; + if (Session::isMultiEntitiesMode()) { + echo "(".Dropdown::getDropdownName("glpi_entities", $_SESSION["glpiactive_entity"]).")"; + } + echo "
".sprintf(__('%1$s%2$s'), __('Type'), $tt->getMandatoryMark('type')).""; + self::dropdownType('type', ['value' => $options['type'], + 'on_change' => 'this.form.submit()']); + echo "
".sprintf(__('%1$s%2$s'), __('Category'), + $tt->getMandatoryMark('itilcategories_id')).""; + + $condition = ['is_helpdeskvisible' => 1]; + switch ($options['type']) { + case self::DEMAND_TYPE : + $condition['is_request'] = 1; + break; + default: // self::INCIDENT_TYPE : + $condition['is_incident'] = 1; + } + $opt = ['value' => $options['itilcategories_id'], + 'condition' => $condition, + 'entity' => $_SESSION["glpiactive_entity"], + 'on_change' => 'this.form.submit()']; + + if ($options['itilcategories_id'] && $tt->isMandatoryField("itilcategories_id")) { + $opt['display_emptychoice'] = false; + } + + ITILCategory::dropdown($opt); + echo "
".sprintf(__('%1$s%2$s'), __('Urgency'), $tt->getMandatoryMark('urgency')). + ""; + self::dropdownUrgency(['value' => $options["urgency"]]); + echo "
".__('Inform me about the actions taken').""; + if ($options["_users_id_requester"] == 0) { + $options['_users_id_requester'] = Session::getLoginUserID(); + } + $_POST['value'] = $options['_users_id_requester']; + $_POST['field'] = '_users_id_requester_notif'; + $_POST['use_notification'] = $options['_users_id_requester_notif']['use_notification']; + include (GLPI_ROOT."/ajax/uemailUpdate.php"); + + echo "
".sprintf(__('%1$s%2$s'), _n('Associated element', 'Associated elements', Session::getPluralNumber()), + $tt->getMandatoryMark('items_id')).""; + $options['_canupdate'] = Session::haveRight('ticket', CREATE); + Item_Ticket::itemAddForm($this, $options); + echo "
"; + printf(__('%1$s%2$s'), __('Location'), $tt->getMandatoryMark('locations_id')); + echo ""; + Location::dropdown(['value' => $options["locations_id"]]); + echo "
".sprintf(__('%1$s%2$s'), _n('Watcher', 'Watchers', 2), + $tt->getMandatoryMark('_users_id_observer')).""; + $options['_right'] = "all"; + + if (!$tt->isHiddenField('_users_id_observer')) { + // Observer + + if ($tt->isPredefinedField('_users_id_observer') + && !is_array($options['_users_id_observer'])) { + + //convert predefined value to array + $options['_users_id_observer'] = [$options['_users_id_observer']]; + $options['_users_id_observer_notif']['use_notification'] = + [$options['_users_id_observer_notif']['use_notification']]; + + // add new line to permit adding more observers + $options['_users_id_observer'][1] = 0; + $options['_users_id_observer_notif']['use_notification'][1] = 1; + } + + echo "
"; + if (isset($options['_users_id_observer'])) { + $observers = $options['_users_id_observer']; + foreach ($observers as $index_observer => $observer) { + $options = array_merge($options, ['_user_index' => $index_observer]); + self::showFormHelpdeskObserver($options); + } + } + echo "
"; + + } else { // predefined value + if (isset($options["_users_id_observer"]) && $options["_users_id_observer"]) { + echo self::getActorIcon('user', CommonITILActor::OBSERVER)." "; + echo Dropdown::getDropdownName("glpi_users", $options["_users_id_observer"]); + echo ""; + } + } + echo "
".sprintf(__('%1$s%2$s'), __('Title'), $tt->getMandatoryMark('name')).""; + if (!$tt->isHiddenField('name')) { + $opt = [ + 'value' => $options['name'], + 'maxlength' => 250, + 'size' => 80, + ]; + + if ($tt->isMandatoryField('name')) { + $opt['required'] = 'required'; + } + echo Html::input('name', $opt); + } else { + echo $options['name']; + echo ""; + } + echo "
".sprintf(__('%1$s%2$s'), __('Description'), $tt->getMandatoryMark('content')); + + $rand = mt_rand(); + $rand_text = mt_rand(); + + $cols = 100; + $rows = 10; + $content_id = "content$rand"; + echo ""; + + $content = $options['content']; + if (!$ticket_template) { + $content = Html::cleanPostForTextArea($options['content']); + } + $content = Html::setRichTextContent($content_id, $content, $rand); + + echo "
"; + $uploads = []; + if (isset($options['_content'])) { + $uploads['_content'] = $options['_content']; + $uploads['_tag_content'] = $options['_tag_content']; + } + Html::textarea([ + 'name' => 'content', + 'filecontainer' => 'content_info', + 'editor_id' => $content_id, + 'required' => $tt->isMandatoryField('content'), + 'cols' => $cols, + 'rows' => $rows, + 'enable_richtext' => true, + 'value' => $content, + 'uploads' => $uploads, + ]); + echo "
"; + + if (!$tt->isHiddenField('_documents_id')) { + if (isset($options['_filename'])) { + $uploads['_filename'] = $options['_filename']; + $uploads['_tag_filename'] = $options['_tag_filename']; + } + Html::file([ + // 'editor_id' => $content_id, + 'showtitle' => false, + 'multiple' => true, + 'uploads' => $uploads, + ]); + } + + echo "
"; + + if ($tt->isField('id') && ($tt->fields['id'] > 0)) { + echo ""; + echo ""; + } + echo ""; + echo "
"; + if (!$ticket_template) { + Html::closeForm(); + } + } + + /** + * Display a single oberver selector + * + * * @param $options array options for default values ($options of showActorAddFormOnCreate) + **/ + static function showFormHelpdeskObserver($options = []) { + global $CFG_GLPI; + + //default values + $ticket = new Ticket(); + $params = [ + '_users_id_observer_notif' => [ + 'use_notification' => true + ], + '_users_id_observer' => 0, + 'entities_id' => $_SESSION["glpiactive_entity"], + '_right' => "all", + ]; + + // overide default value by function parameters + if (is_array($options) && count($options)) { + foreach ($options as $key => $val) { + $params[$key] = $val; + } + } + + // add a user selector + $rand_observer = $ticket->showActorAddFormOnCreate(CommonITILActor::OBSERVER, $params); + + if (isset($params['_tickettemplate'])) { + // Replace template object by ID for ajax + $params['_tickettemplate'] = $params['_tickettemplate']->getID(); + } + + // add an additionnal observer on user selection + Ajax::updateItemOnSelectEvent("dropdown__users_id_observer[]$rand_observer", + "observer_$rand_observer", + $CFG_GLPI["root_doc"]."/ajax/helpdesk_observer.php", + $params); + + //remove 'new observer' anchor on user selection + echo Html::scriptBlock(" + $('#dropdown__users_id_observer__$rand_observer').on('change', function(event) { + $('#addObserver$rand_observer').remove(); + });"); + + // add "new observer" anchor + echo ""; + echo Html::image($CFG_GLPI['root_doc']."/pics/meta_plus.png", ['alt' => __('Add')]); + echo ""; + + // add an additionnal observer on anchor click + Ajax::updateItemOnEvent("addObserver$rand_observer", + "observer_$rand_observer", + $CFG_GLPI["root_doc"]."/ajax/helpdesk_observer.php", + $params, ['click']); + + // div for an additionnal observer + echo "
"; + + } + + static function getDefaultValues($entity = 0) { + global $CFG_GLPI; + + if (is_numeric(Session::getLoginUserID(false))) { + $users_id_requester = Session::getLoginUserID(); + $users_id_assign = Session::getLoginUserID(); + // No default requester if own ticket right = tech and update_ticket right to update requester + if (Session::haveRightsOr(self::$rightname, [UPDATE, self::OWN]) && !$_SESSION['glpiset_default_requester']) { + $users_id_requester = 0; + } + if (!Session::haveRight(self::$rightname, self::OWN) || !$_SESSION['glpiset_default_tech']) { + $users_id_assign = 0; + } + $entity = $_SESSION['glpiactive_entity']; + $requesttype = $_SESSION['glpidefault_requesttypes_id']; + } else { + $users_id_requester = 0; + $users_id_assign = 0; + $requesttype = $CFG_GLPI['default_requesttypes_id']; + } + + $type = Entity::getUsedConfig('tickettype', $entity, '', Ticket::INCIDENT_TYPE); + + $default_use_notif = Entity::getUsedConfig('is_notif_enable_default', $entity, '', 1); + + // Set default values... + return ['_users_id_requester' => $users_id_requester, + '_users_id_requester_notif' => ['use_notification' => [$default_use_notif], + 'alternative_email' => ['']], + '_groups_id_requester' => 0, + '_users_id_assign' => $users_id_assign, + '_users_id_assign_notif' => ['use_notification' => [$default_use_notif], + 'alternative_email' => ['']], + '_groups_id_assign' => 0, + '_users_id_observer' => 0, + '_users_id_observer_notif' => ['use_notification' => [$default_use_notif], + 'alternative_email' => ['']], + '_groups_id_observer' => 0, + '_link' => ['tickets_id_2' => '', + 'link' => ''], + '_suppliers_id_assign' => 0, + '_suppliers_id_assign_notif' => ['use_notification' => [$default_use_notif], + 'alternative_email' => ['']], + 'name' => '', + 'content' => '', + 'itilcategories_id' => 0, + 'urgency' => 3, + 'impact' => 3, + 'priority' => self::computePriority(3, 3), + 'requesttypes_id' => $requesttype, + 'actiontime' => 0, + 'date' => null, + 'entities_id' => $entity, + 'status' => self::INCOMING, + 'followup' => [], + 'itemtype' => '', + 'items_id' => 0, + 'locations_id' => 0, + 'plan' => [], + 'global_validation' => CommonITILValidation::NONE, + 'time_to_resolve' => 'NULL', + 'time_to_own' => 'NULL', + 'slas_id_tto' => 0, + 'slas_id_ttr' => 0, + 'internal_time_to_resolve' => 'NULL', + 'internal_time_to_own' => 'NULL', + 'olas_id_tto' => 0, + 'olas_id_ttr' => 0, + '_add_validation' => 0, + 'users_id_validate' => [], + 'type' => $type, + '_documents_id' => [], + '_tasktemplates_id' => [], + '_content' => [], + '_tag_content' => [], + '_filename' => [], + '_tag_filename' => []]; + } + + /** + * Get ticket template to use + * Use force_template first, then try on template define for type and category + * then use default template of active profile of connected user and then use default entity one + * + * @param $force_template integer itiltemplate_id to used (case of preview for example) + * (default 0) + * @param $type integer type of the ticket (default 0) + * @param $itilcategories_id integer ticket category (default 0) + * @param $entities_id integer (default -1) + * + * @since 0.84 + * @deprecated 9.5.0 + * + * @return ticket template object + **/ + function getTicketTemplateToUse($force_template = 0, $type = 0, $itilcategories_id = 0, + $entities_id = -1) { + Toolbox::deprecated('Use getITILTemplateToUse()'); + return $this->getITILTemplateToUse( + $force_template, + $type, + $itilcategories_id, + $entities_id + ); + } + + + function showForm($ID, $options = []) { + global $CFG_GLPI; + + if (isset($options['_add_fromitem']) && isset($options['itemtype'])) { + $item = new $options['itemtype']; + $item->getFromDB($options['items_id'][$options['itemtype']][0]); + $options['entities_id'] = $item->fields['entities_id']; + } + + $default_values = self::getDefaultValues(); + + // Restore saved value or override with page parameter + $saved = $this->restoreInput(); + + foreach ($default_values as $name => $value) { + if (!isset($options[$name])) { + if (isset($saved[$name])) { + $options[$name] = $saved[$name]; + } else { + $options[$name] = $value; + } + } + } + + if (isset($options['content'])) { + // Clean new lines to be fix encoding + $order = ['\\r', '\\n', "\\'", '\\"', "\\\\"]; + $replace = ["", "", "'", '"', "\\"]; + $options['content'] = str_replace($order, $replace, $options['content']); + } + if (isset($options['name'])) { + $order = ["\\'", '\\"', "\\\\"]; + $replace = ["'", '"', "\\"]; + $options['name'] = str_replace($order, $replace, $options['name']); + } + + if (!isset($options['_skip_promoted_fields'])) { + $options['_skip_promoted_fields'] = false; + } + + if (!$ID) { + // Override defaut values from projecttask if needed + if (isset($options['_projecttasks_id'])) { + $pt = new ProjectTask(); + if ($pt->getFromDB($options['_projecttasks_id'])) { + $options['name'] = $pt->getField('name'); + $options['content'] = $pt->getField('name'); + } + } + // Override defaut values from followup if needed + if (isset($options['_promoted_fup_id']) && !$options['_skip_promoted_fields']) { + $fup = new ITILFollowup(); + if ($fup->getFromDB($options['_promoted_fup_id'])) { + $options['content'] = $fup->getField('content'); + $options['_users_id_requester'] = $fup->fields['users_id']; + $options['_link'] = [ + 'link' => Ticket_Ticket::SON_OF, + 'tickets_id_2' => $fup->fields['items_id'] + ]; + } + //Allow overriding the default values + $options['_skip_promoted_fields'] = true; + } + } + + // Check category / type validity + if ($options['itilcategories_id']) { + $cat = new ITILCategory(); + if ($cat->getFromDB($options['itilcategories_id'])) { + switch ($options['type']) { + case self::INCIDENT_TYPE : + if (!$cat->getField('is_incident')) { + $options['itilcategories_id'] = 0; + } + break; + + case self::DEMAND_TYPE : + if (!$cat->getField('is_request')) { + $options['itilcategories_id'] = 0; + } + break; + + default : + break; + } + } + } + + // Default check + if ($ID > 0) { + $this->check($ID, READ); + } else { + // Create item + $this->check(-1, CREATE, $options); + } + + if (!$ID) { + $this->userentities = []; + if ($options["_users_id_requester"]) { + //Get all the user's entities + $requester_entities = Profile_User::getUserEntities($options["_users_id_requester"], true, + true); + $user_entities = $_SESSION['glpiactiveentities']; + $this->userentities = array_intersect($requester_entities, $user_entities); + } + $this->countentitiesforuser = count($this->userentities); + + if (($this->countentitiesforuser > 0) + && !in_array($this->fields["entities_id"], $this->userentities)) { + // If entity is not in the list of user's entities, + // then use as default value the first value of the user's entites list + $this->fields["entities_id"] = $this->userentities[0]; + // Pass to values + $options['entities_id'] = $this->userentities[0]; + } + } + + if ($options['type'] <= 0) { + $options['type'] = Entity::getUsedConfig('tickettype', $options['entities_id'], '', + Ticket::INCIDENT_TYPE); + } + + if (!isset($options['template_preview'])) { + $options['template_preview'] = 0; + } + + if (!isset($options['_promoted_fup_id'])) { + $options['_promoted_fup_id'] = 0; + } + + // Load template if available : + $tt = $this->getITILTemplateToUse( + $options['template_preview'], + $this->fields['type'], + ($ID ? $this->fields['itilcategories_id'] : $options['itilcategories_id']), + ($ID ? $this->fields['entities_id'] : $options['entities_id']) + ); + + // Predefined fields from template : reset them + if (isset($options['_predefined_fields'])) { + $options['_predefined_fields'] + = Toolbox::decodeArrayFromInput($options['_predefined_fields']); + } else { + $options['_predefined_fields'] = []; + } + + // Store predefined fields to be able not to take into account on change template + // Only manage predefined values on ticket creation + $predefined_fields = []; + $tpl_key = $this->getTemplateFormFieldName(); + if (!$ID) { + + if (isset($tt->predefined) && count($tt->predefined)) { + foreach ($tt->predefined as $predeffield => $predefvalue) { + if (isset($default_values[$predeffield])) { + // Is always default value : not set + // Set if already predefined field + // Set if ticket template change + if (((count($options['_predefined_fields']) == 0) + && ($options[$predeffield] == $default_values[$predeffield])) + || (isset($options['_predefined_fields'][$predeffield]) + && ($options[$predeffield] == $options['_predefined_fields'][$predeffield])) + || (isset($options[$tpl_key]) + && ($options[$tpl_key] != $tt->getID())) + // user pref for requestype can't overwrite requestype from template + // when change category + || (($predeffield == 'requesttypes_id') + && empty($saved))) { + + // Load template data + $options[$predeffield] = $predefvalue; + $this->fields[$predeffield] = $predefvalue; + $predefined_fields[$predeffield] = $predefvalue; + } + } + } + // All predefined override : add option to say predifined exists + if (count($predefined_fields) == 0) { + $predefined_fields['_all_predefined_override'] = 1; + } + + } else { // No template load : reset predefined values + if (count($options['_predefined_fields'])) { + foreach ($options['_predefined_fields'] as $predeffield => $predefvalue) { + if ($options[$predeffield] == $predefvalue) { + $options[$predeffield] = $default_values[$predeffield]; + } + } + } + } + } + // Put ticket template on $options for actors + $options[str_replace('s_id', '', $tpl_key)] = $tt; + + // check right used for this ticket + $canupdate = !$ID + || (Session::getCurrentInterface() == "central" + && $this->canUpdateItem()); + $can_requester = $this->canRequesterUpdateItem(); + $canpriority = Session::haveRight(self::$rightname, self::CHANGEPRIORITY); + $canassign = $this->canAssign(); + $canassigntome = $this->canAssignTome(); + + if ($ID && in_array($this->fields['status'], $this->getClosedStatusArray())) { + $canupdate = false; + // No update for actors + $options['_noupdate'] = true; + } + + $showuserlink = 0; + if (Session::haveRight('user', READ)) { + $showuserlink = 1; + } + + if ($options['template_preview']) { + // Add all values to fields of tickets for template preview + foreach ($options as $key => $val) { + if (!isset($this->fields[$key])) { + $this->fields[$key] = $val; + } + } + } + + // In percent + $colsize1 = '13'; + $colsize2 = '29'; + $colsize3 = '13'; + $colsize4 = '45'; + + $this->showFormHeader($options); + + echo ""; + echo ""; + echo $tt->getBeginHiddenFieldText('date'); + if (!$ID) { + printf(__('%1$s%2$s'), __('Opening date'), $tt->getMandatoryMark('date')); + } else { + echo __('Opening date'); + } + echo $tt->getEndHiddenFieldText('date'); + echo ""; + echo ""; + echo $tt->getBeginHiddenFieldValue('date'); + $date = $this->fields["date"]; + + if ($canupdate) { + Html::showDateTimeField("date", ['value' => $date, + 'maybeempty' => false, + 'required' => ($tt->isMandatoryField('date') && !$ID)]); + } else { + echo Html::convDateTime($date); + } + echo $tt->getEndHiddenFieldValue('date', $this); + echo ""; + + if ($ID) { + echo "".__('By').""; + echo ""; + if ($canupdate) { + User::dropdown(['name' => 'users_id_recipient', + 'value' => $this->fields["users_id_recipient"], + 'entity' => $this->fields["entities_id"], + 'right' => 'all']); + } else { + echo getUserName($this->fields["users_id_recipient"], $showuserlink); + } + + echo ""; + } else { + echo ""; + echo ""; + } + echo ""; + + echo ""; + if ($ID) { + echo "".__('Last update').""; + echo ""; + if ($this->fields['users_id_lastupdater'] > 0) { + //TRANS: %1$s is the update date, %2$s is the last updater name + printf(__('%1$s by %2$s'), Html::convDateTime($this->fields["date_mod"]), + getUserName($this->fields["users_id_lastupdater"], $showuserlink)); + } + echo ""; + } + echo ""; + + // SLAs + echo ""; + echo "".$tt->getBeginHiddenFieldText('time_to_own'); + if (!$ID) { + printf(__('%1$s%2$s'), __('Time to own'), $tt->getMandatoryMark('time_to_own')); + } else { + echo __('Time to own'); + } + echo $tt->getEndHiddenFieldText('time_to_own'); + echo ""; + echo ""; + $sla = new SLA(); + $sla->showForTicket($this, SLM::TTO, $tt, $canupdate); + echo ""; + echo "".$tt->getBeginHiddenFieldText('time_to_resolve'); + if (!$ID) { + printf(__('%1$s%2$s'), __('Time to resolve'), $tt->getMandatoryMark('time_to_resolve')); + } else { + echo __('Time to resolve'); + } + echo $tt->getEndHiddenFieldText('time_to_resolve'); + echo ""; + echo ""; + $sla->showForTicket($this, SLM::TTR, $tt, $canupdate); + echo ""; + echo ""; + + // OLAs + echo ""; + echo "".$tt->getBeginHiddenFieldText('internal_time_to_own'); + if (!$ID) { + printf(__('%1$s%2$s'), __('Internal time to own'), $tt->getMandatoryMark('internal_time_to_own')); + } else { + echo __('Internal time to own'); + } + echo $tt->getEndHiddenFieldText('internal_time_to_own'); + echo ""; + echo ""; + $ola = new OLA(); + $ola->showForTicket($this, SLM::TTO, $tt, $canupdate); + echo ""; + echo "".$tt->getBeginHiddenFieldText('internal_time_to_resolve'); + if (!$ID) { + printf(__('%1$s%2$s'), __('Internal time to resolve'), $tt->getMandatoryMark('internal_time_to_resolve')); + } else { + echo __('Internal time to resolve'); + } + echo $tt->getEndHiddenFieldText('internal_time_to_resolve'); + echo ""; + echo ""; + $ola->showForTicket($this, SLM::TTR, $tt, $canupdate); + echo ""; + echo ""; + + if ($ID + && (in_array($this->fields["status"], $this->getSolvedStatusArray()) + || in_array($this->fields["status"], $this->getClosedStatusArray()))) { + + echo ""; + echo "".__('Resolution date').""; + echo ""; + Html::showDateTimeField("solvedate", ['value' => $this->fields["solvedate"], + 'maybeempty' => false, + 'canedit' => $canupdate]); + echo ""; + if (in_array($this->fields["status"], $this->getClosedStatusArray())) { + echo "".__('Close date').""; + echo ""; + Html::showDateTimeField("closedate", ['value' => $this->fields["closedate"], + 'maybeempty' => false, + 'canedit' => $canupdate]); + echo ""; + } else { + echo " "; + } + echo ""; + } + + if ($ID) { + echo ""; + echo ""; + } + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + if (!$ID) { + echo "
".sprintf(__('%1$s%2$s'), __('Type'), + $tt->getMandatoryMark('type')).""; + // Permit to set type when creating ticket without update right + if ($canupdate) { + $opt = ['value' => $this->fields["type"]]; + /// Auto submit to load template + if (!$ID) { + $opt['on_change'] = 'this.form.submit()'; + } + $rand = self::dropdownType('type', $opt); + if ($ID) { + $params = ['type' => '__VALUE__', + 'entity_restrict' => $this->fields['entities_id'], + 'value' => $this->fields['itilcategories_id'], + 'currenttype' => $this->fields['type']]; + + Ajax::updateItemOnSelectEvent("dropdown_type$rand", "show_category_by_type", + $CFG_GLPI["root_doc"]."/ajax/dropdownTicketCategories.php", + $params); + } + } else { + echo self::getTicketTypeName($this->fields["type"]); + } + echo "".sprintf(__('%1$s%2$s'), __('Category'), + $tt->getMandatoryMark('itilcategories_id')).""; + // Permit to set category when creating ticket without update right + if ($canupdate || $can_requester) { + $conditions = []; + + $opt = ['value' => $this->fields["itilcategories_id"], + 'entity' => $this->fields["entities_id"]]; + if (Session::getCurrentInterface() == "helpdesk") { + $conditions['is_helpdeskvisible'] = 1; + } + /// Auto submit to load template + if (!$ID) { + $opt['on_change'] = 'this.form.submit()'; + } + /// if category mandatory, no empty choice + /// no empty choice is default value set on ticket creation, else yes + if (($ID || $options['itilcategories_id']) + && $tt->isMandatoryField("itilcategories_id") + && ($this->fields["itilcategories_id"] > 0)) { + $opt['display_emptychoice'] = false; + } + + switch ($this->fields["type"]) { + case self::INCIDENT_TYPE : + $conditions['is_incident'] = 1; + break; + + case self::DEMAND_TYPE : + $conditions['is_request'] = 1; + break; + + default : + break; + } + echo ""; + $opt['condition'] = $conditions; + ITILCategory::dropdown($opt); + echo ""; + } else { + echo Dropdown::getDropdownName("glpi_itilcategories", $this->fields["itilcategories_id"]); + } + echo "
"; + $this->showActorsPartForm($ID, $options); + echo ""; + } + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + // Display validation state + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + if (!$ID) { + echo ""; + echo ""; + } else { + echo ""; + echo ""; + } + echo ""; + + if (!$ID + && Session::haveRight('followup', ITILFollowup::ADDALLTICKET)) { + + echo ""; + // Need comment right to add a followup with the actiontime + echo ""; + echo ""; + echo ""; + } + + echo "
".$tt->getBeginHiddenFieldText('status'); + printf(__('%1$s%2$s'), __('Status'), $tt->getMandatoryMark('status')); + echo $tt->getEndHiddenFieldText('status').""; + echo $tt->getBeginHiddenFieldValue('status'); + if ($canupdate) { + self::dropdownStatus(['value' => $this->fields["status"], + 'showtype' => 'allowed']); + TicketValidation::alertValidation($this, 'status'); + } else { + echo self::getStatus($this->fields["status"]); + if ($this->canReopen()) { + $link = $this->getLinkURL(). "&_openfollowup=1&forcetab="; + $link .= "Ticket$1"; + echo " ". __('Reopen').""; + } + } + echo $tt->getEndHiddenFieldValue('status', $this); + + echo "".$tt->getBeginHiddenFieldText('requesttypes_id'); + printf(__('%1$s%2$s'), __('Request source'), $tt->getMandatoryMark('requesttypes_id')); + echo $tt->getEndHiddenFieldText('requesttypes_id').""; + echo $tt->getBeginHiddenFieldValue('requesttypes_id'); + if ($canupdate) { + RequestType::dropdown(['value' => $this->fields["requesttypes_id"], 'condition' => ['is_active' => 1, 'is_ticketheader' => 1]]); + } else { + echo Dropdown::getDropdownName('glpi_requesttypes', $this->fields["requesttypes_id"]); + echo Html::hidden('requesttypes_id', ['value' => $this->fields["requesttypes_id"]]); + } + echo $tt->getEndHiddenFieldValue('requesttypes_id', $this); + echo "
".$tt->getBeginHiddenFieldText('urgency'); + printf(__('%1$s%2$s'), __('Urgency'), $tt->getMandatoryMark('urgency')); + echo $tt->getEndHiddenFieldText('urgency').""; + + if ($canupdate || $can_requester) { + echo $tt->getBeginHiddenFieldValue('urgency'); + $idurgency = self::dropdownUrgency(['value' => $this->fields["urgency"]]); + echo $tt->getEndHiddenFieldValue('urgency', $this); + + } else { + $idurgency = "value_urgency".mt_rand(); + echo ""; + echo $tt->getBeginHiddenFieldValue('urgency'); + echo parent::getUrgencyName($this->fields["urgency"]); + echo $tt->getEndHiddenFieldValue('urgency', $this); + } + echo ""; + if (!$ID) { + echo $tt->getBeginHiddenFieldText('_add_validation'); + printf(__('%1$s%2$s'), __('Approval request'), $tt->getMandatoryMark('_add_validation')); + echo $tt->getEndHiddenFieldText('_add_validation'); + } else { + echo $tt->getBeginHiddenFieldText('global_validation'); + echo __('Approval'); + echo $tt->getEndHiddenFieldText('global_validation'); + } + echo ""; + if (!$ID) { + echo $tt->getBeginHiddenFieldValue('_add_validation'); + $validation_right = ''; + if (($options['type'] == self::INCIDENT_TYPE) + && Session::haveRight('ticketvalidation', TicketValidation::CREATEINCIDENT)) { + $validation_right = 'validate_incident'; + } + if (($options['type'] == self::DEMAND_TYPE) + && Session::haveRight('ticketvalidation', TicketValidation::CREATEREQUEST)) { + $validation_right = 'validate_request'; + } + + if (!empty($validation_right)) { + echo ""; + + $params = ['name' => "users_id_validate", + 'entity' => $this->fields['entities_id'], + 'right' => $validation_right, + 'users_id_validate' => $options['users_id_validate']]; + TicketValidation::dropdownValidator($params); + } + echo $tt->getEndHiddenFieldValue('_add_validation', $this); + if ($tt->isPredefinedField('global_validation')) { + echo ""; + } + } else { + echo $tt->getBeginHiddenFieldValue('global_validation'); + + if (Session::haveRightsOr('ticketvalidation', TicketValidation::getCreateRights()) + && $canupdate) { + TicketValidation::dropdownStatus('global_validation', + ['global' => true, + 'value' => $this->fields['global_validation']]); + } else { + echo TicketValidation::getStatus($this->fields['global_validation']); + } + echo $tt->getEndHiddenFieldValue('global_validation', $this); + + } + echo "
".$tt->getBeginHiddenFieldText('impact'); + printf(__('%1$s%2$s'), __('Impact'), $tt->getMandatoryMark('impact')); + echo $tt->getEndHiddenFieldText('impact').""; + echo $tt->getBeginHiddenFieldValue('impact'); + + if ($canupdate) { + $idimpact = self::dropdownImpact(['value' => $this->fields["impact"]]); + } else { + $idimpact = "value_impact".mt_rand(); + echo ""; + echo parent::getImpactName($this->fields["impact"]); + } + echo $tt->getEndHiddenFieldValue('impact', $this); + echo "".$tt->getBeginHiddenFieldText('locations_id'); + printf(__('%1$s%2$s'), __('Location'), $tt->getMandatoryMark('locations_id')); + echo $tt->getEndHiddenFieldText('locations_id').""; + echo $tt->getBeginHiddenFieldValue('locations_id'); + if ($canupdate) { + Location::dropdown(['value' => $this->fields['locations_id'], + 'entity' => $this->fields['entities_id']]); + } else { + echo Dropdown::getDropdownName('glpi_locations', $this->fields["locations_id"]); + } + echo $tt->getEndHiddenFieldValue('locations_id', $this); + echo "
".$tt->getBeginHiddenFieldText('priority'); + printf(__('%1$s%2$s'), __('Priority'), $tt->getMandatoryMark('priority')); + echo $tt->getEndHiddenFieldText('priority').""; + $idajax = 'change_priority_' . mt_rand(); + + if ($canpriority + && !$tt->isHiddenField('priority')) { + $idpriority = parent::dropdownPriority(['value' => $this->fields["priority"], + 'withmajor' => true]); + $idpriority = 'dropdown_priority'.$idpriority; + echo " "; + + } else { + $idpriority = 0; + echo $tt->getBeginHiddenFieldValue('priority'); + echo "".parent::getPriorityName($this->fields["priority"]).""; + echo ""; + echo $tt->getEndHiddenFieldValue('priority', $this); + } + + if ($canupdate || $can_requester) { + $params = ['urgency' => '__VALUE0__', + 'impact' => '__VALUE1__', + 'priority' => $idpriority]; + Ajax::updateItemOnSelectEvent(['dropdown_urgency'.$idurgency, + 'dropdown_impact'.$idimpact], + $idajax, + $CFG_GLPI["root_doc"]."/ajax/priority.php", $params); + } + echo "".$tt->getBeginHiddenFieldText('items_id'); + printf(__('%1$s%2$s'), _n('Associated element', 'Associated elements', Session::getPluralNumber()), $tt->getMandatoryMark('items_id')); + echo $tt->getEndHiddenFieldText('items_id'); + echo ""; + echo $tt->getBeginHiddenFieldValue('items_id'); + $options['_canupdate'] = Session::haveRight('ticket', CREATE); + if ($options['_canupdate']) { + Item_Ticket::itemAddForm($this, $options); + } + echo $tt->getEndHiddenFieldValue('items_id', $this); + echo "
".$tt->getBeginHiddenFieldText('actiontime'); + printf(__('%1$s%2$s'), __('Total duration'), $tt->getMandatoryMark('actiontime')); + echo $tt->getEndHiddenFieldText('actiontime').""; + echo $tt->getBeginHiddenFieldValue('actiontime'); + Dropdown::showTimeStamp('actiontime', ['value' => $options['actiontime'], + 'addfirstminutes' => true]); + echo $tt->getEndHiddenFieldValue('actiontime', $this); + echo "
"; + if ($ID) { + $this->showActorsPartForm($ID, $options); + } + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo "'; + echo ""; + echo ""; + + if (!in_array($this->fields['status'], $this->getClosedStatusArray())) { + // View files added + echo ""; + // Permit to add doc when creating a ticket + echo ""; + echo ""; + echo ""; + } + + Plugin::doHook("post_item_form", ['item' => $this, 'options' => &$options]); + + echo "
".$tt->getBeginHiddenFieldText('name'); + printf(__('%1$s%2$s'), __('Title'), $tt->getMandatoryMark('name')); + echo $tt->getEndHiddenFieldText('name').""; + if ($canupdate || $can_requester) { + echo $tt->getBeginHiddenFieldValue('name'); + echo "isMandatoryField('name') ? " required='required'" : '') . + " value=\"".Html::cleanInputText($this->fields["name"])."\">"; + echo $tt->getEndHiddenFieldValue('name', $this); + } else { + if (empty($this->fields["name"])) { + echo __('Without title'); + } else { + echo $this->fields["name"]; + } + } + echo "
".$tt->getBeginHiddenFieldText('content'); + printf(__('%1$s%2$s'), __('Description'), $tt->getMandatoryMark('content')); + if ($canupdate || $can_requester) { + $content = Toolbox::unclean_cross_side_scripting_deep(Html::entity_decode_deep($this->fields['content'])); + Html::showTooltip(nl2br(Html::Clean($content))); + } + echo $tt->getEndHiddenFieldText('content').""; + + echo $tt->getBeginHiddenFieldValue('content'); + $rand = mt_rand(); + $rand_text = mt_rand(); + $rows = 10; + $content_id = "content$rand"; + + $content = $this->fields['content']; + if (!isset($options['template_preview'])) { + $content = Html::cleanPostForTextArea($content); + } + + $content = Html::setRichTextContent( + $content_id, + $content, + $rand, + !$canupdate + ); + + echo "
"; + if ($canupdate || $can_requester) { + $uploads = []; + if (isset($this->input['_content'])) { + $uploads['_content'] = $this->input['_content']; + $uploads['_tag_content'] = $this->input['_tag_content']; + } + Html::textarea([ + 'name' => 'content', + 'filecontainer' => 'content_info', + 'editor_id' => $content_id, + 'required' => $tt->isMandatoryField('content'), + 'rows' => $rows, + 'enable_richtext' => true, + 'value' => $content, + 'uploads' => $uploads, + ]); + echo "
"; + } else { + echo Toolbox::getHtmlToDisplay($content); + } + echo $tt->getEndHiddenFieldValue('content', $this); + + echo "
". _n('Linked ticket', 'Linked tickets', + Session::getPluralNumber()); + $rand_linked_ticket = mt_rand(); + if ($canupdate) { + echo "" . __s('Add') . ""; + } + echo '"; + if ($canupdate) { + echo ""; + + if (isset($options["_link"]) + && !empty($options["_link"]['tickets_id_2'])) { + echo ""; + } + } + + Ticket_Ticket::displayLinkedTicketsTo($ID); + echo "
"; + echo $tt->getBeginHiddenFieldText('_documents_id'); + $doctitle = sprintf(__('File (%s)'), Document::getMaxUploadSize()); + printf(__('%1$s%2$s'), $doctitle, $tt->getMandatoryMark('_documents_id')); + // Do not show if hidden. + if (!$tt->isHiddenField('_documents_id')) { + DocumentType::showAvailableTypesLink(); + } + echo $tt->getEndHiddenFieldText('_documents_id'); + echo ""; + // Do not set values + echo $tt->getEndHiddenFieldValue('_documents_id'); + if ($tt->isPredefinedField('_documents_id')) { + if (isset($options['_documents_id']) + && is_array($options['_documents_id']) + && count($options['_documents_id'])) { + + echo "".__('Default documents:').''; + echo "
"; + $doc = new Document(); + foreach ($options['_documents_id'] as $key => $val) { + if ($doc->getFromDB($val)) { + echo ""; + echo "- ".$doc->getNameID()."
"; + } + } + } + } + if (!$tt->isHiddenField('_documents_id')) { + $uploads = []; + if (isset($this->input['_filename'])) { + $uploads['_filename'] = $this->input['_filename']; + $uploads['_tag_filename'] = $this->input['_tag_filename']; + } + Html::file([ + 'filecontainer' => 'fileupload_info_ticket', + // 'editor_id' => $content_id, + 'showtitle' => false, + 'multiple' => true, + 'uploads' => $uploads, + ]); + } + echo "
"; + + $display_save_btn = (!array_key_exists('locked', $options) || !$options['locked']) + && ($canupdate || $can_requester || $canpriority || $canassign || $canassigntome); + + if ($display_save_btn + && !$options['template_preview']) { + if ($ID) { + echo "
"; + if ($this->fields["is_deleted"] == 1) { + if (self::canDelete()) { + echo "      "; + } + } else { + if ($display_save_btn) { + echo "      "; + } + } + if ($this->fields["is_deleted"] == 1) { + if (self::canPurge()) { + echo ""; + } + } else { + if ($this->canDeleteItem()) { + echo ""; + } + } + echo ""; + echo "
"; + } else { + echo "
"; + $add_params = ['name' => 'add']; + if ($options['_promoted_fup_id']) { + $add_params['confirm'] = __('Confirm the promotion?'); + } + echo Html::submit(_x('button', 'Add'), $add_params); + if ($tt->isField('id') && ($tt->fields['id'] > 0)) { + echo ""; + echo ""; + } + echo Html::hidden('_promoted_fup_id', ['value' => $options['_promoted_fup_id']]); + echo Html::hidden('_skip_promoted_fields', ['value' => $options['_skip_promoted_fields']]); + echo '
'; + } + } + + echo ""; + echo ""; + + echo ""; + + if (!$options['template_preview']) { + Html::closeForm(); + } + + return true; + } + + + /** + * @param $size (default 25) + **/ + static function showDocumentAddButton($size = 25) { + echo ""; + echo "'); + nbfiles++; + if (nbfiles==maxfiles) { + ".Html::jsHide('addfilebutton')." + } + }\" + " . __s('Add') . ""; + } + + + /** + * @param $start + * @param $status (default ''process) + * @param $showgrouptickets (true by default) + */ + static function showCentralList($start, $status = "process", $showgrouptickets = true) { + global $DB; + + if (!Session::haveRightsOr(self::$rightname, [CREATE, self::READALL, self::READASSIGN]) + && !Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())) { + + return false; + } + + $JOINS = []; + $WHERE = [ + 'is_deleted' => 0 + ]; + $search_users_id = [ + 'glpi_tickets_users.users_id' => Session::getLoginUserID(), + 'glpi_tickets_users.type' => CommonITILActor::REQUESTER + ]; + $search_assign = [ + 'glpi_tickets_users.users_id' => Session::getLoginUserID(), + 'glpi_tickets_users.type' => CommonITILActor::ASSIGN + ]; + $search_observer = [ + 'glpi_tickets_users.users_id' => Session::getLoginUserID(), + 'glpi_tickets_users.type' => CommonITILActor::OBSERVER + ]; + + if ($showgrouptickets) { + $search_users_id = [0]; + $search_assign = [0]; + + if (count($_SESSION['glpigroups'])) { + $search_assign = [ + 'glpi_groups_tickets.groups_id' => $_SESSION['glpigroups'], + 'glpi_groups_tickets.type' => CommonITILActor::ASSIGN + ]; + + if (Session::haveRight(self::$rightname, self::READGROUP)) { + $search_users_id = [ + 'glpi_groups_tickets.groups_id' => $_SESSION['glpigroups'], + 'glpi_groups_tickets.type' => CommonITILActor::REQUESTER + ]; + $search_observer = [ + 'glpi_groups_tickets.groups_id' => $_SESSION['glpigroups'], + 'glpi_groups_tickets.type' => CommonITILActor::OBSERVER + ]; + } + } + } + + switch ($status) { + case "waiting" : // waiting tickets + $WHERE = array_merge( + $WHERE, + $search_assign, + ['glpi_tickets.status' => self::WAITING] + ); + break; + + case "process" : // planned or assigned tickets + $WHERE = array_merge( + $WHERE, + $search_assign, + ['glpi_tickets.status' => self::getProcessStatusArray()] + ); + break; + + case "toapprove" : //tickets waiting for approval + $ORWHERE = ['AND' => $search_users_id]; + if (!$showgrouptickets && Session::haveRight('ticket', Ticket::SURVEY)) { + $ORWHERE[] = ['glpi_tickets.users_id_recipient' => Session::getLoginUserID()]; + } + $WHERE[] = ['OR' => $ORWHERE]; + $WHERE['glpi_tickets.status'] = self::SOLVED; + break; + + case "tovalidate" : // tickets waiting for validation + $JOINS['LEFT JOIN'] = [ + 'glpi_ticketvalidations' => [ + 'ON' => [ + 'glpi_ticketvalidations' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ] + ]; + $WHERE = array_merge( + $WHERE, + [ + 'users_id_validate' => Session::getLoginUserID(), + 'glpi_ticketvalidations.status' => CommonITILValidation::WAITING, + 'glpi_tickets.global_validation' => CommonITILValidation::WAITING, + 'NOT' => [ + 'glpi_tickets.status' => [self::SOLVED, self::CLOSED] + ] + ] + ); + break; + + case "validation.rejected" : // tickets with rejected validation (approval) + case "rejected": //old ambiguous key + $WHERE = array_merge( + $WHERE, + $search_assign, + [ + 'glpi_tickets.status' => ['<>', self::CLOSED], + 'glpi_tickets.global_validation' => CommonITILValidation::REFUSED + ] + ); + break; + + case "solution.rejected" : // tickets with rejected solution + $subq = new QuerySubQuery([ + 'SELECT' => 'last_solution.id', + 'FROM' => 'glpi_itilsolutions AS last_solution', + 'WHERE' => [ + 'last_solution.items_id' => new QueryExpression($DB->quoteName('glpi_tickets.id')), + 'last_solution.itemtype' => 'Ticket' + ], + 'ORDER' => 'last_solution.id DESC', + 'LIMIT' => 1 + ]); + + $JOINS['LEFT JOIN'] = [ + 'glpi_itilsolutions' => [ + 'ON' => [ + 'glpi_itilsolutions' => 'id', + $subq + ] + ] + ]; + + $WHERE = array_merge( + $WHERE, + $search_assign, + [ + 'glpi_tickets.status' => ['<>', self::CLOSED], + 'glpi_itilsolutions.status' => CommonITILValidation::REFUSED + ] + ); + break; + case "observed" : + $WHERE = array_merge( + $WHERE, + $search_observer, + [ + 'glpi_tickets.status' => [ + self::INCOMING, + self::PLANNED, + self::ASSIGNED, + self::WAITING + ], + 'NOT' => [ + $search_assign, + $search_users_id + ] + ] + ); + break; + + case "survey" : // tickets dont l'enqu??te de satisfaction n'est pas remplie et encore valide + $JOINS['INNER JOIN'] = [ + 'glpi_ticketsatisfactions' => [ + 'ON' => [ + 'glpi_ticketsatisfactions' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ], + 'glpi_entities' => [ + 'ON' => [ + 'glpi_tickets' => 'entities_id', + 'glpi_entities' => 'id' + ] + ] + ]; + $ORWHERE = ['AND' => $search_users_id]; + if (!$showgrouptickets && Session::haveRight('ticket', Ticket::SURVEY)) { + $ORWHERE[] = ['glpi_tickets.users_id_recipient' => Session::getLoginUserID()]; + } + $WHERE[] = ['OR' => $ORWHERE]; + + $WHERE = array_merge( + $WHERE, + [ + 'glpi_tickets.status' => self::CLOSED, + ['OR' => [ + 'glpi_entities.inquest_duration' => 0, + new \QueryExpression( + 'DATEDIFF(ADDDATE(' . $DB->quoteName('glpi_ticketsatisfactions.date_begin') . + ', INTERVAL ' . $DB->quoteName('glpi_entities.inquest_duration') . ' DAY), CURDATE()) > 0' + ) + ]], + 'glpi_ticketsatisfactions.date_answered' => null + ] + ); + break; + + case "requestbyself" : // on affiche les tickets demand??s le user qui sont planifi??s ou assign??s + // ?? quelqu'un d'autre (exclut les self-tickets) + + default : + $WHERE = array_merge( + $WHERE, + $search_users_id, + [ + 'glpi_tickets.status' => [ + self::INCOMING, + self::PLANNED, + self::ASSIGNED, + self::WAITING + ], + 'NOT' => $search_assign + ] + ); + } + + $criteria = [ + 'SELECT' => ['glpi_tickets.id', 'glpi_tickets.date_mod'], + 'DISTINCT' => true, + 'FROM' => 'glpi_tickets', + 'LEFT JOIN' => [ + 'glpi_tickets_users' => [ + 'ON' => [ + 'glpi_tickets_users' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ], + 'glpi_groups_tickets' => [ + 'ON' => [ + 'glpi_groups_tickets' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ] + ], + 'WHERE' => $WHERE + getEntitiesRestrictCriteria('glpi_tickets'), + 'ORDERBY' => 'glpi_tickets.date_mod DESC' + ]; + if (count($JOINS)) { + $criteria = array_merge_recursive($criteria, $JOINS); + } + $iterator = $DB->request($criteria); + $numrows = count($iterator); + $number = 0; + + if ($_SESSION['glpidisplay_count_on_home'] > 0) { + $iterator = $DB->request( + $criteria + [ + 'START' => (int)$start, + 'LIMIT' => (int)$_SESSION['glpidisplay_count_on_home'] + ] + ); + $number = count($iterator); + } + + if ($numrows > 0) { + echo ""; + echo ""; + if ($number) { + echo ""; + echo ""; + echo ""; + echo ""; + while ($data = $iterator->next()) { + self::showVeryShort($data['id'], $forcetab); + } + } + echo "
"; + + $options = [ + 'criteria' => [], + 'reset' => 'reset', + ]; + $forcetab = ''; + if ($showgrouptickets) { + switch ($status) { + case "toapprove" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::SOLVED; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 71; // groups_id + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'mygroups'; + $options['criteria'][1]['link'] = 'AND'; + $forcetab = 'Ticket$2'; + + echo "". + Html::makeTitle(__('Your tickets to close'), $number, $numrows).""; + break; + + case "waiting" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::WAITING; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 8; // groups_id_assign + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'mygroups'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Tickets on pending status'), $number, $numrows).""; + break; + + case "process" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = 'process'; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 8; // groups_id_assign + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'mygroups'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Tickets to be processed'), $number, $numrows).""; + break; + + case "observed": + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = 'notold'; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 65; // groups_id + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'mygroups'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your observed tickets'), $number, $numrows).""; + break; + + case "requestbyself" : + default : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = 'notold'; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 71; // groups_id + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'mygroups'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your tickets in progress'), $number, $numrows).""; + } + + } else { + switch ($status) { + case "waiting" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::WAITING; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 5; // users_id_assign + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = Session::getLoginUserID(); + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Tickets on pending status'), $number, $numrows).""; + break; + + case "process" : + $options['criteria'][0]['field'] = 5; // users_id_assign + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = Session::getLoginUserID(); + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 12; // status + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'process'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Tickets to be processed'), $number, $numrows).""; + break; + + case "tovalidate" : + $options['criteria'][0]['field'] = 55; // validation status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = CommonITILValidation::WAITING; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 59; // validation aprobator + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = Session::getLoginUserID(); + $options['criteria'][1]['link'] = 'AND'; + + $options['criteria'][2]['field'] = 12; // validation aprobator + $options['criteria'][2]['searchtype'] = 'equals'; + $options['criteria'][2]['value'] = 'old'; + $options['criteria'][2]['link'] = 'AND NOT'; + + $options['criteria'][3]['field'] = 52; // global validation status + $options['criteria'][3]['searchtype'] = 'equals'; + $options['criteria'][3]['value'] = CommonITILValidation::WAITING; + $options['criteria'][3]['link'] = 'AND'; + $forcetab = 'TicketValidation$1'; + + echo "". + Html::makeTitle(__('Your tickets to validate'), $number, $numrows).""; + + break; + + case "validation.rejected" : + case "rejected" : // old ambiguous key + $options['criteria'][0]['field'] = 52; // validation status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = CommonITILValidation::REFUSED; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 5; // assign user + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = Session::getLoginUserID(); + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your tickets having rejected approval status'), $number, $numrows).""; + + break; + + case "solution.rejected" : + $options['criteria'][0]['field'] = 39; // last solution status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = CommonITILValidation::REFUSED; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 5; // assign user + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = Session::getLoginUserID(); + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your tickets having rejected solution'), $number, $numrows).""; + + break; + + case "toapprove" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::SOLVED; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 4; // users_id_assign + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = Session::getLoginUserID(); + $options['criteria'][1]['link'] = 'AND'; + + $options['criteria'][2]['field'] = 22; // users_id_recipient + $options['criteria'][2]['searchtype'] = 'equals'; + $options['criteria'][2]['value'] = Session::getLoginUserID(); + $options['criteria'][2]['link'] = 'OR'; + + $options['criteria'][3]['field'] = 12; // status + $options['criteria'][3]['searchtype'] = 'equals'; + $options['criteria'][3]['value'] = self::SOLVED; + $options['criteria'][3]['link'] = 'AND'; + + $forcetab = 'Ticket$2'; + + echo "". + Html::makeTitle(__('Your tickets to close'), $number, $numrows).""; + break; + + case "observed" : + $options['criteria'][0]['field'] = 66; // users_id + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = Session::getLoginUserID(); + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 12; // status + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'notold'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your observed tickets'), $number, $numrows).""; + break; + + case "survey" : + $options['criteria'][0]['field'] = 12; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::CLOSED; + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 60; // enquete generee + $options['criteria'][1]['searchtype'] = 'contains'; + $options['criteria'][1]['value'] = '^'; + $options['criteria'][1]['link'] = 'AND'; + + $options['criteria'][2]['field'] = 61; // date_answered + $options['criteria'][2]['searchtype'] = 'contains'; + $options['criteria'][2]['value'] = 'NULL'; + $options['criteria'][2]['link'] = 'AND'; + + if (Session::haveRight('ticket', Ticket::SURVEY)) { + $options['criteria'][3]['field'] = 22; // author + $options['criteria'][3]['searchtype'] = 'equals'; + $options['criteria'][3]['value'] = Session::getLoginUserID(); + $options['criteria'][3]['link'] = 'AND'; + } else { + $options['criteria'][3]['field'] = 4; // requester + $options['criteria'][3]['searchtype'] = 'equals'; + $options['criteria'][3]['value'] = Session::getLoginUserID(); + $options['criteria'][3]['link'] = 'AND'; + } + $forcetab = 'Ticket$3'; + + echo "". + Html::makeTitle(__('Satisfaction survey'), $number, $numrows).""; + break; + + case "requestbyself" : + default : + $options['criteria'][0]['field'] = 4; // users_id + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = Session::getLoginUserID(); + $options['criteria'][0]['link'] = 'AND'; + + $options['criteria'][1]['field'] = 12; // status + $options['criteria'][1]['searchtype'] = 'equals'; + $options['criteria'][1]['value'] = 'notold'; + $options['criteria'][1]['link'] = 'AND'; + + echo "". + Html::makeTitle(__('Your tickets in progress'), $number, $numrows).""; + } + } + + echo "
".__('ID')."".__('Requester').""._n('Associated element', 'Associated elements', Session::getPluralNumber())."".__('Description')."
"; + + } + } + + /** + * Get tickets count + * + * @param $foruser boolean : only for current login user as requester (false by default) + **/ + static function showCentralCount($foruser = false) { + global $DB, $CFG_GLPI; + + // show a tab with count of jobs in the central and give link + if (!Session::haveRight(self::$rightname, self::READALL) && !self::canCreate()) { + return false; + } + if (!Session::haveRight(self::$rightname, self::READALL)) { + $foruser = true; + } + + $table = self::getTable(); + $criteria = [ + 'SELECT' => [ + 'glpi_tickets.status', + 'COUNT DISTINCT' => ["$table.id AS COUNT"], + ], + 'FROM' => $table, + 'WHERE' => getEntitiesRestrictCriteria($table), + 'GROUPBY' => 'status' + ]; + + if ($foruser) { + $criteria['LEFT JOIN'] = [ + 'glpi_tickets_users' => [ + 'ON' => [ + 'glpi_tickets_users' => 'tickets_id', + $table => 'id', [ + 'AND' => [ + 'glpi_tickets_users.type' => CommonITILActor::REQUESTER + ] + ] + ] + ], + 'glpi_ticketvalidations' => [ + 'ON' => [ + 'glpi_ticketvalidations' => 'tickets_id', + $table => 'id' + ] + ] + ]; + + if (Session::haveRight(self::$rightname, self::READGROUP) + && isset($_SESSION["glpigroups"]) + && count($_SESSION["glpigroups"])) { + $criteria['LEFT JOIN']['glpi_groups_tickets'] = [ + 'ON' => [ + 'glpi_groups_tickets' => 'tickets_id', + $table => 'id', [ + 'AND' => ['glpi_groups_tickets.type' => CommonITILActor::REQUESTER] + ] + ] + ]; + } + } + + if ($foruser) { + $ORWHERE = ['OR' => [ + 'glpi_tickets_users.users_id' => Session::getLoginUserID(), + 'glpi_tickets.users_id_recipient' => Session::getLoginUserID(), + 'glpi_ticketvalidations.users_id_validate' => Session::getLoginUserID() + ]]; + + if (Session::haveRight(self::$rightname, self::READGROUP) + && isset($_SESSION["glpigroups"]) + && count($_SESSION["glpigroups"])) { + $ORWHERE['OR']['glpi_groups_tickets.groups_id'] = $_SESSION['glpigroups']; + } + $criteria['WHERE'][] = $ORWHERE; + } + + $deleted_criteria = $criteria; + $criteria['WHERE']['glpi_tickets.is_deleted'] = 0; + $deleted_criteria['WHERE']['glpi_tickets.is_deleted'] = 1; + $iterator = $DB->request($criteria); + $deleted_iterator = $DB->request($deleted_criteria); + + $status = []; + foreach (self::getAllStatusArray() as $key => $val) { + $status[$key] = 0; + } + + while ($data = $iterator->next()) { + $status[$data["status"]] = $data["COUNT"]; + } + + $number_deleted = 0; + while ($data = $deleted_iterator->next()) { + $number_deleted += $data["COUNT"]; + } + + $options = [ + 'criteria' => [], + 'reset' => 'reset', + ]; + $options['criteria'][0]['field'] = 12; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = 'process'; + $options['criteria'][0]['link'] = 'AND'; + + echo ""; + echo ""; + echo " + "; + + if (Session::haveRightsOr('ticketvalidation', TicketValidation::getValidateRights())) { + $number_waitapproval = TicketValidation::getNumberToValidate(Session::getLoginUserID()); + + $opt = [ + 'criteria' => [], + 'reset' => 'reset', + ]; + $opt['criteria'][0]['field'] = 55; // validation status + $opt['criteria'][0]['searchtype'] = 'equals'; + $opt['criteria'][0]['value'] = CommonITILValidation::WAITING; + $opt['criteria'][0]['link'] = 'AND'; + + $opt['criteria'][1]['field'] = 59; // validation aprobator + $opt['criteria'][1]['searchtype'] = 'equals'; + $opt['criteria'][1]['value'] = Session::getLoginUserID(); + $opt['criteria'][1]['link'] = 'AND'; + + echo ""; + echo ""; + echo ""; + } + + foreach ($status as $key => $val) { + $options['criteria'][0]['value'] = $key; + echo ""; + echo ""; + echo ""; + } + + $options['criteria'][0]['value'] = 'all'; + $options['is_deleted'] = 1; + echo ""; + echo ""; + echo ""; + + echo "
"; + + if (Session::getCurrentInterface() != "central") { + echo "". + __('Create a ticket')." ". __s('Add').""; + } else { + echo "".__('Ticket followup').""; + } + echo "
"._n('Ticket', 'Tickets', Session::getPluralNumber()).""._x('quantity', 'Number')."
".__('Ticket waiting for your approval')."".$number_waitapproval."
".self::getStatus($key)."$val
".__('Deleted')."".$number_deleted."

"; + } + + + static function showCentralNewList() { + global $DB; + + if (!Session::haveRight(self::$rightname, self::READALL)) { + return false; + } + + $criteria = self::getCommonCriteria(); + $criteria['WHERE'] = [ + 'status' => self::INCOMING, + 'is_deleted' => 0 + ] + getEntitiesRestrictCriteria(self::getTable()); + $criteria['LIMIT'] = (int)$_SESSION['glpilist_limit']; + $iterator = $DB->request($criteria); + $number = count($iterator); + + if ($number > 0) { + Session::initNavigateListItems('Ticket'); + + $options = [ + 'criteria' => [], + 'reset' => 'reset', + ]; + $options['criteria'][0]['field'] = 12; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = self::INCOMING; + $options['criteria'][0]['link'] = 'AND'; + + echo "
"; + //TRANS: %d is the number of new tickets + echo ""; + + self::commonListHeader(Search::HTML_OUTPUT); + + while ($data = $iterator->next()) { + Session::addToNavigateListItems('Ticket', $data["id"]); + self::showShort($data["id"]); + } + echo "
".sprintf(_n('%d new ticket', '%d new tickets', $number), $number); + echo "".__('Show all').""; + echo "
"; + + } else { + echo "
"; + echo ""; + echo ""; + echo "
".__('No ticket found.')."
"; + echo "

"; + } + } + + /** + * Display tickets for an item + * + * Will also display tickets of linked items + * + * @param CommonDBTM $item CommonDBTM object + * @param integer $withtemplate (default 0) + * + * @return void (display a table) + **/ + static function showListForItem(CommonDBTM $item, $withtemplate = 0) { + global $DB; + + if (!Session::haveRightsOr(self::$rightname, + [self::READALL, self::READMY, self::READASSIGN, CREATE])) { + return false; + } + + if ($item->isNewID($item->getID())) { + return false; + } + + $criteria = self::getCommonCriteria(); + $restrict = []; + $options = [ + 'criteria' => [], + 'reset' => 'reset', + ]; + + switch ($item->getType()) { + case 'User' : + $restrict['glpi_tickets_users.users_id'] = $item->getID(); + $restrict['glpi_tickets_users.type'] = CommonITILActor::REQUESTER; + + $options['criteria'][0]['field'] = 4; // status + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = $item->getID(); + $options['criteria'][0]['link'] = 'AND'; + break; + + case 'SLA' : + $restrict[] = [ + 'OR' => [ + 'slas_id_tto' => $item->getID(), + 'slas_id_ttr' => $item->getID() + ] + ]; + $criteria['ORDERBY'] = 'glpi_tickets.time_to_resolve DESC'; + + $options['criteria'][0]['field'] = 30; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = $item->getID(); + $options['criteria'][0]['link'] = 'AND'; + break; + + case 'OLA' : + $restrict[] = [ + 'OR' => [ + 'olas_id_tto' => $item->getID(), + 'olas_id_ttr' => $item->getID() + ] + ]; + $criteria['ORDERBY'] = 'glpi_tickets.internal_time_to_resolve DESC'; + + $options['criteria'][0]['field'] = 30; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = $item->getID(); + $options['criteria'][0]['link'] = 'AND'; + break; + + case 'Supplier' : + $restrict['glpi_suppliers_tickets.suppliers_id'] = $item->getID(); + $restrict['glpi_suppliers_tickets.type'] = CommonITILActor::ASSIGN; + + $options['criteria'][0]['field'] = 6; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = $item->getID(); + $options['criteria'][0]['link'] = 'AND'; + break; + + case 'Group' : + // Mini search engine + if ($item->haveChildren()) { + $tree = Session::getSavedOption(__CLASS__, 'tree', 0); + echo ""; + echo ""; + echo "
".__('Last tickets')."
"; + echo __('Child groups')." "; + Dropdown::showYesNo('tree', $tree, -1, + ['on_change' => 'reloadTab("start=0&tree="+this.value)']); + } else { + $tree = 0; + } + echo "
"; + + $restrict['glpi_groups_tickets.groups_id'] = ($tree ? getSonsOf('glpi_groups', $item->getID()) : $item->getID()); + $restrict['glpi_groups_tickets.type'] = CommonITILActor::REQUESTER; + + $options['criteria'][0]['field'] = 71; + $options['criteria'][0]['searchtype'] = ($tree ? 'under' : 'equals'); + $options['criteria'][0]['value'] = $item->getID(); + $options['criteria'][0]['link'] = 'AND'; + break; + + default : + $restrict['glpi_items_tickets.items_id'] = $item->getID(); + $restrict['glpi_items_tickets.itemtype'] = $item->getType(); + + // you can only see your tickets + if (!Session::haveRight(self::$rightname, self::READALL)) { + $or = [ + 'glpi_tickets.users_id_recipient' => Session::getLoginUserID(), + [ + 'AND' => [ + 'glpi_tickets_users.tickets_id' => new \QueryExpression('glpi_tickets.id'), + 'glpi_tickets_users.users_id' => Session::getLoginUserID() + ] + ] + ]; + if (count($_SESSION['glpigroups'])) { + $or['glpi_groups_tickets.groups_id'] = $_SESSION['glpigroups']; + } + $restrict[] = ['OR' => $or]; + } + + $options['criteria'][0]['field'] = 12; + $options['criteria'][0]['searchtype'] = 'equals'; + $options['criteria'][0]['value'] = 'all'; + $options['criteria'][0]['link'] = 'AND'; + + $options['metacriteria'][0]['itemtype'] = $item->getType(); + $options['metacriteria'][0]['field'] = Search::getOptionNumber($item->getType(), + 'id'); + $options['metacriteria'][0]['searchtype'] = 'equals'; + $options['metacriteria'][0]['value'] = $item->getID(); + $options['metacriteria'][0]['link'] = 'AND'; + break; + } + + $criteria['WHERE'] = $restrict + getEntitiesRestrictCriteria(self::getTable()); + $criteria['WHERE']['glpi_tickets.is_deleted'] = 0; + $criteria['LIMIT'] = (int)$_SESSION['glpilist_limit']; + $iterator = $DB->request($criteria); + $number = count($iterator); + + $colspan = 11; + if (count($_SESSION["glpiactiveentities"]) > 1) { + $colspan++; + } + + // Ticket for the item + // Link to open a new ticket + if ($item->getID() + && !$item->isDeleted() + && Ticket::isPossibleToAssignType($item->getType()) + && self::canCreate() + && !(!empty($withtemplate) && ($withtemplate == 2)) + && (!isset($item->fields['is_template']) || ($item->fields['is_template'] == 0))) { + echo "
"; + Html::showSimpleForm(Ticket::getFormURL(), + '_add_fromitem', __('New ticket for this item...'), + ['itemtype' => $item->getType(), + 'items_id' => $item->getID()]); + echo "
"; + } + + if ($item->getID() + && ($item->getType() == 'User') + && self::canCreate() + && !(!empty($withtemplate) && ($withtemplate == 2))) { + echo "
"; + Html::showSimpleForm(Ticket::getFormURL(), + '_add_fromitem', __('New ticket for this item...'), + ['_users_id_requester' => $item->getID()]); + echo "
"; + } + + echo "
"; + + if ($number > 0) { + echo ""; + if (Session::haveRight(self::$rightname, self::READALL)) { + Session::initNavigateListItems('Ticket', + //TRANS : %1$s is the itemtype name, %2$s is the name of the item (used for headings of a list) + sprintf(__('%1$s = %2$s'), $item->getTypeName(1), + $item->getName())); + + echo ""; + } else { + echo ""; + } + + } else { + echo "
"; + $title = sprintf(_n('Last %d ticket', 'Last %d tickets', $number), $number); + $link = "".__('Show all').""; + $title = printf(__('%1$s (%2$s)'), $title, $link); + echo "
".__("You don't have right to see all tickets")."
"; + echo ""; + } + + // Ticket list + if ($number > 0) { + self::commonListHeader(Search::HTML_OUTPUT); + + while ($data = $iterator->next()) { + Session::addToNavigateListItems('Ticket', $data["id"]); + self::showShort($data["id"]); + } + self::commonListHeader(Search::HTML_OUTPUT); + } + + echo "
".__('No ticket found.')."
"; + + // Tickets for linked items + $linkeditems = $item->getLinkedItems(); + $restrict = []; + if (count($linkeditems)) { + foreach ($linkeditems as $ltype => $tab) { + foreach ($tab as $lID) { + $restrict[] = ['AND' => ['itemtype' => $ltype, 'items_id' => $lID]]; + } + } + } + + if (count($restrict) + && Session::haveRight(self::$rightname, self::READALL)) { + $criteria = self::getCommonCriteria(); + $criteria['WHERE'] = ['OR' => $restrict] + + getEntitiesRestrictCriteria(self::getTable()); + $iterator = $DB->request($criteria); + $number = count($iterator); + + echo "
"; + echo ""; + if ($number > 0) { + self::commonListHeader(Search::HTML_OUTPUT); + while ($data = $iterator->next()) { + // Session::addToNavigateListItems(TRACKING_TYPE,$data["id"]); + self::showShort($data["id"]); + } + self::commonListHeader(Search::HTML_OUTPUT); + } else { + echo ""; + } + echo "
"; + echo _n('Ticket on linked items', 'Tickets on linked items', $number); + echo "
".__('No ticket found.')."
"; + + } // Subquery for linked item + + } + + /** + * @param $ID + * @param $forcetab string name of the tab to force at the display (default '') + **/ + static function showVeryShort($ID, $forcetab = '') { + // Prints a job in short form + // Should be called in a -segment + // Print links or not in case of user view + // Make new job object and fill it from database, if success, print it + $showprivate = false; + if (Session::haveRight('followup', ITILFollowup::SEEPRIVATE)) { + $showprivate = true; + } + + $job = new self(); + $rand = mt_rand(); + if ($job->getFromDBwithData($ID, 0)) { + $bgcolor = $_SESSION["glpipriority_".$job->fields["priority"]]; + $name = sprintf(__('%1$s: %2$s'), __('ID'), $job->fields["id"]); + // $rand = mt_rand(); + echo ""; + echo ""; + echo ""; + + echo ""; + + // Finish Line + echo ""; + } else { + echo ""; + echo ""; + } + } + + + public static function getCommonCriteria() { + $criteria = parent::getCommonCriteria(); + + $criteria['LEFT JOIN']['glpi_tickettasks'] = [ + 'ON' => [ + self::getTable() => 'id', + 'glpi_tickettasks' => 'tickets_id' + ] + ]; + + return $criteria; + } + + + /** + * @deprecated 9.5.0 + */ + static function getCommonSelect() { + Toolbox::deprecated('Use getCommonCriteria with db iterator'); + $SELECT = ""; + if (count($_SESSION["glpiactiveentities"])>1) { + $SELECT .= ", `glpi_entities`.`completename` AS entityname, + `glpi_tickets`.`entities_id` AS entityID "; + } + + return " DISTINCT `glpi_tickets`.*, + `glpi_itilcategories`.`completename` AS catname + $SELECT"; + } + + + /** + * @deprecated 9.5.0 + */ + static function getCommonLeftJoin() { + Toolbox::deprecated('Use getCommonCriteria with db iterator'); + + $FROM = ""; + if (count($_SESSION["glpiactiveentities"])>1) { + $FROM .= " LEFT JOIN `glpi_entities` + ON (`glpi_entities`.`id` = `glpi_tickets`.`entities_id`) "; + } + + return " LEFT JOIN `glpi_groups_tickets` + ON (`glpi_tickets`.`id` = `glpi_groups_tickets`.`tickets_id`) + LEFT JOIN `glpi_tickets_users` + ON (`glpi_tickets`.`id` = `glpi_tickets_users`.`tickets_id`) + LEFT JOIN `glpi_suppliers_tickets` + ON (`glpi_tickets`.`id` = `glpi_suppliers_tickets`.`tickets_id`) + LEFT JOIN `glpi_itilcategories` + ON (`glpi_tickets`.`itilcategories_id` = `glpi_itilcategories`.`id`) + LEFT JOIN `glpi_tickettasks` + ON (`glpi_tickets`.`id` = `glpi_tickettasks`.`tickets_id`) + LEFT JOIN `glpi_items_tickets` + ON (`glpi_tickets`.`id` = `glpi_items_tickets`.`tickets_id`) + $FROM"; + + } + + + /** + * @param $output + **/ + static function showPreviewAssignAction($output) { + + //If ticket is assign to an object, display this information first + if (isset($output["entities_id"]) + && isset($output["items_id"]) + && isset($output["itemtype"])) { + + if ($item = getItemForItemtype($output["itemtype"])) { + if ($item->getFromDB($output["items_id"])) { + echo ""; + echo ""; + + echo ""; + echo ""; + } + } + + unset($output["items_id"]); + unset($output["itemtype"]); + } + unset($output["entities_id"]); + return $output; + } + + + /** + * Give cron information + * + * @param $name : task's name + * + * @return array of information + **/ + static function cronInfo($name) { + + switch ($name) { + case 'closeticket' : + return ['description' => __('Automatic tickets closing')]; + + case 'alertnotclosed' : + return ['description' => __('Not solved tickets')]; + + case 'createinquest' : + return ['description' => __('Generation of satisfaction surveys')]; + + case 'purgeticket': + return ['description' => __('Automatic closed tickets purge')]; + } + return []; + } + + + /** + * Cron for ticket's automatic close + * + * @param $task : crontask object + * + * @return integer (0 : nothing done - 1 : done) + **/ + static function cronCloseTicket($task) { + global $DB; + + $ticket = new self(); + + // Recherche des entit??s + $tot = 0; + + $entities = $DB->request( + [ + 'SELECT' => 'id', + 'FROM' => Entity::getTable(), + ] + ); + foreach ($entities as $entity) { + $delay = Entity::getUsedConfig('autoclose_delay', $entity['id'], '', Entity::CONFIG_NEVER); + if ($delay >= 0) { + $criteria = [ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'entities_id' => $entity['id'], + 'status' => self::SOLVED, + 'is_deleted' => 0 + ] + ]; + + if ($delay > 0) { + $calendars_id = Entity::getUsedConfig('calendars_id', $entity['id']); + $calendar = new Calendar(); + if ($calendars_id && $calendar->getFromDB($calendars_id) && $calendar->hasAWorkingDay()) { + $end_date = $calendar->computeEndDate( + date('Y-m-d H:i:s'), + - $delay * DAY_TIMESTAMP, + 0, + true + ); + $criteria['WHERE']['solvedate'] = ['<=', $end_date]; + } else { + // no calendar, remove all days + $criteria['WHERE'][] = new \QueryExpression( + "ADDDATE(" . $DB->quoteName('solvedate') . ", INTERVAL $delay DAY) < NOW()" + ); + } + } + + $nb = 0; + $iterator = $DB->request($criteria); + while ($tick = $iterator->next()) { + $ticket->update([ + 'id' => $tick['id'], + 'status' => self::CLOSED, + '_auto_update' => true + ]); + $nb++; + } + + if ($nb) { + $tot += $nb; + $task->addVolume($nb); + $task->log(Dropdown::getDropdownName('glpi_entities', $entity['id'])." : $nb"); + } + } + } + + return ($tot > 0 ? 1 : 0); + } + + + /** + * Cron for alert old tickets which are not solved + * + * @param $task : crontask object + * + * @return integer (0 : nothing done - 1 : done) + **/ + static function cronAlertNotClosed($task) { + global $DB, $CFG_GLPI; + + if (!$CFG_GLPI["use_notifications"]) { + return 0; + } + // Recherche des entit??s + $tot = 0; + foreach (Entity::getEntitiesToNotify('notclosed_delay') as $entity => $value) { + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'entities_id' => $entity, + 'is_deleted' => 0, + 'status' => [ + self::INCOMING, + self::ASSIGNED, + self::PLANNED, + self::WAITING + ], + 'closedate' => null, + new QueryExpression("ADDDATE(" . $DB->quoteName('date') . ", INTERVAL $value DAY) < NOW()") + ] + ]); + $tickets = []; + while ($tick = $iterator->next()) { + $tickets[] = $tick; + } + + if (!empty($tickets)) { + if (NotificationEvent::raiseEvent('alertnotclosed', new self(), + ['items' => $tickets, + 'entities_id' => $entity])) { + + $tot += count($tickets); + $task->addVolume(count($tickets)); + $task->log(sprintf(__('%1$s: %2$s'), + Dropdown::getDropdownName('glpi_entities', $entity), + count($tickets))); + } + } + } + + return ($tot > 0 ? 1 : 0); + } + + + /** + * Cron for ticketsatisfaction's automatic generated + * + * @param $task : crontask object + * + * @return integer (0 : nothing done - 1 : done) + **/ + static function cronCreateInquest($task) { + global $DB; + + $conf = new Entity(); + $inquest = new TicketSatisfaction(); + $tot = 0; + $maxentity = []; + $tabentities = []; + + $rate = Entity::getUsedConfig('inquest_config', 0, 'inquest_rate'); + if ($rate > 0) { + $tabentities[0] = $rate; + } + + foreach ($DB->request('glpi_entities') as $entity) { + $rate = Entity::getUsedConfig('inquest_config', $entity['id'], 'inquest_rate'); + $parent = Entity::getUsedConfig('inquest_config', $entity['id'], 'entities_id'); + + if ($rate > 0) { + $tabentities[$entity['id']] = $rate; + } + } + + foreach ($tabentities as $entity => $rate) { + $parent = Entity::getUsedConfig('inquest_config', $entity, 'entities_id'); + $delay = Entity::getUsedConfig('inquest_config', $entity, 'inquest_delay'); + $duration = Entity::getUsedConfig('inquest_config', $entity, 'inquest_duration'); + $type = Entity::getUsedConfig('inquest_config', $entity); + $max_closedate = Entity::getUsedConfig('inquest_config', $entity, 'max_closedate'); + + $table = self::getTable(); + $iterator = $DB->request([ + 'SELECT' => [ + "$table.id", + "$table.closedate", + "$table.entities_id" + ], + 'FROM' => $table, + 'LEFT JOIN' => [ + 'glpi_ticketsatisfactions' => [ + 'ON' => [ + 'glpi_ticketsatisfactions' => 'tickets_id', + 'glpi_tickets' => 'id' + ] + ], + 'glpi_entities' => [ + 'ON' => [ + 'glpi_tickets' => 'entities_id', + 'glpi_entities' => 'id' + ] + ] + ], + 'WHERE' => [ + "$table.entities_id" => $entity, + "$table.is_deleted" => 0, + "$table.status" => self::CLOSED, + "$table.closedate" => ['>', $max_closedate], + new QueryExpression("ADDDATE(" . $DB->quoteName("$table.closedate") . ", INTERVAL $delay DAY) <= NOW()"), + new QueryExpression("ADDDATE(" . $DB->quoteName("glpi_entities.max_closedate") . ", INTERVAL $duration DAY) <= NOW()"), + "glpi_ticketsatisfactions.id" => null + ], + 'ORDERBY' => 'closedate ASC' + ]); + + $nb = 0; + $max_closedate = ''; + + while ($tick = $iterator->next()) { + $max_closedate = $tick['closedate']; + if (mt_rand(1, 100) <= $rate) { + if ($inquest->add(['tickets_id' => $tick['id'], + 'date_begin' => $_SESSION["glpi_currenttime"], + 'entities_id' => $tick['entities_id'], + 'type' => $type])) { + $nb++; + } + } + } + + // conservation de toutes les max_closedate des entites filles + if (!empty($max_closedate) + && (!isset($maxentity[$parent]) + || ($max_closedate > $maxentity[$parent]))) { + $maxentity[$parent] = $max_closedate; + } + + if ($nb) { + $tot += $nb; + $task->addVolume($nb); + $task->log(sprintf(__('%1$s: %2$s'), + Dropdown::getDropdownName('glpi_entities', $entity), $nb)); + } + } + + // Sauvegarde du max_closedate pour ne pas tester les m??me tickets 2 fois + foreach ($maxentity as $parent => $maxdate) { + $conf->getFromDB($parent); + $conf->update(['id' => $conf->fields['id'], + //'entities_id' => $parent, + 'max_closedate' => $maxdate]); + } + + return ($tot > 0 ? 1 : 0); + } + + + /** + * Cron for ticket's automatic purge + * + * @param CronTask $task CronTask object + * + * @return integer (0 : nothing done - 1 : done) + **/ + static function cronPurgeTicket(CronTask $task) { + global $DB; + + $ticket = new self(); + + //search entities + $tot = 0; + + $entities = $DB->request( + [ + 'SELECT' => 'id', + 'FROM' => Entity::getTable(), + ] + ); + + foreach ($entities as $entity) { + $delay = Entity::getUsedConfig('autopurge_delay', $entity['id'], '', Entity::CONFIG_NEVER); + if ($delay >= 0) { + $criteria = [ + 'FROM' => $ticket->getTable(), + 'WHERE' => [ + 'entities_id' => $entity['id'], + 'status' => $ticket->getClosedStatusArray(), + ] + ]; + + if ($delay > 0) { + // remove all days + $criteria['WHERE'][] = new \QueryExpression("ADDDATE(`closedate`, INTERVAL ".$delay." DAY) < NOW()"); + } + + $iterator = $DB->request($criteria); + $nb = 0; + + foreach ($iterator as $tick) { + $ticket->delete( + [ + 'id' => $tick['id'], + '_auto_update' => true + ], + true + ); + $nb++; + } + + if ($nb) { + $tot += $nb; + $task->addVolume($nb); + $task->log(Dropdown::getDropdownName('glpi_entities', $entity['id'])." : $nb"); + } + } + } + + return ($tot > 0 ? 1 : 0); + } + + /** + * Display debug information for current object + **/ + function showDebug() { + NotificationEvent::debugEvent($this); + } + + + /** + * @since 0.85 + * + * @see commonDBTM::getRights() + **/ + function getRights($interface = 'central') { + + $values = parent::getRights(); + unset($values[READ]); + $values[self::READMY] = __('See my ticket'); + //TRANS: short for : See tickets created by my groups + $values[self::READGROUP] = ['short' => __('See group ticket'), + 'long' => __('See tickets created by my groups')]; + if ($interface == 'central') { + $values[self::READALL] = __('See all tickets'); + //TRANS: short for : See assigned tickets (group associated) + $values[self::READASSIGN] = ['short' => __('See assigned'), + 'long' => __('See assigned tickets')]; + //TRANS: short for : Assign a ticket + $values[self::ASSIGN] = ['short' => __('Assign'), + 'long' => __('Assign a ticket')]; + //TRANS: short for : Steal a ticket + $values[self::STEAL] = ['short' => __('Steal'), + 'long' => __('Steal a ticket')]; + //TRANS: short for : To be in charge of a ticket + $values[self::OWN] = ['short' => __('Beeing in charge'), + 'long' => __('To be in charge of a ticket')]; + $values[self::CHANGEPRIORITY] = __('Change the priority'); + $values[self::SURVEY] = ['short' => __('Approve solution/Reply survey (my ticket)'), + 'long' => __('Approve solution and reply to survey for ticket created by me')]; + } + if ($interface == 'helpdesk') { + unset($values[UPDATE], $values[DELETE], $values[PURGE]); + } + return $values; + } + + /** + * Convert img of the collector for ticket + * + * @since 0.85 + * + * @param string $html html content of input + * @param array $files filenames + * @param array $tags image tags + * + * @return string html content + **/ + static function convertContentForTicket($html, $files, $tags) { + + preg_match_all("/src\s*=\s*['|\"](.+?)['|\"]/", $html, $matches, PREG_PATTERN_ORDER); + if (isset($matches[1]) && count($matches[1])) { + // Get all image src + + foreach ($matches[1] as $src) { + // Set tag if image matches + foreach ($files as $data => $filename) { + if (preg_match("/".$data."/i", $src)) { + $html = preg_replace("`]*\>`", "

".Document::getImageTag($tags[$filename])."

", $html); + } + } + } + } + + return $html; + + } + + + /** + * @since 0.90 + * + * @param $tickets_id + * @param $action (default 'add') + **/ + static function getSplittedSubmitButtonHtml($tickets_id, $action = "add") { + + $locale = _sx('button', 'Add'); + if ($action == 'update') { + $locale = _x('button', 'Save'); + } + $ticket = new self(); + $ticket->getFromDB($tickets_id); + $all_status = Ticket::getAllowedStatusArray($ticket->fields['status']); + $rand = mt_rand(); + + $html = "
+ +   +
    "; + foreach ($all_status as $status_key => $status_label) { + $checked = ""; + if ($status_key == $ticket->fields['status']) { + $checked = "checked='checked'"; + } + $html .= "
  • "; + $html .= ""; + $html .= ""; + $html .= "
  • "; + } + $html .= "
"; + + $html.= ""; + return $html; + } + + + /** + * Get correct Calendar: Entity or Sla + * + * @since 0.90.4 + * + **/ + function getCalendar() { + + if (isset($this->fields['slas_id_ttr']) && $this->fields['slas_id_ttr'] > 0) { + $slm = new SLM(); + if ($slm->getFromDB($this->fields['slas_id_ttr'])) { + // not -1: calendar of the entity + if ($slm->getField('calendars_id') >= 0) { + return $slm->getField('calendars_id'); + } + } + } + return parent::getCalendar(); + } + + + /** + * Select a field using standard system + * + * @since 9.1 + */ + function getValueToSelect($field_id_or_search_options, $name = '', $values = '', $options = []) { + if (isset($field_id_or_search_options['linkfield'])) { + switch ($field_id_or_search_options['linkfield']) { + case 'requesttypes_id': + if (isset($field_id_or_search_options['joinparams']) && Toolbox::in_array_recursive('glpi_itilfollowups', $field_id_or_search_options['joinparams'])) { + $opt = ['is_itilfollowup' => 1]; + } else { + $opt = [ + 'OR' => [ + 'is_mail_default' => 1, + 'is_ticketheader' => 1 + ] + ]; + } + if ($field_id_or_search_options['linkfield'] == $name) { + $opt['is_active'] = 1; + } + if (isset( $options['condition'] )) { + if (!is_array($options['condition'])) { + $options['condition'] = [$options['condition']]; + } + $opt = array_merge($opt, $options['condition']); + } + $options['condition'] = $opt; + break; + } + } + return parent::getValueToSelect($field_id_or_search_options, $name, $values, $options); + } + + function showStatsDates() { + $now = time(); + $date_creation = strtotime($this->fields['date']); + $date_takeintoaccount = $date_creation + $this->fields['takeintoaccount_delay_stat']; + if ($date_takeintoaccount == $date_creation) { + $date_takeintoaccount = 0; + } + $internal_time_to_own = strtotime($this->fields['internal_time_to_own']); + $time_to_own = strtotime($this->fields['time_to_own']); + $internal_time_to_resolve = strtotime($this->fields['internal_time_to_resolve']); + $time_to_resolve = strtotime($this->fields['time_to_resolve']); + $solvedate = strtotime($this->fields['solvedate']); + $closedate = strtotime($this->fields['closedate']); + $goal_takeintoaccount = ($date_takeintoaccount > 0 ? $date_takeintoaccount : $now); + $goal_solvedate = ($solvedate > 0 ? $solvedate : $now); + + $sla = new SLA; + $ola = new OLA; + $sla_tto_link = + $sla_ttr_link = + $ola_tto_link = + $ola_ttr_link = ""; + + if ($sla->getFromDB($this->fields['slas_id_tto'])) { + $sla_tto_link = " + "; + } + if ($sla->getFromDB($this->fields['slas_id_ttr'])) { + $sla_ttr_link = " + "; + } + if ($ola->getFromDB($this->fields['olas_id_tto'])) { + $ola_tto_link = " + "; + } + if ($ola->getFromDB($this->fields['olas_id_ttr'])) { + $ola_ttr_link = " + "; + } + + $dates = [ + $date_creation.'_date_creation' => [ + 'timestamp' => $date_creation, + 'label' => __('Opening date'), + 'class' => 'creation' + ], + $date_takeintoaccount.'_date_takeintoaccount' => [ + 'timestamp' => $date_takeintoaccount, + 'label' => __('Take into account'), + 'class' => 'checked' + ], + $internal_time_to_own.'_internal_time_to_own' => [ + 'timestamp' => $internal_time_to_own, + 'label' => __('Internal time to own')." ".$ola_tto_link, + 'class' => ($internal_time_to_own < $goal_takeintoaccount + ? 'passed' : '')." ". + ($date_takeintoaccount != '' + ? 'checked' : ''), + ], + $time_to_own.'_time_to_own' => [ + 'timestamp' => $time_to_own, + 'label' => __('Time to own')." ".$sla_tto_link, + 'class' => ($time_to_own < $goal_takeintoaccount + ? 'passed' : '')." ". + ($date_takeintoaccount != '' + ? 'checked' : ''), + ], + $internal_time_to_resolve.'_internal_time_to_resolve' => [ + 'timestamp' => $internal_time_to_resolve, + 'label' => __('Internal time to resolve')." ".$ola_ttr_link, + 'class' => ($internal_time_to_resolve < $goal_solvedate + ? 'passed' : '')." ". + ($solvedate != '' + ? 'checked' : '') + ], + $time_to_resolve.'_time_to_resolve' => [ + 'timestamp' => $time_to_resolve, + 'label' => __('Time to resolve')." ".$sla_ttr_link, + 'class' => ($time_to_resolve < $goal_solvedate + ? 'passed' : '')." ". + ($solvedate != '' + ? 'checked' : '') + ], + $solvedate.'_solvedate' => [ + 'timestamp' => $solvedate, + 'label' => __('Resolution date'), + 'class' => 'checked' + ], + $closedate.'_closedate' => [ + 'timestamp' => $closedate, + 'label' => __('Closing date'), + 'class' => 'end' + ] + ]; + + Html::showDatesTimelineGraph([ + 'title' => _n('Date', 'Dates', Session::getPluralNumber()), + 'dates' => $dates, + 'add_now' => $this->getField('closedate') == "" + ]); + } + + /** + * Fill input with values related to business rules. + * + * @param array $input + * + * @return void + */ + private function fillInputForBusinessRules(array &$input) { + global $DB; + + $entities_id = isset($input['entities_id']) + ? $input['entities_id'] + : $this->fields['entities_id']; + + // If creation date is not set, then we're called during ticket creation + $creation_date = !empty($this->fields['date_creation']) + ? strtotime($this->fields['date_creation']) + : time(); + + // add calendars matching date creation (for business rules) + $calendars = []; + $ite_calendar = $DB->request([ + 'SELECT' => ['id'], + 'FROM' => Calendar::getTable(), + 'WHERE' => getEntitiesRestrictCriteria('', '', $entities_id, true) + ]); + foreach ($ite_calendar as $calendar_data) { + $calendar = new Calendar(); + $calendar->getFromDB($calendar_data['id']); + if ($calendar->isAWorkingHour($creation_date)) { + $calendars[] = $calendar_data['id']; + } + } + if (count($calendars)) { + $input['_date_creation_calendars_id'] = $calendars; + } + } + + /** + * Build parent condition for search + * + * @param string $fieldID field used in the condition: tickets_id, items_id + * + * @return string + */ + public static function buildCanViewCondition($fieldID) { + + $condition = ""; + $user = Session::getLoginUserID(); + $groups = "'" . implode("','", $_SESSION['glpigroups']) . "'"; + + $requester = CommonITILActor::REQUESTER; + $assign = CommonITILActor::ASSIGN; + $obs = CommonITILActor::OBSERVER; + + // Avoid empty IN () + if ($groups == "''") { + $groups = '-1'; + } + + if (Session::haveRight("ticket", Ticket::READMY)) { + // Add tickets where the users is requester, observer or recipient + // Subquery for requester/observer user + $user_query = "SELECT `tickets_id` + FROM `glpi_tickets_users` + WHERE `users_id` = '$user' AND type IN ($requester, $obs)"; + $condition .= "OR `$fieldID` IN ($user_query) "; + + // Subquery for recipient + $recipient_query = "SELECT `id` + FROM `glpi_tickets` + WHERE `users_id_recipient` = '$user'"; + $condition .= "OR `$fieldID` IN ($recipient_query) "; + } + + if (Session::haveRight("ticket", Ticket::READGROUP)) { + // Add tickets where the users is in a requester or observer group + // Subquery for requester/observer group + $group_query = "SELECT `tickets_id` + FROM `glpi_groups_tickets` + WHERE `groups_id` IN ($groups) AND type IN ($requester, $obs)"; + $condition .= "OR `$fieldID` IN ($group_query) "; + } + + if (Session::haveRightsOr("ticket", [ + Ticket::OWN, + Ticket::READASSIGN + ])) { + // Add tickets where the users is assigned + // Subquery for assigned user + $user_query = "SELECT `tickets_id` + FROM `glpi_tickets_users` + WHERE `users_id` = '$user' AND type = $assign"; + $condition .= "OR `$fieldID` IN ($user_query) "; + } + + if (Session::haveRight("ticket", Ticket::READASSIGN)) { + // Add tickets where the users is part of an assigned group + // Subquery for assigned group + $group_query = "SELECT `tickets_id` + FROM `glpi_groups_tickets` + WHERE `groups_id` IN ($groups) AND type = $assign"; + $condition .= "OR `$fieldID` IN ($group_query) "; + + if (Session::haveRight('ticket', Ticket::ASSIGN)) { + // Add new tickets + $tickets_query = "SELECT `id` + FROM `glpi_tickets` + WHERE `status` = '" . CommonITILObject::INCOMING . "'"; + $condition .= "OR `$fieldID` IN ($tickets_query) "; + } + } + + if (Session::haveRightsOr('ticketvalidation', [ + TicketValidation::VALIDATEINCIDENT, + TicketValidation::VALIDATEREQUEST + ])) { + // Add tickets where the users is the validator + // Subquery for validator + $validation_query = "SELECT `tickets_id` + FROM `glpi_ticketvalidations` + WHERE `users_id_validate` = '$user'"; + $condition .= "OR `$fieldID` IN ($validation_query) "; + } + + return $condition; + } + + public function getForbiddenSingleMassiveActions() { + $excluded = parent::getForbiddenSingleMassiveActions(); + if (in_array($this->fields['status'], $this->getClosedStatusArray())) { + //for closed Tickets, only keep transfer and unlock + $excluded[] = 'TicketValidation:submit_validation'; + $excluded[] = 'Ticket:*'; + } + return $excluded; + } + + public function getWhitelistedSingleMassiveActions() { + $whitelist = parent::getWhitelistedSingleMassiveActions(); + + if (!in_array($this->fields['status'], $this->getClosedStatusArray())) { + $whitelist[] = 'Item_Ticket:add_item'; + } + + return $whitelist; + } + + /** + * Merge one or more tickets into another existing ticket. + * Optionally sub-items like followups, documents, and tasks can be copied into the merged ticket. + * If a ticket cannot be merged, the process continues on to the next ticket. + * @param int $merge_target_id The ID of the ticket that the other tickets will be merged into + * @param array $ticket_ids Array of IDs of tickets to merge into the ticket with ID $merge_target_id + * @param array $params Array of parameters for the ticket merge. + * linktypes - Array of itemtypes that will be duplicated into the ticket $merge_target_id. + * By default, no sub-items are copied. Currently supported link types are ITILFollowup, Document, and TicketTask. + * full_transaction - Boolean value indicating if the entire merge must complete successfully, or if partial merges are allowed. + * By default, the full merge must complete. On failure, all database operations performed are rolled back. + * link_type - Integer indicating the link type of the merged tickets (See types in Ticket_Ticket). + * By default, this is Ticket_Ticket::SON_OF. To disable linking, use 0 or a negative value. + * append_actors - Array of actor types to migrate into the ticket $merge_ticket. See types in CommonITILActor. + * By default, all actors are added to the ticket. + * @param array $status Reference array that this function uses to store the status of each ticket attempted to be merged. + * id => status (0 = Success, 1 = Error, 2 = Insufficient Rights). + * @return boolean True if the merge was successful if "full_transaction" is true. + * Otherwise, true if any ticket was successfully merged. + * @since 9.5.0 + */ + public static function merge(int $merge_target_id, array $ticket_ids, array &$status, array $params = []) { + global $DB; + $p = [ + 'linktypes' => [], + 'full_transaction' => true, + 'link_type' => Ticket_Ticket::SON_OF, + 'append_actors' => [CommonITILActor::REQUESTER, CommonITILActor::OBSERVER, CommonITILActor::ASSIGN] + ]; + $p = array_replace($p, $params); + $ticket = new Ticket(); + $merge_target = new Ticket(); + $merge_target->getFromDB($merge_target_id); + $fup = new ITILFollowup(); + $document_item = new Document_Item(); + $task = new TicketTask(); + + if (!$merge_target->canAddFollowups()) { + foreach ($ticket_ids as $id) { + Toolbox::logError(sprintf(__('Not enough rights to merge tickets %d and %d'), $merge_target_id, $id)); + // Set status = 2 : Rights issue + $status[$id] = 2; + } + return false; + } + $in_transaction = $DB->inTransaction(); + + if ($p['full_transaction'] && !$in_transaction) { + $DB->beginTransaction(); + } + foreach ($ticket_ids as $id) { + try { + if (!$p['full_transaction'] && !$in_transaction) { + $DB->beginTransaction(); + } + if ($merge_target->canUpdateItem() && $ticket->can($id, DELETE)) { + if (!$ticket->getFromDB($id)) { + //Cannot retrieve ticket. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to load ticket %d'), $id), 1); + } + //Build followup from the original ticket + $input = [ + 'itemtype' => 'Ticket', + 'items_id' => $merge_target_id, + 'content' => $DB->escape($ticket->fields['name']."\n\n".$ticket->fields['content']), + 'users_id' => $ticket->fields['users_id_recipient'], + 'date_creation' => $ticket->fields['date_creation'], + 'date_mod' => $ticket->fields['date_mod'], + 'date' => $ticket->fields['date_creation'], + 'sourceitems_id' => $ticket->getID() + ]; + if (!$fup->add($input)) { + //Cannot add followup. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to add followup to ticket %d'), $merge_target_id), 1); + } + if (in_array('ITILFollowup', $p['linktypes'])) { + // Copy any followups to the ticket + $tomerge = $fup->find([ + 'items_id' => $id, + 'itemtype' => 'Ticket' + ]); + foreach ($tomerge as $fup2) { + $fup2['items_id'] = $merge_target_id; + $fup2['sourceitems_id'] = $id; + $fup2['content'] = $DB->escape($fup2['content']); + unset($fup2['id']); + if (!$fup->add($fup2)) { + // Cannot add followup. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to add followup to ticket %d'), $merge_target_id), 1); + } + } + } + if (in_array('TicketTask', $p['linktypes'])) { + $merge_tmp = ['tickets_id' => $merge_target_id]; + if (!$task->can(-1, CREATE, $merge_tmp)) { + throw new \RuntimeException(sprintf(__('Not enough rights to merge tickets %d and %d'), $merge_target_id, $id), 2); + } + // Copy any tasks to the ticket + $tomerge = $task->find([ + 'tickets_id' => $id + ]); + foreach ($tomerge as $task2) { + $task2['tickets_id'] = $merge_target_id; + $task2['sourceitems_id'] = $id; + $task2['content'] = $DB->escape($task2['content']); + unset($task2['id']); + unset($task2['uuid']); + if (!$task->add($task2)) { + //Cannot add followup. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to add task to ticket %d'), $merge_target_id), 1); + } + } + } + if (in_array('Document', $p['linktypes'])) { + if (!$merge_target->canAddItem('Document')) { + throw new \RuntimeException(sprintf(__('Not enough rights to merge tickets %d and %d'), $merge_target_id, $id), 2); + } + $tomerge = $document_item->find([ + 'itemtype' => 'Ticket', + 'items_id' => $id, + 'NOT' => [ + 'documents_id' => new \QuerySubQuery([ + 'SELECT' => 'documents_id', + 'FROM' => $document_item->getTable(), + 'WHERE' => [ + 'itemtype' => 'Ticket', + 'items_id' => $merge_target_id + ] + ]) + ] + ]); + + foreach ($tomerge as $document_item2) { + $document_item2['items_id'] = $merge_target_id; + unset($document_item2['id']); + if (!$document_item->add($document_item2)) { + //Cannot add document. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to add document to ticket %d'), $merge_target_id), 1); + } + } + } + if ($p['link_type'] > 0 && $p['link_type'] < 5) { + //Add relation (this is parent of merge target) + $tt = new Ticket_Ticket(); + $linkparams = [ + 'link' => $p['link_type'], + 'tickets_id_1' => $id, + 'tickets_id_2' => $merge_target_id + ]; + $tt->deleteByCriteria([ + 'OR' => [ + [ + 'AND' => [ + 'tickets_id_1' => $merge_target_id, + 'tickets_id_2' => $id + ] + ], + [ + 'AND' => [ + 'tickets_id_2' => $merge_target_id, + 'tickets_id_1' => $id + ] + ] + ] + ]); + if (!$tt->add($linkparams)) { + //Cannot link tickets. Abort/fail the merge + throw new \RuntimeException(sprintf(__('Failed to link tickets %d and %d'), $merge_target_id, $id), 1); + } + } + if (isset($p['append_actors'])) { + $tu = new Ticket_User(); + $existing_users = $tu->find(['tickets_id' => $merge_target_id]); + $gt = new Group_Ticket(); + $existing_groups = $gt->find(['tickets_id' => $merge_target_id]); + $st = new Supplier_Ticket(); + $existing_suppliers = $st->find(['tickets_id' => $merge_target_id]); + + foreach ($p['append_actors'] as $actor_type) { + $users = $tu->find([ + 'tickets_id' => $id, + 'type' => $actor_type + ]); + $groups = $gt->find([ + 'tickets_id' => $id, + 'type' => $actor_type + ]); + $suppliers = $st->find([ + 'tickets_id' => $id, + 'type' => $actor_type + ]); + $users = array_filter($users, function($user) use ($existing_users) { + foreach ($existing_users as $existing_user) { + if ($existing_user['users_id'] > 0 && $user['users_id'] > 0 && + $existing_user['users_id'] === $user['users_id'] && + $existing_user['type'] === $user['type']) { + // Internal users + return false; + } else if ($existing_user['users_id'] == 0 && $user['users_id'] == 0 && + $existing_user['alternative_email'] === $user['alternative_email'] && + $existing_user['type'] === $user['type']) { + // External users + return false; + } + } + return true; + }); + $groups = array_filter($groups, function($group) use ($existing_groups) { + foreach ($existing_groups as $existing_group) { + if ($existing_group['groups_id'] === $group['groups_id'] && + $existing_group['type'] === $group['type']) { + return false; + } + } + return true; + }); + $suppliers = array_filter($suppliers, function($supplier) use ($existing_suppliers) { + foreach ($existing_suppliers as $existing_supplier) { + if ($existing_supplier['suppliers_id'] > 0 && $supplier['suppliers_id'] > 0 && + $existing_supplier['suppliers_id'] === $supplier['suppliers_id'] && + $existing_supplier['type'] === $supplier['type']) { + // Internal suppliers + return false; + } else if ($existing_supplier['suppliers_id'] == 0 && $supplier['suppliers_id'] == 0 && + $existing_supplier['alternative_email'] === $supplier['alternative_email'] && + $existing_supplier['type'] === $supplier['type']) { + // External suppliers + return false; + } + } + return true; + }); + foreach ($users as $user) { + $user['tickets_id'] = $merge_target_id; + unset($user['id']); + $tu->add($user); + } + foreach ($groups as $group) { + $group['tickets_id'] = $merge_target_id; + unset($group['id']); + $gt->add($group); + } + foreach ($suppliers as $supplier) { + $supplier['tickets_id'] = $merge_target_id; + unset($supplier['id']); + $st->add($supplier); + } + } + } + //Delete this ticket + if (!$ticket->delete(['id' => $id, '_disablenotif' => true])) { + throw new \RuntimeException(sprintf(__('Failed to delete ticket %d'), $id), 1); + } + if (!$p['full_transaction'] && !$in_transaction) { + $DB->commit(); + } + $status[$id] = 0; + Event::log($merge_target_id, 'ticket', 4, 'tracking', + sprintf(__('%s merges ticket %s into %s'), $_SESSION['glpiname'], + $id, $merge_target_id)); + } else { + throw new \RuntimeException(sprintf(__('Not enough rights to merge tickets %d and %d'), $merge_target_id, $id), 2); + } + } catch (\RuntimeException $e) { + if ($e->getCode() < 1 || $e->getCode() > 2) { + $status[$id] = 1; + } else { + $status[$id] = $e->getCode(); + } + Toolbox::logError($e->getMessage()); + if (!$in_transaction) { + $DB->rollBack(); + } + if ($p['full_transaction']) { + return false; + } + } + } + if ($p['full_transaction'] && !$in_transaction) { + $DB->commit(); + } + return true; + } + + + static function getIcon() { + return "fas fa-exclamation-circle"; + } +}
+
+  $name +
+
"; + + if (isset($job->users[CommonITILActor::REQUESTER]) + && count($job->users[CommonITILActor::REQUESTER])) { + foreach ($job->users[CommonITILActor::REQUESTER] as $d) { + if ($d["users_id"] > 0) { + $userdata = getUserName($d["users_id"], 2); + $name = "".$userdata['name'].""; + $name = sprintf(__('%1$s %2$s'), $name, + Html::showToolTip($userdata["comment"], + ['link' => $userdata["link"], + 'display' => false])); + echo $name; + } else { + echo $d['alternative_email']." "; + } + echo "
"; + } + } + + if (isset($job->groups[CommonITILActor::REQUESTER]) + && count($job->groups[CommonITILActor::REQUESTER])) { + foreach ($job->groups[CommonITILActor::REQUESTER] as $d) { + echo Dropdown::getDropdownName("glpi_groups", $d["groups_id"]); + echo "
"; + } + } + + echo "
"; + if (!empty($job->hardwaredatas)) { + foreach ($job->hardwaredatas as $hardwaredatas) { + if ($hardwaredatas->canView()) { + echo $hardwaredatas->getTypeName()." - "; + echo "".$hardwaredatas->getLink()."
"; + } else if ($hardwaredatas) { + echo $hardwaredatas->getTypeName()." - "; + echo "".$hardwaredatas->getNameID()."
"; + } + } + } else { + echo __('General'); + } + echo "
"; + + $link = "getNameID().""; + $link = sprintf(__('%1$s (%2$s)'), $link, + sprintf(__('%1$s - %2$s'), $job->numberOfFollowups($showprivate), + $job->numberOfTasks($showprivate))); + $content = Toolbox::unclean_cross_side_scripting_deep(html_entity_decode($job->fields['content'], + ENT_QUOTES, + "UTF-8")); + $link = printf(__('%1$s %2$s'), $link, + Html::showToolTip(nl2br(Html::Clean($content)), + ['applyto' => 'ticket'.$job->fields["id"].$rand, + 'display' => false])); + echo "
".__('No ticket in progress.')."
".__('Assign equipment')."".$item->getLink(['comments' => true])."