PageRenderTime 42ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 1ms

/api/DAO.class.php

https://github.com/stolf/cerb4
PHP | 7276 lines | 5017 code | 1388 blank | 871 comment | 454 complexity | b4be966c677f6ad2bfba842536469640 MD5 | raw file
  1. <?php
  2. /***********************************************************************
  3. | Cerberus Helpdesk(tm) developed by WebGroup Media, LLC.
  4. |-----------------------------------------------------------------------
  5. | All source code & content (c) Copyright 2007, WebGroup Media LLC
  6. | unless specifically noted otherwise.
  7. |
  8. | This source code is released under the Cerberus Public License.
  9. | The latest version of this license can be found here:
  10. | http://www.cerberusweb.com/license.php
  11. |
  12. | By using this software, you acknowledge having read this license
  13. | and agree to be bound thereby.
  14. | ______________________________________________________________________
  15. | http://www.cerberusweb.com http://www.webgroupmedia.com/
  16. ***********************************************************************/
  17. /*
  18. * IMPORTANT LICENSING NOTE from your friends on the Cerberus Helpdesk Team
  19. *
  20. * Sure, it would be so easy to just cheat and edit this file to use the
  21. * software without paying for it. But we trust you anyway. In fact, we're
  22. * writing this software for you!
  23. *
  24. * Quality software backed by a dedicated team takes money to develop. We
  25. * don't want to be out of the office bagging groceries when you call up
  26. * needing a helping hand. We'd rather spend our free time coding your
  27. * feature requests than mowing the neighbors' lawns for rent money.
  28. *
  29. * We've never believed in encoding our source code out of paranoia over not
  30. * getting paid. We want you to have the full source code and be able to
  31. * make the tweaks your organization requires to get more done -- despite
  32. * having less of everything than you might need (time, people, money,
  33. * energy). We shouldn't be your bottleneck.
  34. *
  35. * We've been building our expertise with this project since January 2002. We
  36. * promise spending a couple bucks [Euro, Yuan, Rupees, Galactic Credits] to
  37. * let us take over your shared e-mail headache is a worthwhile investment.
  38. * It will give you a sense of control over your in-box that you probably
  39. * haven't had since spammers found you in a game of "E-mail Address
  40. * Battleship". Miss. Miss. You sunk my in-box!
  41. *
  42. * A legitimate license entitles you to support, access to the developer
  43. * mailing list, the ability to participate in betas and the warm fuzzy
  44. * feeling of feeding a couple obsessed developers who want to help you get
  45. * more done than 'the other guy'.
  46. *
  47. * - Jeff Standen, Mike Fogg, Brenan Cavish, Darren Sugita, Dan Hildebrandt
  48. * and Joe Geck.
  49. * WEBGROUP MEDIA LLC. - Developers of Cerberus Helpdesk
  50. */
  51. class C4_ORMHelper extends DevblocksORMHelper {
  52. static public function qstr($str) {
  53. $db = DevblocksPlatform::getDatabaseService();
  54. return $db->qstr($str);
  55. }
  56. static protected function _appendSelectJoinSqlForCustomFieldTables($tables, $params, $key, $select_sql, $join_sql) {
  57. $custom_fields = DAO_CustomField::getAll();
  58. $field_ids = array();
  59. $return_multiple_values = false; // can our CF return more than one hit? (GROUP BY)
  60. if(is_array($tables))
  61. foreach($tables as $tbl_name => $null) {
  62. // Filter and sanitize
  63. if(substr($tbl_name,0,3) != "cf_" // not a custom field
  64. || 0 == ($field_id = intval(substr($tbl_name,3)))) // not a field_id
  65. continue;
  66. // Make sure the field exists for this source
  67. if(!isset($custom_fields[$field_id]))
  68. continue;
  69. $field_table = sprintf("cf_%d", $field_id);
  70. $value_table = '';
  71. // Join value by field data type
  72. switch($custom_fields[$field_id]->type) {
  73. case 'T': // multi-line CLOB
  74. $value_table = 'custom_field_clobvalue';
  75. break;
  76. case 'C': // checkbox
  77. case 'E': // date
  78. case 'N': // number
  79. case 'W': // worker
  80. $value_table = 'custom_field_numbervalue';
  81. break;
  82. default:
  83. case 'S': // single-line
  84. case 'D': // dropdown
  85. case 'U': // URL
  86. $value_table = 'custom_field_stringvalue';
  87. break;
  88. }
  89. $has_multiple_values = false;
  90. switch($custom_fields[$field_id]->type) {
  91. case Model_CustomField::TYPE_MULTI_PICKLIST:
  92. case Model_CustomField::TYPE_MULTI_CHECKBOX:
  93. $has_multiple_values = true;
  94. break;
  95. }
  96. // If we have multiple values but we don't need to WHERE the JOIN, be efficient and don't GROUP BY
  97. if(!isset($params['cf_'.$field_id])) {
  98. $select_sql .= sprintf(",(SELECT field_value FROM %s WHERE %s=source_id AND field_id=%d LIMIT 0,1) AS %s ",
  99. $value_table,
  100. $key,
  101. $field_id,
  102. $field_table
  103. );
  104. } else {
  105. $select_sql .= sprintf(", %s.field_value as %s ",
  106. $field_table,
  107. $field_table
  108. );
  109. $join_sql .= sprintf("LEFT JOIN %s %s ON (%s=%s.source_id AND %s.field_id=%d) ",
  110. $value_table,
  111. $field_table,
  112. $key,
  113. $field_table,
  114. $field_table,
  115. $field_id
  116. );
  117. // If we do need to WHERE this JOIN, make sure we GROUP BY
  118. if($has_multiple_values)
  119. $return_multiple_values = true;
  120. }
  121. }
  122. return array($select_sql, $join_sql, $return_multiple_values);
  123. }
  124. }
  125. /**
  126. * Global Settings DAO
  127. */
  128. class DAO_Setting extends DevblocksORMHelper {
  129. static function set($key, $value) {
  130. $db = DevblocksPlatform::getDatabaseService();
  131. $db->Replace('setting',array('setting'=>$db->qstr($key),'value'=>$db->qstr($value)),array('setting'),false);
  132. }
  133. static function get($key) {
  134. $db = DevblocksPlatform::getDatabaseService();
  135. $sql = sprintf("SELECT value FROM setting WHERE setting = %s",
  136. $db->qstr($key)
  137. );
  138. $value = $db->GetOne($sql) or die(__CLASS__ . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  139. return $value;
  140. }
  141. static function getSettings() {
  142. $cache = DevblocksPlatform::getCacheService();
  143. if(null === ($settings = $cache->load(CerberusApplication::CACHE_SETTINGS_DAO))) {
  144. $db = DevblocksPlatform::getDatabaseService();
  145. $settings = array();
  146. $sql = sprintf("SELECT setting,value FROM setting");
  147. $rs = $db->Execute($sql) or die(__CLASS__ . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  148. if(is_a($rs,'ADORecordSet'))
  149. while(!$rs->EOF) {
  150. $settings[$rs->Fields('setting')] = $rs->Fields('value');
  151. $rs->MoveNext();
  152. }
  153. $cache->save($settings, CerberusApplication::CACHE_SETTINGS_DAO);
  154. }
  155. return $settings;
  156. }
  157. };
  158. /**
  159. * Bayesian Anti-Spam DAO
  160. */
  161. class DAO_Bayes {
  162. private function DAO_Bayes() {}
  163. /**
  164. * @return CerberusWord[]
  165. */
  166. static function lookupWordIds($words) {
  167. $db = DevblocksPlatform::getDatabaseService();
  168. $tmp = array();
  169. $outwords = array(); // CerberusWord
  170. // Escaped set
  171. if(is_array($words))
  172. foreach($words as $word) {
  173. $tmp[] = $db->escape($word);
  174. }
  175. if(empty($words))
  176. return array();
  177. $sql = sprintf("SELECT id,word,spam,nonspam FROM bayes_words WHERE word IN ('%s')",
  178. implode("','", $tmp)
  179. );
  180. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  181. // [JAS]: Keep a list of words we can check off as we index them with IDs
  182. $tmp = array_flip($words); // words are now keys
  183. // Existing Words
  184. if(is_a($rs,'ADORecordSet'))
  185. while(!$rs->EOF) {
  186. $w = new CerberusBayesWord();
  187. $w->id = intval($rs->fields['id']);
  188. $w->word = mb_convert_case($rs->fields['word'], MB_CASE_LOWER);
  189. $w->spam = intval($rs->fields['spam']);
  190. $w->nonspam = intval($rs->fields['nonspam']);
  191. $outwords[mb_convert_case($w->word, MB_CASE_LOWER)] = $w;
  192. unset($tmp[$w->word]); // check off we've indexed this word
  193. $rs->MoveNext();
  194. }
  195. // Insert new words
  196. if(is_array($tmp))
  197. foreach($tmp as $new_word => $v) {
  198. $new_id = $db->GenID('bayes_words_seq');
  199. $sql = sprintf("INSERT INTO bayes_words (id,word) VALUES (%d,%s)",
  200. $new_id,
  201. $db->qstr($new_word)
  202. );
  203. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  204. $w = new CerberusBayesWord();
  205. $w->id = $new_id;
  206. $w->word = $new_word;
  207. $outwords[$w->word] = $w;
  208. }
  209. return $outwords;
  210. }
  211. /**
  212. * @return array Two element array (keys: spam,nonspam)
  213. */
  214. static function getStatistics() {
  215. $db = DevblocksPlatform::getDatabaseService();
  216. // [JAS]: [TODO] Change this into a 'replace' index?
  217. $sql = "SELECT spam, nonspam FROM bayes_stats";
  218. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  219. if($rs->NumRows()) {
  220. $spam = intval($rs->Fields('spam'));
  221. $nonspam = intval($rs->Fields('nonspam'));
  222. } else {
  223. $spam = 0;
  224. $nonspam = 0;
  225. $sql = "INSERT INTO bayes_stats (spam, nonspam) VALUES (0,0)";
  226. $db->Execute($sql);
  227. }
  228. return array('spam' => $spam,'nonspam' => $nonspam);
  229. }
  230. static function addOneToSpamTotal() {
  231. $db = DevblocksPlatform::getDatabaseService();
  232. $sql = "UPDATE bayes_stats SET spam = spam + 1";
  233. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  234. }
  235. static function addOneToNonSpamTotal() {
  236. $db = DevblocksPlatform::getDatabaseService();
  237. $sql = "UPDATE bayes_stats SET nonspam = nonspam + 1";
  238. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  239. }
  240. static function addOneToSpamWord($word_ids=array()) {
  241. if(!is_array($word_ids)) $word_ids = array($word_ids);
  242. if(empty($word_ids)) return;
  243. $db = DevblocksPlatform::getDatabaseService();
  244. $sql = sprintf("UPDATE bayes_words SET spam = spam + 1 WHERE id IN(%s)", implode(',',$word_ids));
  245. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  246. }
  247. static function addOneToNonSpamWord($word_ids=array()) {
  248. if(!is_array($word_ids)) $word_ids = array($word_ids);
  249. if(empty($word_ids)) return;
  250. $db = DevblocksPlatform::getDatabaseService();
  251. $sql = sprintf("UPDATE bayes_words SET nonspam = nonspam + 1 WHERE id IN(%s)", implode(',',$word_ids));
  252. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  253. }
  254. };
  255. /**
  256. * Worker DAO
  257. */
  258. class DAO_Worker extends C4_ORMHelper {
  259. private function DAO_Worker() {}
  260. const CACHE_ALL = 'ch_workers';
  261. const ID = 'id';
  262. const FIRST_NAME = 'first_name';
  263. const LAST_NAME = 'last_name';
  264. const TITLE = 'title';
  265. const EMAIL = 'email';
  266. const PASSWORD = 'pass';
  267. const IS_SUPERUSER = 'is_superuser';
  268. const IS_DISABLED = 'is_disabled';
  269. const LAST_ACTIVITY_DATE = 'last_activity_date';
  270. const LAST_ACTIVITY = 'last_activity';
  271. // [TODO] Convert to ::create($id, $fields)
  272. static function create($email, $password, $first_name, $last_name, $title) {
  273. if(empty($email) || empty($password))
  274. return null;
  275. $db = DevblocksPlatform::getDatabaseService();
  276. $id = $db->GenID('generic_seq');
  277. $sql = sprintf("INSERT INTO worker (id, email, pass, first_name, last_name, title, is_superuser, is_disabled) ".
  278. "VALUES (%d, %s, %s, %s, %s, %s,0,0)",
  279. $id,
  280. $db->qstr($email),
  281. $db->qstr(md5($password)),
  282. $db->qstr($first_name),
  283. $db->qstr($last_name),
  284. $db->qstr($title)
  285. );
  286. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  287. self::clearCache();
  288. return $id;
  289. }
  290. static function clearCache() {
  291. $cache = DevblocksPlatform::getCacheService();
  292. $cache->remove(self::CACHE_ALL);
  293. }
  294. static function getAllActive() {
  295. return self::getAll(false, false);
  296. }
  297. static function getAllWithDisabled() {
  298. return self::getAll(false, true);
  299. }
  300. static function getAllOnline() {
  301. list($whos_online_workers, $null) = self::search(
  302. array(),
  303. array(
  304. new DevblocksSearchCriteria(SearchFields_Worker::LAST_ACTIVITY_DATE,DevblocksSearchCriteria::OPER_GT,(time()-60*15)), // idle < 15 mins
  305. new DevblocksSearchCriteria(SearchFields_Worker::LAST_ACTIVITY,DevblocksSearchCriteria::OPER_NOT_LIKE,'%translation_code";N;%'), // translation code not null (not just logged out)
  306. ),
  307. -1,
  308. 0,
  309. SearchFields_Worker::LAST_ACTIVITY_DATE,
  310. false,
  311. false
  312. );
  313. if(!empty($whos_online_workers))
  314. return self::getList(array_keys($whos_online_workers));
  315. return array();
  316. }
  317. static function getAll($nocache=false, $with_disabled=true) {
  318. $cache = DevblocksPlatform::getCacheService();
  319. if($nocache || null === ($workers = $cache->load(self::CACHE_ALL))) {
  320. $workers = self::getList();
  321. $cache->save($workers, self::CACHE_ALL);
  322. }
  323. /*
  324. * If the caller doesn't want disabled workers then remove them from the results,
  325. * but don't bother caching two different versions (always cache all)
  326. */
  327. if(!$with_disabled) {
  328. foreach($workers as $worker_id => $worker) { /* @var $worker CerberusWorker */
  329. if($worker->is_disabled)
  330. unset($workers[$worker_id]);
  331. }
  332. }
  333. return $workers;
  334. }
  335. static function getList($ids=array()) {
  336. if(!is_array($ids)) $ids = array($ids);
  337. $db = DevblocksPlatform::getDatabaseService();
  338. $workers = array();
  339. $sql = "SELECT a.id, a.first_name, a.last_name, a.email, a.pass, a.title, a.is_superuser, a.is_disabled, a.last_activity_date, a.last_activity ".
  340. "FROM worker a ".
  341. ((!empty($ids) ? sprintf("WHERE a.id IN (%s) ",implode(',',$ids)) : " ").
  342. "ORDER BY a.first_name, a.last_name "
  343. );
  344. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  345. if(is_a($rs,'ADORecordSet'))
  346. while(!$rs->EOF) {
  347. $worker = new CerberusWorker();
  348. $worker->id = intval($rs->fields['id']);
  349. $worker->first_name = $rs->fields['first_name'];
  350. $worker->last_name = $rs->fields['last_name'];
  351. $worker->email = $rs->fields['email'];
  352. $worker->pass = $rs->fields['pass'];
  353. $worker->title = $rs->fields['title'];
  354. $worker->is_superuser = intval($rs->fields['is_superuser']);
  355. $worker->is_disabled = intval($rs->fields['is_disabled']);
  356. $worker->last_activity_date = intval($rs->fields['last_activity_date']);
  357. if(!empty($rs->fields['last_activity']))
  358. $worker->last_activity = unserialize($rs->fields['last_activity']);
  359. $workers[$worker->id] = $worker;
  360. $rs->MoveNext();
  361. }
  362. return $workers;
  363. }
  364. /**
  365. * @return CerberusWorker
  366. */
  367. static function getAgent($id) {
  368. if(empty($id)) return null;
  369. $workers = self::getAllWithDisabled();
  370. if(isset($workers[$id]))
  371. return $workers[$id];
  372. return null;
  373. }
  374. /**
  375. * Enter description here...
  376. *
  377. * @param string $email
  378. * @return integer $id
  379. */
  380. static function lookupAgentEmail($email) {
  381. if(empty($email)) return null;
  382. $db = DevblocksPlatform::getDatabaseService();
  383. $sql = sprintf("SELECT a.id FROM worker a WHERE a.email = %s",
  384. $db->qstr($email)
  385. );
  386. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  387. if(is_a($rs,'ADORecordSet') && !$rs->EOF) {
  388. return intval($rs->fields['id']);
  389. }
  390. return null;
  391. }
  392. static function updateAgent($ids, $fields, $flush_cache=true) {
  393. if(!is_array($ids)) $ids = array($ids);
  394. $db = DevblocksPlatform::getDatabaseService();
  395. $sets = array();
  396. if(!is_array($fields) || empty($fields) || empty($ids))
  397. return;
  398. foreach($fields as $k => $v) {
  399. $sets[] = sprintf("%s = %s",
  400. $k,
  401. $db->qstr($v)
  402. );
  403. }
  404. $sql = sprintf("UPDATE worker SET %s WHERE id IN (%s)",
  405. implode(', ', $sets),
  406. implode(',', $ids)
  407. );
  408. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  409. if($flush_cache) {
  410. self::clearCache();
  411. }
  412. }
  413. static function maint() {
  414. $db = DevblocksPlatform::getDatabaseService();
  415. $logger = DevblocksPlatform::getConsoleLog();
  416. $sql = "DELETE QUICK view_rss FROM view_rss LEFT JOIN worker ON view_rss.worker_id = worker.id WHERE worker.id IS NULL";
  417. $db->Execute($sql);
  418. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' view_rss records.');
  419. $sql = "DELETE QUICK worker_mail_forward FROM worker_mail_forward LEFT JOIN worker ON worker_mail_forward.worker_id = worker.id WHERE worker.id IS NULL";
  420. $db->Execute($sql);
  421. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' worker_mail_forward records.');
  422. $sql = "DELETE QUICK worker_pref FROM worker_pref LEFT JOIN worker ON worker_pref.worker_id = worker.id WHERE worker.id IS NULL";
  423. $db->Execute($sql);
  424. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' worker_pref records.');
  425. $sql = "DELETE QUICK worker_to_team FROM worker_to_team LEFT JOIN worker ON worker_to_team.agent_id = worker.id WHERE worker.id IS NULL";
  426. $db->Execute($sql);
  427. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' worker_to_team records.');
  428. $sql = "DELETE QUICK worker_workspace_list FROM worker_workspace_list LEFT JOIN worker ON worker_workspace_list.worker_id = worker.id WHERE worker.id IS NULL";
  429. $db->Execute($sql);
  430. // [TODO] Clear out workers from any group_inbox_filter rows
  431. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' worker_workspace_list records.');
  432. }
  433. static function deleteAgent($id) {
  434. if(empty($id)) return;
  435. // [TODO] Delete worker notes, comments, etc.
  436. /* This event fires before the delete takes place in the db,
  437. * so we can denote what is actually changing against the db state
  438. */
  439. $eventMgr = DevblocksPlatform::getEventService();
  440. $eventMgr->trigger(
  441. new Model_DevblocksEvent(
  442. 'worker.delete',
  443. array(
  444. 'worker_ids' => array($id),
  445. )
  446. )
  447. );
  448. $db = DevblocksPlatform::getDatabaseService();
  449. $sql = sprintf("DELETE QUICK FROM worker WHERE id = %d", $id);
  450. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  451. $sql = sprintf("DELETE QUICK FROM address_to_worker WHERE worker_id = %d", $id);
  452. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  453. $sql = sprintf("DELETE QUICK FROM worker_to_team WHERE agent_id = %d", $id);
  454. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  455. $sql = sprintf("DELETE QUICK FROM view_rss WHERE worker_id = %d", $id);
  456. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  457. $sql = sprintf("DELETE QUICK FROM worker_workspace_list WHERE worker_id = %d", $id);
  458. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  459. // Clear assigned workers
  460. $sql = sprintf("UPDATE ticket SET next_worker_id = 0 WHERE next_worker_id = %d", $id);
  461. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  462. // Clear roles
  463. $db->Execute(sprintf("DELETE FROM worker_to_role WHERE worker_id = %d", $id));
  464. // Invalidate caches
  465. self::clearCache();
  466. $cache = DevblocksPlatform::getCacheService();
  467. $cache->remove(DAO_Group::CACHE_ROSTERS);
  468. }
  469. static function login($email, $password) {
  470. $db = DevblocksPlatform::getDatabaseService();
  471. // [TODO] Uniquely salt hashes
  472. $sql = sprintf("SELECT id ".
  473. "FROM worker ".
  474. "WHERE is_disabled = 0 ".
  475. "AND email = %s ".
  476. "AND pass = MD5(%s)",
  477. $db->qstr($email),
  478. $db->qstr($password)
  479. );
  480. $worker_id = $db->GetOne($sql); // or die(__CLASS__ . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  481. if(!empty($worker_id)) {
  482. return self::getAgent($worker_id);
  483. }
  484. return null;
  485. }
  486. static function setAgentTeams($agent_id, $team_ids) {
  487. if(!is_array($team_ids)) $team_ids = array($team_ids);
  488. if(empty($agent_id)) return;
  489. $db = DevblocksPlatform::getDatabaseService();
  490. $sql = sprintf("DELETE QUICK FROM worker_to_team WHERE agent_id = %d",
  491. $agent_id
  492. );
  493. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  494. foreach($team_ids as $team_id) {
  495. $sql = sprintf("INSERT INTO worker_to_team (agent_id, team_id) ".
  496. "VALUES (%d,%d)",
  497. $agent_id,
  498. $team_id
  499. );
  500. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  501. }
  502. // Invalidate caches
  503. $cache = DevblocksPlatform::getCacheService();
  504. $cache->remove(DAO_Group::CACHE_ROSTERS);
  505. }
  506. /**
  507. * @return Model_TeamMember[]
  508. */
  509. static function getWorkerGroups($worker_id) {
  510. // Get the cache
  511. $rosters = DAO_Group::getRosters();
  512. $memberships = array();
  513. // Remove any groups our desired worker isn't in
  514. if(is_array($rosters))
  515. foreach($rosters as $group_id => $members) {
  516. if(isset($members[$worker_id])) {
  517. $memberships[$group_id] = $members[$worker_id];
  518. }
  519. }
  520. return $memberships;
  521. }
  522. /**
  523. * Store the workers last activity (provided by the page extension).
  524. *
  525. * @param integer $worker_id
  526. * @param Model_Activity $activity
  527. */
  528. static function logActivity($worker_id, Model_Activity $activity) {
  529. DAO_Worker::updateAgent($worker_id,array(
  530. DAO_Worker::LAST_ACTIVITY_DATE => time(),
  531. DAO_Worker::LAST_ACTIVITY => serialize($activity)
  532. ),false);
  533. }
  534. /**
  535. * Enter description here...
  536. *
  537. * @param DevblocksSearchCriteria[] $params
  538. * @param integer $limit
  539. * @param integer $page
  540. * @param string $sortBy
  541. * @param boolean $sortAsc
  542. * @param boolean $withCounts
  543. * @return array
  544. */
  545. static function search($columns, $params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  546. $db = DevblocksPlatform::getDatabaseService();
  547. $fields = SearchFields_Worker::getFields();
  548. // Sanitize
  549. if(!isset($fields[$sortBy]))
  550. $sortBy=null;
  551. list($tables,$wheres) = parent::_parseSearchParams($params, $columns, $fields, $sortBy);
  552. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  553. $total = -1;
  554. $select_sql = sprintf("SELECT ".
  555. "w.id as %s, ".
  556. "w.first_name as %s, ".
  557. "w.last_name as %s, ".
  558. "w.title as %s, ".
  559. "w.email as %s, ".
  560. "w.is_superuser as %s, ".
  561. "w.last_activity_date as %s, ".
  562. "w.is_disabled as %s ",
  563. SearchFields_Worker::ID,
  564. SearchFields_Worker::FIRST_NAME,
  565. SearchFields_Worker::LAST_NAME,
  566. SearchFields_Worker::TITLE,
  567. SearchFields_Worker::EMAIL,
  568. SearchFields_Worker::IS_SUPERUSER,
  569. SearchFields_Worker::LAST_ACTIVITY_DATE,
  570. SearchFields_Worker::IS_DISABLED
  571. );
  572. $join_sql = "FROM worker w ";
  573. // Custom field joins
  574. list($select_sql, $join_sql, $has_multiple_values) = self::_appendSelectJoinSqlForCustomFieldTables(
  575. $tables,
  576. $params,
  577. 'w.id',
  578. $select_sql,
  579. $join_sql
  580. );
  581. $where_sql = "".
  582. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  583. $sort_sql = (!empty($sortBy)) ? sprintf("ORDER BY %s %s ",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : " ";
  584. $sql =
  585. $select_sql.
  586. $join_sql.
  587. $where_sql.
  588. ($has_multiple_values ? 'GROUP BY w.id ' : '').
  589. $sort_sql;
  590. // [TODO] Could push the select logic down a level too
  591. if($limit > 0) {
  592. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  593. } else {
  594. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  595. $total = $rs->RecordCount();
  596. }
  597. $results = array();
  598. if(is_a($rs,'ADORecordSet'))
  599. while(!$rs->EOF) {
  600. $result = array();
  601. foreach($rs->fields as $f => $v) {
  602. $result[$f] = $v;
  603. }
  604. $object_id = intval($rs->fields[SearchFields_Worker::ID]);
  605. $results[$object_id] = $result;
  606. $rs->MoveNext();
  607. }
  608. // [JAS]: Count all
  609. if($withCounts) {
  610. $count_sql =
  611. ($has_multiple_values ? "SELECT COUNT(DISTINCT w.id) " : "SELECT COUNT(w.id) ").
  612. $join_sql.
  613. $where_sql;
  614. $total = $db->GetOne($count_sql);
  615. }
  616. return array($results,$total);
  617. }
  618. };
  619. /**
  620. * ...
  621. *
  622. */
  623. class SearchFields_Worker implements IDevblocksSearchFields {
  624. // Worker
  625. const ID = 'w_id';
  626. const FIRST_NAME = 'w_first_name';
  627. const LAST_NAME = 'w_last_name';
  628. const TITLE = 'w_title';
  629. const EMAIL = 'w_email';
  630. const IS_SUPERUSER = 'w_is_superuser';
  631. const LAST_ACTIVITY = 'w_last_activity';
  632. const LAST_ACTIVITY_DATE = 'w_last_activity_date';
  633. const IS_DISABLED = 'w_is_disabled';
  634. /**
  635. * @return DevblocksSearchField[]
  636. */
  637. static function getFields() {
  638. $translate = DevblocksPlatform::getTranslationService();
  639. $columns = array(
  640. self::ID => new DevblocksSearchField(self::ID, 'w', 'id', null, $translate->_('common.id')),
  641. self::FIRST_NAME => new DevblocksSearchField(self::FIRST_NAME, 'w', 'first_name', null, $translate->_('worker.first_name')),
  642. self::LAST_NAME => new DevblocksSearchField(self::LAST_NAME, 'w', 'last_name', null, $translate->_('worker.last_name')),
  643. self::TITLE => new DevblocksSearchField(self::TITLE, 'w', 'title', null, $translate->_('worker.title')),
  644. self::EMAIL => new DevblocksSearchField(self::EMAIL, 'w', 'email', null, ucwords($translate->_('common.email'))),
  645. self::IS_SUPERUSER => new DevblocksSearchField(self::IS_SUPERUSER, 'w', 'is_superuser', null, $translate->_('worker.is_superuser')),
  646. self::LAST_ACTIVITY => new DevblocksSearchField(self::LAST_ACTIVITY, 'w', 'last_activity', null, $translate->_('worker.last_activity')),
  647. self::LAST_ACTIVITY_DATE => new DevblocksSearchField(self::LAST_ACTIVITY_DATE, 'w', 'last_activity_date', null, $translate->_('worker.last_activity_date')),
  648. self::IS_DISABLED => new DevblocksSearchField(self::IS_DISABLED, 'w', 'is_disabled', null, ucwords($translate->_('common.disabled'))),
  649. );
  650. // Custom Fields
  651. $fields = DAO_CustomField::getBySource(ChCustomFieldSource_Worker::ID);
  652. if(is_array($fields))
  653. foreach($fields as $field_id => $field) {
  654. $key = 'cf_'.$field_id;
  655. $columns[$key] = new DevblocksSearchField($key,$key,'field_value',null,$field->name);
  656. }
  657. // Sort by label (translation-conscious)
  658. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  659. return $columns;
  660. }
  661. };
  662. class DAO_WorkerRole extends DevblocksORMHelper {
  663. const _CACHE_ALL = 'ch_acl';
  664. const CACHE_KEY_ROLES = 'roles';
  665. const CACHE_KEY_PRIVS_BY_ROLE = 'privs_by_role';
  666. const CACHE_KEY_WORKERS_BY_ROLE = 'workers_by_role';
  667. const CACHE_KEY_PRIVS_BY_WORKER = 'privs_by_worker';
  668. const ID = 'id';
  669. const NAME = 'name';
  670. static function create($fields) {
  671. $db = DevblocksPlatform::getDatabaseService();
  672. $id = $db->GenID('generic_seq');
  673. $sql = sprintf("INSERT INTO worker_role (id) ".
  674. "VALUES (%d)",
  675. $id
  676. );
  677. $db->Execute($sql);
  678. self::update($id, $fields);
  679. return $id;
  680. }
  681. static function update($ids, $fields) {
  682. parent::_update($ids, 'worker_role', $fields);
  683. }
  684. static function getACL($nocache=false) {
  685. $cache = DevblocksPlatform::getCacheService();
  686. if($nocache || null === ($acl = $cache->load(self::_CACHE_ALL))) {
  687. $db = DevblocksPlatform::getDatabaseService();
  688. // All roles
  689. $all_roles = self::getWhere();
  690. $all_worker_ids = array();
  691. // All privileges by role
  692. $all_privs = array();
  693. $rs = $db->Execute("SELECT role_id, priv_id FROM worker_role_acl WHERE has_priv = 1 ORDER BY role_id, priv_id");
  694. while(!$rs->EOF) {
  695. $role_id = intval($rs->fields['role_id']);
  696. $priv_id = $rs->fields['priv_id'];
  697. if(!isset($all_privs[$role_id]))
  698. $all_privs[$role_id] = array();
  699. $all_privs[$role_id][$priv_id] = $priv_id;
  700. $rs->MoveNext();
  701. }
  702. // All workers by role
  703. $all_rosters = array();
  704. $rs = $db->Execute("SELECT role_id, worker_id FROM worker_to_role");
  705. while(!$rs->EOF) {
  706. $role_id = intval($rs->fields['role_id']);
  707. $worker_id = intval($rs->fields['worker_id']);
  708. if(!isset($all_rosters[$role_id]))
  709. $all_rosters[$role_id] = array();
  710. $all_rosters[$role_id][$worker_id] = $worker_id;
  711. $all_worker_ids[$worker_id] = $worker_id;
  712. $rs->MoveNext();
  713. }
  714. // Aggregate privs by workers' roles (if set anywhere, keep)
  715. $privs_by_worker = array();
  716. if(is_array($all_worker_ids))
  717. foreach($all_worker_ids as $worker_id) {
  718. if(!isset($privs_by_worker[$worker_id]))
  719. $privs_by_worker[$worker_id] = array();
  720. foreach($all_rosters as $role_id => $role_roster) {
  721. if(isset($role_roster[$worker_id]) && isset($all_privs[$role_id])) {
  722. // If we have privs from other groups, merge on the keys
  723. $current_privs = (is_array($privs_by_worker[$worker_id])) ? $privs_by_worker[$worker_id] : array();
  724. $privs_by_worker[$worker_id] = array_merge($current_privs,$all_privs[$role_id]);
  725. }
  726. }
  727. }
  728. $acl = array(
  729. self::CACHE_KEY_ROLES => $all_roles,
  730. self::CACHE_KEY_PRIVS_BY_ROLE => $all_privs,
  731. self::CACHE_KEY_WORKERS_BY_ROLE => $all_rosters,
  732. self::CACHE_KEY_PRIVS_BY_WORKER => $privs_by_worker,
  733. );
  734. $cache->save($acl, self::_CACHE_ALL);
  735. }
  736. return $acl;
  737. }
  738. /**
  739. * @param string $where
  740. * @return Model_WorkerRole[]
  741. */
  742. static function getWhere($where=null) {
  743. $db = DevblocksPlatform::getDatabaseService();
  744. $sql = "SELECT id, name ".
  745. "FROM worker_role ".
  746. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  747. "ORDER BY name asc";
  748. $rs = $db->Execute($sql);
  749. return self::_getObjectsFromResult($rs);
  750. }
  751. /**
  752. * @param integer $id
  753. * @return Model_WorkerRole */
  754. static function get($id) {
  755. $objects = self::getWhere(sprintf("%s = %d",
  756. self::ID,
  757. $id
  758. ));
  759. if(isset($objects[$id]))
  760. return $objects[$id];
  761. return null;
  762. }
  763. /**
  764. * @param ADORecordSet $rs
  765. * @return Model_WorkerRole[]
  766. */
  767. static private function _getObjectsFromResult($rs) {
  768. $objects = array();
  769. while(!$rs->EOF) {
  770. $object = new Model_WorkerRole();
  771. $object->id = $rs->fields['id'];
  772. $object->name = $rs->fields['name'];
  773. $objects[$object->id] = $object;
  774. $rs->MoveNext();
  775. }
  776. return $objects;
  777. }
  778. static function delete($ids) {
  779. if(!is_array($ids)) $ids = array($ids);
  780. $db = DevblocksPlatform::getDatabaseService();
  781. if(empty($ids))
  782. return;
  783. $ids_list = implode(',', $ids);
  784. $db->Execute(sprintf("DELETE FROM worker_role WHERE id IN (%s)", $ids_list));
  785. $db->Execute(sprintf("DELETE FROM worker_to_role WHERE role_id IN (%s)", $ids_list));
  786. $db->Execute(sprintf("DELETE FROM worker_role_acl WHERE role_id IN (%s)", $ids_list));
  787. return true;
  788. }
  789. static function getRolePrivileges($role_id) {
  790. $acl = self::getACL();
  791. if(empty($role_id) || !isset($acl[self::CACHE_KEY_PRIVS_BY_ROLE][$role_id]))
  792. return array();
  793. return $acl[self::CACHE_KEY_PRIVS_BY_ROLE][$role_id];
  794. }
  795. /**
  796. * @param integer $role_id
  797. * @param array $privileges
  798. * @param boolean $replace
  799. */
  800. static function setRolePrivileges($role_id, $privileges) {
  801. if(!is_array($privileges)) $privileges = array($privileges);
  802. $db = DevblocksPlatform::getDatabaseService();
  803. if(empty($role_id))
  804. return;
  805. // Wipe all privileges on blank replace
  806. $sql = sprintf("DELETE FROM worker_role_acl WHERE role_id = %d", $role_id);
  807. $db->Execute($sql);
  808. // Load entire ACL list
  809. $acl = DevblocksPlatform::getAclRegistry();
  810. // Set ACLs according to the new master list
  811. if(!empty($privileges) && !empty($acl)) {
  812. foreach($privileges as $priv) { /* @var $priv DevblocksAclPrivilege */
  813. $sql = sprintf("INSERT INTO worker_role_acl (role_id, priv_id, has_priv) ".
  814. "VALUES (%d, %s, %d)",
  815. $role_id,
  816. $db->qstr($priv),
  817. 1
  818. );
  819. $db->Execute($sql);
  820. }
  821. }
  822. unset($privileges);
  823. self::clearCache();
  824. }
  825. static function getRoleWorkers($role_id) {
  826. $acl = self::getACL();
  827. if(empty($role_id) || !isset($acl[self::CACHE_KEY_WORKERS_BY_ROLE][$role_id]))
  828. return array();
  829. return $acl[self::CACHE_KEY_WORKERS_BY_ROLE][$role_id];
  830. }
  831. static function setRoleWorkers($role_id, $worker_ids) {
  832. if(!is_array($worker_ids)) $worker_ids = array($worker_ids);
  833. $db = DevblocksPlatform::getDatabaseService();
  834. if(empty($role_id))
  835. return;
  836. // Wipe roster
  837. $sql = sprintf("DELETE FROM worker_to_role WHERE role_id = %d", $role_id);
  838. $db->Execute($sql);
  839. // Add desired workers to role's roster
  840. if(is_array($worker_ids))
  841. foreach($worker_ids as $worker_id) {
  842. $sql = sprintf("INSERT INTO worker_to_role (worker_id, role_id) ".
  843. "VALUES (%d, %d)",
  844. $worker_id,
  845. $role_id
  846. );
  847. $db->Execute($sql);
  848. }
  849. self::clearCache();
  850. }
  851. static function clearCache() {
  852. $cache = DevblocksPlatform::getCacheService();
  853. $cache->remove(self::_CACHE_ALL);
  854. }
  855. };
  856. class DAO_WorkerEvent extends DevblocksORMHelper {
  857. const CACHE_COUNT_PREFIX = 'workerevent_count_';
  858. const ID = 'id';
  859. const CREATED_DATE = 'created_date';
  860. const WORKER_ID = 'worker_id';
  861. const TITLE = 'title';
  862. const CONTENT = 'content';
  863. const IS_READ = 'is_read';
  864. const URL = 'url';
  865. static function create($fields) {
  866. $db = DevblocksPlatform::getDatabaseService();
  867. $id = $db->GenID('worker_event_seq');
  868. $sql = sprintf("INSERT INTO worker_event (id) ".
  869. "VALUES (%d)",
  870. $id
  871. );
  872. $db->Execute($sql);
  873. self::update($id, $fields);
  874. // Invalidate the worker notification count cache
  875. if(isset($fields[self::WORKER_ID])) {
  876. $cache = DevblocksPlatform::getCacheService();
  877. self::clearCountCache($fields[self::WORKER_ID]);
  878. }
  879. return $id;
  880. }
  881. static function update($ids, $fields) {
  882. parent::_update($ids, 'worker_event', $fields);
  883. }
  884. static function updateWhere($fields, $where) {
  885. parent::_updateWhere('worker_event', $fields, $where);
  886. }
  887. /**
  888. * @param string $where
  889. * @return Model_WorkerEvent[]
  890. */
  891. static function getWhere($where=null) {
  892. $db = DevblocksPlatform::getDatabaseService();
  893. $sql = "SELECT id, created_date, worker_id, title, content, is_read, url ".
  894. "FROM worker_event ".
  895. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  896. "ORDER BY id asc";
  897. $rs = $db->Execute($sql);
  898. return self::_getObjectsFromResult($rs);
  899. }
  900. /**
  901. * @param integer $id
  902. * @return Model_WorkerEvent */
  903. static function get($id) {
  904. $objects = self::getWhere(sprintf("%s = %d",
  905. self::ID,
  906. $id
  907. ));
  908. if(isset($objects[$id]))
  909. return $objects[$id];
  910. return null;
  911. }
  912. static function getUnreadCountByWorker($worker_id) {
  913. $db = DevblocksPlatform::getDatabaseService();
  914. $cache = DevblocksPlatform::getCacheService();
  915. if(null === ($count = $cache->load(self::CACHE_COUNT_PREFIX.$worker_id))) {
  916. $sql = sprintf("SELECT count(*) ".
  917. "FROM worker_event ".
  918. "WHERE worker_id = %d ".
  919. "AND is_read = 0",
  920. $worker_id
  921. );
  922. $count = $db->GetOne($sql);
  923. $cache->save($count, self::CACHE_COUNT_PREFIX.$worker_id);
  924. }
  925. return intval($count);
  926. }
  927. /**
  928. * @param ADORecordSet $rs
  929. * @return Model_WorkerEvent[]
  930. */
  931. static private function _getObjectsFromResult($rs) {
  932. $objects = array();
  933. while(!$rs->EOF) {
  934. $object = new Model_WorkerEvent();
  935. $object->id = $rs->fields['id'];
  936. $object->created_date = $rs->fields['created_date'];
  937. $object->worker_id = $rs->fields['worker_id'];
  938. $object->title = $rs->fields['title'];
  939. $object->url = $rs->fields['url'];
  940. $object->content = $rs->fields['content'];
  941. $object->is_read = $rs->fields['is_read'];
  942. $objects[$object->id] = $object;
  943. $rs->MoveNext();
  944. }
  945. return $objects;
  946. }
  947. static function delete($ids) {
  948. if(!is_array($ids)) $ids = array($ids);
  949. $db = DevblocksPlatform::getDatabaseService();
  950. if(empty($ids))
  951. return;
  952. $ids_list = implode(',', $ids);
  953. $db->Execute(sprintf("DELETE FROM worker_event WHERE id IN (%s)", $ids_list));
  954. return true;
  955. }
  956. static function clearCountCache($worker_id) {
  957. $cache = DevblocksPlatform::getCacheService();
  958. $cache->remove(self::CACHE_COUNT_PREFIX.$worker_id);
  959. }
  960. /**
  961. * Enter description here...
  962. *
  963. * @param DevblocksSearchCriteria[] $params
  964. * @param integer $limit
  965. * @param integer $page
  966. * @param string $sortBy
  967. * @param boolean $sortAsc
  968. * @param boolean $withCounts
  969. * @return array
  970. */
  971. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  972. $db = DevblocksPlatform::getDatabaseService();
  973. $fields = SearchFields_WorkerEvent::getFields();
  974. // Sanitize
  975. if(!isset($fields[$sortBy]))
  976. $sortBy=null;
  977. list($tables,$wheres) = parent::_parseSearchParams($params, array(),$fields,$sortBy);
  978. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  979. $total = -1;
  980. $sql = sprintf("SELECT ".
  981. "we.id as %s, ".
  982. "we.created_date as %s, ".
  983. "we.worker_id as %s, ".
  984. "we.title as %s, ".
  985. "we.content as %s, ".
  986. "we.is_read as %s, ".
  987. "we.url as %s ".
  988. "FROM worker_event we ",
  989. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  990. SearchFields_WorkerEvent::ID,
  991. SearchFields_WorkerEvent::CREATED_DATE,
  992. SearchFields_WorkerEvent::WORKER_ID,
  993. SearchFields_WorkerEvent::TITLE,
  994. SearchFields_WorkerEvent::CONTENT,
  995. SearchFields_WorkerEvent::IS_READ,
  996. SearchFields_WorkerEvent::URL
  997. ).
  998. // [JAS]: Dynamic table joins
  999. // (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  1000. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  1001. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "")
  1002. ;
  1003. // [TODO] Could push the select logic down a level too
  1004. if($limit > 0) {
  1005. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1006. } else {
  1007. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1008. $total = $rs->RecordCount();
  1009. }
  1010. $results = array();
  1011. if(is_a($rs,'ADORecordSet'))
  1012. while(!$rs->EOF) {
  1013. $result = array();
  1014. foreach($rs->fields as $f => $v) {
  1015. $result[$f] = $v;
  1016. }
  1017. $ticket_id = intval($rs->fields[SearchFields_WorkerEvent::ID]);
  1018. $results[$ticket_id] = $result;
  1019. $rs->MoveNext();
  1020. }
  1021. // [JAS]: Count all
  1022. if($withCounts) {
  1023. $rs = $db->Execute($sql);
  1024. $total = $rs->RecordCount();
  1025. }
  1026. return array($results,$total);
  1027. }
  1028. };
  1029. class SearchFields_WorkerEvent implements IDevblocksSearchFields {
  1030. // Worker Event
  1031. const ID = 'we_id';
  1032. const CREATED_DATE = 'we_created_date';
  1033. const WORKER_ID = 'we_worker_id';
  1034. const TITLE = 'we_title';
  1035. const CONTENT = 'we_content';
  1036. const IS_READ = 'we_is_read';
  1037. const URL = 'we_url';
  1038. /**
  1039. * @return DevblocksSearchField[]
  1040. */
  1041. static function getFields() {
  1042. $translate = DevblocksPlatform::getTranslationService();
  1043. $columns = array(
  1044. self::ID => new DevblocksSearchField(self::ID, 'we', 'id', null, $translate->_('worker_event.id')),
  1045. self::CREATED_DATE => new DevblocksSearchField(self::CREATED_DATE, 'we', 'created_date', null, $translate->_('worker_event.created_date')),
  1046. self::WORKER_ID => new DevblocksSearchField(self::WORKER_ID, 'we', 'worker_id', null, $translate->_('worker_event.worker_id')),
  1047. self::TITLE => new DevblocksSearchField(self::TITLE, 'we', 'title', null, $translate->_('worker_event.title')),
  1048. self::CONTENT => new DevblocksSearchField(self::CONTENT, 'we', 'content', null, $translate->_('worker_event.content')),
  1049. self::IS_READ => new DevblocksSearchField(self::IS_READ, 'we', 'is_read', null, $translate->_('worker_event.is_read')),
  1050. self::URL => new DevblocksSearchField(self::URL, 'we', 'url', null, $translate->_('common.url')),
  1051. );
  1052. // Sort by label (translation-conscious)
  1053. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  1054. return $columns;
  1055. }
  1056. };
  1057. class DAO_ContactOrg extends C4_ORMHelper {
  1058. const ID = 'id';
  1059. const NAME = 'name';
  1060. const STREET = 'street';
  1061. const CITY = 'city';
  1062. const PROVINCE = 'province';
  1063. const POSTAL = 'postal';
  1064. const COUNTRY = 'country';
  1065. const PHONE = 'phone';
  1066. const WEBSITE = 'website';
  1067. const CREATED = 'created';
  1068. private function __construct() {}
  1069. public static function getFields() {
  1070. $translate = DevblocksPlatform::getTranslationService();
  1071. return array(
  1072. 'id' => $translate->_('contact_org.id'),
  1073. 'name' => $translate->_('contact_org.name'),
  1074. 'street' => $translate->_('contact_org.street'),
  1075. 'city' => $translate->_('contact_org.city'),
  1076. 'province' => $translate->_('contact_org.province'),
  1077. 'postal' => $translate->_('contact_org.postal'),
  1078. 'country' => $translate->_('contact_org.country'),
  1079. 'phone' => $translate->_('contact_org.phone'),
  1080. 'website' => $translate->_('contact_org.website'),
  1081. 'created' => $translate->_('contact_org.created'),
  1082. );
  1083. }
  1084. /**
  1085. * Enter description here...
  1086. *
  1087. * @param array $fields
  1088. * @return integer
  1089. */
  1090. static function create($fields) {
  1091. $db = DevblocksPlatform::getDatabaseService();
  1092. $id = $db->GenID('contact_org_seq');
  1093. $sql = sprintf("INSERT INTO contact_org (id,name,street,city,province,postal,country,phone,website,created) ".
  1094. "VALUES (%d,'','','','','','','','',%d)",
  1095. $id,
  1096. time()
  1097. );
  1098. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1099. self::update($id, $fields);
  1100. return $id;
  1101. }
  1102. /**
  1103. * Enter description here...
  1104. *
  1105. * @param array $ids
  1106. * @param array $fields
  1107. * @return Model_ContactOrg
  1108. */
  1109. static function update($ids, $fields) {
  1110. if(!is_array($ids)) $ids = array($ids);
  1111. parent::_update($ids, 'contact_org', $fields);
  1112. }
  1113. /**
  1114. * @param array $ids
  1115. */
  1116. static function delete($ids) {
  1117. if(!is_array($ids)) $ids = array($ids);
  1118. $db = DevblocksPlatform::getDatabaseService();
  1119. if(empty($ids))
  1120. return;
  1121. $id_list = implode(',', $ids);
  1122. // Orgs
  1123. $sql = sprintf("DELETE QUICK FROM contact_org WHERE id IN (%s)",
  1124. $id_list
  1125. );
  1126. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1127. // Clear any associated addresses
  1128. $sql = sprintf("UPDATE address SET contact_org_id = 0 WHERE contact_org_id IN (%s)",
  1129. $id_list
  1130. );
  1131. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1132. // Tasks
  1133. DAO_Task::deleteBySourceIds('cerberusweb.tasks.org', $ids);
  1134. // Custom fields
  1135. DAO_CustomFieldValue::deleteBySourceIds(ChCustomFieldSource_Org::ID, $ids);
  1136. // Notes
  1137. DAO_Note::deleteBySourceIds(ChNotesSource_Org::ID, $ids);
  1138. }
  1139. /**
  1140. * @param string $where
  1141. * @return Model_ContactOrg[]
  1142. */
  1143. static function getWhere($where=null) {
  1144. $db = DevblocksPlatform::getDatabaseService();
  1145. $sql = "SELECT id,name,street,city,province,postal,country,phone,website,created ".
  1146. "FROM contact_org ".
  1147. (!empty($where) ? sprintf("WHERE %s ", $where) : " ")
  1148. ;
  1149. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1150. return self::_getObjectsFromResultSet($rs);
  1151. }
  1152. static private function _getObjectsFromResultSet($rs) {
  1153. $objects = array();
  1154. if(is_a($rs,'ADORecordSet'))
  1155. while(!$rs->EOF) {
  1156. $object = new Model_ContactOrg();
  1157. $object->id = intval($rs->fields['id']);
  1158. $object->name = $rs->fields['name'];
  1159. $object->street = $rs->fields['street'];
  1160. $object->city = $rs->fields['city'];
  1161. $object->province = $rs->fields['province'];
  1162. $object->postal = $rs->fields['postal'];
  1163. $object->country = $rs->fields['country'];
  1164. $object->phone = $rs->fields['phone'];
  1165. $object->website = $rs->fields['website'];
  1166. $object->created = intval($rs->fields['created']);
  1167. $objects[$object->id] = $object;
  1168. $rs->MoveNext();
  1169. }
  1170. return $objects;
  1171. }
  1172. /**
  1173. * @param integer $id
  1174. * @return Model_ContactOrg
  1175. */
  1176. static function get($id) {
  1177. $where = sprintf("%s = %d",
  1178. self::ID,
  1179. $id
  1180. );
  1181. $objects = self::getWhere($where);
  1182. if(isset($objects[$id]))
  1183. return $objects[$id];
  1184. return null;
  1185. }
  1186. /**
  1187. * Enter description here...
  1188. *
  1189. * @param string $name
  1190. * @param boolean $create_if_null
  1191. * @return Model_ContactOrg
  1192. */
  1193. static function lookup($name, $create_if_null=false) {
  1194. $db = DevblocksPlatform::getDatabaseService();
  1195. @$orgs = self::getWhere(
  1196. sprintf('%s = %s', self::NAME, $db->qstr($name))
  1197. );
  1198. if(empty($orgs)) {
  1199. if($create_if_null) {
  1200. $fields = array(
  1201. self::NAME => $name
  1202. );
  1203. return self::create($fields);
  1204. }
  1205. } else {
  1206. return key($orgs);
  1207. }
  1208. return NULL;
  1209. }
  1210. /**
  1211. * Enter description here...
  1212. *
  1213. * @param DevblocksSearchCriteria[] $params
  1214. * @param integer $limit
  1215. * @param integer $page
  1216. * @param string $sortBy
  1217. * @param boolean $sortAsc
  1218. * @param boolean $withCounts
  1219. * @return array
  1220. */
  1221. static function search($columns, $params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  1222. $db = DevblocksPlatform::getDatabaseService();
  1223. $fields = SearchFields_ContactOrg::getFields();
  1224. // Sanitize
  1225. if(!isset($fields[$sortBy]))
  1226. $sortBy=null;
  1227. list($tables,$wheres) = parent::_parseSearchParams($params, $columns, $fields,$sortBy);
  1228. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  1229. $total = -1;
  1230. $select_sql = sprintf("SELECT ".
  1231. "c.id as %s, ".
  1232. "c.name as %s, ".
  1233. "c.street as %s, ".
  1234. "c.city as %s, ".
  1235. "c.province as %s, ".
  1236. "c.postal as %s, ".
  1237. "c.country as %s, ".
  1238. "c.phone as %s, ".
  1239. "c.website as %s, ".
  1240. "c.created as %s ",
  1241. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  1242. SearchFields_ContactOrg::ID,
  1243. SearchFields_ContactOrg::NAME,
  1244. SearchFields_ContactOrg::STREET,
  1245. SearchFields_ContactOrg::CITY,
  1246. SearchFields_ContactOrg::PROVINCE,
  1247. SearchFields_ContactOrg::POSTAL,
  1248. SearchFields_ContactOrg::COUNTRY,
  1249. SearchFields_ContactOrg::PHONE,
  1250. SearchFields_ContactOrg::WEBSITE,
  1251. SearchFields_ContactOrg::CREATED
  1252. );
  1253. $join_sql = 'FROM contact_org c ';
  1254. // Custom field joins
  1255. list($select_sql, $join_sql, $has_multiple_values) = self::_appendSelectJoinSqlForCustomFieldTables(
  1256. $tables,
  1257. $params,
  1258. 'c.id',
  1259. $select_sql,
  1260. $join_sql
  1261. );
  1262. $where_sql = "".
  1263. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  1264. $sort_sql = (!empty($sortBy)) ? sprintf("ORDER BY %s %s ",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : " ";
  1265. $sql =
  1266. $select_sql.
  1267. $join_sql.
  1268. $where_sql.
  1269. ($has_multiple_values ? 'GROUP BY c.id ' : '').
  1270. $sort_sql;
  1271. // [TODO] Could push the select logic down a level too
  1272. if($limit > 0) {
  1273. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1274. } else {
  1275. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1276. $total = $rs->RecordCount();
  1277. }
  1278. $results = array();
  1279. if(is_a($rs,'ADORecordSet'))
  1280. while(!$rs->EOF) {
  1281. $result = array();
  1282. foreach($rs->fields as $f => $v) {
  1283. $result[$f] = $v;
  1284. }
  1285. $ticket_id = intval($rs->fields[SearchFields_ContactOrg::ID]);
  1286. $results[$ticket_id] = $result;
  1287. $rs->MoveNext();
  1288. }
  1289. // [JAS]: Count all
  1290. if($withCounts) {
  1291. $count_sql =
  1292. ($has_multiple_values ? "SELECT COUNT(DISTINCT c.id) " : "SELECT COUNT(c.id) ").
  1293. $join_sql.
  1294. $where_sql;
  1295. $total = $db->GetOne($count_sql);
  1296. }
  1297. return array($results,$total);
  1298. }
  1299. };
  1300. class SearchFields_ContactOrg {
  1301. const ID = 'c_id';
  1302. const NAME = 'c_name';
  1303. const STREET = 'c_street';
  1304. const CITY = 'c_city';
  1305. const PROVINCE = 'c_province';
  1306. const POSTAL = 'c_postal';
  1307. const COUNTRY = 'c_country';
  1308. const PHONE = 'c_phone';
  1309. const WEBSITE = 'c_website';
  1310. const CREATED = 'c_created';
  1311. /**
  1312. * @return DevblocksSearchField[]
  1313. */
  1314. static function getFields() {
  1315. $translate = DevblocksPlatform::getTranslationService();
  1316. $columns = array(
  1317. self::ID => new DevblocksSearchField(self::ID, 'c', 'id', null, $translate->_('contact_org.id')),
  1318. self::NAME => new DevblocksSearchField(self::NAME, 'c', 'name', null, $translate->_('contact_org.name')),
  1319. self::STREET => new DevblocksSearchField(self::STREET, 'c', 'street', null, $translate->_('contact_org.street')),
  1320. self::CITY => new DevblocksSearchField(self::CITY, 'c', 'city', null, $translate->_('contact_org.city')),
  1321. self::PROVINCE => new DevblocksSearchField(self::PROVINCE, 'c', 'province', null, $translate->_('contact_org.province')),
  1322. self::POSTAL => new DevblocksSearchField(self::POSTAL, 'c', 'postal', null, $translate->_('contact_org.postal')),
  1323. self::COUNTRY => new DevblocksSearchField(self::COUNTRY, 'c', 'country', null, $translate->_('contact_org.country')),
  1324. self::PHONE => new DevblocksSearchField(self::PHONE, 'c', 'phone', null, $translate->_('contact_org.phone')),
  1325. self::WEBSITE => new DevblocksSearchField(self::WEBSITE, 'c', 'website', null, $translate->_('contact_org.website')),
  1326. self::CREATED => new DevblocksSearchField(self::CREATED, 'c', 'created', null, $translate->_('contact_org.created')),
  1327. );
  1328. // Custom Fields
  1329. $fields = DAO_CustomField::getBySource(ChCustomFieldSource_Org::ID);
  1330. if(is_array($fields))
  1331. foreach($fields as $field_id => $field) {
  1332. $key = 'cf_'.$field_id;
  1333. $columns[$key] = new DevblocksSearchField($key,$key,'field_value',null,$field->name);
  1334. }
  1335. // Sort by label (translation-conscious)
  1336. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  1337. return $columns;
  1338. }
  1339. };
  1340. /**
  1341. * Address DAO
  1342. *
  1343. */
  1344. class DAO_Address extends C4_ORMHelper {
  1345. const ID = 'id';
  1346. const EMAIL = 'email';
  1347. const FIRST_NAME = 'first_name';
  1348. const LAST_NAME = 'last_name';
  1349. const CONTACT_ORG_ID = 'contact_org_id';
  1350. const NUM_SPAM = 'num_spam';
  1351. const NUM_NONSPAM = 'num_nonspam';
  1352. const IS_BANNED = 'is_banned';
  1353. const LAST_AUTOREPLY = 'last_autoreply';
  1354. const IS_REGISTERED = 'is_registered';
  1355. const PASS = 'pass';
  1356. private function __construct() {}
  1357. public static function getFields() {
  1358. $translate = DevblocksPlatform::getTranslationService();
  1359. return array(
  1360. 'id' => $translate->_('address.id'),
  1361. 'email' => $translate->_('address.email'),
  1362. 'first_name' => $translate->_('address.first_name'),
  1363. 'last_name' => $translate->_('address.last_name'),
  1364. 'contact_org_id' => $translate->_('address.contact_org_id'),
  1365. 'num_spam' => $translate->_('address.num_spam'),
  1366. 'num_nonspam' => $translate->_('address.num_nonspam'),
  1367. 'is_banned' => $translate->_('address.is_banned'),
  1368. 'is_registered' => $translate->_('address.is_registered'),
  1369. 'pass' => ucwords($translate->_('common.password')),
  1370. );
  1371. }
  1372. /**
  1373. * Creates a new e-mail address record.
  1374. *
  1375. * @param array $fields An array of fields=>values
  1376. * @return integer The new address ID
  1377. *
  1378. * DAO_Address::create(array(
  1379. * DAO_Address::EMAIL => 'user@domain'
  1380. * ));
  1381. *
  1382. */
  1383. static function create($fields) {
  1384. $db = DevblocksPlatform::getDatabaseService();
  1385. $id = $db->GenID('address_seq');
  1386. if(null == ($email = @$fields[self::EMAIL]))
  1387. return NULL;
  1388. // [TODO] Validate
  1389. @$addresses = imap_rfc822_parse_adrlist('<'.$email.'>', 'host');
  1390. if(!is_array($addresses) || empty($addresses))
  1391. return NULL;
  1392. $address = array_shift($addresses);
  1393. if(empty($address->host) || $address->host == 'host')
  1394. return NULL;
  1395. $full_address = trim(strtolower($address->mailbox.'@'.$address->host));
  1396. // Make sure the address doesn't exist already
  1397. if(null == ($check = self::getByEmail($full_address))) {
  1398. $sql = sprintf("INSERT INTO address (id,email,first_name,last_name,contact_org_id,num_spam,num_nonspam,is_banned,is_registered,pass,last_autoreply) ".
  1399. "VALUES (%d,%s,'','',0,0,0,0,0,'',0)",
  1400. $id,
  1401. $db->qstr($full_address)
  1402. );
  1403. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1404. } else { // update
  1405. $id = $check->id;
  1406. unset($fields[self::ID]);
  1407. unset($fields[self::EMAIL]);
  1408. }
  1409. self::update($id, $fields);
  1410. return $id;
  1411. }
  1412. static function update($ids, $fields) {
  1413. parent::_update($ids, 'address', $fields);
  1414. }
  1415. static function updateWhere($fields, $where) {
  1416. parent::_updateWhere('address', $fields, $where);
  1417. }
  1418. static function maint() {
  1419. $db = DevblocksPlatform::getDatabaseService();
  1420. $logger = DevblocksPlatform::getConsoleLog();
  1421. $sql = "DELETE QUICK address_to_worker FROM address_to_worker LEFT JOIN worker ON address_to_worker.worker_id=worker.id WHERE worker.id IS NULL";
  1422. $db->Execute($sql);
  1423. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' address_to_worker records.');
  1424. }
  1425. static function delete($ids) {
  1426. if(!is_array($ids)) $ids = array($ids);
  1427. if(empty($ids))
  1428. return;
  1429. $db = DevblocksPlatform::getDatabaseService();
  1430. $address_ids = implode(',', $ids);
  1431. // Addresses
  1432. $sql = sprintf("DELETE QUICK FROM address WHERE id IN (%s)", $address_ids);
  1433. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1434. // Custom fields
  1435. DAO_CustomFieldValue::deleteBySourceIds(ChCustomFieldSource_Address::ID, $ids);
  1436. }
  1437. static function getWhere($where=null) {
  1438. $db = DevblocksPlatform::getDatabaseService();
  1439. $addresses = array();
  1440. $sql = sprintf("SELECT a.id, a.email, a.first_name, a.last_name, a.contact_org_id, a.num_spam, a.num_nonspam, a.is_banned, a.is_registered, a.pass, a.last_autoreply ".
  1441. "FROM address a ".
  1442. ((!empty($where)) ? "WHERE %s " : " ").
  1443. "ORDER BY a.email ",
  1444. $where
  1445. );
  1446. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1447. if(is_a($rs,'ADORecordSet'))
  1448. while(!$rs->EOF) {
  1449. $address = new Model_Address();
  1450. $address->id = intval($rs->fields['id']);
  1451. $address->email = $rs->fields['email'];
  1452. $address->first_name = $rs->fields['first_name'];
  1453. $address->last_name = $rs->fields['last_name'];
  1454. $address->contact_org_id = intval($rs->fields['contact_org_id']);
  1455. $address->num_spam = intval($rs->fields['num_spam']);
  1456. $address->num_nonspam = intval($rs->fields['num_nonspam']);
  1457. $address->is_banned = intval($rs->fields['is_banned']);
  1458. $address->is_registered = intval($rs->fields['is_registered']);
  1459. $address->pass = $rs->fields['pass'];
  1460. $address->last_autoreply = intval($rs->fields['last_autoreply']);
  1461. $addresses[$address->id] = $address;
  1462. $rs->MoveNext();
  1463. }
  1464. return $addresses;
  1465. }
  1466. /**
  1467. * @return Model_Address|null
  1468. */
  1469. static function getByEmail($email) {
  1470. $db = DevblocksPlatform::getDatabaseService();
  1471. $results = self::getWhere(sprintf("%s = %s",
  1472. self::EMAIL,
  1473. $db->qstr(strtolower($email))
  1474. ));
  1475. if(!empty($results))
  1476. return array_shift($results);
  1477. return NULL;
  1478. }
  1479. static function getCountByOrgId($org_id) {
  1480. $db = DevblocksPlatform::getDatabaseService();
  1481. $sql = sprintf("SELECT count(id) FROM address WHERE contact_org_id = %d",
  1482. $org_id
  1483. );
  1484. return intval($db->GetOne($sql));
  1485. }
  1486. /**
  1487. * Enter description here...
  1488. *
  1489. * @param unknown_type $id
  1490. * @return Model_Address
  1491. */
  1492. static function get($id) {
  1493. if(empty($id)) return null;
  1494. $addresses = DAO_Address::getWhere(
  1495. sprintf("%s = %d",
  1496. self::ID,
  1497. $id
  1498. ));
  1499. if(isset($addresses[$id]))
  1500. return $addresses[$id];
  1501. return null;
  1502. }
  1503. /**
  1504. * Enter description here...
  1505. *
  1506. * @param unknown_type $email
  1507. * @param unknown_type $create_if_null
  1508. * @return Model_Address
  1509. */
  1510. static function lookupAddress($email,$create_if_null=false) {
  1511. $db = DevblocksPlatform::getDatabaseService();
  1512. $address = null;
  1513. $email = trim(mb_convert_case($email, MB_CASE_LOWER));
  1514. $addresses = self::getWhere(sprintf("email = %s",
  1515. $db->qstr($email)
  1516. ));
  1517. if(is_array($addresses) && !empty($addresses)) {
  1518. $address = array_shift($addresses);
  1519. } elseif($create_if_null) {
  1520. $fields = array(
  1521. self::EMAIL => $email
  1522. );
  1523. $id = DAO_Address::create($fields);
  1524. $address = DAO_Address::get($id);
  1525. }
  1526. return $address;
  1527. }
  1528. static function addOneToSpamTotal($address_id) {
  1529. $db = DevblocksPlatform::getDatabaseService();
  1530. $sql = sprintf("UPDATE address SET num_spam = num_spam + 1 WHERE id = %d",$address_id);
  1531. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1532. }
  1533. static function addOneToNonSpamTotal($address_id) {
  1534. $db = DevblocksPlatform::getDatabaseService();
  1535. $sql = sprintf("UPDATE address SET num_nonspam = num_nonspam + 1 WHERE id = %d",$address_id);
  1536. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1537. }
  1538. /**
  1539. * Enter description here...
  1540. *
  1541. * @param DevblocksSearchCriteria[] $params
  1542. * @param integer $limit
  1543. * @param integer $page
  1544. * @param string $sortBy
  1545. * @param boolean $sortAsc
  1546. * @param boolean $withCounts
  1547. * @return array
  1548. */
  1549. static function search($columns, $params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  1550. $db = DevblocksPlatform::getDatabaseService();
  1551. $fields = SearchFields_Address::getFields();
  1552. // Sanitize
  1553. if(!isset($fields[$sortBy]))
  1554. $sortBy=null;
  1555. list($tables,$wheres) = parent::_parseSearchParams($params, $columns, $fields,$sortBy);
  1556. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  1557. $select_sql = sprintf("SELECT ".
  1558. "a.id as %s, ".
  1559. "a.email as %s, ".
  1560. "a.first_name as %s, ".
  1561. "a.last_name as %s, ".
  1562. "a.contact_org_id as %s, ".
  1563. "o.name as %s, ".
  1564. "a.num_spam as %s, ".
  1565. "a.num_nonspam as %s, ".
  1566. "a.is_banned as %s, ".
  1567. "a.is_registered as %s, ".
  1568. "a.pass as %s ",
  1569. SearchFields_Address::ID,
  1570. SearchFields_Address::EMAIL,
  1571. SearchFields_Address::FIRST_NAME,
  1572. SearchFields_Address::LAST_NAME,
  1573. SearchFields_Address::CONTACT_ORG_ID,
  1574. SearchFields_Address::ORG_NAME,
  1575. SearchFields_Address::NUM_SPAM,
  1576. SearchFields_Address::NUM_NONSPAM,
  1577. SearchFields_Address::IS_BANNED,
  1578. SearchFields_Address::IS_REGISTERED,
  1579. SearchFields_Address::PASS
  1580. );
  1581. $join_sql =
  1582. "FROM address a ".
  1583. "LEFT JOIN contact_org o ON (o.id=a.contact_org_id) "
  1584. ;
  1585. // [JAS]: Dynamic table joins
  1586. // (isset($tables['o']) ? "LEFT JOIN contact_org o ON (o.id=a.contact_org_id)" : " ").
  1587. // (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=m.id)" : " ").
  1588. // Custom field joins
  1589. list($select_sql, $join_sql, $has_multiple_values) = self::_appendSelectJoinSqlForCustomFieldTables(
  1590. $tables,
  1591. $params,
  1592. 'a.id',
  1593. $select_sql,
  1594. $join_sql
  1595. );
  1596. $where_sql = "".
  1597. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  1598. $sort_sql = (!empty($sortBy) ? sprintf("ORDER BY %s %s ",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : " ");
  1599. $sql =
  1600. $select_sql.
  1601. $join_sql.
  1602. $where_sql.
  1603. ($has_multiple_values ? 'GROUP BY a.id ' : '').
  1604. $sort_sql;
  1605. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1606. $results = array();
  1607. if(is_a($rs,'ADORecordSet'))
  1608. while(!$rs->EOF) {
  1609. $result = array();
  1610. foreach($rs->fields as $f => $v) {
  1611. $result[$f] = $v;
  1612. }
  1613. $id = intval($rs->fields[SearchFields_Address::ID]);
  1614. $results[$id] = $result;
  1615. $rs->MoveNext();
  1616. }
  1617. // [JAS]: Count all
  1618. $total = -1;
  1619. if($withCounts) {
  1620. $count_sql =
  1621. ($has_multiple_values ? "SELECT COUNT(DISTINCT a.id) " : "SELECT COUNT(a.id) ").
  1622. $join_sql.
  1623. $where_sql;
  1624. $total = $db->GetOne($count_sql);
  1625. }
  1626. return array($results,$total);
  1627. }
  1628. };
  1629. class SearchFields_Address implements IDevblocksSearchFields {
  1630. // Address
  1631. const ID = 'a_id';
  1632. const EMAIL = 'a_email';
  1633. const FIRST_NAME = 'a_first_name';
  1634. const LAST_NAME = 'a_last_name';
  1635. const CONTACT_ORG_ID = 'a_contact_org_id';
  1636. const NUM_SPAM = 'a_num_spam';
  1637. const NUM_NONSPAM = 'a_num_nonspam';
  1638. const IS_BANNED = 'a_is_banned';
  1639. const IS_REGISTERED = 'a_is_registered';
  1640. const PASS = 'a_pass';
  1641. const ORG_NAME = 'o_name';
  1642. /**
  1643. * @return DevblocksSearchField[]
  1644. */
  1645. static function getFields() {
  1646. $translate = DevblocksPlatform::getTranslationService();
  1647. $columns = array(
  1648. self::ID => new DevblocksSearchField(self::ID, 'a', 'id', null, $translate->_('address.id')),
  1649. self::EMAIL => new DevblocksSearchField(self::EMAIL, 'a', 'email', null, $translate->_('address.email')),
  1650. self::FIRST_NAME => new DevblocksSearchField(self::FIRST_NAME, 'a', 'first_name', null, $translate->_('address.first_name')),
  1651. self::LAST_NAME => new DevblocksSearchField(self::LAST_NAME, 'a', 'last_name', null, $translate->_('address.last_name')),
  1652. self::NUM_SPAM => new DevblocksSearchField(self::NUM_SPAM, 'a', 'num_spam', null, $translate->_('address.num_spam')),
  1653. self::NUM_NONSPAM => new DevblocksSearchField(self::NUM_NONSPAM, 'a', 'num_nonspam', null, $translate->_('address.num_nonspam')),
  1654. self::IS_BANNED => new DevblocksSearchField(self::IS_BANNED, 'a', 'is_banned', null, $translate->_('address.is_banned')),
  1655. self::IS_REGISTERED => new DevblocksSearchField(self::IS_REGISTERED, 'a', 'is_registered', null, $translate->_('address.is_registered')),
  1656. self::PASS => new DevblocksSearchField(self::PASS, 'a', 'pass', null, ucwords($translate->_('common.password'))),
  1657. self::CONTACT_ORG_ID => new DevblocksSearchField(self::CONTACT_ORG_ID, 'a', 'contact_org_id', null, $translate->_('address.contact_org_id')),
  1658. self::ORG_NAME => new DevblocksSearchField(self::ORG_NAME, 'o', 'name', null, $translate->_('contact_org.name')),
  1659. );
  1660. // Custom Fields
  1661. $fields = DAO_CustomField::getBySource(ChCustomFieldSource_Address::ID);
  1662. if(is_array($fields))
  1663. foreach($fields as $field_id => $field) {
  1664. $key = 'cf_'.$field_id;
  1665. $columns[$key] = new DevblocksSearchField($key,$key,'field_value',null,$field->name);
  1666. }
  1667. // Sort by label (translation-conscious)
  1668. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  1669. return $columns;
  1670. }
  1671. };
  1672. class DAO_AddressToWorker { // extends DevblocksORMHelper
  1673. const ADDRESS = 'address';
  1674. const WORKER_ID = 'worker_id';
  1675. const IS_CONFIRMED = 'is_confirmed';
  1676. const CODE = 'code';
  1677. const CODE_EXPIRE = 'code_expire';
  1678. static function assign($address, $worker_id) {
  1679. $db = DevblocksPlatform::getDatabaseService();
  1680. if(empty($address) || empty($worker_id))
  1681. return NULL;
  1682. // Force lowercase
  1683. $address = strtolower($address);
  1684. $sql = sprintf("INSERT INTO address_to_worker (address, worker_id, is_confirmed, code, code_expire) ".
  1685. "VALUES (%s, %d, 0, '', 0)",
  1686. $db->qstr($address),
  1687. $worker_id
  1688. );
  1689. $db->Execute($sql);
  1690. return $address;
  1691. }
  1692. static function unassign($address) {
  1693. $db = DevblocksPlatform::getDatabaseService();
  1694. if(empty($address))
  1695. return NULL;
  1696. $sql = sprintf("DELETE QUICK FROM address_to_worker WHERE address = %s",
  1697. $db->qstr($address)
  1698. );
  1699. $db->Execute($sql);
  1700. }
  1701. static function unassignAll($worker_id) {
  1702. $db = DevblocksPlatform::getDatabaseService();
  1703. if(empty($worker_id))
  1704. return NULL;
  1705. $sql = sprintf("DELETE QUICK FROM address_to_worker WHERE worker_id = %d",
  1706. $worker_id
  1707. );
  1708. $db->Execute($sql);
  1709. }
  1710. static function update($addresses, $fields) {
  1711. if(!is_array($addresses)) $addresses = array($addresses);
  1712. $db = DevblocksPlatform::getDatabaseService();
  1713. $sets = array();
  1714. if(!is_array($fields) || empty($fields) || empty($addresses))
  1715. return;
  1716. foreach($fields as $k => $v) {
  1717. if(is_null($v))
  1718. $value = 'NULL';
  1719. else
  1720. $value = $db->qstr($v);
  1721. $sets[] = sprintf("%s = %s",
  1722. $k,
  1723. $value
  1724. );
  1725. }
  1726. $sql = sprintf("UPDATE %s SET %s WHERE %s IN ('%s')",
  1727. 'address_to_worker',
  1728. implode(', ', $sets),
  1729. self::ADDRESS,
  1730. implode("','", $addresses)
  1731. );
  1732. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1733. }
  1734. /**
  1735. * Enter description here...
  1736. *
  1737. * @param integer $worker_id
  1738. * @return Model_AddressToWorker[]
  1739. */
  1740. static function getByWorker($worker_id) {
  1741. $db = DevblocksPlatform::getDatabaseService();
  1742. $addresses = self::getWhere(sprintf("%s = %d",
  1743. DAO_AddressToWorker::WORKER_ID,
  1744. $worker_id
  1745. ));
  1746. return $addresses;
  1747. }
  1748. /**
  1749. * Enter description here...
  1750. *
  1751. * @param integer $address
  1752. * @return Model_AddressToWorker
  1753. */
  1754. static function getByAddress($address) {
  1755. $db = DevblocksPlatform::getDatabaseService();
  1756. // Force lower
  1757. $address = strtolower($address);
  1758. $addresses = self::getWhere(sprintf("%s = %s",
  1759. DAO_AddressToWorker::ADDRESS,
  1760. $db->qstr($address)
  1761. ));
  1762. if(isset($addresses[$address]))
  1763. return $addresses[$address];
  1764. return NULL;
  1765. }
  1766. static function getWhere($where=null) {
  1767. $db = DevblocksPlatform::getDatabaseService();
  1768. $sql = "SELECT address, worker_id, is_confirmed, code, code_expire ".
  1769. "FROM address_to_worker ".
  1770. (!empty($where) ? sprintf("WHERE %s ", $where) : " ").
  1771. "ORDER BY address";
  1772. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1773. return self::_getObjectsFromResult($rs);
  1774. }
  1775. /**
  1776. * Enter description here...
  1777. *
  1778. * @param ADORecordSet $rs
  1779. * @return Model_AddressToWorker[]
  1780. */
  1781. private static function _getObjectsFromResult($rs) {
  1782. $objects = array();
  1783. if(is_a($rs,'ADORecordSet'))
  1784. while(!$rs->EOF) {
  1785. $object = new Model_AddressToWorker();
  1786. $object->worker_id = intval($rs->fields['worker_id']);
  1787. $object->address = strtolower($rs->fields['address']);
  1788. $object->is_confirmed = intval($rs->fields['is_confirmed']);
  1789. $object->code = $rs->fields['code'];
  1790. $object->code_expire = intval($rs->fields['code_expire']);
  1791. $objects[$object->address] = $object;
  1792. $rs->MoveNext();
  1793. }
  1794. return $objects;
  1795. }
  1796. };
  1797. class DAO_Message extends DevblocksORMHelper {
  1798. const ID = 'id';
  1799. const TICKET_ID = 'ticket_id';
  1800. const CREATED_DATE = 'created_date';
  1801. const ADDRESS_ID = 'address_id';
  1802. const IS_OUTGOING = 'is_outgoing';
  1803. const WORKER_ID = 'worker_id';
  1804. static function create($fields) {
  1805. $db = DevblocksPlatform::getDatabaseService();
  1806. $newId = $db->GenID('message_seq');
  1807. $sql = sprintf("INSERT INTO message (id,ticket_id,created_date,is_outgoing,worker_id,address_id) ".
  1808. "VALUES (%d,0,0,0,0,0)",
  1809. $newId
  1810. );
  1811. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1812. self::update($newId, $fields);
  1813. return $newId;
  1814. }
  1815. static function update($id, $fields) {
  1816. parent::_update($id, 'message', $fields);
  1817. }
  1818. static function maint() {
  1819. $db = DevblocksPlatform::getDatabaseService();
  1820. $logger = DevblocksPlatform::getConsoleLog();
  1821. $sql = "DELETE QUICK message FROM message LEFT JOIN ticket ON message.ticket_id = ticket.id WHERE ticket.id IS NULL";
  1822. $db->Execute($sql);
  1823. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' message records.');
  1824. $sql = "DELETE QUICK message_header FROM message_header LEFT JOIN message ON message_header.message_id = message.id WHERE message.id IS NULL";
  1825. $db->Execute($sql);
  1826. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' message_header records.');
  1827. $sql = "DELETE QUICK message_content FROM message_content LEFT JOIN message ON message_content.message_id = message.id WHERE message.id IS NULL";
  1828. $db->Execute($sql);
  1829. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' message_content records.');
  1830. $sql = "DELETE QUICK message_note FROM message_note LEFT JOIN message ON message_note.message_id = message.id WHERE message.id IS NULL";
  1831. $db->Execute($sql);
  1832. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' message_note records.');
  1833. DAO_Attachment::maint();
  1834. }
  1835. /**
  1836. * Enter description here...
  1837. *
  1838. * @param DevblocksSearchCriteria[] $params
  1839. * @param integer $limit
  1840. * @param integer $page
  1841. * @param string $sortBy
  1842. * @param boolean $sortAsc
  1843. * @param boolean $withCounts
  1844. * @return array
  1845. */
  1846. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  1847. $db = DevblocksPlatform::getDatabaseService();
  1848. $fields = SearchFields_Message::getFields();
  1849. // Sanitize
  1850. if(!isset($fields[$sortBy]))
  1851. $sortBy=null;
  1852. list($tables,$wheres,$selects) = parent::_parseSearchParams($params, array(),$fields,$sortBy);
  1853. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  1854. $sql = sprintf("SELECT ".
  1855. "m.id as %s, ".
  1856. "m.ticket_id as %s ".
  1857. "FROM message m ",
  1858. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  1859. SearchFields_Message::ID,
  1860. SearchFields_Message::TICKET_ID
  1861. ).
  1862. // [JAS]: Dynamic table joins
  1863. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=m.id)" : " ").
  1864. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=m.id)" : " ").
  1865. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  1866. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "")
  1867. ;
  1868. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1869. $results = array();
  1870. if(is_a($rs,'ADORecordSet'))
  1871. while(!$rs->EOF) {
  1872. $result = array();
  1873. foreach($rs->fields as $f => $v) {
  1874. $result[$f] = $v;
  1875. }
  1876. $ticket_id = intval($rs->fields[SearchFields_Message::ID]);
  1877. $results[$ticket_id] = $result;
  1878. $rs->MoveNext();
  1879. }
  1880. // [JAS]: Count all
  1881. $total = -1;
  1882. if($withCounts) {
  1883. $rs = $db->Execute($sql);
  1884. $total = $rs->RecordCount();
  1885. }
  1886. return array($results,$total);
  1887. }
  1888. };
  1889. class SearchFields_Message implements IDevblocksSearchFields {
  1890. // Message
  1891. const ID = 'm_id';
  1892. const TICKET_ID = 'm_ticket_id';
  1893. // Headers
  1894. const MESSAGE_HEADER_NAME = 'mh_header_name';
  1895. const MESSAGE_HEADER_VALUE = 'mh_header_value';
  1896. // Content
  1897. const MESSAGE_CONTENT = 'mc_content';
  1898. /**
  1899. * @return DevblocksSearchField[]
  1900. */
  1901. static function getFields() {
  1902. $columns = array(
  1903. SearchFields_Message::ID => new DevblocksSearchField(SearchFields_Message::ID, 'm', 'id'),
  1904. SearchFields_Message::TICKET_ID => new DevblocksSearchField(SearchFields_Message::TICKET_ID, 'm', 'ticket_id'),
  1905. SearchFields_Message::MESSAGE_HEADER_NAME => new DevblocksSearchField(SearchFields_Message::MESSAGE_HEADER_NAME, 'mh', 'header_name'),
  1906. SearchFields_Message::MESSAGE_HEADER_VALUE => new DevblocksSearchField(SearchFields_Message::MESSAGE_HEADER_VALUE, 'mh', 'header_value', 'B'),
  1907. SearchFields_Message::MESSAGE_CONTENT => new DevblocksSearchField(SearchFields_Message::MESSAGE_CONTENT, 'mc', 'content', 'B'),
  1908. );
  1909. // Sort by label (translation-conscious)
  1910. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  1911. return $columns;
  1912. }
  1913. };
  1914. class DAO_MessageNote extends DevblocksORMHelper {
  1915. const ID = 'id';
  1916. const TYPE = 'type';
  1917. const MESSAGE_ID = 'message_id';
  1918. const WORKER_ID = 'worker_id';
  1919. const CREATED = 'created';
  1920. const CONTENT = 'content';
  1921. static function create($fields) {
  1922. $db = DevblocksPlatform::getDatabaseService();
  1923. $id = $db->GenID('message_note_seq');
  1924. $sql = sprintf("INSERT INTO message_note (id,type,message_id,worker_id,created,content) ".
  1925. "VALUES (%d,0,0,0,%d,'')",
  1926. $id,
  1927. time()
  1928. );
  1929. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1930. self::update($id, $fields);
  1931. }
  1932. static function getByMessageId($message_id) {
  1933. $db = DevblocksPlatform::getDatabaseService();
  1934. $sql = sprintf("SELECT id,type,message_id,worker_id,created,content ".
  1935. "FROM message_note ".
  1936. "WHERE message_id = %d ".
  1937. "ORDER BY id ASC",
  1938. $message_id
  1939. );
  1940. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1941. return self::_getObjectsFromResultSet($rs);
  1942. }
  1943. static function getByTicketId($ticket_id) {
  1944. $db = DevblocksPlatform::getDatabaseService();
  1945. $sql = sprintf("SELECT n.id,n.type,n.message_id,n.worker_id,n.created,n.content ".
  1946. "FROM message_note n ".
  1947. "INNER JOIN message m ON (m.id=n.message_id) ".
  1948. "WHERE m.ticket_id = %d ".
  1949. "ORDER BY n.id ASC",
  1950. $ticket_id
  1951. );
  1952. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1953. return self::_getObjectsFromResultSet($rs);
  1954. }
  1955. static function getList($ids) {
  1956. if(!is_array($ids)) $ids = array($ids);
  1957. $db = DevblocksPlatform::getDatabaseService();
  1958. $sql = sprintf("SELECT n.id,n.type,n.message_id,n.worker_id,n.created,n.content ".
  1959. "FROM message_note n ".
  1960. "WHERE n.id IN (%s) ".
  1961. "ORDER BY n.id ASC",
  1962. implode(',', $ids)
  1963. );
  1964. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  1965. return self::_getObjectsFromResultSet($rs);
  1966. }
  1967. static function get($id) {
  1968. $objects = self::getList(array($id));
  1969. return @$objects[$id];
  1970. }
  1971. static private function _getObjectsFromResultSet($rs) {
  1972. $objects = array();
  1973. if(is_a($rs,'ADORecordSet'))
  1974. while(!$rs->EOF) {
  1975. $object = new Model_MessageNote();
  1976. $object->id = intval($rs->fields['id']);
  1977. $object->type = intval($rs->fields['type']);
  1978. $object->message_id = intval($rs->fields['message_id']);
  1979. $object->created = intval($rs->fields['created']);
  1980. $object->worker_id = intval($rs->fields['worker_id']);
  1981. $object->content = $rs->fields['content'];
  1982. $objects[$object->id] = $object;
  1983. $rs->MoveNext();
  1984. }
  1985. return $objects;
  1986. }
  1987. static function update($ids, $fields) {
  1988. if(!is_array($ids)) $ids = array($ids);
  1989. $db = DevblocksPlatform::getDatabaseService();
  1990. // Update our blob manually
  1991. if($fields[self::CONTENT]) {
  1992. $db->UpdateBlob('message_note', self::CONTENT, $fields[self::CONTENT], 'id IN('.implode(',',$ids).')');
  1993. unset($fields[self::CONTENT]);
  1994. }
  1995. parent::_update($ids, 'message_note', $fields);
  1996. }
  1997. static function delete($ids) {
  1998. if(!is_array($ids)) $ids = array($ids);
  1999. if(empty($ids))
  2000. return;
  2001. $db = DevblocksPlatform::getDatabaseService();
  2002. $message_ids = implode(',', $ids);
  2003. $sql = sprintf("DELETE QUICK FROM message_note WHERE id IN (%s)", $message_ids);
  2004. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2005. }
  2006. };
  2007. class DAO_MessageContent {
  2008. const MESSAGE_ID = 'message_id';
  2009. const CONTENT = 'content';
  2010. static function create($message_id, $content) {
  2011. $db = DevblocksPlatform::getDatabaseService();
  2012. $db->Execute(sprintf("INSERT INTO message_content (message_id, content) VALUES (%d, %s)",
  2013. $message_id,
  2014. $db->qstr($content)
  2015. ));
  2016. }
  2017. static function update($message_id, $content) {
  2018. $db = DevblocksPlatform::getDatabaseService();
  2019. $db->Replace(
  2020. 'message_content',
  2021. array(
  2022. self::MESSAGE_ID => $message_id,
  2023. self::CONTENT => $db->qstr($content),
  2024. ),
  2025. array('message_id'),
  2026. false
  2027. );
  2028. }
  2029. static function get($message_id) {
  2030. $db = DevblocksPlatform::getDatabaseService();
  2031. $content = '';
  2032. $sql = sprintf("SELECT m.content ".
  2033. "FROM message_content m ".
  2034. "WHERE m.message_id = %d ",
  2035. $message_id
  2036. );
  2037. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2038. if(is_a($rs,'ADORecordSet') && !$rs->EOF) {
  2039. return $rs->fields['content'];
  2040. }
  2041. return '';
  2042. }
  2043. };
  2044. class DAO_MessageHeader {
  2045. const MESSAGE_ID = 'message_id';
  2046. const HEADER_NAME = 'header_name';
  2047. const HEADER_VALUE = 'header_value';
  2048. static function create($message_id, $header, $value) {
  2049. $db = DevblocksPlatform::getDatabaseService();
  2050. if(empty($header) || empty($value) || empty($message_id))
  2051. return;
  2052. $header = strtolower($header);
  2053. // Handle stacked headers
  2054. if(is_array($value)) {
  2055. $value = implode("\r\n",$value);
  2056. }
  2057. $db->Execute(sprintf("INSERT INTO message_header (message_id, header_name, header_value) ".
  2058. "VALUES (%d, %s, %s)",
  2059. $message_id,
  2060. $db->qstr($header),
  2061. $db->qstr($value)
  2062. ));
  2063. }
  2064. static function getAll($message_id) {
  2065. $db = DevblocksPlatform::getDatabaseService();
  2066. $sql = "SELECT header_name, header_value ".
  2067. "FROM message_header ".
  2068. "WHERE message_id = ?";
  2069. $rs = $db->Execute($sql, array($message_id))
  2070. or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2071. $headers = array();
  2072. while(!$rs->EOF) {
  2073. $headers[$rs->fields['header_name']] = $rs->fields['header_value'];
  2074. $rs->MoveNext();
  2075. }
  2076. return $headers;
  2077. }
  2078. static function getUnique() {
  2079. $db = DevblocksPlatform::getDatabaseService();
  2080. $headers = array();
  2081. $sql = "SELECT header_name FROM message_header GROUP BY header_name";
  2082. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2083. if(is_a($rs,'ADORecordSet'))
  2084. while(!$rs->EOF) {
  2085. $headers[] = $rs->fields['header_name'];
  2086. $rs->MoveNext();
  2087. }
  2088. sort($headers);
  2089. return $headers;
  2090. }
  2091. };
  2092. class DAO_Attachment extends DevblocksORMHelper {
  2093. const ID = 'id';
  2094. const MESSAGE_ID = 'message_id';
  2095. const DISPLAY_NAME = 'display_name';
  2096. const MIME_TYPE = 'mime_type';
  2097. const FILE_SIZE = 'file_size';
  2098. const FILEPATH = 'filepath';
  2099. public static function create($fields) {
  2100. $db = DevblocksPlatform::getDatabaseService();
  2101. $id = $db->GenID('attachment_seq');
  2102. $sql = sprintf("INSERT INTO attachment (id,message_id,display_name,mime_type,file_size,filepath) ".
  2103. "VALUES (%d,0,'','',0,'')",
  2104. $id
  2105. );
  2106. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2107. self::update($id, $fields);
  2108. return $id;
  2109. }
  2110. public static function update($id, $fields) {
  2111. self::_update($id, 'attachment', $fields);
  2112. }
  2113. /**
  2114. * Enter description here...
  2115. *
  2116. * @param integer $id
  2117. * @return Model_Attachment
  2118. */
  2119. public static function get($id) {
  2120. $items = self::getList(array($id));
  2121. if(isset($items[$id]))
  2122. return $items[$id];
  2123. return NULL;
  2124. }
  2125. /**
  2126. * Enter description here...
  2127. *
  2128. * @param array $ids
  2129. * @return Model_Attachment[]
  2130. */
  2131. public static function getList($ids=array()) {
  2132. if(!is_array($ids)) $ids = array($ids);
  2133. $db = DevblocksPlatform::getDatabaseService();
  2134. $sql = "SELECT id,message_id,display_name,mime_type,file_size,filepath ".
  2135. "FROM attachment ".
  2136. (!empty($ids) ? sprintf("WHERE id IN (%s) ", implode(',', $ids)) : " ").
  2137. ""
  2138. ;
  2139. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2140. $objects = array();
  2141. if(is_a($rs,'ADORecordSet'))
  2142. while(!$rs->EOF) {
  2143. $object = new Model_Attachment();
  2144. $object->id = intval($rs->fields['id']);
  2145. $object->message_id = intval($rs->fields['message_id']);
  2146. $object->display_name = $rs->fields['display_name'];
  2147. $object->filepath = $rs->fields['filepath'];
  2148. $object->mime_type = $rs->fields['mime_type'];
  2149. $object->file_size = intval($rs->fields['file_size']);
  2150. $objects[$object->id] = $object;
  2151. $rs->MoveNext();
  2152. }
  2153. return $objects;
  2154. }
  2155. /**
  2156. * returns an array of Model_Attachment that
  2157. * correspond to the supplied message id.
  2158. *
  2159. * @param integer $id
  2160. * @return Model_Attachment[]
  2161. */
  2162. static function getByMessageId($id) {
  2163. $db = DevblocksPlatform::getDatabaseService();
  2164. $sql = sprintf("SELECT a.id, a.message_id, a.display_name, a.filepath, a.file_size, a.mime_type ".
  2165. "FROM attachment a ".
  2166. "WHERE a.message_id = %d",
  2167. $id
  2168. );
  2169. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2170. $attachments = array();
  2171. if(is_a($rs,'ADORecordSet'))
  2172. while(!$rs->EOF) {
  2173. $attachment = new Model_Attachment();
  2174. $attachment->id = intval($rs->fields['id']);
  2175. $attachment->message_id = intval($rs->fields['message_id']);
  2176. $attachment->display_name = $rs->fields['display_name'];
  2177. $attachment->filepath = $rs->fields['filepath'];
  2178. $attachment->file_size = intval($rs->fields['file_size']);
  2179. $attachment->mime_type = $rs->fields['mime_type'];
  2180. $attachments[$attachment->id] = $attachment;
  2181. $rs->MoveNext();
  2182. }
  2183. return $attachments;
  2184. }
  2185. static function maint() {
  2186. $db = DevblocksPlatform::getDatabaseService();
  2187. $logger = DevblocksPlatform::getConsoleLog();
  2188. $sql = "SELECT filepath FROM attachment LEFT JOIN message ON attachment.message_id = message.id WHERE message.id IS NULL";
  2189. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2190. $attachment_path = APP_STORAGE_PATH . '/attachments/';
  2191. // Delete the physical files
  2192. if(is_a($rs,'ADORecordSet'))
  2193. while(!$rs->EOF) {
  2194. @unlink($attachment_path . $rs->fields['filepath']);
  2195. $rs->MoveNext();
  2196. }
  2197. $sql = "DELETE attachment FROM attachment LEFT JOIN message ON attachment.message_id = message.id WHERE message.id IS NULL";
  2198. $db->Execute($sql);
  2199. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' attachment records.');
  2200. }
  2201. static function delete($ids) {
  2202. if(!is_array($ids)) $ids = array($ids);
  2203. if(empty($ids))
  2204. return;
  2205. $db = DevblocksPlatform::getDatabaseService();
  2206. $sql = sprintf("SELECT filepath FROM attachment WHERE id IN (%s)", implode(',',$ids));
  2207. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2208. $attachment_path = APP_STORAGE_PATH . '/attachments/';
  2209. // Delete the physical files
  2210. if(is_a($rs,'ADORecordSet'))
  2211. while(!$rs->EOF) {
  2212. @unlink($attachment_path . $rs->fields['filepath']);
  2213. $rs->MoveNext();
  2214. }
  2215. // Delete DB manifests
  2216. $sql = sprintf("DELETE attachment FROM attachment WHERE id IN (%s)", implode(',', $ids));
  2217. $db->Execute($sql);
  2218. }
  2219. /**
  2220. * Enter description here...
  2221. *
  2222. * @param DevblocksSearchCriteria[] $params
  2223. * @param integer $limit
  2224. * @param integer $page
  2225. * @param string $sortBy
  2226. * @param boolean $sortAsc
  2227. * @param boolean $withCounts
  2228. * @return array
  2229. */
  2230. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  2231. $db = DevblocksPlatform::getDatabaseService();
  2232. $fields = SearchFields_Attachment::getFields();
  2233. // Sanitize
  2234. if(!isset($fields[$sortBy]))
  2235. $sortBy=null;
  2236. list($tables,$wheres) = parent::_parseSearchParams($params, array(),$fields,$sortBy);
  2237. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  2238. $total = -1;
  2239. $sql = sprintf("SELECT ".
  2240. "a.id as %s, ".
  2241. "a.message_id as %s, ".
  2242. "a.display_name as %s, ".
  2243. "a.mime_type as %s, ".
  2244. "a.file_size as %s, ".
  2245. "a.filepath as %s, ".
  2246. "m.address_id as %s, ".
  2247. "m.created_date as %s, ".
  2248. "m.is_outgoing as %s, ".
  2249. "t.id as %s, ".
  2250. "t.mask as %s, ".
  2251. "t.subject as %s, ".
  2252. "ad.email as %s ".
  2253. "FROM attachment a ".
  2254. "INNER JOIN message m ON (a.message_id = m.id) ".
  2255. "INNER JOIN ticket t ON (m.ticket_id = t.id) ".
  2256. "INNER JOIN address ad ON (m.address_id = ad.id) ".
  2257. "",
  2258. SearchFields_Attachment::ID,
  2259. SearchFields_Attachment::MESSAGE_ID,
  2260. SearchFields_Attachment::DISPLAY_NAME,
  2261. SearchFields_Attachment::MIME_TYPE,
  2262. SearchFields_Attachment::FILE_SIZE,
  2263. SearchFields_Attachment::FILEPATH,
  2264. SearchFields_Attachment::MESSAGE_ADDRESS_ID,
  2265. SearchFields_Attachment::MESSAGE_CREATED_DATE,
  2266. SearchFields_Attachment::MESSAGE_IS_OUTGOING,
  2267. SearchFields_Attachment::TICKET_ID,
  2268. SearchFields_Attachment::TICKET_MASK,
  2269. SearchFields_Attachment::TICKET_SUBJECT,
  2270. SearchFields_Attachment::ADDRESS_EMAIL
  2271. ).
  2272. // [JAS]: Dynamic table joins
  2273. // (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2274. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  2275. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "")
  2276. ;
  2277. // [TODO] Could push the select logic down a level too
  2278. if($limit > 0) {
  2279. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2280. } else {
  2281. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2282. $total = $rs->RecordCount();
  2283. }
  2284. $results = array();
  2285. if(is_a($rs,'ADORecordSet'))
  2286. while(!$rs->EOF) {
  2287. $result = array();
  2288. foreach($rs->fields as $f => $v) {
  2289. $result[$f] = $v;
  2290. }
  2291. $ticket_id = intval($rs->fields[SearchFields_Attachment::ID]);
  2292. $results[$ticket_id] = $result;
  2293. $rs->MoveNext();
  2294. }
  2295. // [JAS]: Count all
  2296. if($withCounts) {
  2297. $rs = $db->Execute($sql);
  2298. $total = $rs->RecordCount();
  2299. }
  2300. return array($results,$total);
  2301. }
  2302. };
  2303. class SearchFields_Attachment implements IDevblocksSearchFields {
  2304. const ID = 'a_id';
  2305. const MESSAGE_ID = 'a_message_id';
  2306. const DISPLAY_NAME = 'a_display_name';
  2307. const MIME_TYPE = 'a_mime_type';
  2308. const FILE_SIZE = 'a_file_size';
  2309. const FILEPATH = 'a_filepath';
  2310. const MESSAGE_ADDRESS_ID = 'm_address_id';
  2311. const MESSAGE_CREATED_DATE = 'm_created_date';
  2312. const MESSAGE_IS_OUTGOING = 'm_is_outgoing';
  2313. const TICKET_ID = 't_id';
  2314. const TICKET_MASK = 't_mask';
  2315. const TICKET_SUBJECT = 't_subject';
  2316. const ADDRESS_EMAIL = 'ad_email';
  2317. /**
  2318. * @return DevblocksSearchField[]
  2319. */
  2320. static function getFields() {
  2321. $translate = DevblocksPlatform::getTranslationService();
  2322. $columns = array(
  2323. self::ID => new DevblocksSearchField(self::ID, 'a', 'id', null, $translate->_('attachment.id')),
  2324. self::MESSAGE_ID => new DevblocksSearchField(self::MESSAGE_ID, 'a', 'message_id', null, $translate->_('attachment.message_id')),
  2325. self::DISPLAY_NAME => new DevblocksSearchField(self::DISPLAY_NAME, 'a', 'display_name', null, $translate->_('attachment.display_name')),
  2326. self::MIME_TYPE => new DevblocksSearchField(self::MIME_TYPE, 'a', 'mime_type', null, $translate->_('attachment.mime_type')),
  2327. self::FILE_SIZE => new DevblocksSearchField(self::FILE_SIZE, 'a', 'file_size', null, $translate->_('attachment.file_size')),
  2328. self::FILEPATH => new DevblocksSearchField(self::FILEPATH, 'a', 'filepath', null, $translate->_('attachment.filepath')),
  2329. self::MESSAGE_ADDRESS_ID => new DevblocksSearchField(self::MESSAGE_ADDRESS_ID, 'm', 'address_id', null),
  2330. self::MESSAGE_CREATED_DATE => new DevblocksSearchField(self::MESSAGE_CREATED_DATE, 'm', 'created_date', null, $translate->_('message.created_date')),
  2331. self::MESSAGE_IS_OUTGOING => new DevblocksSearchField(self::MESSAGE_IS_OUTGOING, 'm', 'is_outgoing', null, $translate->_('mail.outbound')),
  2332. self::TICKET_ID => new DevblocksSearchField(self::TICKET_ID, 't', 'id', null, $translate->_('ticket.id')),
  2333. self::TICKET_MASK => new DevblocksSearchField(self::TICKET_MASK, 't', 'mask', null, $translate->_('ticket.mask')),
  2334. self::TICKET_SUBJECT => new DevblocksSearchField(self::TICKET_SUBJECT, 't', 'subject', null, $translate->_('ticket.subject')),
  2335. self::ADDRESS_EMAIL => new DevblocksSearchField(self::ADDRESS_EMAIL, 'ad', 'email', null, $translate->_('message.header.from')),
  2336. );
  2337. // Sort by label (translation-conscious)
  2338. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  2339. return $columns;
  2340. }
  2341. };
  2342. /**
  2343. * Enter description here...
  2344. *
  2345. * @addtogroup dao
  2346. */
  2347. class DAO_Ticket extends C4_ORMHelper {
  2348. const ID = 'id';
  2349. const MASK = 'mask';
  2350. const SUBJECT = 'subject';
  2351. const IS_WAITING = 'is_waiting';
  2352. const IS_CLOSED = 'is_closed';
  2353. const IS_DELETED = 'is_deleted';
  2354. const TEAM_ID = 'team_id';
  2355. const CATEGORY_ID = 'category_id';
  2356. const FIRST_MESSAGE_ID = 'first_message_id';
  2357. const LAST_WROTE_ID = 'last_wrote_address_id';
  2358. const FIRST_WROTE_ID = 'first_wrote_address_id';
  2359. const CREATED_DATE = 'created_date';
  2360. const UPDATED_DATE = 'updated_date';
  2361. const DUE_DATE = 'due_date';
  2362. const UNLOCK_DATE = 'unlock_date';
  2363. const SPAM_TRAINING = 'spam_training';
  2364. const SPAM_SCORE = 'spam_score';
  2365. const INTERESTING_WORDS = 'interesting_words';
  2366. const LAST_ACTION_CODE = 'last_action_code';
  2367. const LAST_WORKER_ID = 'last_worker_id';
  2368. const NEXT_WORKER_ID = 'next_worker_id';
  2369. private function DAO_Ticket() {}
  2370. public static function getFields() {
  2371. $translate = DevblocksPlatform::getTranslationService();
  2372. return array(
  2373. 'id' => $translate->_('ticket.id'),
  2374. 'mask' => $translate->_('ticket.mask'),
  2375. 'subject' => $translate->_('ticket.subject'),
  2376. 'is_waiting' => $translate->_('status.waiting'),
  2377. 'is_closed' => $translate->_('status.closed'),
  2378. 'is_deleted' => $translate->_('status.deleted'),
  2379. 'team_id' => $translate->_('ticket.group'),
  2380. 'category_id' => $translate->_('ticket.bucket'),
  2381. 'updated_date' => $translate->_('ticket.updated'),
  2382. 'spam_training' => $translate->_('ticket.spam_training'),
  2383. 'spam_score' => $translate->_('ticket.spam_score'),
  2384. 'interesting_words' => $translate->_('ticket.interesting_words'),
  2385. 'next_worker_id' => $translate->_('ticket.next_worker'),
  2386. );
  2387. }
  2388. /**
  2389. * Enter description here...
  2390. *
  2391. * @param string $mask
  2392. * @return integer
  2393. */
  2394. static function getTicketIdByMask($mask) {
  2395. $db = DevblocksPlatform::getDatabaseService();
  2396. $sql = sprintf("SELECT t.id FROM ticket t WHERE t.mask = %s",
  2397. $db->qstr($mask)
  2398. );
  2399. $ticket_id = $db->GetOne($sql); /* @var $rs ADORecordSet */
  2400. // If we found a hit on a ticket record, return the ID
  2401. if(!empty($ticket_id)) {
  2402. return $ticket_id;
  2403. // Check if this mask was previously forwarded elsewhere
  2404. } else {
  2405. $sql = sprintf("SELECT new_ticket_id FROM ticket_mask_forward WHERE old_mask = %s",
  2406. $db->qstr($mask)
  2407. );
  2408. $ticket_id = $db->GetOne($sql);
  2409. if(!empty($ticket_id))
  2410. return $ticket_id;
  2411. }
  2412. // No match
  2413. return null;
  2414. }
  2415. /**
  2416. * Enter description here...
  2417. *
  2418. * @param string $mask
  2419. * return CerberusTicket
  2420. */
  2421. static function getTicketByMask($mask) {
  2422. if(null != ($id = self::getTicketIdByMask($mask))) {
  2423. return self::getTicket($id);
  2424. }
  2425. return NULL;
  2426. }
  2427. static function getTicketByMessageId($message_id) {
  2428. $db = DevblocksPlatform::getDatabaseService();
  2429. $sql = sprintf("SELECT t.id AS ticket_id, mh.message_id AS message_id ".
  2430. "FROM message_header mh ".
  2431. "INNER JOIN message m ON (m.id=mh.message_id) ".
  2432. "INNER JOIN ticket t ON (t.id=m.ticket_id) ".
  2433. "WHERE mh.header_name = 'message-id' AND mh.header_value = %s",
  2434. $db->qstr($message_id)
  2435. );
  2436. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2437. if(!$rs->EOF) {
  2438. return array(
  2439. 'ticket_id' => intval($rs->fields['ticket_id']),
  2440. 'message_id' => intval($rs->fields['message_id'])
  2441. );
  2442. }
  2443. return null;
  2444. }
  2445. /**
  2446. * creates a new ticket object in the database
  2447. *
  2448. * @param array $fields
  2449. * @return integer
  2450. *
  2451. * [TODO]: Change $last_wrote argument to an ID rather than string?
  2452. */
  2453. static function createTicket($fields) {
  2454. $db = DevblocksPlatform::getDatabaseService();
  2455. $newId = $db->GenID('ticket_seq');
  2456. $sql = sprintf("INSERT INTO ticket (id, mask, subject, first_message_id, last_wrote_address_id, first_wrote_address_id, created_date, updated_date, due_date, unlock_date, team_id, category_id) ".
  2457. "VALUES (%d,'','',0,0,0,%d,%d,0,0,0,0)",
  2458. $newId,
  2459. time(),
  2460. time()
  2461. );
  2462. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2463. self::updateTicket($newId, $fields);
  2464. // send new ticket auto-response
  2465. // DAO_Mail::sendAutoresponse($id, 'new');
  2466. return $newId;
  2467. }
  2468. static function maint() {
  2469. $db = DevblocksPlatform::getDatabaseService();
  2470. $logger = DevblocksPlatform::getConsoleLog();
  2471. $sql = "DELETE QUICK ticket_mask_forward FROM ticket_mask_forward LEFT JOIN ticket ON ticket_mask_forward.new_ticket_id=ticket.id WHERE ticket.id IS NULL";
  2472. $db->Execute($sql);
  2473. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' ticket_mask_forward records.');
  2474. $sql = "DELETE QUICK ticket_comment FROM ticket_comment LEFT JOIN ticket ON ticket_comment.ticket_id=ticket.id WHERE ticket.id IS NULL";
  2475. $db->Execute($sql);
  2476. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' ticket_comment records.');
  2477. $sql = "DELETE QUICK requester FROM requester LEFT JOIN ticket ON requester.ticket_id = ticket.id WHERE ticket.id IS NULL";
  2478. $db->Execute($sql);
  2479. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' requester records.');
  2480. // Ticket tasks
  2481. $sql = "DELETE QUICK task FROM task LEFT JOIN ticket ON task.source_id = ticket.id WHERE task.source_extension = 'cerberusweb.tasks.ticket' AND ticket.id IS NULL";
  2482. $db->Execute($sql);
  2483. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' task records.');
  2484. // Recover any tickets assigned to next_worker_id = NULL
  2485. $sql = "UPDATE ticket LEFT JOIN worker ON ticket.next_worker_id = worker.id SET ticket.next_worker_id = 0 WHERE ticket.next_worker_id > 0 AND worker.id IS NULL";
  2486. $db->Execute($sql);
  2487. $logger->info('[Maint] Fixed ' . $db->Affected_Rows() . ' tickets assigned to missing workers.');
  2488. // Recover any tickets assigned to a NULL bucket
  2489. $sql = "UPDATE ticket LEFT JOIN category ON ticket.category_id = category.id SET ticket.category_id = 0 WHERE ticket.category_id > 0 AND category.id IS NULL";
  2490. $db->Execute($sql);
  2491. $logger->info('[Maint] Fixed ' . $db->Affected_Rows() . ' tickets in missing buckets.');
  2492. // ===========================================================================
  2493. // Ophaned ticket custom fields
  2494. $db->Execute("DELETE QUICK custom_field_stringvalue FROM custom_field_stringvalue LEFT JOIN ticket ON (ticket.id=custom_field_stringvalue.source_id) WHERE custom_field_stringvalue.source_extension = 'cerberusweb.fields.source.ticket' AND ticket.id IS NULL");
  2495. $db->Execute("DELETE QUICK custom_field_numbervalue FROM custom_field_numbervalue LEFT JOIN ticket ON (ticket.id=custom_field_numbervalue.source_id) WHERE custom_field_numbervalue.source_extension = 'cerberusweb.fields.source.ticket' AND ticket.id IS NULL");
  2496. $db->Execute("DELETE QUICK custom_field_clobvalue FROM custom_field_clobvalue LEFT JOIN ticket ON (ticket.id=custom_field_clobvalue.source_id) WHERE custom_field_clobvalue.source_extension = 'cerberusweb.fields.source.ticket' AND ticket.id IS NULL");
  2497. }
  2498. static function merge($ids=array()) {
  2499. if(!is_array($ids) || empty($ids) || count($ids) < 2) {
  2500. return false;
  2501. }
  2502. $db = DevblocksPlatform::getDatabaseService();
  2503. list($merged_tickets, $null) = self::search(
  2504. array(),
  2505. array(
  2506. new DevblocksSearchCriteria(SearchFields_Ticket::TICKET_ID,DevblocksSearchCriteria::OPER_IN,$ids),
  2507. ),
  2508. 50, // safety trigger
  2509. 0,
  2510. SearchFields_Ticket::TICKET_CREATED_DATE,
  2511. true,
  2512. false
  2513. );
  2514. // Merge the rest of the tickets into the oldest
  2515. if(is_array($merged_tickets)) {
  2516. list($oldest_id, $oldest_ticket) = each($merged_tickets);
  2517. unset($merged_tickets[$oldest_id]);
  2518. $merge_ticket_ids = array_keys($merged_tickets);
  2519. if(empty($oldest_id) || empty($merge_ticket_ids))
  2520. return null;
  2521. // Messages
  2522. $sql = sprintf("UPDATE message SET ticket_id = %d WHERE ticket_id IN (%s)",
  2523. $oldest_id,
  2524. implode(',', $merge_ticket_ids)
  2525. );
  2526. $db->Execute($sql);
  2527. // Requesters (merge)
  2528. $sql = sprintf("INSERT IGNORE INTO requester (address_id,ticket_id) ".
  2529. "SELECT address_id, %d FROM requester WHERE ticket_id IN (%s)",
  2530. $oldest_id,
  2531. implode(',', $merge_ticket_ids)
  2532. );
  2533. $db->Execute($sql);
  2534. $sql = sprintf("DELETE FROM requester WHERE ticket_id IN (%s)",
  2535. implode(',', $merge_ticket_ids)
  2536. );
  2537. // Tasks
  2538. $sql = sprintf("UPDATE task SET source_id = %d WHERE source_extension = %s AND source_id IN (%s)",
  2539. $oldest_id,
  2540. $db->qstr('cerberusweb.tasks.ticket'),
  2541. implode(',', $merge_ticket_ids)
  2542. );
  2543. $db->Execute($sql);
  2544. // Comments
  2545. $sql = sprintf("UPDATE ticket_comment SET ticket_id = %d WHERE ticket_id IN (%s)",
  2546. $oldest_id,
  2547. implode(',', $merge_ticket_ids)
  2548. );
  2549. $db->Execute($sql);
  2550. DAO_Ticket::updateTicket($merge_ticket_ids, array(
  2551. DAO_Ticket::IS_CLOSED => 1,
  2552. DAO_Ticket::IS_DELETED => 1,
  2553. ));
  2554. // Sort merge tickets by updated date ascending to find the latest touched
  2555. $tickets = $merged_tickets;
  2556. array_unshift($tickets, $oldest_ticket);
  2557. uasort($tickets, create_function('$a, $b', "return strcmp(\$a[SearchFields_Ticket::TICKET_UPDATED_DATE],\$b[SearchFields_Ticket::TICKET_UPDATED_DATE]);\n"));
  2558. $most_recent_updated_ticket = end($tickets);
  2559. // Set our destination ticket to the latest touched details
  2560. DAO_Ticket::updateTicket($oldest_id,array(
  2561. DAO_Ticket::LAST_ACTION_CODE => $most_recent_updated_ticket[SearchFields_Ticket::TICKET_LAST_ACTION_CODE],
  2562. DAO_Ticket::LAST_WROTE_ID => $most_recent_updated_ticket[SearchFields_Ticket::TICKET_LAST_WROTE_ID],
  2563. DAO_Ticket::LAST_WORKER_ID => $most_recent_updated_ticket[SearchFields_Ticket::TICKET_LAST_WORKER_ID],
  2564. DAO_Ticket::UPDATED_DATE => $most_recent_updated_ticket[SearchFields_Ticket::TICKET_UPDATED_DATE]
  2565. ));
  2566. // Set up forwarders for the old masks to their new mask
  2567. $new_mask = $oldest_ticket[SearchFields_Ticket::TICKET_MASK];
  2568. if(is_array($merged_tickets))
  2569. foreach($merged_tickets as $ticket) {
  2570. // Forward the old mask to the new mask
  2571. $sql = sprintf("INSERT IGNORE INTO ticket_mask_forward (old_mask, new_mask, new_ticket_id) VALUES (%s, %s, %d)",
  2572. $db->qstr($ticket[SearchFields_Ticket::TICKET_MASK]),
  2573. $db->qstr($new_mask),
  2574. $oldest_id
  2575. );
  2576. $db->Execute($sql);
  2577. // If the old mask was a new_mask in a past life, change to its new destination
  2578. $sql = sprintf("UPDATE ticket_mask_forward SET new_mask = %s, new_ticket_id = %d WHERE new_mask = %s",
  2579. $db->qstr($new_mask),
  2580. $oldest_id,
  2581. $db->qstr($ticket[SearchFields_Ticket::TICKET_MASK])
  2582. );
  2583. $db->Execute($sql);
  2584. }
  2585. /*
  2586. * Notify anything that wants to know when tickets merge.
  2587. */
  2588. $eventMgr = DevblocksPlatform::getEventService();
  2589. $eventMgr->trigger(
  2590. new Model_DevblocksEvent(
  2591. 'ticket.merge',
  2592. array(
  2593. 'new_ticket_id' => $oldest_id,
  2594. 'old_ticket_ids' => $merge_ticket_ids,
  2595. )
  2596. )
  2597. );
  2598. return $oldest_id;
  2599. }
  2600. }
  2601. /**
  2602. * Enter description here...
  2603. *
  2604. * @param integer $id
  2605. * @return CerberusTicket
  2606. */
  2607. static function getTicket($id) {
  2608. if(empty($id)) return NULL;
  2609. $tickets = self::getTickets(array($id));
  2610. if(isset($tickets[$id]))
  2611. return $tickets[$id];
  2612. return NULL;
  2613. }
  2614. /**
  2615. * Enter description here...
  2616. *
  2617. * @param array $ids
  2618. * @return CerberusTicket[]
  2619. */
  2620. static function getTickets($ids=array()) {
  2621. if(!is_array($ids)) $ids = array($ids);
  2622. $db = DevblocksPlatform::getDatabaseService();
  2623. $tickets = array();
  2624. if(empty($ids)) return array();
  2625. $sql = "SELECT t.id , t.mask, t.subject, t.is_waiting, t.is_closed, t.is_deleted, t.team_id, t.category_id, t.first_message_id, ".
  2626. "t.first_wrote_address_id, t.last_wrote_address_id, t.created_date, t.updated_date, t.due_date, t.unlock_date, t.spam_training, ".
  2627. "t.spam_score, t.interesting_words, t.last_worker_id, t.next_worker_id ".
  2628. "FROM ticket t ".
  2629. (!empty($ids) ? sprintf("WHERE t.id IN (%s) ",implode(',',$ids)) : " ").
  2630. "ORDER BY t.updated_date DESC"
  2631. ;
  2632. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2633. if(is_a($rs,'ADORecordSet'))
  2634. while(!$rs->EOF) {
  2635. $ticket = new CerberusTicket();
  2636. $ticket->id = intval($rs->fields['id']);
  2637. $ticket->mask = $rs->fields['mask'];
  2638. $ticket->subject = $rs->fields['subject'];
  2639. $ticket->first_message_id = intval($rs->fields['first_message_id']);
  2640. $ticket->team_id = intval($rs->fields['team_id']);
  2641. $ticket->category_id = intval($rs->fields['category_id']);
  2642. $ticket->is_waiting = intval($rs->fields['is_waiting']);
  2643. $ticket->is_closed = intval($rs->fields['is_closed']);
  2644. $ticket->is_deleted = intval($rs->fields['is_deleted']);
  2645. $ticket->last_wrote_address_id = intval($rs->fields['last_wrote_address_id']);
  2646. $ticket->first_wrote_address_id = intval($rs->fields['first_wrote_address_id']);
  2647. $ticket->created_date = intval($rs->fields['created_date']);
  2648. $ticket->updated_date = intval($rs->fields['updated_date']);
  2649. $ticket->due_date = intval($rs->fields['due_date']);
  2650. $ticket->unlock_date = intval($rs->fields['unlock_date']);
  2651. $ticket->spam_score = floatval($rs->fields['spam_score']);
  2652. $ticket->spam_training = $rs->fields['spam_training'];
  2653. $ticket->interesting_words = $rs->fields['interesting_words'];
  2654. $ticket->last_worker_id = intval($rs->fields['last_worker_id']);
  2655. $ticket->next_worker_id = intval($rs->fields['next_worker_id']);
  2656. $tickets[$ticket->id] = $ticket;
  2657. $rs->MoveNext();
  2658. }
  2659. return $tickets;
  2660. }
  2661. static function updateWhere($fields, $where) {
  2662. parent::_updateWhere('ticket', $fields, $where);
  2663. }
  2664. static function updateTicket($ids,$fields) {
  2665. if(!is_array($ids)) $ids = array($ids);
  2666. /* This event fires before the change takes place in the db,
  2667. * so we can denote what is actually changing against the db state
  2668. */
  2669. $eventMgr = DevblocksPlatform::getEventService();
  2670. $eventMgr->trigger(
  2671. new Model_DevblocksEvent(
  2672. 'ticket.property.pre_change',
  2673. array(
  2674. 'ticket_ids' => $ids,
  2675. 'changed_fields' => $fields,
  2676. )
  2677. )
  2678. );
  2679. parent::_update($ids,'ticket',$fields);
  2680. /* This event fires after the change takes place in the db,
  2681. * which is important if the listener needs to stack changes
  2682. */
  2683. $eventMgr = DevblocksPlatform::getEventService();
  2684. $eventMgr->trigger(
  2685. new Model_DevblocksEvent(
  2686. 'ticket.property.post_change',
  2687. array(
  2688. 'ticket_ids' => $ids,
  2689. 'changed_fields' => $fields,
  2690. )
  2691. )
  2692. );
  2693. }
  2694. /**
  2695. * @return CerberusMessage[]
  2696. */
  2697. static function getMessagesByTicket($ticket_id) {
  2698. $db = DevblocksPlatform::getDatabaseService();
  2699. $messages = array();
  2700. $sql = sprintf("SELECT m.id , m.ticket_id, m.created_date, m.address_id, m.is_outgoing, m.worker_id ".
  2701. "FROM message m ".
  2702. "WHERE m.ticket_id = %d ".
  2703. "ORDER BY m.created_date ASC ",
  2704. $ticket_id
  2705. );
  2706. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2707. if(is_a($rs,'ADORecordSet'))
  2708. while(!$rs->EOF) {
  2709. $message = new CerberusMessage();
  2710. $message->id = intval($rs->fields['id']);
  2711. $message->ticket_id = intval($rs->fields['ticket_id']);
  2712. $message->created_date = intval($rs->fields['created_date']);
  2713. $message->address_id = intval($rs->fields['address_id']);
  2714. $message->is_outgoing = intval($rs->fields['is_outgoing']);
  2715. $message->worker_id = intval($rs->fields['worker_id']);
  2716. $messages[$message->id] = $message;
  2717. $rs->MoveNext();
  2718. }
  2719. return $messages;
  2720. }
  2721. /**
  2722. * Enter description here...
  2723. *
  2724. * @param integer $id message id
  2725. * @return CerberusMessage
  2726. */
  2727. static function getMessage($id) {
  2728. $db = DevblocksPlatform::getDatabaseService();
  2729. $message = null;
  2730. $sql = sprintf("SELECT m.id , m.ticket_id, m.created_date, m.address_id, m.is_outgoing, m.worker_id ".
  2731. "FROM message m ".
  2732. "WHERE m.id = %d ".
  2733. "ORDER BY m.created_date ASC ",
  2734. $id
  2735. );
  2736. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2737. if(is_a($rs,'ADORecordSet') && !$rs->EOF) {
  2738. $message = new CerberusMessage();
  2739. $message->id = intval($rs->fields['id']);
  2740. $message->ticket_id = intval($rs->fields['ticket_id']);
  2741. $message->created_date = intval($rs->fields['created_date']);
  2742. $message->address_id = intval($rs->fields['address_id']);
  2743. $message->is_outgoing = intval($rs->fields['is_outgoing']);
  2744. $message->worker_id = intval($rs->fields['worker_id']);
  2745. }
  2746. return $message;
  2747. }
  2748. static function getRequestersByTicket($ticket_id) {
  2749. $db = DevblocksPlatform::getDatabaseService();
  2750. $addresses = array();
  2751. $sql = sprintf("SELECT a.id , a.email ".
  2752. "FROM address a ".
  2753. "INNER JOIN requester r ON (r.ticket_id = %d AND a.id=r.address_id) ".
  2754. "ORDER BY a.email ASC ",
  2755. $ticket_id
  2756. );
  2757. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2758. while(!$rs->EOF) {
  2759. $address = new Model_Address();
  2760. $address->id = intval($rs->fields['id']);
  2761. $address->email = $rs->fields['email'];
  2762. $addresses[$address->id] = $address;
  2763. $rs->MoveNext();
  2764. }
  2765. return $addresses;
  2766. }
  2767. static function isTicketRequester($email, $ticket_id) {
  2768. $db = DevblocksPlatform::getDatabaseService();
  2769. $sql = sprintf("SELECT a.id ".
  2770. "FROM address a ".
  2771. "INNER JOIN requester r ON (r.ticket_id = %d AND a.id=r.address_id) ".
  2772. "WHERE a.email = %s ".
  2773. "ORDER BY a.email ASC ",
  2774. $ticket_id,
  2775. $db->qstr($email)
  2776. );
  2777. $result = $db->GetOne($sql);
  2778. return !empty($result);
  2779. }
  2780. static function createRequester($address_id,$ticket_id) {
  2781. $db = DevblocksPlatform::getDatabaseService();
  2782. $db->Replace(
  2783. 'requester',
  2784. array("address_id"=>$address_id,"ticket_id"=>$ticket_id),
  2785. array('address_id','ticket_id')
  2786. );
  2787. return true;
  2788. }
  2789. static function deleteRequester($id, $address_id) {
  2790. if(empty($id) || empty($address_id))
  2791. return;
  2792. $db = DevblocksPlatform::getDatabaseService();
  2793. $sql = sprintf("DELETE QUICK FROM requester WHERE ticket_id = %d AND address_id = %d",
  2794. $id,
  2795. $address_id
  2796. );
  2797. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  2798. }
  2799. static function analyze($params, $limit=15, $mode="senders", $mode_param=null) { // or "subjects"
  2800. $db = DevblocksPlatform::getDatabaseService();
  2801. list($tables,$wheres) = parent::_parseSearchParams($params, array(),SearchFields_Ticket::getFields());
  2802. $tops = array();
  2803. if($mode=="senders") {
  2804. $senders = array();
  2805. // [JAS]: Most common sender domains in work pile
  2806. $sql = sprintf("SELECT ".
  2807. "count(*) as hits, substring(a1.email from position('@' in a1.email)) as domain ".
  2808. "FROM ticket t ".
  2809. "INNER JOIN team tm ON (tm.id = t.team_id) ".
  2810. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  2811. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) "
  2812. ).
  2813. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  2814. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  2815. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ").
  2816. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2817. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  2818. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  2819. "GROUP BY domain HAVING count(*) > 1 ".
  2820. "ORDER BY hits DESC ";
  2821. $rs_domains = $db->SelectLimit($sql, $limit, 0) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs_domains ADORecordSet */
  2822. $domains = array(); // [TODO] Temporary
  2823. while(!$rs_domains->EOF) {
  2824. $hash = md5('domain'.$rs_domains->fields['domain']);
  2825. $domains[] = $rs_domains->fields['domain']; // [TODO] Temporary
  2826. $tops[$hash] = array('domain',$rs_domains->fields['domain'],$rs_domains->fields['hits']);
  2827. $rs_domains->MoveNext();
  2828. }
  2829. // [TODO] Temporary
  2830. $sender_wheres = $wheres;
  2831. $sender_wheres[] = sprintf("substring(a1.email from position('@' in a1.email)) IN ('%s')",
  2832. implode("','", $domains)
  2833. );
  2834. // [JAS]: Most common senders in work pile
  2835. $sql = sprintf("SELECT ".
  2836. "count(*) as hits, a1.email ".
  2837. "FROM ticket t ".
  2838. "INNER JOIN team tm ON (tm.id = t.team_id) ".
  2839. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  2840. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) "
  2841. ).
  2842. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  2843. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  2844. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ").
  2845. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2846. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  2847. (!empty($sender_wheres) ? sprintf("WHERE %s ",implode(' AND ',$sender_wheres)) : "").
  2848. "GROUP BY a1.email HAVING count(*) > 1 ".
  2849. "ORDER BY hits DESC ";
  2850. $rs_senders = $db->SelectLimit($sql, $limit*2, 0) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs_senders ADORecordSet */
  2851. while(!$rs_senders->EOF) {
  2852. $hash = md5('sender'.$rs_senders->fields['email']);
  2853. $senders[$hash] = array('sender',$rs_senders->fields['email'],$rs_senders->fields['hits']);
  2854. $rs_senders->MoveNext();
  2855. }
  2856. uasort($senders, array('DAO_Ticket','sortByCount'));
  2857. // Thread senders into domains
  2858. foreach($senders as $hash => $sender) {
  2859. $domain = substr($sender[1],strpos($sender[1],'@'));
  2860. $domain_hash = md5('domain' . $domain);
  2861. if(!isset($tops[$domain_hash])) {
  2862. continue; // [TODO] Temporary
  2863. }
  2864. $tops[$domain_hash][3][$hash] = $sender;
  2865. }
  2866. } elseif ($mode=="subjects") {
  2867. $prefixes = array();
  2868. // [JAS]: Most common subjects in work pile
  2869. $sql = sprintf("SELECT ".
  2870. "count(*) as hits, substring(t.subject from 1 for 8) as prefix ".
  2871. "FROM ticket t ".
  2872. "INNER JOIN team tm ON (tm.id = t.team_id) ".
  2873. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  2874. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) "
  2875. ).
  2876. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  2877. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  2878. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ").
  2879. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2880. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  2881. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  2882. "GROUP BY substring(t.subject from 1 for 8) ".
  2883. "ORDER BY hits DESC ";
  2884. $rs_subjects = $db->SelectLimit($sql, $limit, 0) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs_domains ADORecordSet */
  2885. $prefixes = array(); // [TODO] Temporary
  2886. while(!$rs_subjects->EOF) {
  2887. $prefixes[] = $rs_subjects->fields['prefix'];
  2888. $rs_subjects->MoveNext();
  2889. }
  2890. foreach($prefixes as $prefix_idx => $prefix) {
  2891. $prefix_wheres = $wheres;
  2892. $prefix_wheres[] = sprintf("substring(t.subject from 1 for 8) = %s",
  2893. $db->qstr($prefix)
  2894. );
  2895. // [JAS]: Most common subjects in work pile
  2896. $sql = sprintf("SELECT ".
  2897. "t.subject ".
  2898. "FROM ticket t ".
  2899. "INNER JOIN team tm ON (tm.id = t.team_id) ".
  2900. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  2901. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) "
  2902. ).
  2903. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  2904. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  2905. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ").
  2906. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2907. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  2908. (!empty($prefix_wheres) ? sprintf("WHERE %s ",implode(' AND ',$prefix_wheres)) : "").
  2909. "GROUP BY t.id, t.subject ";
  2910. // [TODO] $limit here is completely arbitrary
  2911. $rs_full_subjects = $db->SelectLimit($sql, 2500, 0) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs_senders ADORecordSet */
  2912. $lines = array();
  2913. $subjects = array();
  2914. $patterns = array();
  2915. $subpatterns = array();
  2916. while(!$rs_full_subjects->EOF) {
  2917. $lines[] = $rs_full_subjects->fields['subject'];
  2918. $rs_full_subjects->MoveNext();
  2919. }
  2920. $patterns = self::findPatterns($lines, 8);
  2921. if(!empty($patterns)) {
  2922. @$pattern = array_shift($patterns);
  2923. $tophash = md5('subject'.$pattern.'*');
  2924. $tops[$tophash] = array('subject',$pattern.'*',$rs_full_subjects->RecordCount());
  2925. if(!empty($patterns)) // thread subpatterns
  2926. foreach($patterns as $hits => $pattern) {
  2927. $hash = md5('subject'.$pattern.'*');
  2928. $tops[$tophash][3][$hash] = array('subject',$pattern.'*',0);
  2929. }
  2930. }
  2931. @$rs_full_subjects->free();
  2932. unset($lines);
  2933. }
  2934. } elseif ($mode=="headers") {
  2935. $tables['mh'] = 'mh';
  2936. $wheres[] = sprintf("mh.header_name=%s",$db->qstr($mode_param));
  2937. $sql = sprintf("SELECT ".
  2938. "count(t.id) as hits, mh.header_value ".
  2939. "FROM ticket t ".
  2940. "INNER JOIN team tm ON (tm.id = t.team_id) ".
  2941. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  2942. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) "
  2943. ).
  2944. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  2945. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  2946. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ").
  2947. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  2948. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  2949. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  2950. "GROUP BY mh.header_value HAVING mh.header_value <> '' ".
  2951. "ORDER BY hits DESC ";
  2952. $rs_imports = $db->SelectLimit($sql, 25, 0) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs_subjects ADORecordSet */
  2953. while(!$rs_imports->EOF) {
  2954. $hash = md5('header'.$rs_imports->fields['header_value']);
  2955. $tops[$hash] = array('header',$rs_imports->fields['header_value'],$rs_imports->fields['hits'],array(),$mode_param);
  2956. $rs_imports->MoveNext();
  2957. }
  2958. }
  2959. uasort($tops, array('DAO_Ticket','sortByCount'));
  2960. return $tops;
  2961. }
  2962. private function sortByCount($a,$b) {
  2963. if ($a[2] == $b[2]) {
  2964. return 0;
  2965. }
  2966. return ($a[2] > $b[2]) ? -1 : 1;
  2967. }
  2968. private function findPatterns($list, $min_chars=8) {
  2969. $patterns = array();
  2970. $simil = array();
  2971. $simil_hash = array();
  2972. $MAX_PASS = 15;
  2973. $MAX_HITS = 5;
  2974. // Remove dupes (not sure this makes much diff)
  2975. // array_unique($list);
  2976. // Sort by longest subjects
  2977. usort($list,array('DAO_Ticket','sortByLen'));
  2978. $len = count($list);
  2979. for($x=0;$x<$MAX_PASS;$x++) {
  2980. for($y=0;$y<$len;$y++) {
  2981. if($x==$y) continue; // skip ourselves
  2982. if(!isset($list[$x]) || !isset($list[$y])) break;
  2983. if(0 != ($max = self::str_similar_prefix($list[$x],$list[$y])) && $max >= $min_chars) {
  2984. @$simil[$max] = intval($simil[$max]) + 1;
  2985. @$simil_hash[$max] = trim(substr($list[$x],0,$max));
  2986. }
  2987. }
  2988. }
  2989. // Results from optimial # of chars similar from left
  2990. arsort($simil);
  2991. $max = current($simil);
  2992. $hits = 0;
  2993. foreach($simil as $k=>$v) {
  2994. if($hits>$MAX_HITS)
  2995. continue;
  2996. $patterns[$v] = $simil_hash[$k];
  2997. $hits++;
  2998. }
  2999. return $patterns;
  3000. }
  3001. // Sort by strlen (longest to shortest)
  3002. private function sortByLen($a,$b) {
  3003. $asize = strlen($a);
  3004. $bsize = strlen($b);
  3005. if($asize==$bsize) return 0;
  3006. return ($asize>$bsize)?-1:1;
  3007. }
  3008. private function str_similar_prefix($str1,$str2) {
  3009. $pos = 0;
  3010. $str1 = trim($str1);
  3011. $str2 = trim($str2);
  3012. while((isset($str1[$pos]) && isset($str2[$pos])) && $str1[$pos]==$str2[$pos]) {
  3013. $pos++;
  3014. }
  3015. return $pos;
  3016. }
  3017. static function search($columns, $params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  3018. $db = DevblocksPlatform::getDatabaseService();
  3019. $fields = SearchFields_Ticket::getFields();
  3020. $total = -1;
  3021. // Sanitize
  3022. if(!isset($fields[$sortBy])) {
  3023. $sortBy=null;
  3024. }
  3025. list($tables,$wheres) = parent::_parseSearchParams($params, $columns, $fields, $sortBy);
  3026. $start = ($page * $limit); // [JAS]: 1-based
  3027. $select_sql = sprintf("SELECT ".
  3028. "t.id as %s, ".
  3029. "t.mask as %s, ".
  3030. "t.subject as %s, ".
  3031. "t.is_waiting as %s, ".
  3032. "t.is_closed as %s, ".
  3033. "t.is_deleted as %s, ".
  3034. "t.first_wrote_address_id as %s, ".
  3035. "t.last_wrote_address_id as %s, ".
  3036. "t.first_message_id as %s, ".
  3037. "a1.email as %s, ".
  3038. "a1.num_spam as %s, ".
  3039. "a1.num_nonspam as %s, ".
  3040. "a2.email as %s, ".
  3041. "a1.contact_org_id as %s, ".
  3042. "t.created_date as %s, ".
  3043. "t.updated_date as %s, ".
  3044. "t.due_date as %s, ".
  3045. "t.spam_training as %s, ".
  3046. "t.spam_score as %s, ".
  3047. // "t.interesting_words as %s, ".
  3048. "t.last_action_code as %s, ".
  3049. "t.last_worker_id as %s, ".
  3050. "t.next_worker_id as %s, ".
  3051. "t.team_id as %s, ".
  3052. "t.category_id as %s ",
  3053. SearchFields_Ticket::TICKET_ID,
  3054. SearchFields_Ticket::TICKET_MASK,
  3055. SearchFields_Ticket::TICKET_SUBJECT,
  3056. SearchFields_Ticket::TICKET_WAITING,
  3057. SearchFields_Ticket::TICKET_CLOSED,
  3058. SearchFields_Ticket::TICKET_DELETED,
  3059. SearchFields_Ticket::TICKET_FIRST_WROTE_ID,
  3060. SearchFields_Ticket::TICKET_LAST_WROTE_ID,
  3061. SearchFields_Ticket::TICKET_FIRST_MESSAGE_ID,
  3062. SearchFields_Ticket::TICKET_FIRST_WROTE,
  3063. SearchFields_Ticket::TICKET_FIRST_WROTE_SPAM,
  3064. SearchFields_Ticket::TICKET_FIRST_WROTE_NONSPAM,
  3065. SearchFields_Ticket::TICKET_LAST_WROTE,
  3066. SearchFields_Ticket::TICKET_FIRST_CONTACT_ORG_ID,
  3067. SearchFields_Ticket::TICKET_CREATED_DATE,
  3068. SearchFields_Ticket::TICKET_UPDATED_DATE,
  3069. SearchFields_Ticket::TICKET_DUE_DATE,
  3070. SearchFields_Ticket::TICKET_SPAM_TRAINING,
  3071. SearchFields_Ticket::TICKET_SPAM_SCORE,
  3072. // SearchFields_Ticket::TICKET_INTERESTING_WORDS,
  3073. SearchFields_Ticket::TICKET_LAST_ACTION_CODE,
  3074. SearchFields_Ticket::TICKET_LAST_WORKER_ID,
  3075. SearchFields_Ticket::TICKET_NEXT_WORKER_ID,
  3076. SearchFields_Ticket::TICKET_TEAM_ID,
  3077. SearchFields_Ticket::TICKET_CATEGORY_ID
  3078. );
  3079. $join_sql =
  3080. "FROM ticket t ".
  3081. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  3082. "INNER JOIN address a1 ON (t.first_wrote_address_id=a1.id) ".
  3083. "INNER JOIN address a2 ON (t.last_wrote_address_id=a2.id) ".
  3084. // [JAS]: Dynamic table joins
  3085. (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id) " : " ").
  3086. (isset($tables['ra']) ? "INNER JOIN address ra ON (ra.id=r.address_id) " : " ").
  3087. (isset($tables['msg']) || isset($tables['mc']) ? "INNER JOIN message msg ON (msg.ticket_id=t.id) " : " ").
  3088. (isset($tables['mh']) ? "INNER JOIN message_header mh ON (mh.message_id=t.first_message_id) " : " "). // [TODO] Choose between first message and all?
  3089. (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=msg.id) " : " ")
  3090. ;
  3091. // Org joins
  3092. if(isset($tables['o'])) {
  3093. $select_sql .= ", o.name as o_name ";
  3094. $join_sql .= "LEFT JOIN contact_org o ON (a1.contact_org_id=o.id) ";
  3095. }
  3096. // Custom field joins
  3097. list($select_sql, $join_sql, $has_multiple_values) = self::_appendSelectJoinSqlForCustomFieldTables(
  3098. $tables,
  3099. $params,
  3100. 't.id',
  3101. $select_sql,
  3102. $join_sql
  3103. );
  3104. $where_sql = "".
  3105. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  3106. $sort_sql = (!empty($sortBy) ? sprintf("ORDER BY %s %s ",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : " ");
  3107. $sql =
  3108. $select_sql.
  3109. $join_sql.
  3110. $where_sql.
  3111. ($has_multiple_values ? 'GROUP BY t.id ' : '').
  3112. $sort_sql;
  3113. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3114. $results = array();
  3115. if(is_a($rs,'ADORecordSet'))
  3116. while(!$rs->EOF) {
  3117. $result = array();
  3118. foreach($rs->fields as $f => $v) {
  3119. $result[$f] = $v;
  3120. }
  3121. $ticket_id = intval($rs->fields[SearchFields_Ticket::TICKET_ID]);
  3122. $results[$ticket_id] = $result;
  3123. $rs->MoveNext();
  3124. }
  3125. // [JAS]: Count all
  3126. if($withCounts) {
  3127. $count_sql =
  3128. "SELECT COUNT(DISTINCT t.id) ".
  3129. $join_sql.
  3130. $where_sql;
  3131. $total = $db->GetOne($count_sql);
  3132. }
  3133. return array($results,$total);
  3134. }
  3135. };
  3136. class SearchFields_Ticket implements IDevblocksSearchFields {
  3137. // Ticket
  3138. const TICKET_ID = 't_id';
  3139. const TICKET_MASK = 't_mask';
  3140. const TICKET_WAITING = 't_is_waiting';
  3141. const TICKET_CLOSED = 't_is_closed';
  3142. const TICKET_DELETED = 't_is_deleted';
  3143. const TICKET_SUBJECT = 't_subject';
  3144. const TICKET_FIRST_MESSAGE_ID = 't_first_message_id';
  3145. const TICKET_FIRST_WROTE_ID = 't_first_wrote_address_id';
  3146. const TICKET_FIRST_WROTE = 't_first_wrote';
  3147. const TICKET_FIRST_WROTE_SPAM = 't_first_wrote_spam';
  3148. const TICKET_FIRST_WROTE_NONSPAM = 't_first_wrote_nonspam';
  3149. const TICKET_FIRST_CONTACT_ORG_ID = 't_first_contact_org_id';
  3150. const TICKET_LAST_WROTE_ID = 't_last_wrote_address_id';
  3151. const TICKET_LAST_WROTE = 't_last_wrote';
  3152. const TICKET_CREATED_DATE = 't_created_date';
  3153. const TICKET_UPDATED_DATE = 't_updated_date';
  3154. const TICKET_DUE_DATE = 't_due_date';
  3155. const TICKET_UNLOCK_DATE = 't_unlock_date';
  3156. const TICKET_SPAM_SCORE = 't_spam_score';
  3157. const TICKET_SPAM_TRAINING = 't_spam_training';
  3158. const TICKET_INTERESTING_WORDS = 't_interesting_words';
  3159. const TICKET_LAST_ACTION_CODE = 't_last_action_code';
  3160. const TICKET_LAST_WORKER_ID = 't_last_worker_id';
  3161. const TICKET_NEXT_WORKER_ID = 't_next_worker_id';
  3162. const TICKET_TEAM_ID = 't_team_id';
  3163. const TICKET_CATEGORY_ID = 't_category_id';
  3164. // Message
  3165. // const MESSAGE_CONTENT = 'msg_content';
  3166. const TICKET_MESSAGE_HEADER = 'mh_header_name';
  3167. const TICKET_MESSAGE_HEADER_VALUE = 'mh_header_value';
  3168. const TICKET_MESSAGE_CONTENT = 'mc_content';
  3169. // Sender
  3170. const SENDER_ADDRESS = 'a1_address';
  3171. // Requester
  3172. const REQUESTER_ID = 'ra_id';
  3173. const REQUESTER_ADDRESS = 'ra_email';
  3174. // Sender Org
  3175. const ORG_NAME = 'o_name';
  3176. /**
  3177. * @return DevblocksSearchField[]
  3178. */
  3179. static function getFields() {
  3180. $translate = DevblocksPlatform::getTranslationService();
  3181. $columns = array(
  3182. self::TICKET_ID => new DevblocksSearchField(self::TICKET_ID, 't', 'id', null, $translate->_('ticket.id')),
  3183. self::TICKET_MASK => new DevblocksSearchField(self::TICKET_MASK, 't', 'mask', null, $translate->_('ticket.mask')),
  3184. self::TICKET_SUBJECT => new DevblocksSearchField(self::TICKET_SUBJECT, 't', 'subject',null,$translate->_('ticket.subject')),
  3185. self::TICKET_FIRST_MESSAGE_ID => new DevblocksSearchField(self::TICKET_FIRST_MESSAGE_ID, 't', 'first_message_id'),
  3186. self::TICKET_FIRST_WROTE_ID => new DevblocksSearchField(self::TICKET_FIRST_WROTE_ID, 't', 'first_wrote_address_id'),
  3187. self::TICKET_FIRST_WROTE => new DevblocksSearchField(self::TICKET_FIRST_WROTE, 'a1', 'email',null,$translate->_('ticket.first_wrote')),
  3188. self::TICKET_LAST_WROTE_ID => new DevblocksSearchField(self::TICKET_LAST_WROTE_ID, 't', 'last_wrote_address_id'),
  3189. self::TICKET_LAST_WROTE => new DevblocksSearchField(self::TICKET_LAST_WROTE, 'a2', 'email',null,$translate->_('ticket.last_wrote')),
  3190. self::ORG_NAME => new DevblocksSearchField(self::ORG_NAME, 'o', 'name', null, $translate->_('contact_org.name')),
  3191. self::REQUESTER_ADDRESS => new DevblocksSearchField(self::REQUESTER_ADDRESS, 'ra', 'email',null,$translate->_('ticket.requester')),
  3192. self::TICKET_MESSAGE_CONTENT => new DevblocksSearchField(self::TICKET_MESSAGE_CONTENT, 'mc', 'content', 'B', $translate->_('message.content')),
  3193. self::TICKET_TEAM_ID => new DevblocksSearchField(self::TICKET_TEAM_ID,'t','team_id',null,$translate->_('common.group')),
  3194. self::TICKET_CATEGORY_ID => new DevblocksSearchField(self::TICKET_CATEGORY_ID, 't', 'category_id',null,$translate->_('common.bucket')),
  3195. self::TICKET_CREATED_DATE => new DevblocksSearchField(self::TICKET_CREATED_DATE, 't', 'created_date',null,$translate->_('ticket.created')),
  3196. self::TICKET_UPDATED_DATE => new DevblocksSearchField(self::TICKET_UPDATED_DATE, 't', 'updated_date',null,$translate->_('ticket.updated')),
  3197. self::TICKET_WAITING => new DevblocksSearchField(self::TICKET_WAITING, 't', 'is_waiting',null,$translate->_('status.waiting')),
  3198. self::TICKET_CLOSED => new DevblocksSearchField(self::TICKET_CLOSED, 't', 'is_closed',null,$translate->_('status.closed')),
  3199. self::TICKET_DELETED => new DevblocksSearchField(self::TICKET_DELETED, 't', 'is_deleted',null,$translate->_('status.deleted')),
  3200. self::TICKET_LAST_ACTION_CODE => new DevblocksSearchField(self::TICKET_LAST_ACTION_CODE, 't', 'last_action_code',null,$translate->_('ticket.last_action')),
  3201. self::TICKET_LAST_WORKER_ID => new DevblocksSearchField(self::TICKET_LAST_WORKER_ID, 't', 'last_worker_id',null,$translate->_('ticket.last_worker')),
  3202. self::TICKET_NEXT_WORKER_ID => new DevblocksSearchField(self::TICKET_NEXT_WORKER_ID, 't', 'next_worker_id',null,$translate->_('ticket.next_worker')),
  3203. self::TICKET_SPAM_TRAINING => new DevblocksSearchField(self::TICKET_SPAM_TRAINING, 't', 'spam_training',null,$translate->_('ticket.spam_training')),
  3204. self::TICKET_SPAM_SCORE => new DevblocksSearchField(self::TICKET_SPAM_SCORE, 't', 'spam_score',null,$translate->_('ticket.spam_score')),
  3205. self::TICKET_FIRST_WROTE_SPAM => new DevblocksSearchField(self::TICKET_FIRST_WROTE_SPAM, 'a1', 'num_spam',null,$translate->_('address.num_spam')),
  3206. self::TICKET_FIRST_WROTE_NONSPAM => new DevblocksSearchField(self::TICKET_FIRST_WROTE_NONSPAM, 'a1', 'num_nonspam',null,$translate->_('address.num_nonspam')),
  3207. self::TICKET_INTERESTING_WORDS => new DevblocksSearchField(self::TICKET_INTERESTING_WORDS, 't', 'interesting_words',null,$translate->_('ticket.interesting_words')),
  3208. self::TICKET_DUE_DATE => new DevblocksSearchField(self::TICKET_DUE_DATE, 't', 'due_date',null,$translate->_('ticket.due')),
  3209. self::TICKET_UNLOCK_DATE => new DevblocksSearchField(self::TICKET_UNLOCK_DATE, 't', 'unlock_date', null, $translate->_('ticket.unlock_date')),
  3210. self::TICKET_FIRST_CONTACT_ORG_ID => new DevblocksSearchField(self::TICKET_FIRST_CONTACT_ORG_ID, 'a1', 'contact_org_id'),
  3211. self::REQUESTER_ID => new DevblocksSearchField(self::REQUESTER_ID, 'ra', 'id'),
  3212. self::SENDER_ADDRESS => new DevblocksSearchField(self::SENDER_ADDRESS, 'a1', 'email'),
  3213. self::TICKET_MESSAGE_HEADER => new DevblocksSearchField(self::TICKET_MESSAGE_HEADER, 'mh', 'header_name'),
  3214. self::TICKET_MESSAGE_HEADER_VALUE => new DevblocksSearchField(self::TICKET_MESSAGE_HEADER_VALUE, 'mh', 'header_value', 'B'),
  3215. );
  3216. // Custom Fields
  3217. $fields = DAO_CustomField::getBySource(ChCustomFieldSource_Ticket::ID);
  3218. if(is_array($fields))
  3219. foreach($fields as $field_id => $field) {
  3220. $key = 'cf_'.$field_id;
  3221. $columns[$key] = new DevblocksSearchField($key,$key,'field_value',null,$field->name);
  3222. }
  3223. // Sort by label (translation-conscious)
  3224. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  3225. return $columns;
  3226. }
  3227. };
  3228. class DAO_ViewRss extends DevblocksORMHelper {
  3229. const ID = 'id';
  3230. const TITLE = 'title';
  3231. const HASH = 'hash';
  3232. const WORKER_ID = 'worker_id';
  3233. const CREATED = 'created';
  3234. const SOURCE_EXTENSION = 'source_extension';
  3235. const PARAMS = 'params';
  3236. static function create($fields) {
  3237. $db = DevblocksPlatform::getDatabaseService();
  3238. $newId = $db->GenID('generic_seq');
  3239. $sql = sprintf("INSERT INTO view_rss (id,hash,title,worker_id,created,source_extension,params) ".
  3240. "VALUES (%d,'','',0,0,'','')",
  3241. $newId
  3242. );
  3243. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3244. self::update($newId, $fields);
  3245. }
  3246. /**
  3247. * Enter description here...
  3248. *
  3249. * @param array $ids
  3250. * @return Model_ViewRss[]
  3251. */
  3252. static function getList($ids) {
  3253. if(!is_array($ids)) $ids = array($ids);
  3254. $db = DevblocksPlatform::getDatabaseService();
  3255. $sql = "SELECT id,hash,title,worker_id,created,source_extension,params ".
  3256. "FROM view_rss ".
  3257. (!empty($ids) ? sprintf("WHERE id IN (%s)",implode(',',$ids)) : " ").
  3258. "";
  3259. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3260. return self::_getObjectsFromResults($rs);
  3261. }
  3262. /**
  3263. * Enter description here...
  3264. *
  3265. * @param string $hash
  3266. * @return Model_ViewRss
  3267. */
  3268. static function getByHash($hash) {
  3269. if(empty($hash)) return array();
  3270. $db = DevblocksPlatform::getDatabaseService();
  3271. $sql = sprintf("SELECT id,hash,title,worker_id,created,source_extension,params ".
  3272. "FROM view_rss ".
  3273. "WHERE hash = %s",
  3274. $db->qstr($hash)
  3275. );
  3276. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3277. $objects = self::_getObjectsFromResults($rs);
  3278. if(empty($objects))
  3279. return null;
  3280. return array_shift($objects);
  3281. }
  3282. /**
  3283. * Enter description here...
  3284. *
  3285. * @param integer $worker_id
  3286. * @return Model_ViewRss[]
  3287. */
  3288. static function getByWorker($worker_id) {
  3289. if(empty($worker_id)) return array();
  3290. $db = DevblocksPlatform::getDatabaseService();
  3291. $sql = sprintf("SELECT id,hash,title,worker_id,created,source_extension,params ".
  3292. "FROM view_rss ".
  3293. "WHERE worker_id = %d",
  3294. $worker_id
  3295. );
  3296. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3297. $objects = self::_getObjectsFromResults($rs);
  3298. return $objects;
  3299. }
  3300. /**
  3301. * Enter description here...
  3302. *
  3303. * @param ADORecordSet $rs
  3304. * @return Model_ViewRss[]
  3305. */
  3306. private static function _getObjectsFromResults($rs) { /* @var $rs ADORecordSet */
  3307. $objects = array();
  3308. if(is_a($rs,'ADORecordSet'))
  3309. while(!$rs->EOF) {
  3310. $object = new Model_ViewRss();
  3311. $object->id = intval($rs->fields['id']);
  3312. $object->title = $rs->fields['title'];
  3313. $object->hash = $rs->fields['hash'];
  3314. $object->worker_id = intval($rs->fields['worker_id']);
  3315. $object->created = intval($rs->fields['created']);
  3316. $object->source_extension = $rs->fields['source_extension'];
  3317. $params = $rs->fields['params'];
  3318. if(!empty($params))
  3319. @$object->params = unserialize($params);
  3320. $objects[$object->id] = $object;
  3321. $rs->MoveNext();
  3322. }
  3323. return $objects;
  3324. }
  3325. /**
  3326. * Enter description here...
  3327. *
  3328. * @param integer $id
  3329. * @return Model_ViewRss
  3330. */
  3331. static function getId($id) {
  3332. if(empty($id)) return null;
  3333. $feeds = self::getList($id);
  3334. if(isset($feeds[$id]))
  3335. return $feeds[$id];
  3336. return null;
  3337. }
  3338. static function update($ids, $fields) {
  3339. if(!is_array($ids)) $ids = array($ids);
  3340. $db = DevblocksPlatform::getDatabaseService();
  3341. // [JAS]: Handle our blobs specially
  3342. if(isset($fields[self::PARAMS])) {
  3343. $db->UpdateBlob(
  3344. 'view_rss',
  3345. self::PARAMS,
  3346. $fields[self::PARAMS],
  3347. sprintf('id IN (%s)',implode(',',$ids))
  3348. );
  3349. unset($fields[self::PARAMS]);
  3350. }
  3351. parent::_update($ids, 'view_rss', $fields);
  3352. }
  3353. static function delete($id) {
  3354. if(empty($id))
  3355. return;
  3356. $db = DevblocksPlatform::getDatabaseService();
  3357. $sql = sprintf("DELETE QUICK FROM view_rss WHERE id = %d",
  3358. $id
  3359. );
  3360. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3361. }
  3362. };
  3363. /**
  3364. * Enter description here...
  3365. *
  3366. * @addtogroup dao
  3367. */
  3368. class DAO_Group {
  3369. const CACHE_ALL = 'cerberus_cache_teams_all';
  3370. const CACHE_ROSTERS = 'ch_group_rosters';
  3371. const TEAM_ID = 'id';
  3372. const TEAM_NAME = 'name';
  3373. const TEAM_SIGNATURE = 'signature';
  3374. const IS_DEFAULT = 'is_default';
  3375. // Teams
  3376. /**
  3377. * Enter description here...
  3378. *
  3379. * @param integer $id
  3380. * @return CerberusTeam
  3381. */
  3382. static function getTeam($id) {
  3383. $teams = DAO_Group::getTeams(array($id));
  3384. if(isset($teams[$id]))
  3385. return $teams[$id];
  3386. return null;
  3387. }
  3388. /**
  3389. * Enter description here...
  3390. *
  3391. * @param array $ids
  3392. * @return CerberusTeam[]
  3393. */
  3394. static function getTeams($ids=array()) {
  3395. if(!is_array($ids)) $ids = array($ids);
  3396. $db = DevblocksPlatform::getDatabaseService();
  3397. $teams = array();
  3398. $sql = sprintf("SELECT t.id , t.name, t.signature, t.is_default ".
  3399. "FROM team t ".
  3400. ((is_array($ids) && !empty($ids)) ? sprintf("WHERE t.id IN (%s) ",implode(',',$ids)) : " ").
  3401. "ORDER BY t.name ASC"
  3402. );
  3403. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3404. if(is_a($rs,'ADORecordSet'))
  3405. while(!$rs->EOF) {
  3406. $team = new CerberusTeam();
  3407. $team->id = intval($rs->fields['id']);
  3408. $team->name = $rs->fields['name'];
  3409. $team->signature = $rs->fields['signature'];
  3410. $team->is_default = intval($rs->fields['is_default']);
  3411. $teams[$team->id] = $team;
  3412. $rs->MoveNext();
  3413. }
  3414. return $teams;
  3415. }
  3416. static function getAll($nocache=false) {
  3417. $cache = DevblocksPlatform::getCacheService();
  3418. if($nocache || null === ($teams = $cache->load(self::CACHE_ALL))) {
  3419. $teams = self::getTeams();
  3420. $cache->save($teams, self::CACHE_ALL);
  3421. }
  3422. return $teams;
  3423. }
  3424. /**
  3425. *
  3426. * @return Model_Team|null
  3427. */
  3428. static function getDefaultGroup() {
  3429. $groups = self::getAll();
  3430. if(is_array($groups))
  3431. foreach($groups as $group) { /* @var $group CerberusTeam */
  3432. if($group->is_default)
  3433. return $group;
  3434. }
  3435. return null;
  3436. }
  3437. static function setDefaultGroup($group_id) {
  3438. $db = DevblocksPlatform::getDatabaseService();
  3439. $db->Execute("UPDATE team SET is_default = 0");
  3440. $db->Execute(sprintf("UPDATE team SET is_default = 1 WHERE id = %d", $group_id));
  3441. self::clearCache();
  3442. }
  3443. /**
  3444. * Returns an array of team ticket and task counts, indexed by team id.
  3445. *
  3446. * @param array $ids Team IDs to summarize
  3447. * @return array
  3448. */
  3449. static function getTeamCounts($ids=array(),$with_tickets=true) { // ,$with_tasks=true,$with_unassigned=false
  3450. if(!is_array($ids)) $ids = array($ids);
  3451. $db = DevblocksPlatform::getDatabaseService();
  3452. $team_totals = array('0' => array('tickets'=>0));
  3453. if($with_tickets) {
  3454. $sql = "SELECT count(*) as hits, t.team_id ".
  3455. "FROM ticket t ".
  3456. "WHERE t.category_id = 0 ".
  3457. "AND t.is_closed = 0 ".
  3458. (!empty($ids) ? sprintf("AND t.team_id IN (%s) ", implode(',', $ids)) : " ").
  3459. "GROUP BY t.team_id "
  3460. ;
  3461. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3462. if(is_a($rs,'ADORecordSet'))
  3463. while(!$rs->EOF) {
  3464. $team_id = intval($rs->fields['team_id']);
  3465. $hits = intval($rs->fields['hits']);
  3466. if(!isset($team_totals[$team_id])) {
  3467. $team_totals[$team_id] = array('tickets'=>0);
  3468. }
  3469. $team_totals[$team_id]['tickets'] = $hits;
  3470. $team_totals[0]['tickets'] += $hits;
  3471. $rs->MoveNext();
  3472. }
  3473. }
  3474. return $team_totals;
  3475. }
  3476. /**
  3477. * Enter description here...
  3478. *
  3479. * @param string $name
  3480. * @return integer
  3481. */
  3482. static function createTeam($fields) {
  3483. $db = DevblocksPlatform::getDatabaseService();
  3484. $newId = $db->GenID('generic_seq');
  3485. $sql = sprintf("INSERT INTO team (id, name, signature, is_default) ".
  3486. "VALUES (%d,'','',0)",
  3487. $newId
  3488. );
  3489. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3490. self::updateTeam($newId, $fields);
  3491. self::clearCache();
  3492. return $newId;
  3493. }
  3494. /**
  3495. * Enter description here...
  3496. *
  3497. * @param integer $id
  3498. * @param array $fields
  3499. */
  3500. static function updateTeam($id, $fields) {
  3501. $db = DevblocksPlatform::getDatabaseService();
  3502. $sets = array();
  3503. if(!is_array($fields) || empty($fields) || empty($id))
  3504. return;
  3505. foreach($fields as $k => $v) {
  3506. $sets[] = sprintf("%s = %s",
  3507. $k,
  3508. $db->qstr($v)
  3509. );
  3510. }
  3511. $sql = sprintf("UPDATE team SET %s WHERE id = %d",
  3512. implode(', ', $sets),
  3513. $id
  3514. );
  3515. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3516. self::clearCache();
  3517. }
  3518. /**
  3519. * Enter description here...
  3520. *
  3521. * @param integer $id
  3522. */
  3523. static function deleteTeam($id) {
  3524. if(empty($id)) return;
  3525. $db = DevblocksPlatform::getDatabaseService();
  3526. /*
  3527. * Notify anything that wants to know when groups delete.
  3528. */
  3529. $eventMgr = DevblocksPlatform::getEventService();
  3530. $eventMgr->trigger(
  3531. new Model_DevblocksEvent(
  3532. 'group.delete',
  3533. array(
  3534. 'group_ids' => array($id),
  3535. )
  3536. )
  3537. );
  3538. $sql = sprintf("DELETE QUICK FROM team WHERE id = %d", $id);
  3539. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3540. $sql = sprintf("DELETE QUICK FROM category WHERE team_id = %d", $id);
  3541. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3542. // [TODO] DAO_GroupSettings::deleteById();
  3543. $sql = sprintf("DELETE QUICK FROM group_setting WHERE group_id = %d", $id);
  3544. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3545. $sql = sprintf("DELETE QUICK FROM worker_to_team WHERE team_id = %d", $id);
  3546. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3547. $sql = sprintf("DELETE QUICK FROM group_inbox_filter WHERE group_id = %d", $id);
  3548. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3549. // DAO_GroupInboxFilter::deleteByMoveCodes(array('t'.$id));
  3550. self::clearCache();
  3551. DAO_Bucket::clearCache();
  3552. }
  3553. static function maint() {
  3554. $db = DevblocksPlatform::getDatabaseService();
  3555. $logger = DevblocksPlatform::getConsoleLog();
  3556. $sql = "DELETE QUICK category FROM category LEFT JOIN team ON category.team_id=team.id WHERE team.id IS NULL";
  3557. $db->Execute($sql);
  3558. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' category records.');
  3559. $sql = "DELETE QUICK group_setting FROM group_setting LEFT JOIN team ON group_setting.group_id=team.id WHERE team.id IS NULL";
  3560. $db->Execute($sql);
  3561. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' group_setting records.');
  3562. $sql = "DELETE QUICK custom_field FROM custom_field LEFT JOIN team ON custom_field.group_id=team.id WHERE custom_field.group_id > 0 AND team.id IS NULL";
  3563. $db->Execute($sql);
  3564. $logger->info('[Maint] Purged ' . $db->Affected_Rows() . ' custom_field records.');
  3565. }
  3566. static function setTeamMember($team_id, $worker_id, $is_manager=false) {
  3567. if(empty($worker_id) || empty($team_id))
  3568. return FALSE;
  3569. $db = DevblocksPlatform::getDatabaseService();
  3570. $db->Replace(
  3571. 'worker_to_team',
  3572. array('agent_id' => $worker_id, 'team_id' => $team_id, 'is_manager' => ($is_manager?1:0)),
  3573. array('agent_id','team_id')
  3574. );
  3575. self::clearCache();
  3576. }
  3577. static function unsetTeamMember($team_id, $worker_id) {
  3578. if(empty($worker_id) || empty($team_id))
  3579. return FALSE;
  3580. $db = DevblocksPlatform::getDatabaseService();
  3581. $sql = sprintf("DELETE QUICK FROM worker_to_team WHERE team_id = %d AND agent_id IN (%d)",
  3582. $team_id,
  3583. $worker_id
  3584. );
  3585. $db->Execute($sql);
  3586. self::clearCache();
  3587. }
  3588. static function getRosters() {
  3589. $cache = DevblocksPlatform::getCacheService();
  3590. if(null === ($objects = $cache->load(self::CACHE_ROSTERS))) {
  3591. $db = DevblocksPlatform::getDatabaseService();
  3592. $sql = sprintf("SELECT wt.agent_id, wt.team_id, wt.is_manager ".
  3593. "FROM worker_to_team wt ".
  3594. "INNER JOIN team t ON (wt.team_id=t.id) ".
  3595. "INNER JOIN worker w ON (w.id=wt.agent_id) ".
  3596. "ORDER BY t.name ASC, w.first_name ASC "
  3597. );
  3598. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3599. $objects = array();
  3600. if(is_a($rs,'ADORecordSet'))
  3601. while(!$rs->EOF) {
  3602. $agent_id = intval($rs->fields['agent_id']);
  3603. $team_id = intval($rs->fields['team_id']);
  3604. $is_manager = intval($rs->fields['is_manager']);
  3605. if(!isset($objects[$team_id]))
  3606. $objects[$team_id] = array();
  3607. $member = new Model_TeamMember();
  3608. $member->id = $agent_id;
  3609. $member->team_id = $team_id;
  3610. $member->is_manager = $is_manager;
  3611. $objects[$team_id][$agent_id] = $member;
  3612. $rs->MoveNext();
  3613. }
  3614. $cache->save($objects, self::CACHE_ROSTERS);
  3615. }
  3616. return $objects;
  3617. }
  3618. static function getTeamMembers($team_id) {
  3619. $rosters = self::getRosters();
  3620. if(isset($rosters[$team_id]))
  3621. return $rosters[$team_id];
  3622. return null;
  3623. }
  3624. static public function clearCache() {
  3625. $cache = DevblocksPlatform::getCacheService();
  3626. $cache->remove(self::CACHE_ALL);
  3627. $cache->remove(self::CACHE_ROSTERS);
  3628. $cache->remove(CerberusApplication::CACHE_HELPDESK_FROMS);
  3629. }
  3630. };
  3631. class DAO_GroupSettings {
  3632. const CACHE_ALL = 'ch_group_settings';
  3633. const SETTING_REPLY_FROM = 'reply_from';
  3634. const SETTING_REPLY_PERSONAL = 'reply_personal';
  3635. const SETTING_REPLY_PERSONAL_WITH_WORKER = 'reply_personal_with_worker';
  3636. const SETTING_SUBJECT_HAS_MASK = 'subject_has_mask';
  3637. const SETTING_SUBJECT_PREFIX = 'subject_prefix';
  3638. const SETTING_SPAM_THRESHOLD = 'group_spam_threshold';
  3639. const SETTING_SPAM_ACTION = 'group_spam_action';
  3640. const SETTING_SPAM_ACTION_PARAM = 'group_spam_action_param';
  3641. const SETTING_AUTO_REPLY = 'auto_reply';
  3642. const SETTING_AUTO_REPLY_ENABLED = 'auto_reply_enabled';
  3643. const SETTING_CLOSE_REPLY = 'close_reply';
  3644. const SETTING_CLOSE_REPLY_ENABLED = 'close_reply_enabled';
  3645. const SETTING_INBOX_IS_ASSIGNABLE = 'inbox_is_assignable';
  3646. static function set($group_id, $key, $value) {
  3647. $db = DevblocksPlatform::getDatabaseService();
  3648. $result = $db->Replace(
  3649. 'group_setting',
  3650. array(
  3651. 'group_id'=>$group_id,
  3652. 'setting'=>$db->qstr($key),
  3653. 'value'=>$db->qstr($value) // BlobEncode/UpdateBlob?
  3654. ),
  3655. array('group_id','setting'),
  3656. false
  3657. );
  3658. $cache = DevblocksPlatform::getCacheService();
  3659. $cache->remove(self::CACHE_ALL);
  3660. // Nuke our sender cache
  3661. if($key==self::SETTING_REPLY_FROM) {
  3662. $cache->remove(CerberusApplication::CACHE_HELPDESK_FROMS);
  3663. }
  3664. }
  3665. static function get($group_id, $key, $default=null) {
  3666. $value = null;
  3667. if(null !== ($group = self::getSettings($group_id)) && isset($group[$key])) {
  3668. $value = $group[$key];
  3669. }
  3670. if(null == $value && !is_null($default)) {
  3671. return $default;
  3672. }
  3673. return $value;
  3674. }
  3675. static function getSettings($group_id=0) {
  3676. $cache = DevblocksPlatform::getCacheService();
  3677. if(null === ($groups = $cache->load(self::CACHE_ALL))) {
  3678. $db = DevblocksPlatform::getDatabaseService();
  3679. $groups = array();
  3680. $sql = "SELECT group_id, setting, value FROM group_setting";
  3681. $rs = $db->Execute($sql) or die(__CLASS__ . ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3682. if(is_a($rs,'ADORecordSet'))
  3683. while(!$rs->EOF) {
  3684. $gid = intval($rs->fields['group_id']);
  3685. if(!isset($groups[$gid]))
  3686. $groups[$gid] = array();
  3687. $groups[$gid][$rs->Fields('setting')] = $rs->Fields('value');
  3688. $rs->MoveNext();
  3689. }
  3690. $cache->save($groups, self::CACHE_ALL);
  3691. }
  3692. // Empty
  3693. if(empty($groups))
  3694. return null;
  3695. // Specific group
  3696. if(!empty($group_id)) {
  3697. // Requested group id exists
  3698. if(isset($groups[$group_id]))
  3699. return $groups[$group_id];
  3700. else // doesn't
  3701. return null;
  3702. }
  3703. // All groups
  3704. return $groups;
  3705. }
  3706. };
  3707. class DAO_Bucket extends DevblocksORMHelper {
  3708. const CACHE_ALL = 'cerberus_cache_buckets_all';
  3709. const ID = 'id';
  3710. const POS = 'pos';
  3711. const NAME = 'name';
  3712. const TEAM_ID = 'team_id';
  3713. const IS_ASSIGNABLE = 'is_assignable';
  3714. static function getTeams() {
  3715. $categories = self::getAll();
  3716. $team_categories = array();
  3717. foreach($categories as $cat) {
  3718. $team_categories[$cat->team_id][$cat->id] = $cat;
  3719. }
  3720. return $team_categories;
  3721. }
  3722. // [JAS]: This belongs in API, not DAO
  3723. static function getCategoryNameHash() {
  3724. $category_name_hash = array();
  3725. $teams = DAO_Group::getAll();
  3726. $team_categories = self::getTeams();
  3727. foreach($teams as $team_id => $team) {
  3728. $category_name_hash['t'.$team_id] = $team->name;
  3729. if(@is_array($team_categories[$team_id]))
  3730. foreach($team_categories[$team_id] as $category) {
  3731. $category_name_hash['c'.$category->id] = $team->name . ':' .$category->name;
  3732. }
  3733. }
  3734. return $category_name_hash;
  3735. }
  3736. static function getAll($nocache=false) {
  3737. $cache = DevblocksPlatform::getCacheService();
  3738. if($nocache || null === ($buckets = $cache->load(self::CACHE_ALL))) {
  3739. $buckets = self::getList();
  3740. $cache->save($buckets, self::CACHE_ALL);
  3741. }
  3742. return $buckets;
  3743. }
  3744. static function getNextPos($group_id) {
  3745. if(empty($group_id))
  3746. return 0;
  3747. $db = DevblocksPlatform::getDatabaseService();
  3748. if(null != ($next_pos = $db->GetOne(sprintf("SELECT MAX(pos)+1 FROM category WHERE team_id = %d", $group_id))))
  3749. return $next_pos;
  3750. return 0;
  3751. }
  3752. static function getList($ids=array()) {
  3753. $db = DevblocksPlatform::getDatabaseService();
  3754. $sql = "SELECT tc.id, tc.pos, tc.name, tc.team_id, tc.is_assignable ".
  3755. "FROM category tc ".
  3756. "INNER JOIN team t ON (tc.team_id=t.id) ".
  3757. (!empty($ids) ? sprintf("WHERE tc.id IN (%s) ", implode(',', $ids)) : "").
  3758. "ORDER BY t.name ASC, tc.pos ASC "
  3759. ;
  3760. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3761. $categories = array();
  3762. if(is_a($rs,'ADORecordSet'))
  3763. while(!$rs->EOF) {
  3764. $category = new CerberusCategory();
  3765. $category->id = intval($rs->Fields('id'));
  3766. $category->pos = intval($rs->Fields('pos'));
  3767. $category->name = $rs->Fields('name');
  3768. $category->team_id = intval($rs->Fields('team_id'));
  3769. $category->is_assignable = intval($rs->Fields('is_assignable'));
  3770. $categories[$category->id] = $category;
  3771. $rs->MoveNext();
  3772. }
  3773. return $categories;
  3774. }
  3775. static function getByTeam($team_ids) {
  3776. if(!is_array($team_ids)) $team_ids = array($team_ids);
  3777. $team_buckets = array();
  3778. $buckets = self::getAll();
  3779. foreach($buckets as $bucket) {
  3780. if(false !== array_search($bucket->team_id, $team_ids)) {
  3781. $team_buckets[$bucket->id] = $bucket;
  3782. }
  3783. }
  3784. return $team_buckets;
  3785. }
  3786. static function getAssignableBuckets($group_ids=null) {
  3787. if(!is_array($group_ids)) $group_ids = array($group_ids);
  3788. if(empty($group_ids)) {
  3789. $buckets = self::getAll();
  3790. } else {
  3791. $buckets = self::getByTeam($group_ids);
  3792. }
  3793. // Remove buckets that aren't assignable
  3794. if(is_array($buckets))
  3795. foreach($buckets as $id => $bucket) {
  3796. if(!$bucket->is_assignable)
  3797. unset($buckets[$id]);
  3798. }
  3799. return $buckets;
  3800. }
  3801. static function create($name, $team_id) {
  3802. $db = DevblocksPlatform::getDatabaseService();
  3803. // Check for dupes
  3804. $buckets = self::getAll();
  3805. if(is_array($buckets))
  3806. foreach($buckets as $bucket) {
  3807. if(0==strcasecmp($name,$bucket->name) && $team_id==$bucket->team_id) {
  3808. return $bucket->id;
  3809. }
  3810. }
  3811. $id = $db->GenID('generic_seq');
  3812. $next_pos = self::getNextPos($team_id);
  3813. $sql = sprintf("INSERT INTO category (id,pos,name,team_id,is_assignable) ".
  3814. "VALUES (%d,%d,%s,%d,1)",
  3815. $id,
  3816. $next_pos,
  3817. $db->qstr($name),
  3818. $team_id
  3819. );
  3820. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3821. self::clearCache();
  3822. return $id;
  3823. }
  3824. static function update($id,$fields) {
  3825. parent::_update($id,'category',$fields);
  3826. self::clearCache();
  3827. }
  3828. static function delete($ids) {
  3829. if(!is_array($ids)) $ids = array($ids);
  3830. $db = DevblocksPlatform::getDatabaseService();
  3831. if(empty($ids))
  3832. return;
  3833. /*
  3834. * Notify anything that wants to know when buckets delete.
  3835. */
  3836. $eventMgr = DevblocksPlatform::getEventService();
  3837. $eventMgr->trigger(
  3838. new Model_DevblocksEvent(
  3839. 'bucket.delete',
  3840. array(
  3841. 'bucket_ids' => $ids,
  3842. )
  3843. )
  3844. );
  3845. $sql = sprintf("DELETE QUICK FROM category WHERE id IN (%s)", implode(',',$ids));
  3846. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3847. // Reset any tickets using this category
  3848. $sql = sprintf("UPDATE ticket SET category_id = 0 WHERE category_id IN (%s)", implode(',',$ids));
  3849. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3850. self::clearCache();
  3851. }
  3852. static public function clearCache() {
  3853. $cache = DevblocksPlatform::getCacheService();
  3854. $cache->remove(self::CACHE_ALL);
  3855. }
  3856. };
  3857. class DAO_Mail {
  3858. // Pop3 Accounts
  3859. static function createPop3Account($fields) {
  3860. $db = DevblocksPlatform::getDatabaseService();
  3861. $newId = $db->GenID('generic_seq');
  3862. $sql = sprintf("INSERT INTO pop3_account (id, enabled, nickname, host, username, password) ".
  3863. "VALUES (%d,0,'','','','')",
  3864. $newId
  3865. );
  3866. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3867. self::updatePop3Account($newId, $fields);
  3868. return $newId;
  3869. }
  3870. static function getPop3Accounts($ids=array()) {
  3871. if(!is_array($ids)) $ids = array($ids);
  3872. $db = DevblocksPlatform::getDatabaseService();
  3873. $pop3accounts = array();
  3874. $sql = "SELECT id, enabled, nickname, protocol, host, username, password, port ".
  3875. "FROM pop3_account ".
  3876. ((!empty($ids) ? sprintf("WHERE id IN (%s)", implode(',', $ids)) : " ").
  3877. "ORDER BY nickname "
  3878. );
  3879. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3880. if(is_a($rs,'ADORecordSet'))
  3881. while(!$rs->EOF) {
  3882. $pop3 = new CerberusPop3Account();
  3883. $pop3->id = intval($rs->fields['id']);
  3884. $pop3->enabled = intval($rs->fields['enabled']);
  3885. $pop3->nickname = $rs->fields['nickname'];
  3886. $pop3->protocol = $rs->fields['protocol'];
  3887. $pop3->host = $rs->fields['host'];
  3888. $pop3->username = $rs->fields['username'];
  3889. $pop3->password = $rs->fields['password'];
  3890. $pop3->port = intval($rs->fields['port']);
  3891. $pop3accounts[$pop3->id] = $pop3;
  3892. $rs->MoveNext();
  3893. }
  3894. return $pop3accounts;
  3895. }
  3896. /**
  3897. * Enter description here...
  3898. *
  3899. * @param integer $id
  3900. * @return CerberusPop3Account
  3901. */
  3902. static function getPop3Account($id) {
  3903. $accounts = DAO_Mail::getPop3Accounts(array($id));
  3904. if(isset($accounts[$id]))
  3905. return $accounts[$id];
  3906. return null;
  3907. }
  3908. static function updatePop3Account($id, $fields) {
  3909. $db = DevblocksPlatform::getDatabaseService();
  3910. $sets = array();
  3911. if(!is_array($fields) || empty($fields) || empty($id))
  3912. return;
  3913. foreach($fields as $k => $v) {
  3914. $sets[] = sprintf("%s = %s",
  3915. $k,
  3916. $db->qstr($v)
  3917. );
  3918. }
  3919. $sql = sprintf("UPDATE pop3_account SET %s WHERE id = %d",
  3920. implode(', ', $sets),
  3921. $id
  3922. );
  3923. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3924. }
  3925. static function deletePop3Account($id) {
  3926. if(empty($id))
  3927. return;
  3928. $db = DevblocksPlatform::getDatabaseService();
  3929. $sql = sprintf("DELETE QUICK FROM pop3_account WHERE id = %d",
  3930. $id
  3931. );
  3932. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  3933. }
  3934. };
  3935. class DAO_MailToGroupRule extends DevblocksORMHelper {
  3936. const ID = 'id';
  3937. const POS = 'pos';
  3938. const CREATED = 'created';
  3939. const NAME = 'name';
  3940. const CRITERIA_SER = 'criteria_ser';
  3941. const ACTIONS_SER = 'actions_ser';
  3942. const IS_STICKY = 'is_sticky';
  3943. const STICKY_ORDER = 'sticky_order';
  3944. static function create($fields) {
  3945. $db = DevblocksPlatform::getDatabaseService();
  3946. $id = $db->GenID('generic_seq');
  3947. $sql = sprintf("INSERT INTO mail_to_group_rule (id, created) ".
  3948. "VALUES (%d, %d)",
  3949. $id,
  3950. time()
  3951. );
  3952. $db->Execute($sql);
  3953. self::update($id, $fields);
  3954. return $id;
  3955. }
  3956. static function update($ids, $fields) {
  3957. parent::_update($ids, 'mail_to_group_rule', $fields);
  3958. }
  3959. /**
  3960. * @param string $where
  3961. * @return Model_MailToGroupRule[]
  3962. */
  3963. static function getWhere($where=null) {
  3964. $db = DevblocksPlatform::getDatabaseService();
  3965. $sql = "SELECT id, pos, created, name, criteria_ser, actions_ser, is_sticky, sticky_order ".
  3966. "FROM mail_to_group_rule ".
  3967. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  3968. "ORDER BY is_sticky DESC, sticky_order ASC, pos DESC";
  3969. $rs = $db->Execute($sql);
  3970. return self::_getObjectsFromResult($rs);
  3971. }
  3972. /**
  3973. * @param integer $id
  3974. * @return Model_MailToGroupRule */
  3975. static function get($id) {
  3976. $objects = self::getWhere(sprintf("%s = %d",
  3977. self::ID,
  3978. $id
  3979. ));
  3980. if(isset($objects[$id]))
  3981. return $objects[$id];
  3982. return null;
  3983. }
  3984. /**
  3985. * @param ADORecordSet $rs
  3986. * @return Model_MailToGroupRule[]
  3987. */
  3988. static private function _getObjectsFromResult($rs) {
  3989. $objects = array();
  3990. while(!$rs->EOF) {
  3991. $object = new Model_MailToGroupRule();
  3992. $object->id = $rs->fields['id'];
  3993. $object->pos = $rs->fields['pos'];
  3994. $object->created = $rs->fields['created'];
  3995. $object->name = $rs->fields['name'];
  3996. $criteria_ser = $rs->fields['criteria_ser'];
  3997. $actions_ser = $rs->fields['actions_ser'];
  3998. $object->is_sticky = $rs->fields['is_sticky'];
  3999. $object->sticky_order = $rs->fields['sticky_order'];
  4000. $object->criteria = (!empty($criteria_ser)) ? @unserialize($criteria_ser) : array();
  4001. $object->actions = (!empty($actions_ser)) ? @unserialize($actions_ser) : array();
  4002. $objects[$object->id] = $object;
  4003. $rs->MoveNext();
  4004. }
  4005. return $objects;
  4006. }
  4007. static function delete($ids) {
  4008. if(!is_array($ids)) $ids = array($ids);
  4009. if(empty($ids))
  4010. return;
  4011. $db = DevblocksPlatform::getDatabaseService();
  4012. $ids_list = implode(',', $ids);
  4013. $db->Execute(sprintf("DELETE FROM mail_to_group_rule WHERE id IN (%s)", $ids_list));
  4014. return true;
  4015. }
  4016. /**
  4017. * Increment the number of times we've matched this rule
  4018. *
  4019. * @param integer $id
  4020. */
  4021. static function increment($id) {
  4022. $db = DevblocksPlatform::getDatabaseService();
  4023. $db->Execute(sprintf("UPDATE mail_to_group_rule SET pos = pos + 1 WHERE id = %d",
  4024. $id
  4025. ));
  4026. }
  4027. };
  4028. class DAO_Community extends DevblocksORMHelper {
  4029. const ID = 'id';
  4030. const NAME = 'name';
  4031. public static function create($fields) {
  4032. $db = DevblocksPlatform::getDatabaseService();
  4033. $id = $db->GenID('generic_seq');
  4034. $sql = sprintf("INSERT INTO community (id,name) ".
  4035. "VALUES (%d,'')",
  4036. $id
  4037. );
  4038. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4039. self::update($id, $fields);
  4040. return $id;
  4041. }
  4042. public static function update($id, $fields) {
  4043. self::_update($id, 'community', $fields);
  4044. }
  4045. public static function get($id) {
  4046. $items = self::getList(array($id));
  4047. if(isset($items[$id]))
  4048. return $items[$id];
  4049. return NULL;
  4050. }
  4051. static function getWhere($where=null) {
  4052. $db = DevblocksPlatform::getDatabaseService();
  4053. $sql = "SELECT id, name ".
  4054. "FROM community ".
  4055. (!empty($where)?sprintf("WHERE %s ",$where):" ").
  4056. "ORDER BY name "
  4057. ;
  4058. $rs = $db->Execute($sql);
  4059. return self::_createObjectsFromResultSet($rs);
  4060. }
  4061. public static function getList($ids=array()) {
  4062. if(!is_array($ids)) $ids = array($ids);
  4063. $db = DevblocksPlatform::getDatabaseService();
  4064. $sql = "SELECT id,name ".
  4065. "FROM community ".
  4066. (!empty($ids) ? sprintf("WHERE id IN (%s) ", implode(',', $ids)) : " ").
  4067. "ORDER BY name ASC "
  4068. ;
  4069. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4070. return self::_createObjectsFromResultSet($rs);
  4071. }
  4072. private static function _createObjectsFromResultSet($rs) {
  4073. $objects = array();
  4074. if(is_a($rs,'ADORecordSet'))
  4075. while(!$rs->EOF) {
  4076. $object = new Model_Community();
  4077. $object->id = intval($rs->fields['id']);
  4078. $object->name = $rs->fields['name'];
  4079. $objects[$object->id] = $object;
  4080. $rs->MoveNext();
  4081. }
  4082. return $objects;
  4083. }
  4084. public static function delete($ids) {
  4085. if(!is_array($ids)) $ids = array($ids);
  4086. $db = DevblocksPlatform::getDatabaseService();
  4087. if(empty($ids))
  4088. return;
  4089. $id_list = implode(',', $ids);
  4090. $sql = sprintf("DELETE QUICK FROM community WHERE id IN (%s)", $id_list);
  4091. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4092. // Community Tools
  4093. $tools = DAO_CommunityTool::getWhere(sprintf("%s IN (%s)",
  4094. DAO_CommunityTool::COMMUNITY_ID,
  4095. $id_list
  4096. ));
  4097. DAO_CommunityTool::delete(array_keys($tools));
  4098. // [TODO] cascade foreign key constraints
  4099. }
  4100. /**
  4101. * Enter description here...
  4102. *
  4103. * @param DevblocksSearchCriteria[] $params
  4104. * @param integer $limit
  4105. * @param integer $page
  4106. * @param string $sortBy
  4107. * @param boolean $sortAsc
  4108. * @param boolean $withCounts
  4109. * @return array
  4110. */
  4111. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  4112. $db = DevblocksPlatform::getDatabaseService();
  4113. $fields = SearchFields_Community::getFields();
  4114. // Sanitize
  4115. if(!isset($fields[$sortBy]))
  4116. $sortBy=null;
  4117. list($tables,$wheres) = parent::_parseSearchParams($params, array(), $fields,$sortBy);
  4118. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  4119. $sql = sprintf("SELECT ".
  4120. "t.id as %s, ".
  4121. "t.title as %s ".
  4122. "FROM community c ",
  4123. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  4124. SearchFields_Community::ID,
  4125. SearchFields_Community::NAME
  4126. ).
  4127. // [JAS]: Dynamic table joins
  4128. // (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  4129. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  4130. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "")
  4131. ;
  4132. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4133. $results = array();
  4134. if(is_a($rs,'ADORecordSet'))
  4135. while(!$rs->EOF) {
  4136. $result = array();
  4137. foreach($rs->fields as $f => $v) {
  4138. $result[$f] = $v;
  4139. }
  4140. $ticket_id = intval($rs->fields[SearchFields_Community::ID]);
  4141. $results[$ticket_id] = $result;
  4142. $rs->MoveNext();
  4143. }
  4144. // [JAS]: Count all
  4145. $total = -1;
  4146. if($withCounts) {
  4147. $rs = $db->Execute($sql);
  4148. $total = $rs->RecordCount();
  4149. }
  4150. return array($results,$total);
  4151. }
  4152. };
  4153. class SearchFields_Community implements IDevblocksSearchFields {
  4154. // Table
  4155. const ID = 'c_id';
  4156. const NAME = 'c_name';
  4157. /**
  4158. * @return DevblocksSearchField[]
  4159. */
  4160. static function getFields() {
  4161. $columns = array(
  4162. self::ID => new DevblocksSearchField(self::ID, 'c', 'id'),
  4163. self::NAME => new DevblocksSearchField(self::NAME, 'c', 'name'),
  4164. );
  4165. // Sort by label (translation-conscious)
  4166. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  4167. return $columns;
  4168. }
  4169. };
  4170. class DAO_WorkerWorkspaceList extends DevblocksORMHelper {
  4171. const ID = 'id';
  4172. const WORKER_ID = 'worker_id';
  4173. const WORKSPACE = 'workspace';
  4174. const SOURCE_EXTENSION = 'source_extension';
  4175. const LIST_VIEW = 'list_view';
  4176. const LIST_POS = 'list_pos';
  4177. static function create($fields) {
  4178. $db = DevblocksPlatform::getDatabaseService();
  4179. if(empty($fields))
  4180. return NULL;
  4181. $id = $db->GenID('generic_seq');
  4182. $sql = sprintf("INSERT INTO worker_workspace_list (id, worker_id, workspace, source_extension, list_view, list_pos) ".
  4183. "VALUES (%d, 0, '', '', '',0)",
  4184. $id
  4185. );
  4186. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg());
  4187. self::update($id, $fields);
  4188. return $id;
  4189. }
  4190. /**
  4191. * Enter description here...
  4192. *
  4193. * @param integer $id
  4194. * @return Model_WorkerWorkspaceList
  4195. */
  4196. static function get($id) {
  4197. $objects = self::getWhere(sprintf("%s = %d",
  4198. self::ID,
  4199. $id
  4200. ));
  4201. if(isset($objects[$id]))
  4202. return $objects[$id];
  4203. return null;
  4204. }
  4205. /**
  4206. * Enter description here...
  4207. *
  4208. * @param string $where
  4209. * @return Model_WorkerWorkspaceList[]
  4210. */
  4211. static function getWhere($where) {
  4212. $db = DevblocksPlatform::getDatabaseService();
  4213. $sql = "SELECT id, worker_id, workspace, source_extension, list_view, list_pos ".
  4214. "FROM worker_workspace_list ".
  4215. (!empty($where) ? sprintf("WHERE %s ",$where) : " ").
  4216. "ORDER BY list_pos ASC";
  4217. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4218. $objects = array();
  4219. if(is_a($rs,'ADORecordSet'))
  4220. while(!$rs->EOF) {
  4221. $object = new Model_WorkerWorkspaceList();
  4222. $object->id = intval($rs->fields['id']);
  4223. $object->worker_id = intval($rs->fields['worker_id']);
  4224. $object->workspace = $rs->fields['workspace'];
  4225. $object->source_extension = $rs->fields['source_extension'];
  4226. $object->list_pos = intval($rs->fields['list_pos']);
  4227. $list_view = $rs->fields['list_view'];
  4228. if(!empty($list_view)) {
  4229. @$object->list_view = unserialize($list_view);
  4230. }
  4231. $objects[$object->id] = $object;
  4232. $rs->MoveNext();
  4233. }
  4234. return $objects;
  4235. }
  4236. static function getWorkspaces($worker_id = 0) {
  4237. $workspaces = array();
  4238. $db = DevblocksPlatform::getDatabaseService();
  4239. $sql = "SELECT DISTINCT workspace AS workspace ".
  4240. "FROM worker_workspace_list ".
  4241. (!empty($worker_id) ? sprintf("WHERE worker_id = %d ",$worker_id) : " ").
  4242. "ORDER BY workspace";
  4243. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4244. if(is_a($rs,'ADORecordSet'))
  4245. while(!$rs->EOF) {
  4246. $workspaces[] = $rs->fields['workspace'];
  4247. $rs->MoveNext();
  4248. }
  4249. return $workspaces;
  4250. }
  4251. static function update($ids, $fields) {
  4252. parent::_update($ids, 'worker_workspace_list', $fields);
  4253. }
  4254. static function updateWhere($fields, $where) {
  4255. parent::_updateWhere('worker_workspace_list', $fields, $where);
  4256. }
  4257. static function delete($ids) {
  4258. if(!is_array($ids)) $ids = array($ids);
  4259. if(empty($ids))
  4260. return;
  4261. $db = DevblocksPlatform::getDatabaseService();
  4262. $ids_list = implode(',', $ids);
  4263. $db->Execute(sprintf("DELETE QUICK FROM worker_workspace_list WHERE id IN (%s)", $ids_list)) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg());
  4264. }
  4265. };
  4266. class DAO_WorkerPref extends DevblocksORMHelper {
  4267. const CACHE_PREFIX = 'ch_workerpref_';
  4268. static function set($worker_id, $key, $value) {
  4269. // Persist long-term
  4270. $db = DevblocksPlatform::getDatabaseService();
  4271. $result = $db->Replace(
  4272. 'worker_pref',
  4273. array(
  4274. 'worker_id'=>$worker_id,
  4275. 'setting'=>$db->qstr($key),
  4276. 'value'=>$db->qstr($value) // BlobEncode/UpdateBlob?
  4277. ),
  4278. array('worker_id','setting'),
  4279. false
  4280. );
  4281. // Invalidate cache
  4282. $cache = DevblocksPlatform::getCacheService();
  4283. $cache->remove(self::CACHE_PREFIX.$worker_id);
  4284. }
  4285. static function get($worker_id, $key, $default=null) {
  4286. $value = null;
  4287. if(null !== ($worker_prefs = self::getByWorker($worker_id))) {
  4288. if(isset($worker_prefs[$key])) {
  4289. $value = $worker_prefs[$key];
  4290. }
  4291. }
  4292. if(null === $value && !is_null($default)) {
  4293. return $default;
  4294. }
  4295. return $value;
  4296. }
  4297. static function getByWorker($worker_id) {
  4298. $cache = DevblocksPlatform::getCacheService();
  4299. if(null === ($objects = $cache->load(self::CACHE_PREFIX.$worker_id))) {
  4300. $db = DevblocksPlatform::getDatabaseService();
  4301. $sql = sprintf("SELECT setting, value FROM worker_pref WHERE worker_id = %d", $worker_id);
  4302. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4303. $objects = array();
  4304. if(is_a($rs,'ADORecordSet'))
  4305. while(!$rs->EOF) {
  4306. $objects[$rs->fields['setting']] = $rs->fields['value'];
  4307. $rs->MoveNext();
  4308. }
  4309. $cache->save($objects, self::CACHE_PREFIX.$worker_id);
  4310. }
  4311. return $objects;
  4312. }
  4313. };
  4314. class DAO_Note extends DevblocksORMHelper {
  4315. const ID = 'id';
  4316. const SOURCE_EXTENSION_ID = 'source_extension_id';
  4317. const SOURCE_ID = 'source_id';
  4318. const CREATED = 'created';
  4319. const WORKER_ID = 'worker_id';
  4320. const CONTENT = 'content';
  4321. static function create($fields) {
  4322. $db = DevblocksPlatform::getDatabaseService();
  4323. $id = $db->GenID('note_seq');
  4324. $sql = sprintf("INSERT INTO note (id) ".
  4325. "VALUES (%d)",
  4326. $id
  4327. );
  4328. $db->Execute($sql);
  4329. self::update($id, $fields);
  4330. return $id;
  4331. }
  4332. static function update($ids, $fields) {
  4333. parent::_update($ids, 'note', $fields);
  4334. }
  4335. /**
  4336. * @param string $where
  4337. * @return Model_Note[]
  4338. */
  4339. static function getWhere($where=null) {
  4340. $db = DevblocksPlatform::getDatabaseService();
  4341. $sql = "SELECT id, source_extension_id, source_id, created, worker_id, content ".
  4342. "FROM note ".
  4343. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  4344. "ORDER BY id asc";
  4345. $rs = $db->Execute($sql);
  4346. return self::_getObjectsFromResult($rs);
  4347. }
  4348. /**
  4349. * @param integer $id
  4350. * @return Model_Note */
  4351. static function get($id) {
  4352. $objects = self::getWhere(sprintf("%s = %d",
  4353. self::ID,
  4354. $id
  4355. ));
  4356. if(isset($objects[$id]))
  4357. return $objects[$id];
  4358. return null;
  4359. }
  4360. /**
  4361. * @param ADORecordSet $rs
  4362. * @return Model_Note[]
  4363. */
  4364. static private function _getObjectsFromResult($rs) {
  4365. $objects = array();
  4366. while(!$rs->EOF) {
  4367. $object = new Model_Note();
  4368. $object->id = $rs->fields['id'];
  4369. $object->source_extension_id = $rs->fields['source_extension_id'];
  4370. $object->source_id = $rs->fields['source_id'];
  4371. $object->created = $rs->fields['created'];
  4372. $object->worker_id = $rs->fields['worker_id'];
  4373. $object->content = $rs->fields['content'];
  4374. $objects[$object->id] = $object;
  4375. $rs->MoveNext();
  4376. }
  4377. return $objects;
  4378. }
  4379. /**
  4380. * Enter description here...
  4381. *
  4382. * @param DevblocksSearchCriteria[] $params
  4383. * @param integer $limit
  4384. * @param integer $page
  4385. * @param string $sortBy
  4386. * @param boolean $sortAsc
  4387. * @param boolean $withCounts
  4388. * @return array
  4389. */
  4390. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  4391. $db = DevblocksPlatform::getDatabaseService();
  4392. $fields = SearchFields_Note::getFields();
  4393. // Sanitize
  4394. if(!isset($fields[$sortBy]))
  4395. $sortBy=null;
  4396. list($tables,$wheres) = parent::_parseSearchParams($params, array(),$fields,$sortBy);
  4397. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  4398. $select_sql = sprintf("SELECT ".
  4399. "n.id as %s, ".
  4400. "n.source_extension_id as %s, ".
  4401. "n.source_id as %s, ".
  4402. "n.created as %s, ".
  4403. "n.worker_id as %s, ".
  4404. "n.content as %s ",
  4405. SearchFields_Note::ID,
  4406. SearchFields_Note::SOURCE_EXT_ID,
  4407. SearchFields_Note::SOURCE_ID,
  4408. SearchFields_Note::CREATED,
  4409. SearchFields_Note::WORKER_ID,
  4410. SearchFields_Note::CONTENT
  4411. );
  4412. $join_sql =
  4413. "FROM note n ";
  4414. // "LEFT JOIN contact_org o ON (o.id=a.contact_org_id) "
  4415. // [JAS]: Dynamic table joins
  4416. // (isset($tables['o']) ? "LEFT JOIN contact_org o ON (o.id=a.contact_org_id)" : " ").
  4417. // (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=m.id)" : " ").
  4418. $where_sql = "".
  4419. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  4420. $sql = $select_sql . $join_sql . $where_sql .
  4421. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "");
  4422. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4423. $results = array();
  4424. if(is_a($rs,'ADORecordSet'))
  4425. while(!$rs->EOF) {
  4426. $result = array();
  4427. foreach($rs->fields as $f => $v) {
  4428. $result[$f] = $v;
  4429. }
  4430. $id = intval($rs->fields[SearchFields_Note::ID]);
  4431. $results[$id] = $result;
  4432. $rs->MoveNext();
  4433. }
  4434. // [JAS]: Count all
  4435. $total = -1;
  4436. if($withCounts) {
  4437. $count_sql = "SELECT count(*) " . $join_sql . $where_sql;
  4438. $total = $db->GetOne($count_sql);
  4439. }
  4440. return array($results,$total);
  4441. }
  4442. static function deleteBySourceIds($source_extension, $source_ids) {
  4443. if(!is_array($source_ids)) $source_ids = array($source_ids);
  4444. $db = DevblocksPlatform::getDatabaseService();
  4445. $ids_list = implode(',', $source_ids);
  4446. $db->Execute(sprintf("DELETE FROM note WHERE source_extension_id = %s AND source_id IN (%s)", $db->qstr($source_extension), $ids_list));
  4447. }
  4448. static function delete($ids) {
  4449. if(!is_array($ids)) $ids = array($ids);
  4450. if(empty($ids))
  4451. return;
  4452. $db = DevblocksPlatform::getDatabaseService();
  4453. $ids_list = implode(',', $ids);
  4454. $db->Execute(sprintf("DELETE FROM note WHERE id IN (%s)", $ids_list));
  4455. return true;
  4456. }
  4457. };
  4458. class SearchFields_Note implements IDevblocksSearchFields {
  4459. // Note
  4460. const ID = 'n_id';
  4461. const SOURCE_EXT_ID = 'n_source_ext_id';
  4462. const SOURCE_ID = 'n_source_id';
  4463. const CREATED = 'n_created';
  4464. const WORKER_ID = 'n_worker_id';
  4465. const CONTENT = 'n_content';
  4466. /**
  4467. * @return DevblocksSearchField[]
  4468. */
  4469. static function getFields() {
  4470. $columns = array(
  4471. self::ID => new DevblocksSearchField(self::ID, 'n', 'id'),
  4472. self::SOURCE_EXT_ID => new DevblocksSearchField(self::SOURCE_EXT_ID, 'n', 'source_extension_id'),
  4473. self::SOURCE_ID => new DevblocksSearchField(self::SOURCE_ID, 'n', 'source_id'),
  4474. self::CREATED => new DevblocksSearchField(self::CREATED, 'n', 'created'),
  4475. self::WORKER_ID => new DevblocksSearchField(self::WORKER_ID, 'n', 'worker_id'),
  4476. self::CONTENT => new DevblocksSearchField(self::CONTENT, 'n', 'content'),
  4477. );
  4478. // Sort by label (translation-conscious)
  4479. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  4480. return $columns;
  4481. }
  4482. };
  4483. class DAO_PreParseRule extends DevblocksORMHelper {
  4484. const CACHE_ALL = 'cerberus_cache_preparse_rules_all';
  4485. const ID = 'id';
  4486. const CREATED = 'created';
  4487. const NAME = 'name';
  4488. const CRITERIA_SER = 'criteria_ser';
  4489. const ACTIONS_SER = 'actions_ser';
  4490. const POS = 'pos';
  4491. const IS_STICKY = 'is_sticky';
  4492. const STICKY_ORDER = 'sticky_order';
  4493. static function create($fields) {
  4494. $db = DevblocksPlatform::getDatabaseService();
  4495. $id = $db->GenID('generic_seq');
  4496. $sql = sprintf("INSERT INTO preparse_rule (id,created) ".
  4497. "VALUES (%d,%d)",
  4498. $id,
  4499. time()
  4500. );
  4501. $db->Execute($sql);
  4502. self::update($id, $fields);
  4503. return $id;
  4504. }
  4505. static function update($ids, $fields) {
  4506. parent::_update($ids, 'preparse_rule', $fields);
  4507. self::clearCache();
  4508. }
  4509. static function getAll($nocache=false) {
  4510. $cache = DevblocksPlatform::getCacheService();
  4511. if($nocache || null === ($rules = $cache->load(self::CACHE_ALL))) {
  4512. $rules = self::getWhere();
  4513. $cache->save($rules, self::CACHE_ALL);
  4514. }
  4515. return $rules;
  4516. }
  4517. /**
  4518. * @param string $where
  4519. * @return Model_PreParseRule[]
  4520. */
  4521. static function getWhere($where=null) {
  4522. $db = DevblocksPlatform::getDatabaseService();
  4523. $sql = "SELECT id, created, name, criteria_ser, actions_ser, pos, is_sticky, sticky_order ".
  4524. "FROM preparse_rule ".
  4525. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  4526. "ORDER BY is_sticky DESC, sticky_order ASC, pos desc";
  4527. $rs = $db->Execute($sql);
  4528. return self::_getObjectsFromResult($rs);
  4529. }
  4530. /**
  4531. * @param integer $id
  4532. * @return Model_PreParseRule */
  4533. static function get($id) {
  4534. $objects = self::getWhere(sprintf("%s = %d",
  4535. self::ID,
  4536. $id
  4537. ));
  4538. if(isset($objects[$id]))
  4539. return $objects[$id];
  4540. return null;
  4541. }
  4542. /**
  4543. * Increment the number of times we've matched this filter
  4544. *
  4545. * @param integer $id
  4546. */
  4547. static function increment($id) {
  4548. $db = DevblocksPlatform::getDatabaseService();
  4549. $db->Execute(sprintf("UPDATE preparse_rule SET pos = pos + 1 WHERE id = %d",
  4550. $id
  4551. ));
  4552. }
  4553. /**
  4554. * @param ADORecordSet $rs
  4555. * @return Model_PreParseRule[]
  4556. */
  4557. static private function _getObjectsFromResult($rs) {
  4558. $objects = array();
  4559. if(is_a($rs,'ADORecordSet'))
  4560. while(!$rs->EOF) {
  4561. $object = new Model_PreParseRule();
  4562. $object->created = $rs->fields['created'];
  4563. $object->id = $rs->fields['id'];
  4564. $object->name = $rs->fields['name'];
  4565. $object->criteria = !empty($rs->fields['criteria_ser']) ? @unserialize($rs->fields['criteria_ser']) : array();
  4566. $object->actions = !empty($rs->fields['actions_ser']) ? @unserialize($rs->fields['actions_ser']) : array();
  4567. $object->pos = $rs->fields['pos'];
  4568. $object->is_sticky = $rs->fields['is_sticky'];
  4569. $object->sticky_order = $rs->fields['sticky_order'];
  4570. $objects[$object->id] = $object;
  4571. $rs->MoveNext();
  4572. }
  4573. return $objects;
  4574. }
  4575. static function delete($ids) {
  4576. if(!is_array($ids)) $ids = array($ids);
  4577. $db = DevblocksPlatform::getDatabaseService();
  4578. if(empty($ids))
  4579. return;
  4580. $ids_list = implode(',', $ids);
  4581. $db->Execute(sprintf("DELETE QUICK FROM preparse_rule WHERE id IN (%s)", $ids_list));
  4582. self::clearCache();
  4583. return true;
  4584. }
  4585. static function clearCache() {
  4586. $cache = DevblocksPlatform::getCacheService();
  4587. $cache->remove(self::CACHE_ALL);
  4588. }
  4589. };
  4590. class DAO_GroupInboxFilter extends DevblocksORMHelper {
  4591. const ID = 'id';
  4592. const NAME = 'name';
  4593. const GROUP_ID = 'group_id';
  4594. const CRITERIA_SER = 'criteria_ser';
  4595. const ACTIONS_SER = 'actions_ser';
  4596. const POS = 'pos';
  4597. const IS_STICKY = 'is_sticky';
  4598. const STICKY_ORDER = 'sticky_order';
  4599. const IS_STACKABLE = 'is_stackable';
  4600. public static function create($fields) {
  4601. $db = DevblocksPlatform::getDatabaseService();
  4602. $id = $db->GenID('generic_seq');
  4603. $sql = sprintf("INSERT INTO group_inbox_filter (id,name,created,group_id,criteria_ser,actions_ser,pos,is_sticky,sticky_order,is_stackable) ".
  4604. "VALUES (%d,'',%d,0,'','',0,0,0,0)",
  4605. $id,
  4606. time()
  4607. );
  4608. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4609. self::update($id, $fields);
  4610. return $id;
  4611. }
  4612. public static function increment($id) {
  4613. $db = DevblocksPlatform::getDatabaseService();
  4614. $db->Execute(sprintf("UPDATE group_inbox_filter SET pos = pos + 1 WHERE id = %d",
  4615. $id
  4616. ));
  4617. }
  4618. public static function update($id, $fields) {
  4619. self::_update($id, 'group_inbox_filter', $fields);
  4620. }
  4621. /**
  4622. * Enter description here...
  4623. *
  4624. * @param integer $id
  4625. * @return Model_GroupInboxFilter
  4626. */
  4627. public static function get($id) {
  4628. $items = self::getList(array($id));
  4629. if(isset($items[$id]))
  4630. return $items[$id];
  4631. return NULL;
  4632. }
  4633. /**
  4634. * Enter description here...
  4635. *
  4636. * @param integer $group_id
  4637. * @return Model_GroupInboxFilter
  4638. */
  4639. public static function getByGroupId($group_id) {
  4640. if(empty($group_id)) return array();
  4641. $db = DevblocksPlatform::getDatabaseService();
  4642. $sql = sprintf("SELECT id, name, group_id, criteria_ser, actions_ser, pos, is_sticky, sticky_order, is_stackable ".
  4643. "FROM group_inbox_filter ".
  4644. "WHERE group_id = %d ".
  4645. "ORDER BY is_sticky DESC, sticky_order ASC, pos DESC",
  4646. $group_id
  4647. );
  4648. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4649. return self::_getResultsAsModel($rs);
  4650. }
  4651. /**
  4652. * @return Model_GroupInboxFilter[]
  4653. */
  4654. public static function getList($ids=array()) {
  4655. if(!is_array($ids)) $ids = array($ids);
  4656. $db = DevblocksPlatform::getDatabaseService();
  4657. $sql = "SELECT id, name, group_id, criteria_ser, actions_ser, pos, is_sticky, sticky_order, is_stackable ".
  4658. "FROM group_inbox_filter ".
  4659. (!empty($ids) ? sprintf("WHERE id IN (%s) ", implode(',', $ids)) : " ").
  4660. "ORDER BY is_sticky DESC, sticky_order ASC, pos DESC"
  4661. ;
  4662. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4663. return self::_getResultsAsModel($rs);
  4664. }
  4665. /**
  4666. * @param ADORecordSet $rs
  4667. * @return Model_GroupInboxFilter[]
  4668. */
  4669. private static function _getResultsAsModel($rs) {
  4670. $objects = array();
  4671. if(is_a($rs,'ADORecordSet'))
  4672. while(!$rs->EOF) {
  4673. $object = new Model_GroupInboxFilter();
  4674. $object->id = intval($rs->fields['id']);
  4675. $object->name = $rs->fields['name'];
  4676. $object->group_id = intval($rs->fields['group_id']);
  4677. $object->pos = intval($rs->fields['pos']);
  4678. $object->is_sticky = intval($rs->fields['is_sticky']);
  4679. $object->sticky_order = intval($rs->fields['sticky_order']);
  4680. $object->is_stackable = intval($rs->fields['is_stackable']);
  4681. // Criteria
  4682. $criteria_ser = $rs->fields['criteria_ser'];
  4683. if(!empty($criteria_ser))
  4684. @$criteria = unserialize($criteria_ser);
  4685. if(is_array($criteria))
  4686. $object->criteria = $criteria;
  4687. // Actions
  4688. $actions_ser = $rs->fields['actions_ser'];
  4689. if(!empty($actions_ser))
  4690. @$actions = unserialize($actions_ser);
  4691. if(is_array($actions))
  4692. $object->actions = $actions;
  4693. $objects[$object->id] = $object;
  4694. $rs->MoveNext();
  4695. }
  4696. return $objects;
  4697. }
  4698. public static function delete($ids) {
  4699. if(!is_array($ids)) $ids = array($ids);
  4700. $db = DevblocksPlatform::getDatabaseService();
  4701. if(empty($ids))
  4702. return;
  4703. $id_list = implode(',', $ids);
  4704. $sql = sprintf("DELETE QUICK FROM group_inbox_filter WHERE id IN (%s)", $id_list);
  4705. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4706. }
  4707. /**
  4708. * Enter description here...
  4709. *
  4710. * @param DevblocksSearchCriteria[] $params
  4711. * @param integer $limit
  4712. * @param integer $page
  4713. * @param string $sortBy
  4714. * @param boolean $sortAsc
  4715. * @param boolean $withCounts
  4716. * @return array
  4717. */
  4718. static function search($params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  4719. $db = DevblocksPlatform::getDatabaseService();
  4720. $fields = SearchFields_GroupInboxFilter::getFields();
  4721. // Sanitize
  4722. if(!isset($fields[$sortBy]))
  4723. $sortBy=null;
  4724. list($tables,$wheres) = parent::_parseSearchParams($params, array(), $fields,$sortBy);
  4725. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  4726. $sql = sprintf("SELECT ".
  4727. "trr.id as %s, ".
  4728. "trr.group_id as %s, ".
  4729. "trr.pos as %s, ".
  4730. "trr.is_sticky as %s, ".
  4731. "trr.sticky_order as %s, ".
  4732. "trr.is_stackable as %s ".
  4733. "FROM group_inbox_filter trr ",
  4734. // "INNER JOIN team tm ON (tm.id = t.team_id) ".
  4735. SearchFields_GroupInboxFilter::ID,
  4736. SearchFields_GroupInboxFilter::GROUP_ID,
  4737. SearchFields_GroupInboxFilter::POS,
  4738. SearchFields_GroupInboxFilter::IS_STICKY,
  4739. SearchFields_GroupInboxFilter::STICKY_ORDER,
  4740. SearchFields_GroupInboxFilter::IS_STACKABLE
  4741. ).
  4742. // [JAS]: Dynamic table joins
  4743. // (isset($tables['ra']) ? "INNER JOIN requester r ON (r.ticket_id=t.id)" : " ").
  4744. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "").
  4745. (!empty($sortBy) ? sprintf("ORDER BY %s %s",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : "")
  4746. ;
  4747. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4748. $results = array();
  4749. if(is_a($rs,'ADORecordSet'))
  4750. while(!$rs->EOF) {
  4751. $result = array();
  4752. foreach($rs->fields as $f => $v) {
  4753. $result[$f] = $v;
  4754. }
  4755. $row_id = intval($rs->fields[SearchFields_GroupInboxFilter::ID]);
  4756. $results[$row_id] = $result;
  4757. $rs->MoveNext();
  4758. }
  4759. // [JAS]: Count all
  4760. $total = -1;
  4761. if($withCounts) {
  4762. $rs = $db->Execute($sql);
  4763. $total = $rs->RecordCount();
  4764. }
  4765. return array($results,$total);
  4766. }
  4767. };
  4768. class SearchFields_GroupInboxFilter implements IDevblocksSearchFields {
  4769. // Table
  4770. const ID = 'trr_id';
  4771. const GROUP_ID = 'trr_group_id';
  4772. const POS = 'trr_pos';
  4773. const IS_STICKY = 'trr_is_sticky';
  4774. const STICKY_ORDER = 'trr_sticky_order';
  4775. const IS_STACKABLE = 'trr_is_stackable';
  4776. /**
  4777. * @return DevblocksSearchField[]
  4778. */
  4779. static function getFields() {
  4780. $columns = array(
  4781. self::ID => new DevblocksSearchField(self::ID, 'trr', 'id'),
  4782. self::GROUP_ID => new DevblocksSearchField(self::GROUP_ID, 'trr', 'group_id'),
  4783. self::POS => new DevblocksSearchField(self::POS, 'trr', 'pos'),
  4784. self::IS_STICKY => new DevblocksSearchField(self::IS_STICKY, 'trr', 'is_sticky'),
  4785. self::STICKY_ORDER => new DevblocksSearchField(self::STICKY_ORDER, 'trr', 'sticky_order'),
  4786. self::IS_STACKABLE => new DevblocksSearchField(self::IS_STACKABLE, 'trr', 'is_stackable'),
  4787. );
  4788. // Sort by label (translation-conscious)
  4789. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  4790. return $columns;
  4791. }
  4792. };
  4793. class DAO_MailTemplate extends DevblocksORMHelper {
  4794. const _TABLE = 'mail_template';
  4795. const ID = 'id';
  4796. const TITLE = 'title';
  4797. const DESCRIPTION = 'description';
  4798. const FOLDER = 'folder';
  4799. const TEMPLATE_TYPE = 'template_type';
  4800. const OWNER_ID = 'owner_id';
  4801. const CONTENT = 'content';
  4802. public static function create($fields) {
  4803. $db = DevblocksPlatform::getDatabaseService();
  4804. $id = $db->GenID('generic_seq');
  4805. $sql = sprintf("INSERT INTO %s (id,title,description,folder,template_type,owner_id,content) ".
  4806. "VALUES (%d,'','','',0,0,'')",
  4807. self::_TABLE,
  4808. $id
  4809. );
  4810. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4811. self::update($id, $fields);
  4812. return $id;
  4813. }
  4814. /**
  4815. * Enter description here...
  4816. *
  4817. * @return array
  4818. */
  4819. public static function getFolders($type=null) {
  4820. $db = DevblocksPlatform::getDatabaseService();
  4821. $folders = array();
  4822. $sql = sprintf("SELECT DISTINCT folder FROM %s %s ORDER BY folder",
  4823. self::_TABLE,
  4824. (!empty($type) ? sprintf("WHERE %s = %d ",self::TEMPLATE_TYPE,$type) : " ")
  4825. );
  4826. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4827. if(is_a($rs,'ADORecordSet'))
  4828. while(!$rs->EOF) {
  4829. $folders[] = $rs->fields['folder'];
  4830. $rs->MoveNext();
  4831. }
  4832. return $folders;
  4833. }
  4834. public static function update($ids, $fields) {
  4835. // [TODO] Overload CONTENT as BlobUpdate
  4836. parent::_update($ids, self::_TABLE, $fields);
  4837. }
  4838. public static function delete($ids) {
  4839. if(!is_array($ids)) $ids = array($ids);
  4840. $db = DevblocksPlatform::getDatabaseService();
  4841. if(empty($ids))
  4842. return;
  4843. $sql = sprintf("DELETE QUICK FROM %s WHERE id IN (%s)",
  4844. self::_TABLE,
  4845. implode(',', $ids)
  4846. );
  4847. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4848. }
  4849. public function getByType($type) {
  4850. return self::getWhere(sprintf("%s = %d",
  4851. self::TEMPLATE_TYPE,
  4852. $type
  4853. ));
  4854. }
  4855. /**
  4856. * Enter description here...
  4857. *
  4858. * @param string $where
  4859. * @return Model_MailTemplate[]
  4860. */
  4861. public function getWhere($where=null) {
  4862. $db = DevblocksPlatform::getDatabaseService();
  4863. $sql = sprintf("SELECT id,title,description,folder,template_type,owner_id,content ".
  4864. "FROM %s ".
  4865. (!empty($where) ? ("WHERE $where ") : " ").
  4866. " ORDER BY folder, title ",
  4867. self::_TABLE
  4868. );
  4869. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  4870. return self::_createObjectsFromResultSet($rs);
  4871. }
  4872. /**
  4873. * Enter description here...
  4874. *
  4875. * @param integer $id
  4876. * @return Model_MailTemplate
  4877. */
  4878. public static function get($id) {
  4879. $objects = self::getWhere(sprintf("id = %d", $id));
  4880. if(isset($objects[$id]))
  4881. return $objects[$id];
  4882. return null;
  4883. }
  4884. public static function _createObjectsFromResultSet(ADORecordSet $rs) {
  4885. $objects = array();
  4886. if(is_a($rs,'ADORecordSet'))
  4887. while(!$rs->EOF) {
  4888. $object = new Model_MailTemplate();
  4889. $object->id = intval($rs->fields['id']);
  4890. $object->title = $rs->fields['title'];
  4891. $object->description = $rs->fields['description'];
  4892. $object->folder = $rs->fields['folder'];
  4893. $object->template_type = intval($rs->fields['template_type']);
  4894. $object->owner_id = intval($rs->fields['owner_id']);
  4895. $object->content = $rs->fields['content'];
  4896. $objects[$object->id] = $object;
  4897. $rs->MoveNext();
  4898. }
  4899. return $objects;
  4900. }
  4901. };
  4902. class DAO_TicketComment extends DevblocksORMHelper {
  4903. const ID = 'id';
  4904. const TICKET_ID = 'ticket_id';
  4905. const ADDRESS_ID = 'address_id';
  4906. const CREATED = 'created';
  4907. const COMMENT = 'comment';
  4908. static function create($fields) {
  4909. $db = DevblocksPlatform::getDatabaseService();
  4910. $id = $db->GenID('ticket_comment_seq');
  4911. $sql = sprintf("INSERT INTO ticket_comment (id) ".
  4912. "VALUES (%d)",
  4913. $id
  4914. );
  4915. $db->Execute($sql);
  4916. self::update($id, $fields);
  4917. /* This event fires after the change takes place in the db,
  4918. * which is important if the listener needs to stack changes
  4919. */
  4920. if(!empty($fields[self::TICKET_ID]) && !empty($fields[self::ADDRESS_ID]) && !empty($fields[self::COMMENT])) {
  4921. $eventMgr = DevblocksPlatform::getEventService();
  4922. $eventMgr->trigger(
  4923. new Model_DevblocksEvent(
  4924. 'ticket.comment.create',
  4925. array(
  4926. 'comment_id' => $id,
  4927. 'ticket_id' => $fields[self::TICKET_ID],
  4928. 'address_id' => $fields[self::ADDRESS_ID],
  4929. 'comment' => $fields[self::COMMENT],
  4930. )
  4931. )
  4932. );
  4933. }
  4934. return $id;
  4935. }
  4936. static function update($ids, $fields) {
  4937. parent::_update($ids, 'ticket_comment', $fields);
  4938. }
  4939. /**
  4940. * @param string $where
  4941. * @return Model_TicketComment[]
  4942. */
  4943. static function getWhere($where=null) {
  4944. $db = DevblocksPlatform::getDatabaseService();
  4945. $sql = "SELECT id, ticket_id, address_id, created, comment ".
  4946. "FROM ticket_comment ".
  4947. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  4948. "ORDER BY created asc";
  4949. $rs = $db->Execute($sql);
  4950. return self::_getObjectsFromResult($rs);
  4951. }
  4952. static function getByTicketId($id) {
  4953. return self::getWhere(sprintf("%s = %d",
  4954. self::TICKET_ID,
  4955. $id
  4956. ));
  4957. }
  4958. static function getCountByTicketId($id) {
  4959. $db = DevblocksPlatform::getDatabaseService();
  4960. $sql = sprintf("SELECT count(id) FROM ticket_comment WHERE ticket_id = %d",
  4961. $id
  4962. );
  4963. return $db->GetOne($sql);
  4964. }
  4965. /**
  4966. * @param integer $id
  4967. * @return Model_TicketComment */
  4968. static function get($id) {
  4969. $objects = self::getWhere(sprintf("%s = %d",
  4970. self::ID,
  4971. $id
  4972. ));
  4973. if(isset($objects[$id]))
  4974. return $objects[$id];
  4975. return null;
  4976. }
  4977. /**
  4978. * @param ADORecordSet $rs
  4979. * @return Model_TicketComment[]
  4980. */
  4981. static private function _getObjectsFromResult($rs) {
  4982. $objects = array();
  4983. if(is_a($rs,'ADORecordSet'))
  4984. while(!$rs->EOF) {
  4985. $object = new Model_TicketComment();
  4986. $object->id = $rs->fields['id'];
  4987. $object->ticket_id = $rs->fields['ticket_id'];
  4988. $object->address_id = $rs->fields['address_id'];
  4989. $object->created = $rs->fields['created'];
  4990. $object->comment = $rs->fields['comment'];
  4991. $objects[$object->id] = $object;
  4992. $rs->MoveNext();
  4993. }
  4994. return $objects;
  4995. }
  4996. static function delete($ids) {
  4997. if(!is_array($ids)) $ids = array($ids);
  4998. $db = DevblocksPlatform::getDatabaseService();
  4999. if(empty($ids))
  5000. return;
  5001. $ids_list = implode(',', $ids);
  5002. $db->Execute(sprintf("DELETE QUICK FROM ticket_comment WHERE id IN (%s)", $ids_list));
  5003. return true;
  5004. }
  5005. };
  5006. class DAO_CustomField extends DevblocksORMHelper {
  5007. const ID = 'id';
  5008. const NAME = 'name';
  5009. const TYPE = 'type';
  5010. const GROUP_ID = 'group_id';
  5011. const SOURCE_EXTENSION = 'source_extension';
  5012. const POS = 'pos';
  5013. const OPTIONS = 'options';
  5014. const CACHE_ALL = 'ch_customfields';
  5015. static function create($fields) {
  5016. $db = DevblocksPlatform::getDatabaseService();
  5017. $id = $db->GenID('custom_field_seq');
  5018. $sql = sprintf("INSERT INTO custom_field (id,name,type,source_extension,group_id,pos,options) ".
  5019. "VALUES (%d,'','','',0,0,'')",
  5020. $id
  5021. );
  5022. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5023. self::update($id, $fields);
  5024. return $id;
  5025. }
  5026. static function update($ids, $fields) {
  5027. parent::_update($ids, 'custom_field', $fields);
  5028. self::clearCache();
  5029. }
  5030. /**
  5031. * Enter description here...
  5032. *
  5033. * @param integer $id
  5034. * @return Model_CustomField|null
  5035. */
  5036. static function get($id) {
  5037. $fields = self::getAll();
  5038. if(isset($fields[$id]))
  5039. return $fields[$id];
  5040. return null;
  5041. }
  5042. static function getBySourceAndGroupId($source_ext_id, $group_id) {
  5043. $fields = self::getAll();
  5044. // Filter out groups that don't match
  5045. foreach($fields as $field_id => $field) { /* @var $field Model_CustomField */
  5046. if($group_id != $field->group_id || $source_ext_id != $field->source_extension) {
  5047. unset($fields[$field_id]);
  5048. }
  5049. }
  5050. return $fields;
  5051. }
  5052. static function getBySource($source_ext_id) {
  5053. $fields = self::getAll();
  5054. // Filter fields to only the requested source
  5055. foreach($fields as $idx => $field) { /* @var $field Model_CustomField */
  5056. if(0 != strcasecmp($field->source_extension, $source_ext_id))
  5057. unset($fields[$idx]);
  5058. }
  5059. return $fields;
  5060. }
  5061. static function getAll($nocache=false) {
  5062. $cache = DevblocksPlatform::getCacheService();
  5063. if(null === ($objects = $cache->load(self::CACHE_ALL))) {
  5064. $db = DevblocksPlatform::getDatabaseService();
  5065. $sql = "SELECT id, name, type, source_extension, group_id, pos, options ".
  5066. "FROM custom_field ".
  5067. "ORDER BY group_id ASC, pos ASC "
  5068. ;
  5069. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5070. $objects = self::_createObjectsFromResultSet($rs);
  5071. $cache->save($objects, self::CACHE_ALL);
  5072. }
  5073. return $objects;
  5074. }
  5075. private static function _createObjectsFromResultSet(ADORecordSet $rs) {
  5076. $db = DevblocksPlatform::getDatabaseService();
  5077. $objects = array();
  5078. if($rs instanceof ADORecordSet)
  5079. while(!$rs->EOF) {
  5080. $object = new Model_CustomField();
  5081. $object->id = intval($rs->fields['id']);
  5082. $object->name = $rs->fields['name'];
  5083. $object->type = $rs->fields['type'];
  5084. $object->source_extension = $rs->fields['source_extension'];
  5085. $object->group_id = intval($rs->fields['group_id']);
  5086. $object->pos = intval($rs->fields['pos']);
  5087. $object->options = DevblocksPlatform::parseCrlfString($rs->fields['options']);
  5088. $objects[$object->id] = $object;
  5089. $rs->MoveNext();
  5090. }
  5091. return $objects;
  5092. }
  5093. public static function delete($ids) {
  5094. if(!is_array($ids)) $ids = array($ids);
  5095. if(empty($ids))
  5096. return;
  5097. $db = DevblocksPlatform::getDatabaseService();
  5098. $id_string = implode(',', $ids);
  5099. $sql = sprintf("DELETE QUICK FROM custom_field WHERE id IN (%s)",$id_string);
  5100. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5101. if(is_array($ids))
  5102. foreach($ids as $id) {
  5103. DAO_CustomFieldValue::deleteByFieldId($id);
  5104. }
  5105. self::clearCache();
  5106. }
  5107. public static function clearCache() {
  5108. // Invalidate cache on changes
  5109. $cache = DevblocksPlatform::getCacheService();
  5110. $cache->remove(self::CACHE_ALL);
  5111. }
  5112. };
  5113. class DAO_CustomFieldValue extends DevblocksORMHelper {
  5114. const FIELD_ID = 'field_id';
  5115. const SOURCE_EXTENSION = 'source_extension';
  5116. const SOURCE_ID = 'source_id';
  5117. const FIELD_VALUE = 'field_value';
  5118. public static function getValueTableName($field_id) {
  5119. $field = DAO_CustomField::get($field_id);
  5120. // Determine value table by type
  5121. $table = null;
  5122. switch($field->type) {
  5123. // stringvalue
  5124. case Model_CustomField::TYPE_SINGLE_LINE:
  5125. case Model_CustomField::TYPE_DROPDOWN:
  5126. case Model_CustomField::TYPE_MULTI_CHECKBOX:
  5127. case Model_CustomField::TYPE_MULTI_PICKLIST:
  5128. case Model_CustomField::TYPE_URL:
  5129. $table = 'custom_field_stringvalue';
  5130. break;
  5131. // clobvalue
  5132. case Model_CustomField::TYPE_MULTI_LINE:
  5133. $table = 'custom_field_clobvalue';
  5134. break;
  5135. // number
  5136. case Model_CustomField::TYPE_CHECKBOX:
  5137. case Model_CustomField::TYPE_DATE:
  5138. case Model_CustomField::TYPE_NUMBER:
  5139. case Model_CustomField::TYPE_WORKER:
  5140. $table = 'custom_field_numbervalue';
  5141. break;
  5142. }
  5143. return $table;
  5144. }
  5145. /**
  5146. *
  5147. * @param object $source_ext_id
  5148. * @param object $source_id
  5149. * @param object $values
  5150. * @return
  5151. */
  5152. public static function formatAndSetFieldValues($source_ext_id, $source_id, $values, $is_blank_unset=true) {
  5153. if(empty($source_ext_id) || empty($source_id) || !is_array($values))
  5154. return;
  5155. $fields = DAO_CustomField:: getBySource($source_ext_id);
  5156. foreach($values as $field_id => $value) {
  5157. if(!isset($fields[$field_id]))
  5158. continue;
  5159. $field =& $fields[$field_id]; /* @var $field Model_CustomField */
  5160. $delta = ($field->type==Model_CustomField::TYPE_MULTI_CHECKBOX || $field->type==Model_CustomField::TYPE_MULTI_PICKLIST)
  5161. ? true
  5162. : false
  5163. ;
  5164. // if the field is blank
  5165. if(0==strlen($value)) {
  5166. // ... and blanks should unset
  5167. if($is_blank_unset && !$delta)
  5168. self::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5169. // Skip setting
  5170. continue;
  5171. }
  5172. switch($field->type) {
  5173. case Model_CustomField::TYPE_SINGLE_LINE:
  5174. case Model_CustomField::TYPE_URL:
  5175. $value = (strlen($value) > 255) ? substr($value,0,255) : $value;
  5176. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5177. break;
  5178. case Model_CustomField::TYPE_MULTI_LINE:
  5179. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5180. break;
  5181. case Model_CustomField::TYPE_DROPDOWN:
  5182. case Model_CustomField::TYPE_MULTI_PICKLIST:
  5183. case Model_CustomField::TYPE_MULTI_CHECKBOX:
  5184. // If we're setting a field that doesn't exist yet, add it.
  5185. if(!in_array($value,$field->options) && !empty($value)) {
  5186. $field->options[] = $value;
  5187. DAO_CustomField::update($field_id, array(DAO_CustomField::OPTIONS => implode("\n",$field->options)));
  5188. }
  5189. // If we're allowed to add/remove fields without touching the rest
  5190. self::setFieldValue($source_ext_id, $source_id, $field_id, $value, $delta);
  5191. break;
  5192. case Model_CustomField::TYPE_CHECKBOX:
  5193. $value = !empty($value) ? 1 : 0;
  5194. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5195. break;
  5196. case Model_CustomField::TYPE_DATE:
  5197. @$value = strtotime($value);
  5198. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5199. break;
  5200. case Model_CustomField::TYPE_NUMBER:
  5201. $value = intval($value);
  5202. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5203. break;
  5204. case Model_CustomField::TYPE_WORKER:
  5205. $value = intval($value);
  5206. self::setFieldValue($source_ext_id, $source_id, $field_id, $value);
  5207. break;
  5208. }
  5209. }
  5210. }
  5211. public static function setFieldValue($source_ext_id, $source_id, $field_id, $value, $delta=false) {
  5212. $db = DevblocksPlatform::getDatabaseService();
  5213. if(null == ($field = DAO_CustomField::get($field_id)))
  5214. return FALSE;
  5215. if(null == ($table_name = self::getValueTableName($field_id)))
  5216. return FALSE;
  5217. // Data formating
  5218. switch($field->type) {
  5219. case 'D': // dropdown
  5220. case 'S': // string
  5221. case 'U': // URL
  5222. if(255 < strlen($value))
  5223. $value = substr($value,0,255);
  5224. break;
  5225. case 'N': // number
  5226. case 'W': // worker
  5227. $value = intval($value);
  5228. }
  5229. // Clear existing values (beats replace logic)
  5230. self::unsetFieldValue($source_ext_id, $source_id, $field_id, ($delta?$value:null));
  5231. // Set values consistently
  5232. if(!is_array($value))
  5233. $value = array($value);
  5234. foreach($value as $v) {
  5235. $sql = sprintf("INSERT INTO %s (field_id, source_extension, source_id, field_value) ".
  5236. "VALUES (%d, %s, %d, %s)",
  5237. $table_name,
  5238. $field_id,
  5239. $db->qstr($source_ext_id),
  5240. $source_id,
  5241. $db->qstr($v)
  5242. );
  5243. $db->Execute($sql);
  5244. }
  5245. return TRUE;
  5246. }
  5247. public static function unsetFieldValue($source_ext_id, $source_id, $field_id, $value=null) {
  5248. $db = DevblocksPlatform::getDatabaseService();
  5249. if(null == ($field = DAO_CustomField::get($field_id)))
  5250. return FALSE;
  5251. if(null == ($table_name = self::getValueTableName($field_id)))
  5252. return FALSE;
  5253. // Delete all values or optionally a specific given value
  5254. $sql = sprintf("DELETE QUICK FROM %s WHERE source_extension = '%s' AND source_id = %d AND field_id = %d %s",
  5255. $table_name,
  5256. $source_ext_id,
  5257. $source_id,
  5258. $field_id,
  5259. (!is_null($value) ? sprintf("AND field_value = %s ",$db->qstr($value)) : "")
  5260. );
  5261. return $db->Execute($sql);
  5262. }
  5263. public static function handleBulkPost($do) {
  5264. @$field_ids = DevblocksPlatform::importGPC($_POST['field_ids'],'array',array());
  5265. $fields = DAO_CustomField::getAll();
  5266. if(is_array($field_ids))
  5267. foreach($field_ids as $field_id) {
  5268. if(!isset($fields[$field_id]))
  5269. continue;
  5270. switch($fields[$field_id]->type) {
  5271. case Model_CustomField::TYPE_MULTI_LINE:
  5272. case Model_CustomField::TYPE_SINGLE_LINE:
  5273. case Model_CustomField::TYPE_URL:
  5274. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5275. $do['cf_'.$field_id] = array('value' => $field_value);
  5276. break;
  5277. case Model_CustomField::TYPE_NUMBER:
  5278. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5279. $field_value = (0==strlen($field_value)) ? '' : intval($field_value);
  5280. $do['cf_'.$field_id] = array('value' => $field_value);
  5281. break;
  5282. case Model_CustomField::TYPE_DROPDOWN:
  5283. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5284. $do['cf_'.$field_id] = array('value' => $field_value);
  5285. break;
  5286. case Model_CustomField::TYPE_MULTI_PICKLIST:
  5287. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'array',array());
  5288. $do['cf_'.$field_id] = array('value' => $field_value);
  5289. break;
  5290. case Model_CustomField::TYPE_CHECKBOX:
  5291. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'integer',0);
  5292. $do['cf_'.$field_id] = array('value' => !empty($field_value) ? 1 : 0);
  5293. break;
  5294. case Model_CustomField::TYPE_MULTI_CHECKBOX:
  5295. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'array',array());
  5296. $do['cf_'.$field_id] = array('value' => $field_value);
  5297. break;
  5298. case Model_CustomField::TYPE_DATE:
  5299. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5300. $do['cf_'.$field_id] = array('value' => $field_value);
  5301. break;
  5302. case Model_CustomField::TYPE_WORKER:
  5303. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5304. $do['cf_'.$field_id] = array('value' => $field_value);
  5305. break;
  5306. }
  5307. }
  5308. return $do;
  5309. }
  5310. public static function handleFormPost($source_ext_id, $source_id, $field_ids) {
  5311. $fields = DAO_CustomField::getBySource($source_ext_id);
  5312. if(is_array($field_ids))
  5313. foreach($field_ids as $field_id) {
  5314. if(!isset($fields[$field_id]))
  5315. continue;
  5316. switch($fields[$field_id]->type) {
  5317. case Model_CustomField::TYPE_MULTI_LINE:
  5318. case Model_CustomField::TYPE_SINGLE_LINE:
  5319. case Model_CustomField::TYPE_URL:
  5320. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5321. if(0 != strlen($field_value)) {
  5322. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $field_value);
  5323. } else {
  5324. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5325. }
  5326. break;
  5327. case Model_CustomField::TYPE_DROPDOWN:
  5328. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5329. if(0 != strlen($field_value)) {
  5330. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $field_value);
  5331. } else {
  5332. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5333. }
  5334. break;
  5335. case Model_CustomField::TYPE_MULTI_PICKLIST:
  5336. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'array',array());
  5337. if(!empty($field_value)) {
  5338. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $field_value);
  5339. } else {
  5340. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5341. }
  5342. break;
  5343. case Model_CustomField::TYPE_CHECKBOX:
  5344. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'integer',0);
  5345. $set = !empty($field_value) ? 1 : 0;
  5346. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $set);
  5347. break;
  5348. case Model_CustomField::TYPE_MULTI_CHECKBOX:
  5349. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'array',array());
  5350. if(!empty($field_value)) {
  5351. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $field_value);
  5352. } else {
  5353. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5354. }
  5355. break;
  5356. case Model_CustomField::TYPE_DATE:
  5357. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5358. @$date = strtotime($field_value);
  5359. if(!empty($date)) {
  5360. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, $date);
  5361. } else {
  5362. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5363. }
  5364. break;
  5365. case Model_CustomField::TYPE_NUMBER:
  5366. case Model_CustomField::TYPE_WORKER:
  5367. @$field_value = DevblocksPlatform::importGPC($_POST['field_'.$field_id],'string','');
  5368. if(0 != strlen($field_value)) {
  5369. DAO_CustomFieldValue::setFieldValue($source_ext_id, $source_id, $field_id, intval($field_value));
  5370. } else {
  5371. DAO_CustomFieldValue::unsetFieldValue($source_ext_id, $source_id, $field_id);
  5372. }
  5373. break;
  5374. }
  5375. }
  5376. return true;
  5377. }
  5378. public static function getValuesBySourceIds($source_ext_id, $source_ids) {
  5379. if(!is_array($source_ids)) $source_ids = array($source_ids);
  5380. $db = DevblocksPlatform::getDatabaseService();
  5381. $results = array();
  5382. if(empty($source_ids))
  5383. return array();
  5384. $fields = DAO_CustomField::getAll();
  5385. // [TODO] This is inefficient (and redundant)
  5386. // STRINGS
  5387. $sql = sprintf("SELECT source_id, field_id, field_value ".
  5388. "FROM custom_field_stringvalue ".
  5389. "WHERE source_extension = '%s' AND source_id IN (%s)",
  5390. $source_ext_id,
  5391. implode(',', $source_ids)
  5392. );
  5393. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5394. if(is_a($rs,'ADORecordSet'))
  5395. while(!$rs->EOF) {
  5396. $source_id = intval($rs->fields['source_id']);
  5397. $field_id = intval($rs->fields['field_id']);
  5398. $field_value = $rs->fields['field_value'];
  5399. if(!isset($results[$source_id]))
  5400. $results[$source_id] = array();
  5401. $source =& $results[$source_id];
  5402. // If multiple value type (multi-picklist, multi-checkbox)
  5403. if($fields[$field_id]->type=='M' || $fields[$field_id]->type=='X') {
  5404. if(!isset($source[$field_id]))
  5405. $source[$field_id] = array();
  5406. $source[$field_id][$field_value] = $field_value;
  5407. } else { // single value
  5408. $source[$field_id] = $field_value;
  5409. }
  5410. $rs->MoveNext();
  5411. }
  5412. // CLOBS
  5413. $sql = sprintf("SELECT source_id, field_id, field_value ".
  5414. "FROM custom_field_clobvalue ".
  5415. "WHERE source_extension = '%s' AND source_id IN (%s)",
  5416. $source_ext_id,
  5417. implode(',', $source_ids)
  5418. );
  5419. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5420. if(is_a($rs,'ADORecordSet'))
  5421. while(!$rs->EOF) {
  5422. $source_id = intval($rs->fields['source_id']);
  5423. $field_id = intval($rs->fields['field_id']);
  5424. $field_value = $rs->fields['field_value'];
  5425. if(!isset($results[$source_id]))
  5426. $results[$source_id] = array();
  5427. $source =& $results[$source_id];
  5428. $source[$field_id] = $field_value;
  5429. $rs->MoveNext();
  5430. }
  5431. // NUMBERS
  5432. $sql = sprintf("SELECT source_id, field_id, field_value ".
  5433. "FROM custom_field_numbervalue ".
  5434. "WHERE source_extension = '%s' AND source_id IN (%s)",
  5435. $source_ext_id,
  5436. implode(',', $source_ids)
  5437. );
  5438. $rs = $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5439. if(is_a($rs,'ADORecordSet'))
  5440. while(!$rs->EOF) {
  5441. $source_id = intval($rs->fields['source_id']);
  5442. $field_id = intval($rs->fields['field_id']);
  5443. $field_value = $rs->fields['field_value'];
  5444. if(!isset($results[$source_id]))
  5445. $results[$source_id] = array();
  5446. $source =& $results[$source_id];
  5447. $source[$field_id] = $field_value;
  5448. $rs->MoveNext();
  5449. }
  5450. return $results;
  5451. }
  5452. public static function deleteBySourceIds($source_extension, $source_ids) {
  5453. $db = DevblocksPlatform::getDatabaseService();
  5454. if(!is_array($source_ids)) $source_ids = array($source_ids);
  5455. $ids_list = implode(',', $source_ids);
  5456. $tables = array('custom_field_stringvalue','custom_field_clobvalue','custom_field_numbervalue');
  5457. if(!empty($source_ids))
  5458. foreach($tables as $table) {
  5459. $sql = sprintf("DELETE QUICK FROM %s WHERE source_extension = %s AND source_id IN (%s)",
  5460. $table,
  5461. $db->qstr($source_extension),
  5462. implode(',', $source_ids)
  5463. );
  5464. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg());
  5465. }
  5466. }
  5467. public static function deleteByFieldId($field_id) {
  5468. $db = DevblocksPlatform::getDatabaseService();
  5469. $tables = array('custom_field_stringvalue','custom_field_clobvalue','custom_field_numbervalue');
  5470. foreach($tables as $table) {
  5471. $sql = sprintf("DELETE QUICK FROM %s WHERE field_id = %d",
  5472. $table,
  5473. $field_id
  5474. );
  5475. $db->Execute($sql) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg());
  5476. }
  5477. }
  5478. };
  5479. class DAO_Task extends C4_ORMHelper {
  5480. const ID = 'id';
  5481. const TITLE = 'title';
  5482. const WORKER_ID = 'worker_id';
  5483. const UPDATED_DATE = 'updated_date';
  5484. const DUE_DATE = 'due_date';
  5485. const IS_COMPLETED = 'is_completed';
  5486. const COMPLETED_DATE = 'completed_date';
  5487. const SOURCE_EXTENSION = 'source_extension';
  5488. const SOURCE_ID = 'source_id';
  5489. static function create($fields) {
  5490. $db = DevblocksPlatform::getDatabaseService();
  5491. $id = $db->GenID('task_seq');
  5492. $sql = sprintf("INSERT INTO task (id) ".
  5493. "VALUES (%d)",
  5494. $id
  5495. );
  5496. $db->Execute($sql);
  5497. self::update($id, $fields);
  5498. return $id;
  5499. }
  5500. static function update($ids, $fields) {
  5501. parent::_update($ids, 'task', $fields);
  5502. }
  5503. static function updateWhere($fields, $where) {
  5504. parent::_updateWhere('task', $fields, $where);
  5505. }
  5506. /**
  5507. * @param string $where
  5508. * @return Model_Task[]
  5509. */
  5510. static function getWhere($where=null) {
  5511. $db = DevblocksPlatform::getDatabaseService();
  5512. $sql = "SELECT id, title, worker_id, due_date, updated_date, is_completed, completed_date, source_extension, source_id ".
  5513. "FROM task ".
  5514. (!empty($where) ? sprintf("WHERE %s ",$where) : "").
  5515. "ORDER BY id asc";
  5516. $rs = $db->Execute($sql);
  5517. return self::_getObjectsFromResult($rs);
  5518. }
  5519. /**
  5520. * @param integer $id
  5521. * @return Model_Task */
  5522. static function get($id) {
  5523. $objects = self::getWhere(sprintf("%s = %d",
  5524. self::ID,
  5525. $id
  5526. ));
  5527. if(isset($objects[$id]))
  5528. return $objects[$id];
  5529. return null;
  5530. }
  5531. static function getUnassignedSourceTotals() {
  5532. $db = DevblocksPlatform::getDatabaseService();
  5533. $totals = array();
  5534. $sql = "SELECT count(id) as hits, source_extension ".
  5535. "FROM task ".
  5536. "WHERE is_completed = 0 ".
  5537. "GROUP BY source_extension ";
  5538. $rs = $db->Execute($sql);
  5539. if(is_a($rs,'ADORecordSet'))
  5540. while(!$rs->EOF) {
  5541. $key = !empty($rs->fields['source_extension']) ? $rs->fields['source_extension'] : 'none';
  5542. $totals[$key] = intval($rs->fields['hits']);
  5543. $rs->MoveNext();
  5544. }
  5545. return $totals;
  5546. }
  5547. static function getAssignedSourceTotals() {
  5548. $db = DevblocksPlatform::getDatabaseService();
  5549. $totals = array();
  5550. $sql = "SELECT count(id) as hits, worker_id ".
  5551. "FROM task ".
  5552. "WHERE worker_id > 0 ".
  5553. "AND is_completed = 0 ".
  5554. "GROUP BY worker_id ";
  5555. $rs = $db->Execute($sql);
  5556. if(is_a($rs,'ADORecordSet'))
  5557. while(!$rs->EOF) {
  5558. $totals[$rs->fields['worker_id']] = intval($rs->fields['hits']);
  5559. $rs->MoveNext();
  5560. }
  5561. return $totals;
  5562. }
  5563. /**
  5564. * @param ADORecordSet $rs
  5565. * @return Model_Task[]
  5566. */
  5567. static private function _getObjectsFromResult($rs) {
  5568. $objects = array();
  5569. if(is_a($rs,'ADORecordSet'))
  5570. while(!$rs->EOF) {
  5571. $object = new Model_Task();
  5572. $object->id = $rs->fields['id'];
  5573. $object->title = $rs->fields['title'];
  5574. $object->worker_id = $rs->fields['worker_id'];
  5575. $object->updated_date = $rs->fields['updated_date'];
  5576. $object->due_date = $rs->fields['due_date'];
  5577. $object->is_completed = $rs->fields['is_completed'];
  5578. $object->completed_date = $rs->fields['completed_date'];
  5579. $object->source_extension = $rs->fields['source_extension'];
  5580. $object->source_id = $rs->fields['source_id'];
  5581. $objects[$object->id] = $object;
  5582. $rs->MoveNext();
  5583. }
  5584. return $objects;
  5585. }
  5586. /**
  5587. * Enter description here...
  5588. *
  5589. * @param array $ids
  5590. */
  5591. static function delete($ids) {
  5592. if(!is_array($ids)) $ids = array($ids);
  5593. $db = DevblocksPlatform::getDatabaseService();
  5594. if(empty($ids))
  5595. return;
  5596. $ids_list = implode(',', $ids);
  5597. // Tasks
  5598. $db->Execute(sprintf("DELETE QUICK FROM task WHERE id IN (%s)", $ids_list));
  5599. // Custom fields
  5600. DAO_CustomFieldValue::deleteBySourceIds(ChCustomFieldSource_Task::ID, $ids);
  5601. // Notes
  5602. DAO_Note::deleteBySourceIds(ChNotesSource_Task::ID, $ids);
  5603. return true;
  5604. }
  5605. /**
  5606. * Enter description here...
  5607. *
  5608. * @param string $source
  5609. * @param array $ids
  5610. */
  5611. static function deleteBySourceIds($source_extension, $ids) {
  5612. if(!is_array($ids)) $ids = array($ids);
  5613. $db = DevblocksPlatform::getDatabaseService();
  5614. $ids_list = implode(',', $ids);
  5615. // Tasks
  5616. $db->Execute(sprintf("DELETE QUICK FROM task WHERE source_extension = %s AND source_id IN (%s)",
  5617. $db->qstr($source_extension),
  5618. $ids_list
  5619. ));
  5620. return true;
  5621. }
  5622. static function getCountBySourceObjectId($source_extension, $source_id, $include_completed=false) {
  5623. $db = DevblocksPlatform::getDatabaseService();
  5624. $sql = sprintf("SELECT count(id) ".
  5625. "FROM task ".
  5626. "WHERE source_extension = %s ".
  5627. "AND source_id = %d ".
  5628. (($include_completed) ? " " : "AND is_completed = 0 "),
  5629. $db->qstr($source_extension),
  5630. $source_id
  5631. );
  5632. $total = intval($db->GetOne($sql));
  5633. return $total;
  5634. }
  5635. /**
  5636. * Enter description here...
  5637. *
  5638. * @param DevblocksSearchCriteria[] $params
  5639. * @param integer $limit
  5640. * @param integer $page
  5641. * @param string $sortBy
  5642. * @param boolean $sortAsc
  5643. * @param boolean $withCounts
  5644. * @return array
  5645. */
  5646. static function search($columns, $params, $limit=10, $page=0, $sortBy=null, $sortAsc=null, $withCounts=true) {
  5647. $db = DevblocksPlatform::getDatabaseService();
  5648. $fields = SearchFields_Task::getFields();
  5649. // Sanitize
  5650. if(!isset($fields[$sortBy]))
  5651. $sortBy=null;
  5652. list($tables,$wheres) = parent::_parseSearchParams($params, $columns, $fields,$sortBy);
  5653. $start = ($page * $limit); // [JAS]: 1-based [TODO] clean up + document
  5654. $select_sql = sprintf("SELECT ".
  5655. "t.id as %s, ".
  5656. "t.updated_date as %s, ".
  5657. "t.due_date as %s, ".
  5658. "t.is_completed as %s, ".
  5659. "t.completed_date as %s, ".
  5660. "t.title as %s, ".
  5661. "t.worker_id as %s, ".
  5662. "t.source_extension as %s, ".
  5663. "t.source_id as %s ",
  5664. // "o.name as %s ".
  5665. SearchFields_Task::ID,
  5666. SearchFields_Task::UPDATED_DATE,
  5667. SearchFields_Task::DUE_DATE,
  5668. SearchFields_Task::IS_COMPLETED,
  5669. SearchFields_Task::COMPLETED_DATE,
  5670. SearchFields_Task::TITLE,
  5671. SearchFields_Task::WORKER_ID,
  5672. SearchFields_Task::SOURCE_EXTENSION,
  5673. SearchFields_Task::SOURCE_ID
  5674. );
  5675. $join_sql =
  5676. "FROM task t ";
  5677. // "LEFT JOIN contact_org o ON (o.id=a.contact_org_id) "
  5678. // [JAS]: Dynamic table joins
  5679. // (isset($tables['o']) ? "LEFT JOIN contact_org o ON (o.id=a.contact_org_id)" : " ").
  5680. // (isset($tables['mc']) ? "INNER JOIN message_content mc ON (mc.message_id=m.id)" : " ").
  5681. // Custom field joins
  5682. list($select_sql, $join_sql, $has_multiple_values) = self::_appendSelectJoinSqlForCustomFieldTables(
  5683. $tables,
  5684. $params,
  5685. 't.id',
  5686. $select_sql,
  5687. $join_sql
  5688. );
  5689. $where_sql = "".
  5690. (!empty($wheres) ? sprintf("WHERE %s ",implode(' AND ',$wheres)) : "");
  5691. $sort_sql = (!empty($sortBy) ? sprintf("ORDER BY %s %s ",$sortBy,($sortAsc || is_null($sortAsc))?"ASC":"DESC") : " ");
  5692. $sql =
  5693. $select_sql.
  5694. $join_sql.
  5695. $where_sql.
  5696. ($has_multiple_values ? 'GROUP BY t.id ' : '').
  5697. $sort_sql;
  5698. $rs = $db->SelectLimit($sql,$limit,$start) or die(__CLASS__ . '('.__LINE__.')'. ':' . $db->ErrorMsg()); /* @var $rs ADORecordSet */
  5699. $results = array();
  5700. if(is_a($rs,'ADORecordSet'))
  5701. while(!$rs->EOF) {
  5702. $result = array();
  5703. foreach($rs->fields as $f => $v) {
  5704. $result[$f] = $v;
  5705. }
  5706. $id = intval($rs->fields[SearchFields_Task::ID]);
  5707. $results[$id] = $result;
  5708. $rs->MoveNext();
  5709. }
  5710. // [JAS]: Count all
  5711. $total = -1;
  5712. if($withCounts) {
  5713. $count_sql =
  5714. ($has_multiple_values ? "SELECT COUNT(DISTINCT t.id) " : "SELECT COUNT(t.id) ").
  5715. $join_sql.
  5716. $where_sql;
  5717. $total = $db->GetOne($count_sql);
  5718. }
  5719. return array($results,$total);
  5720. }
  5721. };
  5722. class SearchFields_Task implements IDevblocksSearchFields {
  5723. // Task
  5724. const ID = 't_id';
  5725. const UPDATED_DATE = 't_updated_date';
  5726. const DUE_DATE = 't_due_date';
  5727. const IS_COMPLETED = 't_is_completed';
  5728. const COMPLETED_DATE = 't_completed_date';
  5729. const TITLE = 't_title';
  5730. const WORKER_ID = 't_worker_id';
  5731. const SOURCE_EXTENSION = 't_source_extension';
  5732. const SOURCE_ID = 't_source_id';
  5733. /**
  5734. * @return DevblocksSearchField[]
  5735. */
  5736. static function getFields() {
  5737. $translate = DevblocksPlatform::getTranslationService();
  5738. $columns = array(
  5739. self::ID => new DevblocksSearchField(self::ID, 't', 'id', null, $translate->_('task.id')),
  5740. self::UPDATED_DATE => new DevblocksSearchField(self::UPDATED_DATE, 't', 'updated_date', null, $translate->_('task.updated_date')),
  5741. self::TITLE => new DevblocksSearchField(self::TITLE, 't', 'title', null, $translate->_('task.title')),
  5742. self::IS_COMPLETED => new DevblocksSearchField(self::IS_COMPLETED, 't', 'is_completed', null, $translate->_('task.is_completed')),
  5743. self::DUE_DATE => new DevblocksSearchField(self::DUE_DATE, 't', 'due_date', null, $translate->_('task.due_date')),
  5744. self::COMPLETED_DATE => new DevblocksSearchField(self::COMPLETED_DATE, 't', 'completed_date', null, $translate->_('task.completed_date')),
  5745. self::WORKER_ID => new DevblocksSearchField(self::WORKER_ID, 't', 'worker_id', null, $translate->_('task.worker_id')),
  5746. self::SOURCE_EXTENSION => new DevblocksSearchField(self::SOURCE_EXTENSION, 't', 'source_extension', null, $translate->_('task.source_extension')),
  5747. self::SOURCE_ID => new DevblocksSearchField(self::SOURCE_ID, 't', 'source_id', null, $translate->_('task.source_id')),
  5748. );
  5749. // Custom Fields
  5750. $fields = DAO_CustomField::getBySource(ChCustomFieldSource_Task::ID);
  5751. if(is_array($fields))
  5752. foreach($fields as $field_id => $field) {
  5753. $key = 'cf_'.$field_id;
  5754. $columns[$key] = new DevblocksSearchField($key,$key,'field_value',null,$field->name);
  5755. }
  5756. // Sort by label (translation-conscious)
  5757. uasort($columns, create_function('$a, $b', "return strcasecmp(\$a->db_label,\$b->db_label);\n"));
  5758. return $columns;
  5759. }
  5760. };
  5761. class DAO_Overview {
  5762. static function getGroupTotals() {
  5763. $db = DevblocksPlatform::getDatabaseService();
  5764. $active_worker = CerberusApplication::getActiveWorker();
  5765. $memberships = $active_worker->getMemberships();
  5766. // Does the active worker want to filter anything out?
  5767. // [TODO] DAO_WorkerPref should really auto serialize/deserialize
  5768. if(empty($memberships))
  5769. return array();
  5770. // Group Loads
  5771. $sql = sprintf("SELECT count(*) AS hits, team_id, category_id ".
  5772. "FROM ticket ".
  5773. "WHERE is_waiting = 0 AND is_closed = 0 AND is_deleted = 0 ".
  5774. "GROUP BY team_id, category_id "
  5775. );
  5776. $rs_buckets = $db->Execute($sql);
  5777. $group_counts = array();
  5778. while(!$rs_buckets->EOF) {
  5779. $team_id = intval($rs_buckets->fields['team_id']);
  5780. $category_id = intval($rs_buckets->fields['category_id']);
  5781. $hits = intval($rs_buckets->fields['hits']);
  5782. if(isset($memberships[$team_id])) {
  5783. // If the active worker is filtering out these buckets, don't total.
  5784. if(!isset($group_counts[$team_id]))
  5785. $group_counts[$team_id] = array();
  5786. $group_counts[$team_id][$category_id] = $hits;
  5787. @$group_counts[$team_id]['total'] = intval($group_counts[$team_id]['total']) + $hits;
  5788. }
  5789. $rs_buckets->MoveNext();
  5790. }
  5791. return $group_counts;
  5792. }
  5793. static function getWaitingTotals() {
  5794. $db = DevblocksPlatform::getDatabaseService();
  5795. $active_worker = CerberusApplication::getActiveWorker();
  5796. $memberships = $active_worker->getMemberships();
  5797. if(empty($memberships))
  5798. return array();
  5799. // Waiting For Reply Loads
  5800. $sql = sprintf("SELECT count(*) AS hits, team_id, category_id ".
  5801. "FROM ticket ".
  5802. "WHERE is_waiting = 1 AND is_closed = 0 AND is_deleted = 0 ".
  5803. "GROUP BY team_id, category_id "
  5804. );
  5805. $rs_buckets = $db->Execute($sql);
  5806. $waiting_counts = array();
  5807. while(!$rs_buckets->EOF) {
  5808. $team_id = intval($rs_buckets->fields['team_id']);
  5809. $category_id = intval($rs_buckets->fields['category_id']);
  5810. $hits = intval($rs_buckets->fields['hits']);
  5811. if(isset($memberships[$team_id])) {
  5812. if(!isset($waiting_counts[$team_id]))
  5813. $waiting_counts[$team_id] = array();
  5814. $waiting_counts[$team_id][$category_id] = $hits;
  5815. @$waiting_counts[$team_id]['total'] = intval($waiting_counts[$team_id]['total']) + $hits;
  5816. }
  5817. $rs_buckets->MoveNext();
  5818. }
  5819. return $waiting_counts;
  5820. }
  5821. static function getWorkerTotals() {
  5822. $db = DevblocksPlatform::getDatabaseService();
  5823. $active_worker = CerberusApplication::getActiveWorker();
  5824. $memberships = $active_worker->getMemberships();
  5825. if(empty($memberships))
  5826. return array();
  5827. // Worker Loads
  5828. $sql = sprintf("SELECT count(*) AS hits, t.team_id, t.next_worker_id ".
  5829. "FROM ticket t ".
  5830. "WHERE t.is_waiting = 0 AND t.is_closed = 0 AND t.is_deleted = 0 ".
  5831. "AND t.next_worker_id > 0 ".
  5832. "AND t.team_id IN (%s) ".
  5833. "GROUP BY t.team_id, t.next_worker_id ",
  5834. implode(',', array_keys($memberships))
  5835. );
  5836. $rs_workers = $db->Execute($sql);
  5837. $worker_counts = array();
  5838. while(!$rs_workers->EOF) {
  5839. $hits = intval($rs_workers->fields['hits']);
  5840. $team_id = intval($rs_workers->fields['team_id']);
  5841. $worker_id = intval($rs_workers->fields['next_worker_id']);
  5842. if(!isset($worker_counts[$worker_id]))
  5843. $worker_counts[$worker_id] = array();
  5844. $worker_counts[$worker_id][$team_id] = $hits;
  5845. @$worker_counts[$worker_id]['total'] = intval($worker_counts[$worker_id]['total']) + $hits;
  5846. $rs_workers->MoveNext();
  5847. }
  5848. return $worker_counts;
  5849. }
  5850. }
  5851. class DAO_WorkflowView {
  5852. static function getGroupTotals() {
  5853. $db = DevblocksPlatform::getDatabaseService();
  5854. $active_worker = CerberusApplication::getActiveWorker();
  5855. $memberships = $active_worker->getMemberships();
  5856. if(empty($memberships))
  5857. return array();
  5858. // Group Loads
  5859. $sql = sprintf("SELECT count(t.id) AS hits, t.team_id, t.category_id ".
  5860. "FROM ticket t ".
  5861. "LEFT JOIN category c ON (t.category_id=c.id) ".
  5862. "WHERE t.is_waiting = 0 AND t.is_closed = 0 AND t.is_deleted = 0 ".
  5863. "AND t.next_worker_id = 0 ".
  5864. "AND (c.id IS NULL OR c.is_assignable = 1) ".
  5865. "GROUP BY t.team_id, c.pos "
  5866. );
  5867. $rs_buckets = $db->Execute($sql);
  5868. $group_counts = array();
  5869. while(!$rs_buckets->EOF) {
  5870. $team_id = intval($rs_buckets->fields['team_id']);
  5871. $category_id = intval($rs_buckets->fields['category_id']);
  5872. $hits = intval($rs_buckets->fields['hits']);
  5873. if(isset($memberships[$team_id])) {
  5874. // If the group manager doesn't want this group inbox assignable (default to YES)
  5875. if(empty($category_id) && !DAO_GroupSettings::get($team_id, DAO_GroupSettings::SETTING_INBOX_IS_ASSIGNABLE, 1)) {
  5876. // ...skip the unassignable inbox
  5877. } else {
  5878. if(!isset($group_counts[$team_id]))
  5879. $group_counts[$team_id] = array();
  5880. $group_counts[$team_id][$category_id] = $hits;
  5881. @$group_counts[$team_id]['total'] = intval($group_counts[$team_id]['total']) + $hits;
  5882. }
  5883. }
  5884. $rs_buckets->MoveNext();
  5885. }
  5886. return $group_counts;
  5887. }
  5888. };