PageRenderTime 79ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/api/DAO.class.php

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