ticket.class.php 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148
  1. <?php
  2. /* Copyright (C) 2013-2018 Jean-François Ferry <hello@librethic.io>
  3. * Copyright (C) 2016 Christophe Battarel <christophe@altairis.fr>
  4. * Copyright (C) 2019-2023 Frédéric France <frederic.france@netlogic.fr>
  5. * Copyright (C) 2020 Laurent Destailleur <eldy@users.sourceforge.net>
  6. * Copyright (C) 2023 Charlene Benke <charlene@patas-monkey.com>
  7. * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
  8. * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
  9. *
  10. * This program is free software; you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation; either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  22. */
  23. /**
  24. * \file ticket/class/ticket.class.php
  25. * \ingroup ticket
  26. * \brief Class file for object ticket
  27. */
  28. // Put here all includes required by your class file
  29. require_once DOL_DOCUMENT_ROOT."/core/class/commonobject.class.php";
  30. require_once DOL_DOCUMENT_ROOT.'/fichinter/class/fichinter.class.php';
  31. require_once DOL_DOCUMENT_ROOT.'/core/lib/ticket.lib.php';
  32. /**
  33. * Class to manage ticket
  34. */
  35. class Ticket extends CommonObject
  36. {
  37. /**
  38. * @var DoliDb Database handler
  39. */
  40. public $db;
  41. /**
  42. * @var string ID to identify managed object
  43. */
  44. public $element = 'ticket';
  45. /**
  46. * @var string Name of table without prefix where object is stored
  47. */
  48. public $table_element = 'ticket';
  49. /**
  50. * @var string Name of field for link to tickets
  51. */
  52. public $fk_element = 'fk_ticket';
  53. /**
  54. * @var int Does ticketcore support multicompany module ? 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  55. */
  56. public $ismultientitymanaged = 1;
  57. /**
  58. * @var int Does ticketcore support extrafields ? 0=No, 1=Yes
  59. */
  60. public $isextrafieldmanaged = 1;
  61. /**
  62. * @var string String with name of icon for ticketcore. Must be the part after the 'object_' into object_ticketcore.png
  63. */
  64. public $picto = 'ticket';
  65. /**
  66. * @var string Hash to identify ticket publically
  67. */
  68. public $track_id;
  69. /**
  70. * @var int Thirdparty ID
  71. */
  72. public $fk_soc;
  73. public $socid;
  74. /**
  75. * @var int Project ID
  76. */
  77. public $fk_project;
  78. /**
  79. * @var int Contract ID
  80. */
  81. public $fk_contract;
  82. /**
  83. * @var string Person email who have create ticket
  84. */
  85. public $origin_email;
  86. /**
  87. * @var int User id who have create ticket
  88. */
  89. public $fk_user_create;
  90. /**
  91. * @var int User id who have ticket assigned
  92. */
  93. public $fk_user_assign;
  94. /**
  95. * var string Ticket subject
  96. */
  97. public $subject;
  98. /**
  99. * @var string Ticket message
  100. */
  101. public $message;
  102. /**
  103. * @var string Private message
  104. */
  105. public $private;
  106. /**
  107. * @var int Ticket statut
  108. * @deprecated use status
  109. * @see $status
  110. */
  111. public $fk_statut;
  112. /**
  113. * @var int Ticket status
  114. */
  115. public $status;
  116. /**
  117. * @var string State resolution
  118. */
  119. public $resolution;
  120. /**
  121. * @var int Progress in percent
  122. */
  123. public $progress;
  124. /**
  125. * @var string Duration for ticket
  126. */
  127. public $timing;
  128. /**
  129. * @var string Type code
  130. */
  131. public $type_code;
  132. /**
  133. * @var string Category code
  134. */
  135. public $category_code;
  136. /**
  137. * @var string Severity code
  138. */
  139. public $severity_code;
  140. /**
  141. * Type label
  142. */
  143. public $type_label;
  144. /**
  145. * Category label
  146. */
  147. public $category_label;
  148. /**
  149. * Severity label
  150. */
  151. public $severity_label;
  152. /**
  153. * Email from user
  154. */
  155. public $email_from;
  156. /**
  157. * @var int Creation date
  158. */
  159. public $datec = '';
  160. /**
  161. * @var int Last modification date
  162. */
  163. public $tms = '';
  164. /**
  165. * @var int Read date
  166. */
  167. public $date_read = '';
  168. /**
  169. * @var int Last message date
  170. */
  171. public $date_last_msg_sent = '';
  172. /**
  173. * @var int Close ticket date
  174. */
  175. public $date_close = '';
  176. /**
  177. * @var array cache_types_tickets
  178. */
  179. public $cache_types_tickets;
  180. /**
  181. * @var array tickets categories
  182. */
  183. public $cache_category_tickets;
  184. /**
  185. * @var array tickets severity
  186. */
  187. public $cache_severity_tickets;
  188. /**
  189. * @var array cache msgs ticket
  190. */
  191. public $cache_msgs_ticket;
  192. /**
  193. * @var array status labels
  194. */
  195. public $labelStatus;
  196. /**
  197. * @var array status short labels
  198. */
  199. public $labelStatusShort;
  200. /**
  201. * @var int Notify thirdparty at create
  202. */
  203. public $notify_tiers_at_create;
  204. /**
  205. * @var string Email MSGID
  206. */
  207. public $email_msgid;
  208. /**
  209. * @var string Email Date
  210. */
  211. public $email_date;
  212. /**
  213. * @var string IP address
  214. */
  215. public $ip;
  216. /**
  217. * @var Ticket $oldcopy State of this ticket as it was stored before an update operation (for triggers)
  218. */
  219. public $oldcopy;
  220. /**
  221. * @var Ticket[] array of Tickets
  222. */
  223. public $lines;
  224. /**
  225. * @var string Regex pour les images
  226. */
  227. public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
  228. /**
  229. * Status
  230. */
  231. const STATUS_NOT_READ = 0;
  232. const STATUS_READ = 1;
  233. const STATUS_ASSIGNED = 2;
  234. const STATUS_IN_PROGRESS = 3;
  235. const STATUS_NEED_MORE_INFO = 5; // waiting requester feedback
  236. const STATUS_WAITING = 7; // on hold
  237. const STATUS_CLOSED = 8; // Closed - Solved
  238. const STATUS_CANCELED = 9; // Closed - Not solved
  239. /**
  240. * 'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
  241. * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
  242. * 'label' the translation key.
  243. * 'picto' is code of a picto to show before value in forms
  244. * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
  245. * 'position' is the sort order of field.
  246. * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
  247. * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
  248. * 'noteditable' says if field is not editable (1 or 0)
  249. * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
  250. * 'index' if we want an index in database.
  251. * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
  252. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
  253. * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
  254. * 'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200'
  255. * 'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
  256. * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
  257. * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
  258. * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
  259. * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
  260. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
  261. *
  262. * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
  263. */
  264. // BEGIN MODULEBUILDER PROPERTIES
  265. public $fields = array(
  266. 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'visible'=>-2, 'enabled'=>1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id"),
  267. 'entity' => array('type'=>'integer', 'label'=>'Entity', 'visible'=>0, 'enabled'=>1, 'position'=>5, 'notnull'=>1, 'index'=>1),
  268. 'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'visible'=>1, 'enabled'=>1, 'position'=>10, 'notnull'=>1, 'index'=>1, 'searchall'=>1, 'comment'=>"Reference of object", 'css'=>'', 'showoncombobox'=>1),
  269. 'track_id' => array('type'=>'varchar(255)', 'label'=>'TicketTrackId', 'visible'=>-2, 'enabled'=>1, 'position'=>11, 'notnull'=>-1, 'searchall'=>1, 'help'=>"Help text"),
  270. 'fk_user_create' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Author', 'visible'=>1, 'enabled'=>1, 'position'=>15, 'notnull'=>1, 'csslist'=>'tdoverflowmax100 maxwidth150onsmartphone'),
  271. 'origin_email' => array('type'=>'mail', 'label'=>'OriginEmail', 'visible'=>-2, 'enabled'=>1, 'position'=>16, 'notnull'=>1, 'index'=>1, 'searchall'=>1, 'comment'=>"Reference of object", 'csslist'=>'tdoverflowmax150'),
  272. 'subject' => array('type'=>'varchar(255)', 'label'=>'Subject', 'visible'=>1, 'enabled'=>1, 'position'=>18, 'notnull'=>-1, 'searchall'=>1, 'help'=>"", 'css'=>'maxwidth200 tdoverflowmax200', 'autofocusoncreate'=>1),
  273. 'type_code' => array('type'=>'varchar(32)', 'label'=>'Type', 'visible'=>1, 'enabled'=>1, 'position'=>20, 'notnull'=>-1, 'help'=>"", 'csslist'=>'maxwidth125 tdoverflowmax50'),
  274. 'category_code' => array('type'=>'varchar(32)', 'label'=>'TicketCategory', 'visible'=>-1, 'enabled'=>1, 'position'=>21, 'notnull'=>-1, 'help'=>"", 'css'=>'maxwidth100 tdoverflowmax200'),
  275. 'severity_code' => array('type'=>'varchar(32)', 'label'=>'Severity', 'visible'=>1, 'enabled'=>1, 'position'=>22, 'notnull'=>-1, 'help'=>"", 'css'=>'maxwidth100'),
  276. 'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'visible'=>1, 'enabled'=>'isModEnabled("societe")', 'position'=>50, 'notnull'=>-1, 'index'=>1, 'searchall'=>1, 'help'=>"OrganizationEventLinkToThirdParty", 'css'=>'tdoverflowmax150 maxwidth150onsmartphone'),
  277. 'notify_tiers_at_create' => array('type'=>'integer', 'label'=>'NotifyThirdparty', 'visible'=>-1, 'enabled'=>0, 'position'=>51, 'notnull'=>1, 'index'=>1),
  278. 'fk_project' => array('type'=>'integer:Project:projet/class/project.class.php', 'label'=>'Project', 'visible'=>-1, 'enabled'=>'$conf->project->enabled', 'position'=>52, 'notnull'=>-1, 'index'=>1, 'help'=>"LinkToProject"),
  279. 'fk_contract' => array('type'=>'integer:Contrat:contrat/class/contrat.class.php', 'label'=>'Contract', 'visible'=>-1, 'enabled'=>'$conf->contract->enabled', 'position'=>53, 'notnull'=>-1, 'index'=>1, 'help'=>"LinkToContract"),
  280. //'timing' => array('type'=>'varchar(20)', 'label'=>'Timing', 'visible'=>-1, 'enabled'=>1, 'position'=>42, 'notnull'=>-1, 'help'=>""), // what is this ?
  281. 'datec' => array('type'=>'datetime', 'label'=>'DateCreation', 'visible'=>1, 'enabled'=>1, 'position'=>500, 'notnull'=>1, 'csslist'=>'nowraponall'),
  282. 'date_read' => array('type'=>'datetime', 'label'=>'TicketReadOn', 'visible'=>-1, 'enabled'=>1, 'position'=>501, 'notnull'=>1, 'csslist'=>'nowraponall'),
  283. 'date_last_msg_sent' => array('type'=>'datetime', 'label'=>'TicketLastMessageDate', 'visible'=>0, 'enabled'=>1, 'position'=>502, 'notnull'=>-1),
  284. 'fk_user_assign' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'AssignedTo', 'visible'=>1, 'enabled'=>1, 'position'=>505, 'notnull'=>1, 'csslist'=>'tdoverflowmax100 maxwidth150onsmartphone'),
  285. 'date_close' => array('type'=>'datetime', 'label'=>'TicketCloseOn', 'visible'=>-1, 'enabled'=>1, 'position'=>510, 'notnull'=>1),
  286. 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'visible'=>-1, 'enabled'=>1, 'position'=>520, 'notnull'=>1),
  287. 'message' => array('type'=>'html', 'label'=>'Message', 'visible'=>-2, 'enabled'=>1, 'position'=>540, 'notnull'=>-1,),
  288. 'email_msgid' => array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'visible'=>-2, 'enabled'=>1, 'position'=>540, 'notnull'=>-1, 'help'=>'EmailMsgIDDesc', 'csslist'=>'tdoverflowmax100'),
  289. 'email_date' => array('type'=>'datetime', 'label'=>'EmailDate', 'visible'=>-2, 'enabled'=>1, 'position'=>541),
  290. 'progress' => array('type'=>'integer', 'label'=>'Progression', 'visible'=>-1, 'enabled'=>1, 'position'=>540, 'notnull'=>-1, 'css'=>'right', 'help'=>"", 'isameasure'=>2, 'csslist'=>'width50'),
  291. 'resolution' => array('type'=>'integer', 'label'=>'Resolution', 'visible'=>-1, 'enabled'=>'getDolGlobalString("TICKET_ENABLE_RESOLUTION")', 'position'=>550, 'notnull'=>1),
  292. 'fk_statut' => array('type'=>'integer', 'label'=>'Status', 'visible'=>1, 'enabled'=>1, 'position'=>600, 'notnull'=>1, 'index'=>1, 'arrayofkeyval'=>array(0 => 'Unread', 1 => 'Read', 2 => 'Assigned', 3 => 'InProgress', 5 => 'NeedMoreInformation', 7 => 'OnHold', 8 => 'SolvedClosed', 9 => 'Deleted')),
  293. 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
  294. );
  295. // END MODULEBUILDER PROPERTIES
  296. /**
  297. * Constructor
  298. *
  299. * @param DoliDb $db Database handler
  300. */
  301. public function __construct($db)
  302. {
  303. global $conf;
  304. $this->db = $db;
  305. $this->labelStatusShort = array(
  306. self::STATUS_NOT_READ => 'Unread',
  307. self::STATUS_READ => 'Read',
  308. self::STATUS_ASSIGNED => 'Assigned',
  309. self::STATUS_IN_PROGRESS => 'InProgress',
  310. self::STATUS_WAITING => 'OnHold',
  311. self::STATUS_NEED_MORE_INFO => 'NeedMoreInformationShort',
  312. self::STATUS_CLOSED => 'SolvedClosed',
  313. self::STATUS_CANCELED => 'Canceled'
  314. );
  315. $this->labelStatus = array(
  316. self::STATUS_NOT_READ => 'Unread',
  317. self::STATUS_READ => 'Read',
  318. self::STATUS_ASSIGNED => 'Assigned',
  319. self::STATUS_IN_PROGRESS => 'InProgress',
  320. self::STATUS_WAITING => 'OnHold',
  321. self::STATUS_NEED_MORE_INFO => 'NeedMoreInformation',
  322. self::STATUS_CLOSED => 'SolvedClosed',
  323. self::STATUS_CANCELED => 'Canceled'
  324. );
  325. }
  326. /**
  327. * Check properties of ticket are ok (like ref, track_id, ...).
  328. * All properties must be already loaded on object (this->ref, this->track_id, ...).
  329. *
  330. * @return int 0 if OK, <0 if KO
  331. */
  332. private function verify()
  333. {
  334. $this->errors = array();
  335. $result = 0;
  336. // Clean parameters
  337. if (isset($this->ref)) {
  338. $this->ref = trim($this->ref);
  339. }
  340. if (isset($this->track_id)) {
  341. $this->track_id = trim($this->track_id);
  342. }
  343. if (isset($this->fk_soc)) {
  344. $this->fk_soc = (int) $this->fk_soc;
  345. }
  346. if (isset($this->fk_project)) {
  347. $this->fk_project = (int) $this->fk_project;
  348. }
  349. if (isset($this->origin_email)) {
  350. $this->origin_email = trim($this->origin_email);
  351. }
  352. if (isset($this->fk_user_create)) {
  353. $this->fk_user_create = (int) $this->fk_user_create;
  354. }
  355. if (isset($this->fk_user_assign)) {
  356. $this->fk_user_assign = (int) $this->fk_user_assign;
  357. }
  358. if (isset($this->subject)) {
  359. $this->subject = trim($this->subject);
  360. }
  361. if (isset($this->message)) {
  362. $this->message = trim($this->message);
  363. if (dol_strlen($this->message) > 65000) {
  364. $this->errors[] = 'ErrorFieldTooLong';
  365. dol_syslog(get_class($this).'::create error -1 message too long', LOG_ERR);
  366. $result = -1;
  367. }
  368. }
  369. if (isset($this->fk_statut)) {
  370. $this->fk_statut = (int) $this->fk_statut;
  371. }
  372. if (isset($this->resolution)) {
  373. $this->resolution = trim($this->resolution);
  374. }
  375. if (isset($this->progress)) {
  376. $this->progress = trim($this->progress);
  377. }
  378. if (isset($this->timing)) {
  379. $this->timing = trim($this->timing);
  380. }
  381. if (isset($this->type_code)) {
  382. $this->type_code = trim($this->type_code);
  383. }
  384. if (isset($this->category_code)) {
  385. $this->category_code = trim($this->category_code);
  386. }
  387. if (isset($this->severity_code)) {
  388. $this->severity_code = trim($this->severity_code);
  389. }
  390. if (empty($this->ref)) {
  391. $this->errors[] = 'ErrorTicketRefRequired';
  392. dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
  393. $result = -1;
  394. }
  395. return $result;
  396. }
  397. /**
  398. * Create object into database
  399. *
  400. * @param User $user User that creates
  401. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  402. * @return int Return integer <0 if KO, Id of created object if OK
  403. */
  404. public function create($user, $notrigger = 0)
  405. {
  406. global $conf;
  407. $error = 0;
  408. // Clean parameters
  409. $this->datec = dol_now();
  410. if (empty($this->track_id)) {
  411. $this->track_id = generate_random_id(16);
  412. }
  413. // Check more parameters
  414. // If error, this->errors[] is filled
  415. $result = $this->verify();
  416. if ($result >= 0) {
  417. $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
  418. // Insert request
  419. $sql = "INSERT INTO ".MAIN_DB_PREFIX."ticket(";
  420. $sql .= "ref,";
  421. $sql .= "track_id,";
  422. $sql .= "fk_soc,";
  423. $sql .= "fk_project,";
  424. $sql .= "fk_contract,";
  425. $sql .= "origin_email,";
  426. $sql .= "fk_user_create,";
  427. $sql .= "fk_user_assign,";
  428. $sql .= "email_msgid,";
  429. $sql .= "email_date,";
  430. $sql .= "subject,";
  431. $sql .= "message,";
  432. $sql .= "fk_statut,";
  433. $sql .= "resolution,";
  434. $sql .= "progress,";
  435. $sql .= "timing,";
  436. $sql .= "type_code,";
  437. $sql .= "category_code,";
  438. $sql .= "severity_code,";
  439. $sql .= "datec,";
  440. $sql .= "date_read,";
  441. $sql .= "date_close,";
  442. $sql .= "entity,";
  443. $sql .= "notify_tiers_at_create,";
  444. $sql .= "ip";
  445. $sql .= ") VALUES (";
  446. $sql .= " ".(!isset($this->ref) ? '' : "'".$this->db->escape($this->ref)."'").",";
  447. $sql .= " ".(!isset($this->track_id) ? 'NULL' : "'".$this->db->escape($this->track_id)."'").",";
  448. $sql .= " ".($this->fk_soc > 0 ? $this->db->escape($this->fk_soc) : "null").",";
  449. $sql .= " ".($this->fk_project > 0 ? $this->db->escape($this->fk_project) : "null").",";
  450. $sql .= " ".($this->fk_contract > 0 ? $this->db->escape($this->fk_contract) : "null").",";
  451. $sql .= " ".(!isset($this->origin_email) ? 'NULL' : "'".$this->db->escape($this->origin_email)."'").",";
  452. $sql .= " ".(!isset($this->fk_user_create) ? ($user->id > 0 ? $user->id : 'NULL') : ($this->fk_user_create > 0 ? $this->fk_user_create : 'NULL')).",";
  453. $sql .= " ".($this->fk_user_assign > 0 ? $this->fk_user_assign : 'NULL').",";
  454. $sql .= " ".(empty($this->email_msgid) ? 'NULL' : "'".$this->db->escape($this->email_msgid)."'").",";
  455. $sql .= " ".(empty($this->email_date) ? 'NULL' : "'".$this->db->idate($this->email_date)."'").",";
  456. $sql .= " ".(!isset($this->subject) ? 'NULL' : "'".$this->db->escape($this->subject)."'").",";
  457. $sql .= " ".(!isset($this->message) ? 'NULL' : "'".$this->db->escape($this->message)."'").",";
  458. $sql .= " ".(!isset($this->fk_statut) ? '0' : "'".$this->db->escape($this->fk_statut)."'").",";
  459. $sql .= " ".(!isset($this->resolution) ? 'NULL' : "'".$this->db->escape($this->resolution)."'").",";
  460. $sql .= " ".(!isset($this->progress) ? '0' : "'".$this->db->escape($this->progress)."'").",";
  461. $sql .= " ".(!isset($this->timing) ? 'NULL' : "'".$this->db->escape($this->timing)."'").",";
  462. $sql .= " ".(!isset($this->type_code) ? 'NULL' : "'".$this->db->escape($this->type_code)."'").",";
  463. $sql .= " ".(empty($this->category_code) || $this->category_code == '-1' ? 'NULL' : "'".$this->db->escape($this->category_code)."'").",";
  464. $sql .= " ".(!isset($this->severity_code) ? 'NULL' : "'".$this->db->escape($this->severity_code)."'").",";
  465. $sql .= " ".(!isset($this->datec) || dol_strlen($this->datec) == 0 ? 'NULL' : "'".$this->db->idate($this->datec)."'").",";
  466. $sql .= " ".(!isset($this->date_read) || dol_strlen($this->date_read) == 0 ? 'NULL' : "'".$this->db->idate($this->date_read)."'").",";
  467. $sql .= " ".(!isset($this->date_close) || dol_strlen($this->date_close) == 0 ? 'NULL' : "'".$this->db->idate($this->date_close)."'");
  468. $sql .= ", ".((int) $this->entity);
  469. $sql .= ", ".(!isset($this->notify_tiers_at_create) ? '1' : "'".$this->db->escape($this->notify_tiers_at_create)."'");
  470. $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
  471. $sql .= ")";
  472. $this->db->begin();
  473. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  474. $resql = $this->db->query($sql);
  475. if (!$resql) {
  476. $error++;
  477. $this->errors[] = "Error ".$this->db->lasterror();
  478. }
  479. if (!$error) {
  480. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."ticket");
  481. }
  482. if (!$error && getDolGlobalString('TICKET_ADD_AUTHOR_AS_CONTACT')) {
  483. // add creator as contributor
  484. if ($this->add_contact($user->id, 'CONTRIBUTOR', 'internal') < 0) {
  485. $error++;
  486. }
  487. }
  488. if (!$error && $this->fk_user_assign > 0) {
  489. if ($this->add_contact($this->fk_user_assign, 'SUPPORTTEC', 'internal') < 0) {
  490. $error++;
  491. }
  492. }
  493. //Update extrafield
  494. if (!$error) {
  495. $result = $this->insertExtraFields();
  496. if ($result < 0) {
  497. $error++;
  498. }
  499. }
  500. if (!$error && !$notrigger) {
  501. // Call trigger
  502. $result = $this->call_trigger('TICKET_CREATE', $user);
  503. if ($result < 0) {
  504. $error++;
  505. }
  506. // End call triggers
  507. }
  508. // Commit or rollback
  509. if ($error) {
  510. foreach ($this->errors as $errmsg) {
  511. dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
  512. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  513. }
  514. $this->db->rollback();
  515. return -1 * $error;
  516. } else {
  517. $this->db->commit();
  518. return $this->id;
  519. }
  520. } else {
  521. $this->db->rollback();
  522. dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
  523. return -3;
  524. }
  525. }
  526. /**
  527. * Load object in memory from the database
  528. *
  529. * @param int $id Id object
  530. * @param string $ref Ref
  531. * @param string $track_id Track id, a hash like ref
  532. * @param string $email_msgid Email msgid
  533. * @return int Return integer <0 if KO, >0 if OK
  534. */
  535. public function fetch($id = '', $ref = '', $track_id = '', $email_msgid = '')
  536. {
  537. global $langs;
  538. // Check parameters
  539. if (empty($id) && empty($ref) && empty($track_id) && empty($email_msgid)) {
  540. $this->error = 'ErrorWrongParameters';
  541. dol_print_error('', get_class($this)."::fetch ".$this->error);
  542. return -1;
  543. }
  544. $sql = "SELECT";
  545. $sql .= " t.rowid,";
  546. $sql .= " t.entity,";
  547. $sql .= " t.ref,";
  548. $sql .= " t.track_id,";
  549. $sql .= " t.fk_soc,";
  550. $sql .= " t.fk_project,";
  551. $sql .= " t.fk_contract,";
  552. $sql .= " t.origin_email,";
  553. $sql .= " t.fk_user_create,";
  554. $sql .= " t.fk_user_assign,";
  555. $sql .= " t.email_msgid,";
  556. $sql .= " t.email_date,";
  557. $sql .= " t.subject,";
  558. $sql .= " t.message,";
  559. $sql .= " t.fk_statut as status,";
  560. $sql .= " t.resolution,";
  561. $sql .= " t.progress,";
  562. $sql .= " t.timing,";
  563. $sql .= " t.type_code,";
  564. $sql .= " t.category_code,";
  565. $sql .= " t.severity_code,";
  566. $sql .= " t.datec,";
  567. $sql .= " t.date_read,";
  568. $sql .= " t.date_last_msg_sent,";
  569. $sql .= " t.date_close,";
  570. $sql .= " t.tms,";
  571. $sql .= " t.ip,";
  572. $sql .= " type.label as type_label, category.label as category_label, severity.label as severity_label";
  573. $sql .= " FROM ".MAIN_DB_PREFIX."ticket as t";
  574. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_type as type ON type.code=t.type_code";
  575. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_category as category ON category.code=t.category_code";
  576. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_severity as severity ON severity.code=t.severity_code";
  577. if ($id) {
  578. $sql .= " WHERE t.rowid = ".((int) $id);
  579. } else {
  580. $sql .= " WHERE t.entity IN (".getEntity($this->element, 1).")";
  581. if (!empty($ref)) {
  582. $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
  583. } elseif ($track_id) {
  584. $sql .= " AND t.track_id = '".$this->db->escape($track_id)."'";
  585. } else {
  586. $sql .= " AND t.email_msgid = '".$this->db->escape($email_msgid)."'";
  587. }
  588. }
  589. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  590. $resql = $this->db->query($sql);
  591. if ($resql) {
  592. if ($this->db->num_rows($resql)) {
  593. $obj = $this->db->fetch_object($resql);
  594. $this->id = $obj->rowid;
  595. $this->entity = $obj->entity;
  596. $this->ref = $obj->ref;
  597. $this->track_id = $obj->track_id;
  598. $this->fk_soc = $obj->fk_soc;
  599. $this->socid = $obj->fk_soc; // for fetch_thirdparty() method
  600. $this->fk_project = $obj->fk_project;
  601. $this->fk_contract = $obj->fk_contract;
  602. $this->origin_email = $obj->origin_email;
  603. $this->fk_user_create = $obj->fk_user_create;
  604. $this->fk_user_assign = $obj->fk_user_assign;
  605. $this->email_msgid = $obj->email_msgid;
  606. $this->email_date = $this->db->jdate($obj->email_date);
  607. $this->subject = $obj->subject;
  608. $this->message = $obj->message;
  609. $this->ip = $obj->ip;
  610. $this->status = $obj->status;
  611. $this->fk_statut = $this->status; // For backward compatibility
  612. $this->resolution = $obj->resolution;
  613. $this->progress = $obj->progress;
  614. $this->timing = $obj->timing;
  615. $this->type_code = $obj->type_code;
  616. $label_type = ($langs->trans("TicketTypeShort".$obj->type_code) != "TicketTypeShort".$obj->type_code ? $langs->trans("TicketTypeShort".$obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
  617. $this->type_label = $label_type;
  618. $this->category_code = $obj->category_code;
  619. $label_category = ($langs->trans("TicketCategoryShort".$obj->category_code) != "TicketCategoryShort".$obj->category_code ? $langs->trans("TicketCategoryShort".$obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
  620. $this->category_label = $label_category;
  621. $this->severity_code = $obj->severity_code;
  622. $label_severity = ($langs->trans("TicketSeverityShort".$obj->severity_code) != "TicketSeverityShort".$obj->severity_code ? $langs->trans("TicketSeverityShort".$obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
  623. $this->severity_label = $label_severity;
  624. $this->datec = $this->db->jdate($obj->datec);
  625. $this->date_creation = $this->db->jdate($obj->datec);
  626. $this->date_read = $this->db->jdate($obj->date_read);
  627. $this->date_validation = $this->db->jdate($obj->date_read);
  628. $this->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
  629. $this->date_close = $this->db->jdate($obj->date_close);
  630. $this->tms = $this->db->jdate($obj->tms);
  631. $this->date_modification = $this->db->jdate($obj->tms);
  632. $this->fetch_optionals();
  633. $this->db->free($resql);
  634. return 1;
  635. } else {
  636. return 0;
  637. }
  638. } else {
  639. $this->error = "Error ".$this->db->lasterror();
  640. dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
  641. return -1;
  642. }
  643. }
  644. /**
  645. * Load all objects in memory from database
  646. *
  647. * @param User $user User for action
  648. * @param string $sortorder Sort order
  649. * @param string $sortfield Sort field
  650. * @param int $limit page number
  651. * @param int $offset Offset for query
  652. * @param int $arch archive or not (not used)
  653. * @param array $filter Filter for query
  654. * @return int Return integer <0 if KO, >0 if OK
  655. */
  656. public function fetchAll($user, $sortorder = 'ASC', $sortfield = 't.datec', $limit = '', $offset = 0, $arch = '', $filter = '')
  657. {
  658. global $langs, $extrafields;
  659. // fetch optionals attributes and labels
  660. $extrafields->fetch_name_optionals_label($this->table_element);
  661. $sql = "SELECT";
  662. $sql .= " t.rowid,";
  663. $sql .= " t.ref,";
  664. $sql .= " t.track_id,";
  665. $sql .= " t.fk_soc,";
  666. $sql .= " t.fk_project,";
  667. $sql .= " t.fk_contract,";
  668. $sql .= " t.origin_email,";
  669. $sql .= " t.fk_user_create, uc.lastname as user_create_lastname, uc.firstname as user_create_firstname,";
  670. $sql .= " t.fk_user_assign, ua.lastname as user_assign_lastname, ua.firstname as user_assign_firstname,";
  671. $sql .= " t.subject,";
  672. $sql .= " t.message,";
  673. $sql .= " t.fk_statut as status,";
  674. $sql .= " t.resolution,";
  675. $sql .= " t.progress,";
  676. $sql .= " t.timing,";
  677. $sql .= " t.type_code,";
  678. $sql .= " t.category_code,";
  679. $sql .= " t.severity_code,";
  680. $sql .= " t.datec,";
  681. $sql .= " t.date_read,";
  682. $sql .= " t.date_last_msg_sent,";
  683. $sql .= " t.date_close,";
  684. $sql .= " t.tms";
  685. $sql .= ", type.label as type_label, category.label as category_label, severity.label as severity_label";
  686. // Add fields for extrafields
  687. if ($extrafields->attributes[$this->table_element]['count']> 0) {
  688. foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
  689. $sql .= ($extrafields->attributes[$this->table_element]['type'][$key] != 'separate' ? ",ef.".$key." as options_".$key : '');
  690. }
  691. }
  692. $sql .= " FROM ".MAIN_DB_PREFIX."ticket as t";
  693. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_type as type ON type.code=t.type_code";
  694. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_category as category ON category.code=t.category_code";
  695. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_severity as severity ON severity.code=t.severity_code";
  696. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid=t.fk_soc";
  697. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as uc ON uc.rowid=t.fk_user_create";
  698. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as ua ON ua.rowid=t.fk_user_assign";
  699. if ($extrafields->attributes[$this->table_element]['count']> 0) {
  700. if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
  701. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."ticket_extrafields as ef on (t.rowid = ef.fk_object)";
  702. }
  703. }
  704. if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  705. $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
  706. }
  707. $sql .= " WHERE t.entity IN (".getEntity('ticket').")";
  708. // Manage filter
  709. if (!empty($filter)) {
  710. foreach ($filter as $key => $value) {
  711. if (strpos($key, 'date')) { // To allow $filter['YEAR(s.dated)']=>$year
  712. $sql .= " AND ".$key." = '".$this->db->escape($value)."'";
  713. } elseif (($key == 't.fk_user_assign') || ($key == 't.type_code') || ($key == 't.category_code') || ($key == 't.severity_code') || ($key == 't.fk_soc')) {
  714. $sql .= " AND ".$key." = '".$this->db->escape($value)."'";
  715. } elseif ($key == 't.fk_statut') {
  716. if (is_array($value) && count($value) > 0) {
  717. $sql .= " AND ".$key." IN (".$this->db->sanitize(implode(',', $value)).")";
  718. } else {
  719. $sql .= " AND ".$key.' = '.((int) $value);
  720. }
  721. } elseif ($key == 't.fk_contract') {
  722. $sql .= " AND ".$key.' = '.((int) $value);
  723. } else {
  724. $sql .= " AND ".$key." LIKE '%".$this->db->escape($value)."%'";
  725. }
  726. }
  727. }
  728. if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  729. $sql .= " AND t.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
  730. } elseif ($user->socid) {
  731. $sql .= " AND t.fk_soc = ".((int) $user->socid);
  732. }
  733. $sql .= $this->db->order($sortfield, $sortorder);
  734. if (!empty($limit)) {
  735. $sql .= $this->db->plimit($limit + 1, $offset);
  736. }
  737. dol_syslog(get_class($this)."::fetchAll", LOG_DEBUG);
  738. $resql = $this->db->query($sql);
  739. if ($resql) {
  740. $this->lines = array();
  741. $num = $this->db->num_rows($resql);
  742. $i = 0;
  743. if ($num) {
  744. while ($i < $num) {
  745. $obj = $this->db->fetch_object($resql);
  746. $line = new self($this->db);
  747. $line->id = $obj->rowid;
  748. //$line->rowid = $obj->rowid;
  749. $line->ref = $obj->ref;
  750. $line->track_id = $obj->track_id;
  751. $line->fk_soc = $obj->fk_soc;
  752. $line->fk_project = $obj->fk_project;
  753. $line->fk_contract = $obj->fk_contract;
  754. $line->origin_email = $obj->origin_email;
  755. $line->fk_user_create = $obj->fk_user_create;
  756. $line->fk_user_assign = $obj->fk_user_assign;
  757. $line->subject = $obj->subject;
  758. $line->message = $obj->message;
  759. $line->fk_statut = $obj->status;
  760. $line->status = $obj->status;
  761. $line->resolution = $obj->resolution;
  762. $line->progress = $obj->progress;
  763. $line->timing = $obj->timing;
  764. $label_type = ($langs->trans("TicketTypeShort".$obj->type_code) != "TicketTypeShort".$obj->type_code ? $langs->trans("TicketTypeShort".$obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
  765. $line->type_label = $label_type;
  766. $this->category_code = $obj->category_code;
  767. $label_category = ($langs->trans("TicketCategoryShort".$obj->category_code) != "TicketCategoryShort".$obj->category_code ? $langs->trans("TicketCategoryShort".$obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
  768. $line->category_label = $label_category;
  769. $this->severity_code = $obj->severity_code;
  770. $label_severity = ($langs->trans("TicketSeverityShort".$obj->severity_code) != "TicketSeverityShort".$obj->severity_code ? $langs->trans("TicketSeverityShort".$obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
  771. $line->severity_label = $label_severity;
  772. $line->datec = $this->db->jdate($obj->datec);
  773. $line->date_read = $this->db->jdate($obj->date_read);
  774. $line->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
  775. $line->date_close = $this->db->jdate($obj->date_close);
  776. // Extra fields
  777. if ($extrafields->attributes[$this->table_element]['count']> 0) {
  778. if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
  779. foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
  780. $tmpkey = 'options_'.$key;
  781. $line->{$tmpkey} = $obj->$tmpkey;
  782. }
  783. }
  784. }
  785. $this->lines[$i] = $line;
  786. $i++;
  787. }
  788. }
  789. $this->db->free($resql);
  790. return $num;
  791. } else {
  792. $this->error = "Error ".$this->db->lasterror();
  793. dol_syslog(get_class($this)."::fetchAll ".$this->error, LOG_ERR);
  794. return -1;
  795. }
  796. }
  797. /**
  798. * Update object into database
  799. *
  800. * @param User $user User that modifies
  801. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  802. * @return int Return integer <0 if KO, >0 if OK
  803. */
  804. public function update($user, $notrigger = 0)
  805. {
  806. $error = 0;
  807. // $this->oldcopy should have been set by the caller of update (here properties were already modified)
  808. //if (empty($this->oldcopy)) {
  809. // $this->oldcopy = dol_clone($this);
  810. //}
  811. // Clean parameters
  812. if (isset($this->ref)) {
  813. $this->ref = trim($this->ref);
  814. }
  815. if (isset($this->track_id)) {
  816. $this->track_id = trim($this->track_id);
  817. }
  818. if (isset($this->fk_soc)) {
  819. $this->fk_soc = (int) $this->fk_soc;
  820. }
  821. if (isset($this->fk_project)) {
  822. $this->fk_project = (int) $this->fk_project;
  823. }
  824. if (isset($this->fk_contract)) {
  825. $this->fk_contract = (int) $this->fk_contract;
  826. }
  827. if (isset($this->origin_email)) {
  828. $this->origin_email = trim($this->origin_email);
  829. }
  830. if (isset($this->fk_user_create)) {
  831. $this->fk_user_create = (int) $this->fk_user_create;
  832. }
  833. if (isset($this->fk_user_assign)) {
  834. $this->fk_user_assign = (int) $this->fk_user_assign;
  835. }
  836. if (isset($this->subject)) {
  837. $this->subject = trim($this->subject);
  838. }
  839. if (isset($this->message)) {
  840. $this->message = trim($this->message);
  841. if (dol_strlen($this->message) > 65000) {
  842. $this->errors[] = 'ErrorFieldTooLong';
  843. dol_syslog(get_class($this).'::update error -1 message too long', LOG_ERR);
  844. return -1;
  845. }
  846. }
  847. if (isset($this->fk_statut)) {
  848. $this->fk_statut = (int) $this->fk_statut;
  849. }
  850. if (isset($this->resolution)) {
  851. $this->resolution = trim($this->resolution);
  852. }
  853. if (isset($this->progress)) {
  854. $this->progress = trim($this->progress);
  855. }
  856. if (isset($this->timing)) {
  857. $this->timing = trim($this->timing);
  858. }
  859. if (isset($this->type_code)) {
  860. $this->timing = trim($this->type_code);
  861. }
  862. if (isset($this->category_code)) {
  863. $this->timing = trim($this->category_code);
  864. }
  865. if (isset($this->severity_code)) {
  866. $this->timing = trim($this->severity_code);
  867. }
  868. // Check parameters
  869. // Put here code to add a control on parameters values
  870. // Update request
  871. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket SET";
  872. $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "").",";
  873. $sql .= " track_id=".(isset($this->track_id) ? "'".$this->db->escape($this->track_id)."'" : "null").",";
  874. $sql .= " fk_soc=".(isset($this->fk_soc) ? "'".$this->db->escape($this->fk_soc)."'" : "null").",";
  875. $sql .= " fk_project=".(isset($this->fk_project) ? "'".$this->db->escape($this->fk_project)."'" : "null").",";
  876. $sql .= " fk_contract=".(isset($this->fk_contract) ? "'".$this->db->escape($this->fk_contract)."'" : "null").",";
  877. $sql .= " origin_email=".(isset($this->origin_email) ? "'".$this->db->escape($this->origin_email)."'" : "null").",";
  878. $sql .= " fk_user_create=".(isset($this->fk_user_create) ? $this->fk_user_create : "null").",";
  879. $sql .= " fk_user_assign=".(isset($this->fk_user_assign) ? $this->fk_user_assign : "null").",";
  880. $sql .= " subject=".(isset($this->subject) ? "'".$this->db->escape($this->subject)."'" : "null").",";
  881. $sql .= " message=".(isset($this->message) ? "'".$this->db->escape($this->message)."'" : "null").",";
  882. $sql .= " fk_statut=".(isset($this->fk_statut) ? $this->fk_statut : "0").",";
  883. $sql .= " resolution=".(isset($this->resolution) ? $this->resolution : "null").",";
  884. $sql .= " progress=".(isset($this->progress) ? "'".$this->db->escape($this->progress)."'" : "null").",";
  885. $sql .= " timing=".(isset($this->timing) ? "'".$this->db->escape($this->timing)."'" : "null").",";
  886. $sql .= " type_code=".(isset($this->type_code) ? "'".$this->db->escape($this->type_code)."'" : "null").",";
  887. $sql .= " category_code=".(isset($this->category_code) ? "'".$this->db->escape($this->category_code)."'" : "null").",";
  888. $sql .= " severity_code=".(isset($this->severity_code) ? "'".$this->db->escape($this->severity_code)."'" : "null").",";
  889. $sql .= " datec=".(dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').",";
  890. $sql .= " date_read=".(dol_strlen($this->date_read) != 0 ? "'".$this->db->idate($this->date_read)."'" : 'null').",";
  891. $sql .= " date_last_msg_sent=".(dol_strlen($this->date_last_msg_sent) != 0 ? "'".$this->db->idate($this->date_last_msg_sent)."'" : 'null').",";
  892. $sql .= " date_close=".(dol_strlen($this->date_close) != 0 ? "'".$this->db->idate($this->date_close)."'" : 'null');
  893. $sql .= " WHERE rowid=".((int) $this->id);
  894. $this->db->begin();
  895. $resql = $this->db->query($sql);
  896. if (!$resql) {
  897. $error++;
  898. $this->errors[] = "Error ".$this->db->lasterror();
  899. }
  900. if (!$error) {
  901. // Update extrafields
  902. $result = $this->insertExtraFields();
  903. if ($result < 0) {
  904. $error++;
  905. }
  906. }
  907. if (!$error && !$notrigger) {
  908. // Call trigger
  909. $result = $this->call_trigger('TICKET_MODIFY', $user);
  910. if ($result < 0) {
  911. $error++;
  912. }
  913. // End call triggers
  914. }
  915. // Commit or rollback
  916. if ($error) {
  917. foreach ($this->errors as $errmsg) {
  918. dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
  919. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  920. }
  921. $this->db->rollback();
  922. return -1 * $error;
  923. } else {
  924. $this->db->commit();
  925. return 1;
  926. }
  927. }
  928. /**
  929. * Delete object in database
  930. *
  931. * @param User $user User that deletes
  932. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  933. * @return int Return integer <0 if KO, >0 if OK
  934. */
  935. public function delete($user, $notrigger = 0)
  936. {
  937. global $conf, $langs;
  938. $error = 0;
  939. $this->db->begin();
  940. if (!$error) {
  941. if (!$notrigger) {
  942. // Call trigger
  943. $result = $this->call_trigger('TICKET_DELETE', $user);
  944. if ($result < 0) {
  945. $error++;
  946. }
  947. // End call triggers
  948. }
  949. }
  950. if (!$error) {
  951. // Delete linked contacts
  952. $res = $this->delete_linked_contact();
  953. if ($res < 0) {
  954. dol_syslog(get_class($this)."::delete error", LOG_ERR);
  955. $error++;
  956. }
  957. }
  958. if (!$error) {
  959. // Delete linked object
  960. $res = $this->deleteObjectLinked();
  961. if ($res < 0) {
  962. $error++;
  963. }
  964. }
  965. // Removed extrafields
  966. if (!$error) {
  967. $result = $this->deleteExtraFields();
  968. if ($result < 0) {
  969. $error++;
  970. dol_syslog(get_class($this)."::delete error -3 ".$this->error, LOG_ERR);
  971. }
  972. }
  973. // Delete all child tables
  974. if (!$error) {
  975. $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_ticket";
  976. $sql .= " WHERE fk_ticket = ".(int) $this->id;
  977. $result = $this->db->query($sql);
  978. if (!$result) {
  979. $error++;
  980. $this->errors[] = $this->db->lasterror();
  981. }
  982. }
  983. if (!$error) {
  984. $sql = "DELETE FROM ".MAIN_DB_PREFIX."ticket";
  985. $sql .= " WHERE rowid=".((int) $this->id);
  986. dol_syslog(get_class($this)."::delete sql=".$sql);
  987. $resql = $this->db->query($sql);
  988. if (!$resql) {
  989. $error++;
  990. $this->errors[] = "Error ".$this->db->lasterror();
  991. }
  992. }
  993. // Commit or rollback
  994. if ($error) {
  995. foreach ($this->errors as $errmsg) {
  996. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  997. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  998. }
  999. $this->db->rollback();
  1000. return -1 * $error;
  1001. } else {
  1002. $this->db->commit();
  1003. return 1;
  1004. }
  1005. }
  1006. /**
  1007. * Load an object from its id and create a new one in database
  1008. *
  1009. * @param User $user User that clone
  1010. * @param int $fromid Id of object to clone
  1011. * @return int New id of clone
  1012. */
  1013. public function createFromClone(User $user, $fromid)
  1014. {
  1015. $error = 0;
  1016. $object = new Ticket($this->db);
  1017. $this->db->begin();
  1018. // Load source object
  1019. $object->fetch($fromid);
  1020. $object->id = 0;
  1021. $object->statut = 0;
  1022. // Clear fields
  1023. // ...
  1024. // Create clone
  1025. $object->context['createfromclone'] = 'createfromclone';
  1026. $result = $object->create($user);
  1027. // Other options
  1028. if ($result < 0) {
  1029. $this->error = $object->error;
  1030. $error++;
  1031. }
  1032. if (!$error) {
  1033. }
  1034. unset($object->context['createfromclone']);
  1035. // End
  1036. if (!$error) {
  1037. $this->db->commit();
  1038. return $object->id;
  1039. } else {
  1040. $this->db->rollback();
  1041. return -1;
  1042. }
  1043. }
  1044. /**
  1045. * Initialise object with example values
  1046. * Id must be 0 if object instance is a specimen
  1047. *
  1048. * @return int
  1049. */
  1050. public function initAsSpecimen()
  1051. {
  1052. $this->id = 0;
  1053. $this->entity = 1;
  1054. $this->ref = 'TI0501-001';
  1055. $this->track_id = 'XXXXaaaa';
  1056. $this->origin_email = 'email@email.com';
  1057. $this->fk_project = 1;
  1058. $this->fk_user_create = 1;
  1059. $this->fk_user_assign = 1;
  1060. $this->subject = 'Subject of ticket';
  1061. $this->message = 'Message of ticket';
  1062. $this->status = 0;
  1063. $this->resolution = '1';
  1064. $this->progress = '10';
  1065. //$this->timing = '30';
  1066. $this->type_code = 'TYPECODE';
  1067. $this->category_code = 'CATEGORYCODE';
  1068. $this->severity_code = 'SEVERITYCODE';
  1069. $this->datec = '';
  1070. $this->date_read = '';
  1071. $this->date_last_msg_sent = '';
  1072. $this->date_close = '';
  1073. $this->tms = '';
  1074. return 1;
  1075. }
  1076. /**
  1077. * Print selected status
  1078. *
  1079. * @param string $selected Selected status
  1080. * @return void
  1081. */
  1082. public function printSelectStatus($selected = "")
  1083. {
  1084. print Form::selectarray('search_fk_statut', $this->labelStatusShort, $selected, $show_empty = 1, $key_in_label = 0, $value_as_key = 0, $option = '', $translate = 1, $maxlen = 0, $disabled = 0, $sort = '', $morecss = '');
  1085. }
  1086. /**
  1087. * Load into a cache the types of tickets (setup done into dictionaries)
  1088. *
  1089. * @return int Number of lines loaded, 0 if already loaded, <0 if KO
  1090. */
  1091. public function loadCacheTypesTickets()
  1092. {
  1093. global $langs;
  1094. if (!empty($this->cache_types_tickets) && count($this->cache_types_tickets)) {
  1095. return 0;
  1096. }
  1097. // Cache deja charge
  1098. $sql = "SELECT rowid, code, label, use_default, pos, description";
  1099. $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_type";
  1100. $sql .= " WHERE entity IN (".getEntity('c_ticket_type').")";
  1101. $sql .= " AND active > 0";
  1102. $sql .= " ORDER BY pos";
  1103. dol_syslog(get_class($this)."::load_cache_type_tickets", LOG_DEBUG);
  1104. $resql = $this->db->query($sql);
  1105. if ($resql) {
  1106. $num = $this->db->num_rows($resql);
  1107. $i = 0;
  1108. while ($i < $num) {
  1109. $obj = $this->db->fetch_object($resql);
  1110. $label = ($langs->trans("TicketTypeShort".$obj->code) != "TicketTypeShort".$obj->code ? $langs->trans("TicketTypeShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
  1111. $this->cache_types_tickets[$obj->rowid]['code'] = $obj->code;
  1112. $this->cache_types_tickets[$obj->rowid]['label'] = $label;
  1113. $this->cache_types_tickets[$obj->rowid]['use_default'] = $obj->use_default;
  1114. $this->cache_types_tickets[$obj->rowid]['pos'] = $obj->pos;
  1115. $i++;
  1116. }
  1117. return $num;
  1118. } else {
  1119. dol_print_error($this->db);
  1120. return -1;
  1121. }
  1122. }
  1123. /**
  1124. * Load into a cache array, the list of ticket categories (setup done into dictionary)
  1125. *
  1126. * @param int $publicgroup 0=No public group, 1=Public group only, -1=All
  1127. * @return int Number of lines loaded, 0 if already loaded, <0 if KO
  1128. */
  1129. public function loadCacheCategoriesTickets($publicgroup = -1)
  1130. {
  1131. global $conf, $langs;
  1132. if ($publicgroup == -1 && !empty($this->cache_category_ticket) && count($this->cache_category_tickets)) {
  1133. // Cache already loaded
  1134. return 0;
  1135. }
  1136. $sql = "SELECT rowid, code, label, use_default, pos, description, public, active, force_severity, fk_parent";
  1137. $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_category";
  1138. $sql .= " WHERE entity IN (".getEntity('c_ticket_category').")";
  1139. $sql .= " AND active > 0";
  1140. if ($publicgroup > -1) {
  1141. $sql .= " AND public = ".((int) $publicgroup);
  1142. }
  1143. $sql .= " ORDER BY pos";
  1144. dol_syslog(get_class($this)."::load_cache_categories_tickets", LOG_DEBUG);
  1145. $resql = $this->db->query($sql);
  1146. if ($resql) {
  1147. $num = $this->db->num_rows($resql);
  1148. $i = 0;
  1149. while ($i < $num) {
  1150. $obj = $this->db->fetch_object($resql);
  1151. $this->cache_category_tickets[$obj->rowid]['code'] = $obj->code;
  1152. $this->cache_category_tickets[$obj->rowid]['use_default'] = $obj->use_default;
  1153. $this->cache_category_tickets[$obj->rowid]['pos'] = $obj->pos;
  1154. $this->cache_category_tickets[$obj->rowid]['public'] = $obj->public;
  1155. $this->cache_category_tickets[$obj->rowid]['active'] = $obj->active;
  1156. $this->cache_category_tickets[$obj->rowid]['force_severity'] = $obj->force_severity;
  1157. $this->cache_category_tickets[$obj->rowid]['fk_parent'] = $obj->fk_parent;
  1158. // If translation exists, we use it to store already translated string.
  1159. // Warning: You should not use this and recompute the translated string into caller code to get the value into expected language
  1160. $label = ($langs->trans("TicketCategoryShort".$obj->code) != "TicketCategoryShort".$obj->code ? $langs->trans("TicketCategoryShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
  1161. $this->cache_category_tickets[$obj->rowid]['label'] = $label;
  1162. $i++;
  1163. }
  1164. return $num;
  1165. } else {
  1166. dol_print_error($this->db);
  1167. return -1;
  1168. }
  1169. }
  1170. /**
  1171. * Charge dans cache la liste des sévérité de tickets (paramétrable dans dictionnaire)
  1172. *
  1173. * @return int Number of lines loaded, 0 if already loaded, <0 if KO
  1174. */
  1175. public function loadCacheSeveritiesTickets()
  1176. {
  1177. global $langs;
  1178. if (!empty($this->cache_severity_tickets) && count($this->cache_severity_tickets)) {
  1179. return 0;
  1180. }
  1181. // Cache deja charge
  1182. $sql = "SELECT rowid, code, label, use_default, pos, description";
  1183. $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_severity";
  1184. $sql .= " WHERE entity IN (".getEntity('c_ticket_severity').")";
  1185. $sql .= " AND active > 0";
  1186. $sql .= " ORDER BY pos";
  1187. dol_syslog(get_class($this)."::loadCacheSeveritiesTickets", LOG_DEBUG);
  1188. $resql = $this->db->query($sql);
  1189. if ($resql) {
  1190. $num = $this->db->num_rows($resql);
  1191. $i = 0;
  1192. while ($i < $num) {
  1193. $obj = $this->db->fetch_object($resql);
  1194. $this->cache_severity_tickets[$obj->rowid]['code'] = $obj->code;
  1195. $label = ($langs->trans("TicketSeverityShort".$obj->code) != "TicketSeverityShort".$obj->code ? $langs->trans("TicketSeverityShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
  1196. $this->cache_severity_tickets[$obj->rowid]['label'] = $label;
  1197. $this->cache_severity_tickets[$obj->rowid]['use_default'] = $obj->use_default;
  1198. $this->cache_severity_tickets[$obj->rowid]['pos'] = $obj->pos;
  1199. $i++;
  1200. }
  1201. return $num;
  1202. } else {
  1203. dol_print_error($this->db);
  1204. return -1;
  1205. }
  1206. }
  1207. /**
  1208. * Return status label of object
  1209. *
  1210. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
  1211. * @return string Label
  1212. */
  1213. public function getLibStatut($mode = 0)
  1214. {
  1215. return $this->LibStatut($this->fk_statut, $mode, 0, $this->progress);
  1216. }
  1217. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1218. /**
  1219. * Return status label of object
  1220. *
  1221. * @param string $status Id status
  1222. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
  1223. * @param int $notooltip 1=No tooltip
  1224. * @param int $progress Progression (0 to 100)
  1225. * @return string Label
  1226. */
  1227. public function LibStatut($status, $mode = 0, $notooltip = 0, $progress = 0)
  1228. {
  1229. // phpcs:enable
  1230. global $langs, $hookmanager;
  1231. $labelStatus = (isset($status) && !empty($this->labelStatus[$status])) ? $this->labelStatus[$status] : '';
  1232. $labelStatusShort = (isset($status) && !empty($this->labelStatusShort[$status]))? $this->labelStatusShort[$status] : '';
  1233. switch ($status) {
  1234. case self::STATUS_NOT_READ:
  1235. $statusType = 'status0';
  1236. break;
  1237. case self::STATUS_READ:
  1238. $statusType = 'status1';
  1239. break;
  1240. case self::STATUS_ASSIGNED:
  1241. $statusType = 'status2';
  1242. break;
  1243. case self::STATUS_IN_PROGRESS:
  1244. $statusType = 'status4';
  1245. break;
  1246. case self::STATUS_WAITING:
  1247. $statusType = 'status7';
  1248. break;
  1249. case self::STATUS_NEED_MORE_INFO:
  1250. $statusType = 'status3';
  1251. break;
  1252. case self::STATUS_CANCELED:
  1253. $statusType = 'status9';
  1254. break;
  1255. case self::STATUS_CLOSED:
  1256. $statusType = 'status6';
  1257. break;
  1258. default:
  1259. $labelStatus = 'Unknown';
  1260. $labelStatusShort = 'Unknown';
  1261. $statusType = 'status0';
  1262. $mode = 0;
  1263. }
  1264. $parameters = array(
  1265. 'status' => $status,
  1266. 'mode' => $mode,
  1267. );
  1268. // Note that $action and $object may have been modified by hook
  1269. $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this);
  1270. if ($reshook > 0) {
  1271. return $hookmanager->resPrint;
  1272. }
  1273. $params = array();
  1274. if ($notooltip) {
  1275. $params = array('tooltip' => 'no');
  1276. }
  1277. $labelStatus = $langs->transnoentitiesnoconv($labelStatus);
  1278. $labelStatusShort = $langs->transnoentitiesnoconv($labelStatusShort);
  1279. if ($status == self::STATUS_IN_PROGRESS && $progress > 0) {
  1280. $labelStatus .= ' ('.round($progress).'%)';
  1281. $labelStatusShort .= ' ('.round($progress).'%)';
  1282. }
  1283. return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $params);
  1284. }
  1285. /**
  1286. * getTooltipContentArray
  1287. *
  1288. * @param array $params ex option, infologin
  1289. * @since v18
  1290. * @return array
  1291. */
  1292. public function getTooltipContentArray($params)
  1293. {
  1294. global $langs;
  1295. $langs->load('ticket');
  1296. $nofetch = !empty($params['nofetch']);
  1297. $datas = array();
  1298. $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Ticket").'</u>';
  1299. $datas['picto'] .= ' '.$this->getLibStatut(4);
  1300. $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
  1301. $datas['track_id'] = '<br><b>'.$langs->trans('TicketTrackId').':</b> '.$this->track_id;
  1302. $datas['subject'] = '<br><b>'.$langs->trans('Subject').':</b> '.$this->subject;
  1303. if ($this->date_creation) {
  1304. $datas['date_creation'] = '<br><b>'.$langs->trans('DateCreation').':</b> '.dol_print_date($this->date_creation, 'dayhour');
  1305. }
  1306. if ($this->date_modification) {
  1307. $datas['date_modification'] = '<br><b>'.$langs->trans('DateModification').':</b> '.dol_print_date($this->date_modification, 'dayhour');
  1308. }
  1309. // show categories for this record only in ajax to not overload lists
  1310. if (isModEnabled('categorie') && !$nofetch) {
  1311. require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
  1312. $form = new Form($this->db);
  1313. $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_TICKET, 1);
  1314. }
  1315. return $datas;
  1316. }
  1317. /**
  1318. * Return a link to the object card (with optionaly the picto)
  1319. *
  1320. * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
  1321. * @param string $option On what the link point to ('nolink', ...)
  1322. * @param int $notooltip 1=Disable tooltip
  1323. * @param string $morecss Add more css on link
  1324. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  1325. * @return string String with URL
  1326. */
  1327. public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
  1328. {
  1329. global $conf, $langs;
  1330. if (!empty($conf->dol_no_mouse_hover)) {
  1331. $notooltip = 1; // Force disable tooltips
  1332. }
  1333. $result = '';
  1334. $params = [
  1335. 'id' => $this->id,
  1336. 'objecttype' => $this->element,
  1337. 'option' => $option,
  1338. 'nofetch' => 1,
  1339. ];
  1340. $classfortooltip = 'classfortooltip';
  1341. $dataparams = '';
  1342. if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
  1343. $classfortooltip = 'classforajaxtooltip';
  1344. $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
  1345. $label = '';
  1346. } else {
  1347. $label = implode($this->getTooltipContentArray($params));
  1348. }
  1349. $url = DOL_URL_ROOT.'/ticket/card.php?id='.$this->id;
  1350. if ($option != 'nolink') {
  1351. // Add param to save lastsearch_values or not
  1352. $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
  1353. if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
  1354. $add_save_lastsearch_values = 1;
  1355. }
  1356. if ($add_save_lastsearch_values) {
  1357. $url .= '&save_lastsearch_values=1';
  1358. }
  1359. }
  1360. $linkclose = '';
  1361. if (empty($notooltip)) {
  1362. if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
  1363. $label = $langs->trans("ShowTicket");
  1364. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  1365. }
  1366. $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
  1367. $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
  1368. } else {
  1369. $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
  1370. }
  1371. $linkstart = '<a href="'.$url.'"';
  1372. $linkstart .= $linkclose.'>';
  1373. $linkend = '</a>';
  1374. $result .= $linkstart;
  1375. if ($withpicto) {
  1376. $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
  1377. }
  1378. if ($withpicto != 2) {
  1379. $result .= $this->ref;
  1380. }
  1381. $result .= $linkend;
  1382. //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
  1383. return $result;
  1384. }
  1385. /**
  1386. * Mark a message as read
  1387. *
  1388. * @param User $user Object user
  1389. * @param int $notrigger No trigger
  1390. * @return int Return integer <0 if KO, 0=nothing done, >0 if OK
  1391. */
  1392. public function markAsRead($user, $notrigger = 0)
  1393. {
  1394. global $langs;
  1395. $error = 0;
  1396. if ($this->statut != self::STATUS_CANCELED) { // no closed
  1397. $this->oldcopy = dol_clone($this);
  1398. $this->db->begin();
  1399. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1400. $sql .= " SET fk_statut = ".Ticket::STATUS_READ.", date_read = '".$this->db->idate(dol_now())."'";
  1401. $sql .= " WHERE rowid = ".((int) $this->id);
  1402. dol_syslog(get_class($this)."::markAsRead");
  1403. $resql = $this->db->query($sql);
  1404. if ($resql) {
  1405. $this->context['actionmsg'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
  1406. $this->context['actionmsg2'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
  1407. if (!$error && !$notrigger) {
  1408. // Call trigger
  1409. $result = $this->call_trigger('TICKET_MODIFY', $user);
  1410. if ($result < 0) {
  1411. $error++;
  1412. }
  1413. // End call triggers
  1414. }
  1415. if (!$error) {
  1416. $this->db->commit();
  1417. return 1;
  1418. } else {
  1419. $this->db->rollback();
  1420. $this->error = join(',', $this->errors);
  1421. dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR);
  1422. return -1;
  1423. }
  1424. } else {
  1425. $this->db->rollback();
  1426. $this->error = $this->db->lasterror();
  1427. dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR);
  1428. return -1;
  1429. }
  1430. }
  1431. return 0;
  1432. }
  1433. /**
  1434. * Set an assigned user to a ticket.
  1435. *
  1436. * @param User $user Object user
  1437. * @param int $id_assign_user ID of user assigned
  1438. * @param int $notrigger Disable trigger
  1439. * @return int Return integer <0 if KO, 0=Nothing done, >0 if OK
  1440. */
  1441. public function assignUser($user, $id_assign_user, $notrigger = 0)
  1442. {
  1443. $error = 0;
  1444. $this->oldcopy = dol_clone($this);
  1445. $this->db->begin();
  1446. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1447. if ($id_assign_user > 0) {
  1448. $sql .= " SET fk_user_assign=".((int) $id_assign_user).", fk_statut = ".Ticket::STATUS_ASSIGNED;
  1449. } else {
  1450. $sql .= " SET fk_user_assign=null, fk_statut = ".Ticket::STATUS_READ;
  1451. }
  1452. $sql .= " WHERE rowid = ".((int) $this->id);
  1453. dol_syslog(get_class($this)."::assignUser sql=".$sql);
  1454. $resql = $this->db->query($sql);
  1455. if ($resql) {
  1456. $this->fk_user_assign = $id_assign_user; // May be used by trigger
  1457. if (!$notrigger) {
  1458. // Call trigger
  1459. $result = $this->call_trigger('TICKET_ASSIGNED', $user);
  1460. if ($result < 0) {
  1461. $error++;
  1462. }
  1463. // End call triggers
  1464. }
  1465. if (!$error) {
  1466. $this->db->commit();
  1467. return 1;
  1468. } else {
  1469. $this->db->rollback();
  1470. $this->error = join(',', $this->errors);
  1471. dol_syslog(get_class($this)."::assignUser ".$this->error, LOG_ERR);
  1472. return -1;
  1473. }
  1474. } else {
  1475. $this->db->rollback();
  1476. $this->error = $this->db->lasterror();
  1477. dol_syslog(get_class($this)."::assignUser ".$this->error, LOG_ERR);
  1478. return -1;
  1479. }
  1480. }
  1481. /**
  1482. * Add message into database
  1483. *
  1484. * @param User $user User that creates
  1485. * @param int $notrigger 0=launch triggers after, 1=disable triggers
  1486. * @param array $filename_list List of files to attach (full path of filename on file system)
  1487. * @param array $mimetype_list List of MIME type of attached files
  1488. * @param array $mimefilename_list List of attached file name in message
  1489. * @param boolean $send_email Whether the message is sent by email
  1490. * @param int $public_area 0=Default, 1 if we are creating the message from a public area (so we can search contact from email to add it as contact of ticket if TICKET_ASSIGN_CONTACT_TO_MESSAGE is set)
  1491. * @return int Return integer <0 if KO, >0 if OK
  1492. */
  1493. public function createTicketMessage($user, $notrigger = 0, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $send_email = false, $public_area = 0)
  1494. {
  1495. global $conf, $langs;
  1496. $error = 0;
  1497. $now = dol_now();
  1498. // Clean parameters
  1499. if (isset($this->fk_track_id)) {
  1500. $this->fk_track_id = trim($this->fk_track_id);
  1501. }
  1502. if (isset($this->message)) {
  1503. $this->message = trim($this->message);
  1504. }
  1505. $this->db->begin();
  1506. // Insert entry into agenda with code 'TICKET_MSG'
  1507. include_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
  1508. $actioncomm = new ActionComm($this->db);
  1509. $actioncomm->type_code = 'AC_OTH_AUTO'; // This is not an entry that must appears into manual calendar but only into CRM calendar
  1510. $actioncomm->code = 'TICKET_MSG';
  1511. if ($this->private) {
  1512. $actioncomm->code = 'TICKET_MSG_PRIVATE';
  1513. }
  1514. if ($send_email) {
  1515. $actioncomm->code .= '_SENTBYMAIL';
  1516. }
  1517. if ((empty($user->id) || $user->id == 0) && isset($_SESSION['email_customer'])) {
  1518. $actioncomm->email_from = $_SESSION['email_customer'];
  1519. }
  1520. $actioncomm->socid = $this->socid;
  1521. $actioncomm->label = $this->subject;
  1522. $actioncomm->note_private = $this->message;
  1523. $actioncomm->userassigned = array($user->id);
  1524. $actioncomm->userownerid = $user->id;
  1525. $actioncomm->datep = $now;
  1526. $actioncomm->percentage = -1; // percentage is not relevant for punctual events
  1527. $actioncomm->elementtype = 'ticket';
  1528. $actioncomm->fk_element = $this->id;
  1529. $actioncomm->fk_project = $this->fk_project;
  1530. // add contact id from author email on public interface
  1531. if ($public_area && !empty($this->origin_email) && getDolGlobalString('TICKET_ASSIGN_CONTACT_TO_MESSAGE')) {
  1532. $contacts = $this->searchContactByEmail($this->origin_email);
  1533. if (!empty($contacts)) {
  1534. // Ensure that contact is active and select first active contact
  1535. foreach ($contacts as $contact) {
  1536. if ((int) $contact->statut == 1) {
  1537. $actioncomm->contact_id = $contact->id;
  1538. break;
  1539. }
  1540. }
  1541. }
  1542. }
  1543. $attachedfiles = array();
  1544. $attachedfiles['paths'] = $filename_list;
  1545. $attachedfiles['names'] = $mimefilename_list;
  1546. $attachedfiles['mimes'] = $mimetype_list;
  1547. if (is_array($attachedfiles) && count($attachedfiles) > 0) {
  1548. $actioncomm->attachedfiles = $attachedfiles;
  1549. }
  1550. if (!empty($mimefilename_list) && is_array($mimefilename_list)) {
  1551. $actioncomm->note_private = dol_concatdesc($actioncomm->note_private, "\n".$langs->transnoentities("AttachedFiles").': '.join(';', $mimefilename_list));
  1552. }
  1553. $actionid = $actioncomm->create($user);
  1554. if ($actionid <= 0) {
  1555. $error++;
  1556. $this->error = $actioncomm->error;
  1557. $this->errors = $actioncomm->errors;
  1558. }
  1559. // Commit or rollback
  1560. if ($error) {
  1561. $this->db->rollback();
  1562. return -1 * $error;
  1563. } else {
  1564. $this->db->commit();
  1565. return 1;
  1566. }
  1567. }
  1568. /**
  1569. * Load the list of event on ticket into ->cache_msgs_ticket
  1570. *
  1571. * @return int Number of lines loaded, 0 if already loaded, <0 if KO
  1572. */
  1573. public function loadCacheMsgsTicket()
  1574. {
  1575. if (!empty($this->cache_msgs_ticket) && is_array($this->cache_msgs_ticket) && count($this->cache_msgs_ticket)) {
  1576. return 0;
  1577. }
  1578. // Cache already loaded
  1579. $sql = "SELECT id as rowid, fk_user_author, email_from, datec, datep, label, note as message, code";
  1580. $sql .= " FROM ".MAIN_DB_PREFIX."actioncomm";
  1581. $sql .= " WHERE fk_element = ".(int) $this->id;
  1582. $sql .= " AND elementtype = 'ticket'";
  1583. $sql .= " ORDER BY datep DESC";
  1584. dol_syslog(get_class($this)."::load_cache_actions_ticket", LOG_DEBUG);
  1585. $resql = $this->db->query($sql);
  1586. if ($resql) {
  1587. $num = $this->db->num_rows($resql);
  1588. $i = 0;
  1589. while ($i < $num) {
  1590. $obj = $this->db->fetch_object($resql);
  1591. $this->cache_msgs_ticket[$i]['id'] = $obj->rowid;
  1592. $this->cache_msgs_ticket[$i]['fk_user_author'] = $obj->fk_user_author;
  1593. if ($obj->code == 'TICKET_MSG' && empty($obj->fk_user_author)) {
  1594. $this->cache_msgs_ticket[$i]['fk_contact_author'] = $obj->email_from;
  1595. }
  1596. $this->cache_msgs_ticket[$i]['datec'] = $this->db->jdate($obj->datec);
  1597. $this->cache_msgs_ticket[$i]['datep'] = $this->db->jdate($obj->datep);
  1598. $this->cache_msgs_ticket[$i]['subject'] = $obj->label;
  1599. $this->cache_msgs_ticket[$i]['message'] = $obj->message;
  1600. $this->cache_msgs_ticket[$i]['private'] = (preg_match('/^TICKET_MSG_PRIVATE/', $obj->code) ? 1 : 0);
  1601. $i++;
  1602. }
  1603. return $num;
  1604. } else {
  1605. $this->error = "Error ".$this->db->lasterror();
  1606. dol_syslog(get_class($this)."::load_cache_actions_ticket ".$this->error, LOG_ERR);
  1607. return -1;
  1608. }
  1609. }
  1610. /**
  1611. * Close a ticket
  1612. *
  1613. * @param User $user User that close
  1614. * @param int $mode 0=Close solved, 1=Close abandonned
  1615. * @return int Return integer <0 if KO, 0=nothing done, >0 if OK
  1616. */
  1617. public function close(User $user, $mode = 0)
  1618. {
  1619. global $conf;
  1620. if ($this->status != Ticket::STATUS_CLOSED && $this->status != Ticket::STATUS_CANCELED) { // not closed
  1621. $this->db->begin();
  1622. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1623. $sql .= " SET fk_statut=".($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED).", progress=100, date_close='".$this->db->idate(dol_now())."'";
  1624. $sql .= " WHERE rowid = ".((int) $this->id);
  1625. dol_syslog(get_class($this)."::close mode=".$mode);
  1626. $resql = $this->db->query($sql);
  1627. if ($resql) {
  1628. $error = 0;
  1629. // Valid and close fichinter linked
  1630. if (isModEnabled('ficheinter') && getDolGlobalString('WORKFLOW_TICKET_CLOSE_INTERVENTION')) {
  1631. dol_syslog("We have closed the ticket, so we close all linked interventions");
  1632. $this->fetchObjectLinked($this->id, $this->element, null, 'fichinter');
  1633. if ($this->linkedObjectsIds) {
  1634. foreach ($this->linkedObjectsIds['fichinter'] as $fichinter_id) {
  1635. $fichinter = new Fichinter($this->db);
  1636. $fichinter->fetch($fichinter_id);
  1637. if ($fichinter->statut == 0) {
  1638. $result = $fichinter->setValid($user);
  1639. if (!$result) {
  1640. $this->errors[] = $fichinter->error;
  1641. $error++;
  1642. }
  1643. }
  1644. if ($fichinter->statut < 3) {
  1645. $result = $fichinter->setStatut(3);
  1646. if (!$result) {
  1647. $this->errors[] = $fichinter->error;
  1648. $error++;
  1649. }
  1650. }
  1651. }
  1652. }
  1653. }
  1654. // Call trigger
  1655. $result = $this->call_trigger('TICKET_CLOSE', $user);
  1656. if ($result < 0) {
  1657. $error++;
  1658. }
  1659. // End call triggers
  1660. if (!$error) {
  1661. $this->db->commit();
  1662. return 1;
  1663. } else {
  1664. $this->db->rollback();
  1665. $this->error = join(',', $this->errors);
  1666. dol_syslog(get_class($this)."::close ".$this->error, LOG_ERR);
  1667. return -1;
  1668. }
  1669. } else {
  1670. $this->db->rollback();
  1671. $this->error = $this->db->lasterror();
  1672. dol_syslog(get_class($this)."::close ".$this->error, LOG_ERR);
  1673. return -1;
  1674. }
  1675. }
  1676. return 0;
  1677. }
  1678. /**
  1679. * Search and fetch thirparties by email
  1680. *
  1681. * @param string $email Email
  1682. * @param int $type Type of thirdparties (0=any, 1=customer, 2=prospect, 3=supplier)
  1683. * @param array $filters Array of couple field name/value to filter the companies with the same name
  1684. * @param string $clause Clause for filters
  1685. * @return array|int Array of thirdparties object
  1686. */
  1687. public function searchSocidByEmail($email, $type = '0', $filters = array(), $clause = 'AND')
  1688. {
  1689. $thirdparties = array();
  1690. $exact = 0;
  1691. // Generation requete recherche
  1692. $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."societe";
  1693. $sql .= " WHERE entity IN (".getEntity('ticket', 1).")";
  1694. if (!empty($type)) {
  1695. if ($type == 1 || $type == 2) {
  1696. $sql .= " AND client = ".((int) $type);
  1697. } elseif ($type == 3) {
  1698. $sql .= " AND fournisseur = 1";
  1699. }
  1700. }
  1701. if (!empty($email)) {
  1702. if (empty($exact)) {
  1703. $regs = array();
  1704. if (preg_match('/^([\*])?[^*]+([\*])?$/', $email, $regs) && count($regs) > 1) {
  1705. $email = str_replace('*', '%', $email);
  1706. } else {
  1707. $email = '%'.$email.'%';
  1708. }
  1709. }
  1710. $sql .= " AND ";
  1711. if (is_array($filters) && !empty($filters)) {
  1712. $sql .= "(";
  1713. }
  1714. $sql .= "email LIKE '".$this->db->escape($email)."'";
  1715. }
  1716. if (is_array($filters) && !empty($filters)) {
  1717. foreach ($filters as $field => $value) {
  1718. $sql .= " ".$clause." ".$field." LIKE '".$this->db->escape($value)."'";
  1719. }
  1720. if (!empty($email)) {
  1721. $sql .= ")";
  1722. }
  1723. }
  1724. $res = $this->db->query($sql);
  1725. if ($res) {
  1726. while ($rec = $this->db->fetch_array($res)) {
  1727. $soc = new Societe($this->db);
  1728. $soc->fetch($rec['rowid']);
  1729. $thirdparties[] = $soc;
  1730. }
  1731. return $thirdparties;
  1732. } else {
  1733. $this->error = $this->db->error().' sql='.$sql;
  1734. dol_syslog(get_class($this)."::searchSocidByEmail ".$this->error, LOG_ERR);
  1735. return -1;
  1736. }
  1737. }
  1738. /**
  1739. * Search and fetch contacts by email
  1740. *
  1741. * @param string $email Email
  1742. * @param array $socid Limit to a thirdparty
  1743. * @param string $case Respect case
  1744. * @return array|int Array of contacts object
  1745. */
  1746. public function searchContactByEmail($email, $socid = '', $case = '')
  1747. {
  1748. $contacts = array();
  1749. // Forge the search SQL
  1750. $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."socpeople";
  1751. $sql .= " WHERE entity IN (".getEntity('contact').")";
  1752. if (!empty($socid)) {
  1753. $sql .= " AND fk_soc = ".((int) $socid);
  1754. }
  1755. if (!empty($email)) {
  1756. $sql .= " AND ";
  1757. if (!$case) {
  1758. $sql .= "email = '".$this->db->escape($email)."'";
  1759. } else {
  1760. $sql .= "email LIKE BINARY '".$this->db->escape($this->db->escapeforlike($email))."'";
  1761. }
  1762. }
  1763. $res = $this->db->query($sql);
  1764. if ($res) {
  1765. while ($rec = $this->db->fetch_object($res)) {
  1766. include_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
  1767. $contactstatic = new Contact($this->db);
  1768. $contactstatic->fetch($rec->rowid);
  1769. $contacts[] = $contactstatic;
  1770. }
  1771. return $contacts;
  1772. } else {
  1773. $this->error = $this->db->error().' sql='.$sql;
  1774. dol_syslog(get_class($this)."::searchContactByEmail ".$this->error, LOG_ERR);
  1775. return -1;
  1776. }
  1777. }
  1778. /**
  1779. * Define parent commany of current ticket
  1780. *
  1781. * @param int $id Id of thirdparty to set or '' to remove
  1782. * @return int Return integer <0 if KO, >0 if OK
  1783. */
  1784. public function setCustomer($id)
  1785. {
  1786. if ($this->id) {
  1787. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1788. $sql .= " SET fk_soc = ".($id > 0 ? $id : "null");
  1789. $sql .= " WHERE rowid = ".((int) $this->id);
  1790. dol_syslog(get_class($this).'::setCustomer sql='.$sql);
  1791. $resql = $this->db->query($sql);
  1792. if ($resql) {
  1793. return 1;
  1794. } else {
  1795. return -1;
  1796. }
  1797. } else {
  1798. return -1;
  1799. }
  1800. }
  1801. /**
  1802. * Define progression of current ticket
  1803. *
  1804. * @param int $percent Progression percent
  1805. * @return int Return integer <0 if KO, >0 if OK
  1806. */
  1807. public function setProgression($percent)
  1808. {
  1809. if ($this->id) {
  1810. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1811. $sql .= " SET progress = ".($percent > 0 ? $percent : "null");
  1812. $sql .= " WHERE rowid = ".((int) $this->id);
  1813. dol_syslog(get_class($this).'::set_progression sql='.$sql);
  1814. $resql = $this->db->query($sql);
  1815. if ($resql) {
  1816. return 1;
  1817. } else {
  1818. return -1;
  1819. }
  1820. } else {
  1821. return -1;
  1822. }
  1823. }
  1824. /**
  1825. * Link element with a contract
  1826. *
  1827. * @param int $contractid Contract id to link element to
  1828. * @return int Return integer <0 if KO, >0 if OK
  1829. */
  1830. public function setContract($contractid)
  1831. {
  1832. if ($this->id) {
  1833. $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
  1834. $sql .= " SET fk_contract = ".($contractid > 0 ? $contractid : "null");
  1835. $sql .= " WHERE rowid = ".((int) $this->id);
  1836. dol_syslog(get_class($this).'::setContract sql='.$sql);
  1837. $resql = $this->db->query($sql);
  1838. if ($resql) {
  1839. return 1;
  1840. } else {
  1841. return -1;
  1842. }
  1843. } else {
  1844. return -1;
  1845. }
  1846. }
  1847. /* gestion des contacts d'un ticket */
  1848. /**
  1849. * Return id des contacts interne de suivi
  1850. *
  1851. * @return array Liste des id contacts suivi ticket
  1852. */
  1853. public function getIdTicketInternalContact()
  1854. {
  1855. return $this->getIdContact('internal', 'SUPPORTTEC');
  1856. }
  1857. /**
  1858. * Retrieve informations about internal contacts
  1859. *
  1860. * @param int $status Status of user or company
  1861. * @return array Array with datas : firstname, lastname, socid (-1 for internal users), email, code, libelle, status
  1862. */
  1863. public function getInfosTicketInternalContact($status = -1)
  1864. {
  1865. return $this->listeContact(-1, 'internal', 0, '', $status);
  1866. }
  1867. /**
  1868. * Return id des contacts clients pour le suivi ticket
  1869. *
  1870. * @return array Liste des id contacts suivi ticket
  1871. */
  1872. public function getIdTicketCustomerContact()
  1873. {
  1874. return $this->getIdContact('external', 'SUPPORTCLI');
  1875. }
  1876. /**
  1877. * Retrieve informations about external contacts
  1878. *
  1879. * @param int $status Status of user or company
  1880. * @return array Array with datas : firstname, lastname, socid (-1 for internal users), email, code, libelle, status
  1881. */
  1882. public function getInfosTicketExternalContact($status = -1)
  1883. {
  1884. return $this->listeContact(-1, 'external', 0, '', $status);
  1885. }
  1886. /**
  1887. * Return id des contacts clients des intervenants
  1888. *
  1889. * @return array Liste des id contacts intervenants
  1890. */
  1891. public function getIdTicketInternalInvolvedContact()
  1892. {
  1893. return $this->getIdContact('internal', 'CONTRIBUTOR');
  1894. }
  1895. /**
  1896. * Return id des contacts clients des intervenants
  1897. *
  1898. * @return array Liste des id contacts intervenants
  1899. */
  1900. public function getIdTicketCustomerInvolvedContact()
  1901. {
  1902. return $this->getIdContact('external', 'CONTRIBUTOR');
  1903. }
  1904. /**
  1905. * Return id of all contacts for ticket
  1906. *
  1907. * @return array Array of contacts for tickets
  1908. */
  1909. public function getTicketAllContacts()
  1910. {
  1911. $array_contact = array();
  1912. $array_contact = $this->getIdTicketInternalContact();
  1913. $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
  1914. $array_contact = array_merge($array_contact, $this->getIdTicketInternalInvolvedContact());
  1915. $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
  1916. return $array_contact;
  1917. }
  1918. /**
  1919. * Return id of all contacts for ticket
  1920. *
  1921. * @return array Array of contacts
  1922. */
  1923. public function getTicketAllCustomerContacts()
  1924. {
  1925. $array_contact = array();
  1926. $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
  1927. $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
  1928. return $array_contact;
  1929. }
  1930. /**
  1931. * Get array of all contacts for a ticket
  1932. * Override method of file commonobject.class.php to add phone number
  1933. *
  1934. * @param int $statusoflink Status of lines to get (-1=all)
  1935. * @param string $source Source of contact: external or thirdparty (llx_socpeople) or internal (llx_user)
  1936. * @param int $list 0:Return array contains all properties, 1:Return array contains just id
  1937. * @param string $code Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
  1938. * @param int $status Status of user or company
  1939. * @return array|int Array of contacts
  1940. */
  1941. public function listeContact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1)
  1942. {
  1943. global $langs;
  1944. $tab = array();
  1945. $sql = "SELECT ec.rowid, ec.statut as statuslink, ec.fk_socpeople as id, ec.fk_c_type_contact"; // This field contains id of llx_socpeople or id of llx_user
  1946. if ($source == 'internal') {
  1947. $sql .= ", '-1' as socid, t.statut as statuscontact";
  1948. }
  1949. if ($source == 'external' || $source == 'thirdparty') {
  1950. $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
  1951. }
  1952. $sql .= ", t.civility, t.lastname as lastname, t.firstname, t.email";
  1953. if ($source == 'internal') {
  1954. $sql .= ", t.office_phone as phone, t.user_mobile as phone_mobile";
  1955. }
  1956. if ($source == 'external') {
  1957. $sql .= ", t.phone as phone, t.phone_mobile as phone_mobile, t.phone_perso as phone_perso";
  1958. }
  1959. $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_contact_label";
  1960. $sql .= " FROM ".MAIN_DB_PREFIX."c_type_contact tc";
  1961. $sql .= ", ".MAIN_DB_PREFIX."element_contact ec";
  1962. if ($source == 'internal') {
  1963. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user t on ec.fk_socpeople = t.rowid";
  1964. }
  1965. if ($source == 'external' || $source == 'thirdparty') {
  1966. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople t on ec.fk_socpeople = t.rowid";
  1967. }
  1968. $sql .= " WHERE ec.element_id = ".((int) $this->id);
  1969. $sql .= " AND ec.fk_c_type_contact=tc.rowid";
  1970. $sql .= " AND tc.element='".$this->db->escape($this->element)."'";
  1971. if ($source == 'internal') {
  1972. $sql .= " AND tc.source = 'internal'";
  1973. if ($status >= 0) {
  1974. $sql .= " AND t.statut = ".((int) $status);
  1975. }
  1976. }
  1977. if ($source == 'external' || $source == 'thirdparty') {
  1978. $sql .= " AND tc.source = 'external'";
  1979. if ($status >= 0) {
  1980. $sql .= " AND t.statut = ".((int) $status);
  1981. }
  1982. }
  1983. if (!empty($code)) {
  1984. $sql .= " AND tc.code = '".$this->db->escape($code)."'";
  1985. }
  1986. $sql .= " AND tc.active=1";
  1987. if ($statusoflink >= 0) {
  1988. $sql .= " AND ec.statut = ".((int) $statusoflink);
  1989. }
  1990. $sql .= " ORDER BY t.lastname ASC";
  1991. $resql = $this->db->query($sql);
  1992. if ($resql) {
  1993. $num = $this->db->num_rows($resql);
  1994. $i = 0;
  1995. while ($i < $num) {
  1996. $obj = $this->db->fetch_object($resql);
  1997. if (!$list) {
  1998. $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
  1999. $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_contact_label);
  2000. $tab[$i] = array(
  2001. 'source' => $obj->source,
  2002. 'socid' => $obj->socid,
  2003. 'id' => $obj->id,
  2004. 'nom' => $obj->lastname, // For backward compatibility
  2005. 'civility' => $obj->civility,
  2006. 'lastname' => $obj->lastname,
  2007. 'firstname' => $obj->firstname,
  2008. 'email' => $obj->email,
  2009. 'rowid' => $obj->rowid,
  2010. 'code' => $obj->code,
  2011. 'libelle' => $libelle_type,
  2012. 'status' => $obj->statuslink,
  2013. 'statuscontact'=>$obj->statuscontact,
  2014. 'fk_c_type_contact' => $obj->fk_c_type_contact,
  2015. 'phone' => $obj->phone,
  2016. 'phone_mobile' => $obj->phone_mobile);
  2017. } else {
  2018. $tab[$i] = $obj->id;
  2019. }
  2020. $i++;
  2021. }
  2022. return $tab;
  2023. } else {
  2024. $this->error = $this->db->error();
  2025. dol_print_error($this->db);
  2026. return -1;
  2027. }
  2028. }
  2029. /**
  2030. * Get a default reference.
  2031. *
  2032. * @param Societe $thirdparty Thirdparty
  2033. * @return string Reference
  2034. */
  2035. public function getDefaultRef($thirdparty = '')
  2036. {
  2037. global $conf;
  2038. $defaultref = '';
  2039. $modele = !getDolGlobalString('TICKET_ADDON') ? 'mod_ticket_simple' : $conf->global->TICKET_ADDON;
  2040. // Search template files
  2041. $file = '';
  2042. $classname = '';
  2043. $filefound = 0;
  2044. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  2045. foreach ($dirmodels as $reldir) {
  2046. $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
  2047. if (file_exists($file)) {
  2048. $filefound = 1;
  2049. $classname = $modele;
  2050. break;
  2051. }
  2052. }
  2053. if ($filefound) {
  2054. $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
  2055. $modTicket = new $classname();
  2056. $defaultref = $modTicket->getNextValue($thirdparty, $this);
  2057. }
  2058. if (is_numeric($defaultref) && $defaultref <= 0) {
  2059. $defaultref = '';
  2060. }
  2061. return $defaultref;
  2062. }
  2063. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2064. /**
  2065. * Return if at least one photo is available
  2066. *
  2067. * @param string $sdir Directory to scan
  2068. * @return boolean True if at least one photo is available, False if not
  2069. */
  2070. public function is_photo_available($sdir)
  2071. {
  2072. // phpcs:enable
  2073. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  2074. global $conf;
  2075. $dir = $sdir.'/';
  2076. $nbphoto = 0;
  2077. $dir_osencoded = dol_osencode($dir);
  2078. if (file_exists($dir_osencoded)) {
  2079. $handle = opendir($dir_osencoded);
  2080. if (is_resource($handle)) {
  2081. while (($file = readdir($handle)) !== false) {
  2082. if (!utf8_check($file)) {
  2083. $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1'); // To be sure data is stored in UTF8 in memory
  2084. }
  2085. if (dol_is_file($dir.$file)) {
  2086. return true;
  2087. }
  2088. }
  2089. }
  2090. }
  2091. return false;
  2092. }
  2093. /**
  2094. * Copy files defined into $_SESSION array into the ticket directory of attached files.
  2095. * Used for files linked into messages.
  2096. * Files may be renamed during copy to avoid overwriting existing files.
  2097. *
  2098. * @param string $forcetrackid Force trackid used for $keytoavoidconflict into get_attached_files()
  2099. * @return array|int Array with final path/name/mime of files.
  2100. */
  2101. public function copyFilesForTicket($forcetrackid = null)
  2102. {
  2103. global $conf;
  2104. // Create form object
  2105. include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
  2106. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  2107. include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
  2108. $maxwidthsmall = 270;
  2109. $maxheightsmall = 150;
  2110. $maxwidthmini = 128;
  2111. $maxheightmini = 72;
  2112. $formmail = new FormMail($this->db);
  2113. $formmail->trackid = (is_null($forcetrackid) ? 'tic'.$this->id : '');
  2114. $attachedfiles = $formmail->get_attached_files();
  2115. $filepath = $attachedfiles['paths']; // path is for example user->dir_temp.'/'.$user->id.'/'...
  2116. $filename = $attachedfiles['names'];
  2117. $mimetype = $attachedfiles['mimes'];
  2118. // Copy files into ticket directory
  2119. $destdir = $conf->ticket->dir_output.'/'.$this->ref;
  2120. if (!dol_is_dir($destdir)) {
  2121. dol_mkdir($destdir);
  2122. }
  2123. $listofpaths = array();
  2124. $listofnames = array();
  2125. foreach ($filename as $i => $val) {
  2126. $destfile = $destdir.'/'.$filename[$i];
  2127. // If destination file already exists, we add a suffix to avoid to overwrite
  2128. if (is_file($destfile)) {
  2129. $pathinfo = pathinfo($filename[$i]);
  2130. $now = dol_now();
  2131. $destfile = $destdir.'/'.$pathinfo['filename'].' - '.dol_print_date($now, 'dayhourlog').'.'.$pathinfo['extension'];
  2132. }
  2133. $moreinfo = array('description'=>'File saved by copyFilesForTicket', 'src_object_type' => $this->element, 'src_object_id' => $this->id);
  2134. $res = dol_move($filepath[$i], $destfile, 0, 1, 0, 1, $moreinfo);
  2135. if (!$res) {
  2136. // Move has failed
  2137. $this->error = "Failed to move file ".dirbasename($filepath[$i])." into ".dirbasename($destfile);
  2138. return -1;
  2139. } else {
  2140. // If file is an image, we create thumbs
  2141. if (image_format_supported($destfile) == 1) {
  2142. // Create small thumbs for image (Ratio is near 16/9)
  2143. // Used on logon for example
  2144. $imgThumbSmall = vignette($destfile, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
  2145. // Create mini thumbs for image (Ratio is near 16/9)
  2146. // Used on menu or for setup page for example
  2147. $imgThumbMini = vignette($destfile, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
  2148. }
  2149. }
  2150. // Clear variables into session
  2151. $formmail->remove_attached_files($i);
  2152. // Fill array with new names
  2153. $listofpaths[$i] = $destfile;
  2154. $listofnames[$i] = basename($destfile);
  2155. }
  2156. return array('listofpaths'=>$listofpaths, 'listofnames'=>$listofnames, 'listofmimes'=>$mimetype);
  2157. }
  2158. /**
  2159. * Sets object to supplied categories.
  2160. *
  2161. * Deletes object from existing categories not supplied.
  2162. * Adds it to non existing supplied categories.
  2163. * Existing categories are left untouch.
  2164. *
  2165. * @param int[]|int $categories Category or categories IDs
  2166. * @return int Return integer <0 if KO, >0 if OK
  2167. */
  2168. public function setCategories($categories)
  2169. {
  2170. // Handle single category
  2171. if (!is_array($categories)) {
  2172. $categories = array($categories);
  2173. }
  2174. // Get current categories
  2175. include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
  2176. $c = new Categorie($this->db);
  2177. $existing = $c->containing($this->id, Categorie::TYPE_TICKET, 'id');
  2178. // Diff
  2179. if (is_array($existing)) {
  2180. $to_del = array_diff($existing, $categories);
  2181. $to_add = array_diff($categories, $existing);
  2182. } else {
  2183. $to_del = array(); // Nothing to delete
  2184. $to_add = $categories;
  2185. }
  2186. // Process
  2187. foreach ($to_del as $del) {
  2188. if ($c->fetch($del) > 0) {
  2189. $c->del_type($this, Categorie::TYPE_TICKET);
  2190. }
  2191. }
  2192. foreach ($to_add as $add) {
  2193. if ($c->fetch($add) > 0) {
  2194. $c->add_type($this, Categorie::TYPE_TICKET);
  2195. }
  2196. }
  2197. return 1;
  2198. }
  2199. /**
  2200. * Add new message on a ticket (private/public area).
  2201. * Can also send it by email if GETPOST('send_email', 'int') is set. For such email, header and footer is added.
  2202. *
  2203. * @param User $user User for action
  2204. * @param string $action Action string
  2205. * @param int $private 1=Message is private (must not be visible by external users)
  2206. * @param int $public_area 0=Default,
  2207. * 1=If we are creating the message from a public area, so confirmation email will be sent to the author
  2208. * and we can search contact from email to add it as contact of ticket if TICKET_ASSIGN_CONTACT_TO_MESSAGE is set
  2209. * @return int Return integer <0 if KO, >= 0 if OK
  2210. */
  2211. public function newMessage($user, &$action, $private = 1, $public_area = 0)
  2212. {
  2213. global $mysoc, $conf, $langs;
  2214. $error = 0;
  2215. $object = new Ticket($this->db);
  2216. $ret = $object->fetch('', '', GETPOST('track_id', 'alpha'));
  2217. $object->socid = $object->fk_soc;
  2218. $object->fetch_thirdparty();
  2219. $object->fetch_project();
  2220. if ($ret < 0) {
  2221. $error++;
  2222. array_push($this->errors, $langs->trans("ErrorTicketIsNotValid"));
  2223. $action = '';
  2224. }
  2225. if (!GETPOST("message")) {
  2226. $error++;
  2227. array_push($this->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("message")));
  2228. $action = 'add_message';
  2229. }
  2230. if (!$error) {
  2231. $object->subject = GETPOST('subject', 'alphanohtml');
  2232. $object->message = GETPOST("message", "restricthtml");
  2233. $object->private = GETPOST("private_message", "alpha");
  2234. $send_email = GETPOST('send_email', 'int');
  2235. // Copy attached files (saved into $_SESSION) as linked files to ticket. Return array with final name used.
  2236. $resarray = $object->copyFilesForTicket();
  2237. if (is_numeric($resarray) && $resarray == -1) {
  2238. setEventMessages($object->error, $object->errors, 'errors');
  2239. return -1;
  2240. }
  2241. $listofpaths = $resarray['listofpaths'];
  2242. $listofnames = $resarray['listofnames'];
  2243. $listofmimes = $resarray['listofmimes'];
  2244. $id = $object->createTicketMessage($user, 0, $listofpaths, $listofmimes, $listofnames, $send_email, $public_area);
  2245. if ($id <= 0) {
  2246. $error++;
  2247. $this->error = $object->error;
  2248. $this->errors = $object->errors;
  2249. $action = 'add_message';
  2250. }
  2251. if (!$error && $id > 0) {
  2252. setEventMessages($langs->trans('TicketMessageSuccessfullyAdded'), null, 'mesgs');
  2253. //var_dump($_SESSION);
  2254. //var_dump($listofpaths);exit;
  2255. if (!empty($public_area)) {
  2256. /*
  2257. * Message created from the Public interface
  2258. *
  2259. * Send emails to assigned users (public area notification)
  2260. */
  2261. if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ENABLED')) {
  2262. // Retrieve internal contact datas
  2263. $internal_contacts = $object->getInfosTicketInternalContact(1);
  2264. $assigned_user_dont_have_email = '';
  2265. $sendto = array();
  2266. if ($this->fk_user_assign > 0) {
  2267. $assigned_user = new User($this->db);
  2268. $assigned_user->fetch($this->fk_user_assign);
  2269. if (!empty($assigned_user->email)) {
  2270. $sendto[$assigned_user->email] = $assigned_user->getFullName($langs)." <".$assigned_user->email.">";
  2271. } else {
  2272. $assigned_user_dont_have_email = $assigned_user->getFullName($langs);
  2273. }
  2274. }
  2275. // Build array to display recipient list
  2276. foreach ($internal_contacts as $key => $info_sendto) {
  2277. // Avoid duplicate notifications
  2278. if ($info_sendto['id'] == $user->id) {
  2279. continue;
  2280. }
  2281. if ($info_sendto['email'] != '') {
  2282. if (!empty($info_sendto['email'])) {
  2283. $sendto[] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'])." <".$info_sendto['email'].">";
  2284. }
  2285. }
  2286. }
  2287. if (empty($sendto)) {
  2288. if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')) {
  2289. $sendto[getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')] = $conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL;
  2290. } elseif (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
  2291. $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
  2292. }
  2293. }
  2294. // Add global email address recipient
  2295. if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') &&
  2296. getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') && !array_key_exists($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)
  2297. ) {
  2298. $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
  2299. }
  2300. if (!empty($sendto)) {
  2301. $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
  2302. $subject = '['.$appli.'- ticket #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
  2303. // Message send
  2304. $message = $langs->trans('TicketMessageMailIntroText');
  2305. $message .= '<br><br>';
  2306. $messagePost = GETPOST('message', 'restricthtml');
  2307. if (!dol_textishtml($messagePost)) {
  2308. $messagePost = dol_nl2br($messagePost);
  2309. }
  2310. $message .= $messagePost;
  2311. // Customer company infos
  2312. $message .= '<br><br>';
  2313. $message .= "==============================================";
  2314. $message .= !empty($object->thirdparty->name) ? '<br>'.$langs->trans('Thirdparty')." : ".$object->thirdparty->name : '';
  2315. $message .= !empty($object->thirdparty->town) ? '<br>'.$langs->trans('Town')." : ".$object->thirdparty->town : '';
  2316. $message .= !empty($object->thirdparty->phone) ? '<br>'.$langs->trans('Phone')." : ".$object->thirdparty->phone : '';
  2317. // Email send to
  2318. $message .= '<br><br>';
  2319. if (!empty($assigned_user_dont_have_email)) {
  2320. $message .= '<br>'.$langs->trans('NoEMail').' : '.$assigned_user_dont_have_email;
  2321. }
  2322. foreach ($sendto as $val) {
  2323. $message .= '<br>'.$langs->trans('TicketNotificationRecipient').' : '.$val;
  2324. }
  2325. // URL ticket
  2326. $url_internal_ticket = dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id;
  2327. $message .= '<br><br>';
  2328. $message .= $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal').' : <a href="'.$url_internal_ticket.'">'.$object->track_id.'</a>';
  2329. $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
  2330. }
  2331. }
  2332. } else {
  2333. /*
  2334. * Message send from the Backoffice / Private area
  2335. *
  2336. * Send emails to internal users (linked contacts) then, if private is not set, to external users (linked contacts or thirdparty email if no contact set)
  2337. */
  2338. if ($send_email > 0) {
  2339. // Retrieve internal contact datas
  2340. $internal_contacts = $object->getInfosTicketInternalContact(1);
  2341. $sendto = array();
  2342. if (is_array($internal_contacts) && count($internal_contacts) > 0) {
  2343. // Set default subject
  2344. $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
  2345. $subject = GETPOST('subject', 'alphanohtml') ? GETPOST('subject', 'alphanohtml') : '['.$appli.' - '.$langs->trans("Ticket").' #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
  2346. $message_intro = $langs->trans('TicketNotificationEmailBody', "#".$object->id);
  2347. $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
  2348. $message = $langs->trans('TicketMessageMailIntroText');
  2349. $message .= '<br><br>';
  2350. $messagePost = GETPOST('message', 'restricthtml');
  2351. if (!dol_textishtml($messagePost)) {
  2352. $messagePost = dol_nl2br($messagePost);
  2353. }
  2354. $message .= $messagePost;
  2355. // Data about customer
  2356. $message .= '<br><br>';
  2357. $message .= "==============================================<br>";
  2358. $message .= !empty($object->thirdparty->name) ? $langs->trans('Thirdparty')." : ".$object->thirdparty->name : '';
  2359. $message .= !empty($object->thirdparty->town) ? '<br>'.$langs->trans('Town')." : ".$object->thirdparty->town : '';
  2360. $message .= !empty($object->thirdparty->phone) ? '<br>'.$langs->trans('Phone')." : ".$object->thirdparty->phone : '';
  2361. // Build array to display recipient list
  2362. foreach ($internal_contacts as $key => $info_sendto) {
  2363. // Avoid duplicate notifications
  2364. if ($info_sendto['id'] == $user->id) {
  2365. continue;
  2366. }
  2367. if ($info_sendto['email'] != '') {
  2368. if (!empty($info_sendto['email'])) {
  2369. $sendto[$info_sendto['email']] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'])." <".$info_sendto['email'].">";
  2370. }
  2371. // Contact type
  2372. $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1').' ('.strtolower($info_sendto['libelle']).')';
  2373. $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient').' : '.$recipient.'<br>' : '');
  2374. }
  2375. }
  2376. $message .= '<br>';
  2377. // URL ticket
  2378. $url_internal_ticket = dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id;
  2379. // Add html link on url
  2380. $message .= '<br>'.$langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal').' : <a href="'.$url_internal_ticket.'">'.$object->track_id.'</a><br>';
  2381. // Add global email address recipient
  2382. if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') !== '' && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
  2383. if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') !== '') {
  2384. $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
  2385. }
  2386. }
  2387. // dont try to send email if no recipient
  2388. if (!empty($sendto)) {
  2389. $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
  2390. }
  2391. }
  2392. /*
  2393. * Send emails for externals users if not private (linked contacts)
  2394. */
  2395. if (empty($object->private)) {
  2396. // Retrieve email of all contacts (external)
  2397. $external_contacts = $object->getInfosTicketExternalContact(1);
  2398. // If no contact, get email from thirdparty
  2399. if (is_array($external_contacts) && count($external_contacts) === 0) {
  2400. if (!empty($object->fk_soc)) {
  2401. $object->fetch_thirdparty($object->fk_soc);
  2402. $array_company = array(array('firstname' => '', 'lastname' => $object->thirdparty->name, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
  2403. $external_contacts = array_merge($external_contacts, $array_company);
  2404. } elseif (empty($object->fk_soc) && !empty($object->origin_email)) {
  2405. $array_external = array(array('firstname' => '', 'lastname' => $object->origin_email, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
  2406. $external_contacts = array_merge($external_contacts, $array_external);
  2407. }
  2408. }
  2409. $sendto = array();
  2410. if (is_array($external_contacts) && count($external_contacts) > 0) {
  2411. // Get default subject for email to external contacts
  2412. $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
  2413. $subject = GETPOST('subject') ? GETPOST('subject') : '['.$appli.' - '.$langs->trans("Ticket").' #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
  2414. $message_intro = GETPOST('mail_intro') ? GETPOST('mail_intro', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_INTRO');
  2415. $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
  2416. if (!dol_textishtml($message_intro)) {
  2417. $message_intro = dol_nl2br($message_intro);
  2418. }
  2419. if (!dol_textishtml($message_signature)) {
  2420. $message_signature = dol_nl2br($message_signature);
  2421. }
  2422. // We put intro after
  2423. $messagePost = GETPOST('message', 'restricthtml');
  2424. if (!dol_textishtml($messagePost)) {
  2425. $messagePost = dol_nl2br($messagePost);
  2426. }
  2427. $message = $messagePost;
  2428. $message .= '<br><br>';
  2429. foreach ($external_contacts as $key => $info_sendto) {
  2430. // altairis: avoid duplicate emails to external contacts
  2431. if ($info_sendto['id'] == $user->contact_id) {
  2432. continue;
  2433. }
  2434. if ($info_sendto['email'] != '' && $info_sendto['email'] != $object->origin_email) {
  2435. if (!empty($info_sendto['email'])) {
  2436. $sendto[$info_sendto['email']] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">";
  2437. }
  2438. $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1').' ('.strtolower($info_sendto['libelle']).')';
  2439. $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient').' : '.$recipient.'<br>' : '');
  2440. }
  2441. }
  2442. // If public interface is not enable, use link to internal page into mail
  2443. $url_public_ticket = (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE') ?
  2444. (getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE') !== '' ? getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE') . '/view.php' : dol_buildpath('/public/ticket/view.php', 2)) : dol_buildpath('/ticket/card.php', 2)).'?track_id='.$object->track_id;
  2445. $message .= '<br>'.$langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer').' : <a href="'.$url_public_ticket.'">'.$object->track_id.'</a><br>';
  2446. // Build final message
  2447. $message = $message_intro.'<br><br>'.$message;
  2448. // Add signature
  2449. $message .= '<br>'.$message_signature;
  2450. if (!empty($object->origin_email)) {
  2451. $sendto[$object->origin_email] = $object->origin_email;
  2452. }
  2453. if ($object->fk_soc > 0 && !array_key_exists($object->origin_email, $sendto)) {
  2454. $object->socid = $object->fk_soc;
  2455. $object->fetch_thirdparty();
  2456. if (!empty($object->thirdparty->email)) {
  2457. $sendto[$object->thirdparty->email] = $object->thirdparty->email;
  2458. }
  2459. }
  2460. // Add global email address recipient
  2461. if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') !== '' && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
  2462. if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') !== '') {
  2463. $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
  2464. }
  2465. }
  2466. // Dont try to send email when no recipient
  2467. if (!empty($sendto)) {
  2468. $result = $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
  2469. if ($result) {
  2470. // update last_msg_sent date (for last message sent to external users)
  2471. $this->date_last_msg_sent = dol_now();
  2472. $this->update($user, 1); // disable trigger when updating date_last_msg_sent. sendTicketMessageByEmail already create an event in actioncomm table.
  2473. }
  2474. }
  2475. }
  2476. }
  2477. }
  2478. }
  2479. // Set status back to "In progress" if not set yet, but only if internal user and not a private message
  2480. // Or set status to "In porgress" if the client has answered and if the ticket has started
  2481. // So we are sure to leave the STATUS_DRAFT, STATUS_NEED_INFO.
  2482. if (($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) ||
  2483. ($object->status > self::STATUS_IN_PROGRESS && $public_area)
  2484. ) {
  2485. $object->setStatut($object::STATUS_IN_PROGRESS);
  2486. }
  2487. return 1;
  2488. } else {
  2489. setEventMessages($object->error, $object->errors, 'errors');
  2490. return -1;
  2491. }
  2492. } else {
  2493. setEventMessages($this->error, $this->errors, 'errors');
  2494. return -1;
  2495. }
  2496. }
  2497. /**
  2498. * Send ticket by email to linked contacts
  2499. *
  2500. * @param string $subject Email subject
  2501. * @param string $message Email message
  2502. * @param int $send_internal_cc Receive a copy on internal email ($conf->global->TICKET_NOTIFICATION_EMAIL_FROM)
  2503. * @param array $array_receiver Array of receiver. exemple array('name' => 'John Doe', 'email' => 'john@doe.com', etc...)
  2504. * @param array $filename_list List of files to attach (full path of filename on file system)
  2505. * @param array $mimetype_list List of MIME type of attached files
  2506. * @param array $mimefilename_list List of attached file name in message
  2507. * @return boolean True if mail sent to at least one receiver, false otherwise
  2508. */
  2509. public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array())
  2510. {
  2511. global $conf, $langs, $user;
  2512. if (getDolGlobalInt('TICKET_DISABLE_ALL_MAILS')) {
  2513. dol_syslog(get_class($this).'::sendTicketMessageByEmail: Emails are disable into ticket setup by option TICKET_DISABLE_ALL_MAILS', LOG_WARNING);
  2514. return false;
  2515. }
  2516. $langs->load("mails");
  2517. include_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
  2518. //$contactstatic = new Contact($this->db);
  2519. // If no receiver defined, load all ticket linked contacts
  2520. if (!is_array($array_receiver) || !count($array_receiver) > 0) {
  2521. $array_receiver = $this->getInfosTicketInternalContact(1);
  2522. $array_receiver = array_merge($array_receiver, $this->getInfosTicketExternalContact(1));
  2523. }
  2524. if ($send_internal_cc) {
  2525. $sendtocc = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
  2526. } else {
  2527. $sendtocc = '';
  2528. }
  2529. $from = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
  2530. $is_sent = false;
  2531. if (is_array($array_receiver) && count($array_receiver) > 0) {
  2532. foreach ($array_receiver as $key => $receiver) {
  2533. $deliveryreceipt = 0;
  2534. $filepath = $filename_list;
  2535. $filename = $mimefilename_list;
  2536. $mimetype = $mimetype_list;
  2537. // Send email
  2538. $old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
  2539. if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO') !== '') {
  2540. $conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
  2541. }
  2542. $upload_dir_tmp = $conf->user->dir_output."/".$user->id.'/temp';
  2543. include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
  2544. $trackid = "tic".$this->id;
  2545. $moreinheader = 'X-Dolibarr-Info: sendTicketMessageByEmail'."\r\n";
  2546. if (!empty($this->email_msgid)) {
  2547. $moreinheader .= 'References <'.$this->email_msgid.'>'."\r\n";
  2548. }
  2549. $mailfile = new CMailFile($subject, $receiver, $from, $message, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', $trackid, $moreinheader, 'ticket', '', $upload_dir_tmp);
  2550. if ($mailfile->error) {
  2551. setEventMessages($mailfile->error, null, 'errors');
  2552. } else {
  2553. $result = $mailfile->sendfile();
  2554. if ($result) {
  2555. setEventMessages($langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($from, 2), $mailfile->getValidAddress($receiver, 2)), null, 'mesgs');
  2556. $is_sent = true;
  2557. } else {
  2558. $langs->load("other");
  2559. if ($mailfile->error) {
  2560. setEventMessages($langs->trans('ErrorFailedToSendMail', $from, $receiver), null, 'errors');
  2561. dol_syslog($langs->trans('ErrorFailedToSendMail', $from, $receiver).' : '.$mailfile->error);
  2562. } else {
  2563. setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'errors');
  2564. }
  2565. }
  2566. }
  2567. if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO') !== '') {
  2568. $conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
  2569. }
  2570. }
  2571. } else {
  2572. $langs->load("other");
  2573. setEventMessages($langs->trans('ErrorMailRecipientIsEmptyForSendTicketMessage'), null, 'warnings');
  2574. }
  2575. return $is_sent;
  2576. }
  2577. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2578. /**
  2579. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  2580. *
  2581. * @param User $user Object user
  2582. * @param int $mode "opened" for askprice to close, "signed" for proposal to invoice
  2583. * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
  2584. */
  2585. public function load_board($user, $mode)
  2586. {
  2587. // phpcs:enable
  2588. global $user, $langs;
  2589. $now = dol_now();
  2590. $delay_warning = 0;
  2591. $clause = " WHERE";
  2592. $sql = "SELECT p.rowid, p.ref, p.datec as datec";
  2593. $sql .= " FROM ".MAIN_DB_PREFIX."ticket as p";
  2594. if (isModEnabled('societe') && !$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  2595. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
  2596. $sql .= " WHERE sc.fk_user = ".((int) $user->id);
  2597. $clause = " AND";
  2598. }
  2599. $sql .= $clause." p.entity IN (".getEntity('ticket').")";
  2600. if ($mode == 'opened') {
  2601. $sql .= " AND p.fk_statut NOT IN (".Ticket::STATUS_CLOSED.", ".Ticket::STATUS_CANCELED.")";
  2602. }
  2603. if ($user->socid) {
  2604. $sql .= " AND p.fk_soc = ".((int) $user->socid);
  2605. }
  2606. $resql = $this->db->query($sql);
  2607. if ($resql) {
  2608. $label = $labelShort = '';
  2609. $status = '';
  2610. if ($mode == 'opened') {
  2611. $status = 'openall';
  2612. //$delay_warning = $conf->ticket->warning_delay;
  2613. $delay_warning = 0;
  2614. $label = $langs->trans("MenuListNonClosed");
  2615. $labelShort = $langs->trans("MenuListNonClosed");
  2616. }
  2617. $response = new WorkboardResponse();
  2618. //$response->warning_delay = $delay_warning / 60 / 60 / 24;
  2619. $response->label = $label;
  2620. $response->labelShort = $labelShort;
  2621. $response->url = DOL_URL_ROOT.'/ticket/list.php?search_fk_statut[]='.$status;
  2622. $response->img = img_object('', "ticket");
  2623. // This assignment in condition is not a bug. It allows walking the results.
  2624. while ($obj = $this->db->fetch_object($resql)) {
  2625. $response->nbtodo++;
  2626. if ($mode == 'opened') {
  2627. $datelimit = $this->db->jdate($obj->datec) + $delay_warning;
  2628. if ($datelimit < $now) {
  2629. //$response->nbtodolate++;
  2630. }
  2631. }
  2632. }
  2633. return $response;
  2634. } else {
  2635. $this->error = $this->db->lasterror();
  2636. return -1;
  2637. }
  2638. }
  2639. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  2640. /**
  2641. * Load indicator this->nb of global stats widget
  2642. *
  2643. * @return int Return integer <0 if ko, >0 if ok
  2644. */
  2645. public function load_state_board()
  2646. {
  2647. // phpcs:enable
  2648. global $conf, $user;
  2649. $this->nb = array();
  2650. $clause = "WHERE";
  2651. $sql = "SELECT count(p.rowid) as nb";
  2652. $sql .= " FROM ".MAIN_DB_PREFIX."ticket as p";
  2653. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
  2654. if (!$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
  2655. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
  2656. $sql .= " WHERE sc.fk_user = ".((int) $user->id);
  2657. $clause = "AND";
  2658. }
  2659. $sql .= " ".$clause." p.entity IN (".getEntity('ticket').")";
  2660. $resql = $this->db->query($sql);
  2661. if ($resql) {
  2662. // This assignment in condition is not a bug. It allows walking the results.
  2663. while ($obj = $this->db->fetch_object($resql)) {
  2664. $this->nb["ticket"] = $obj->nb;
  2665. }
  2666. $this->db->free($resql);
  2667. return 1;
  2668. } else {
  2669. dol_print_error($this->db);
  2670. $this->error = $this->db->lasterror();
  2671. return -1;
  2672. }
  2673. }
  2674. /**
  2675. * Function used to replace a thirdparty id with another one.
  2676. *
  2677. * @param DoliDB $db Database handler
  2678. * @param int $origin_id Old thirdparty id
  2679. * @param int $dest_id New thirdparty id
  2680. * @return bool
  2681. */
  2682. public static function replaceThirdparty($db, $origin_id, $dest_id)
  2683. {
  2684. $tables = array('ticket');
  2685. return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
  2686. }
  2687. /**
  2688. * Return clicable link of object (with eventually picto)
  2689. *
  2690. * @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
  2691. * @param array $arraydata Array of data
  2692. * @return string HTML Code for Kanban thumb.
  2693. */
  2694. public function getKanbanView($option = '', $arraydata = null)
  2695. {
  2696. global $langs;
  2697. $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
  2698. $return = '<div class="box-flex-item box-flex-grow-zero">';
  2699. $return .= '<div class="info-box info-box-sm">';
  2700. $return .= '<span class="info-box-icon bg-infobox-action">';
  2701. $return .= img_picto('', $this->picto);
  2702. $return .= '</span>';
  2703. $return .= '<div class="info-box-content">';
  2704. $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
  2705. if ($selected >= 0) {
  2706. $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
  2707. }
  2708. if (!empty($arraydata['user_assignment'])) {
  2709. $return .= '<br><span class="info-box-label" title="'.dol_escape_htmltag($langs->trans("AssignedTo")).'">'.$arraydata['user_assignment'].'</span>';
  2710. }
  2711. if (property_exists($this, 'type_code') && !empty($this->type_code)) {
  2712. $return .= '<br>';
  2713. $return .= '<div class="tdoverflowmax125 inline-block">'.$langs->getLabelFromKey($this->db, 'TicketTypeShort'.$this->type_code, 'c_ticket_type', 'code', 'label', $this->type_code).'</div>';
  2714. }
  2715. if (method_exists($this, 'getLibStatut')) {
  2716. $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
  2717. }
  2718. $return .= '</div>';
  2719. $return .= '</div>';
  2720. $return .= '</div>';
  2721. return $return;
  2722. }
  2723. }