ticket.class.php 105 KB

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