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

/tine20/Tinebase/Notes.php

https://gitlab.com/rsilveira1987/Expresso
PHP | 676 lines | 367 code | 91 blank | 218 comment | 46 complexity | b595ffbff4bc17f106c83482841c70ef MD5 | raw file
  1. <?php
  2. /**
  3. * Tine 2.0
  4. *
  5. * @package Tinebase
  6. * @subpackage Notes
  7. * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
  8. * @copyright Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
  9. * @author Philipp Schuele <p.schuele@metaways.de>
  10. *
  11. * @todo delete notes completely or just set the is_deleted flag?
  12. */
  13. /**
  14. * Class for handling notes
  15. *
  16. * @package Tinebase
  17. * @subpackage Notes
  18. */
  19. class Tinebase_Notes implements Tinebase_Backend_Sql_Interface
  20. {
  21. /**
  22. * @var Zend_Db_Adapter_Pdo_Mysql
  23. */
  24. protected $_db;
  25. /**
  26. * @var Tinebase_Db_Table
  27. */
  28. protected $_notesTable;
  29. /**
  30. * @var Tinebase_Db_Table
  31. */
  32. protected $_noteTypesTable;
  33. /**
  34. * default record backend
  35. */
  36. const DEFAULT_RECORD_BACKEND = 'Sql';
  37. /**
  38. * number of notes per record for activities panel
  39. * (NOT the tab panel)
  40. */
  41. const NUMBER_RECORD_NOTES = 8;
  42. /**
  43. * don't clone. Use the singleton.
  44. */
  45. private function __clone()
  46. {
  47. }
  48. /**
  49. * holds the instance of the singleton
  50. *
  51. * @var Tinebase_Notes
  52. */
  53. private static $_instance = NULL;
  54. /**
  55. * the singleton pattern
  56. *
  57. * @return Tinebase_Notes
  58. */
  59. public static function getInstance()
  60. {
  61. if (self::$_instance === NULL) {
  62. self::$_instance = new Tinebase_Notes;
  63. }
  64. return self::$_instance;
  65. }
  66. /**
  67. * the private constructor
  68. *
  69. */
  70. private function __construct()
  71. {
  72. $this->_db = Tinebase_Core::getDb();
  73. $this->_notesTable = new Tinebase_Db_Table(array(
  74. 'name' => SQL_TABLE_PREFIX . 'notes',
  75. 'primary' => 'id',
  76. 'db' => $this->_db
  77. ));
  78. $this->_noteTypesTable = new Tinebase_Db_Table(array(
  79. 'name' => SQL_TABLE_PREFIX . 'note_types',
  80. 'primary' => 'id',
  81. 'db' => $this->_db
  82. ));
  83. }
  84. /************************** sql backend interface ************************/
  85. /**
  86. * get table name
  87. *
  88. * @return string
  89. */
  90. public function getTableName()
  91. {
  92. return 'notes';
  93. }
  94. /**
  95. * get table prefix
  96. *
  97. * @return string
  98. */
  99. public function getTablePrefix()
  100. {
  101. return $this->_db->table_prefix;
  102. }
  103. /**
  104. * get db adapter
  105. *
  106. * @return Zend_Db_Adapter_Abstract
  107. */
  108. public function getAdapter()
  109. {
  110. return $this->_db;
  111. }
  112. /**
  113. * returns the db schema
  114. *
  115. * @return array
  116. */
  117. public function getSchema()
  118. {
  119. return Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . 'notes', $this->_db);
  120. }
  121. /**
  122. * resolve note settings
  123. *
  124. * @param string $_note
  125. * @return string
  126. */
  127. protected function _resolveNote($_note)
  128. {
  129. $note = $_note;
  130. $translate = Tinebase_Translation::getTranslation('Tinebase');
  131. // the attributes that are collections and need to be resolved
  132. $attrs = array('tags', 'attendee', 'alarms');
  133. foreach ($attrs as $attr) {
  134. $regexp = "/$attr\s?\((.*?)\)/";
  135. $works = preg_match_all($regexp, $note, $matches);
  136. if(count($matches[1]) == 0){ // try translated in case of no results
  137. $regexp = '/'.$translate->_($attr).'\s?\((.*?)\)/';
  138. $works = preg_match_all($regexp, $note, $matches);
  139. }
  140. if(count($matches[1]) > 0){
  141. $newNote = $translate->_($attr) . ': ';
  142. $aux1 = explode('->', $matches[1][0]);
  143. // New action ...
  144. $auxNew = json_decode(substr(trim($aux1[1]),0));
  145. // All old actions ...
  146. $auxOlds = explode('},', substr(trim($aux1[0]),1, -1));
  147. foreach($auxOlds as $action) {
  148. if(substr($action,-1) != '}') {
  149. $action = $action . '}';
  150. }
  151. $actionObj = json_decode($action);
  152. if(is_null($actionObj)){
  153. $newNote .= '() ';
  154. }else{
  155. switch ($attr) {
  156. case "tags":
  157. $newNote .= $actionObj->name . '(' . $actionObj->type . '), ';
  158. break;
  159. case "attendee":
  160. $newNote .= Addressbook_Controller_Contact::getInstance()->get($actionObj->user_id)->n_fn . ', ';
  161. break;
  162. case "alarms":
  163. $actionObj->alarm_time = Tinebase_Translation::dateToStringInTzAndLocaleFormat(new Tinebase_DateTime($actionObj->alarm_time));
  164. $actionObj->minutes_before = ($actionObj->minutes_before !== 'custom') ? $actionObj->minutes_before . ' ' .$translate->_('minutes before') : '';
  165. $newNote .= $actionObj->alarm_time . '(' . $actionObj->minutes_before . '), ';
  166. break;
  167. }
  168. }
  169. }
  170. $newNote .= ' -> ';
  171. foreach ($auxNew as $key => $value) {
  172. if(count($value) > 0) {
  173. if ($key != 'model' && ($key != 'modified' || $attr != 'attendee') ) {
  174. $newNote .= $translate->_($key) . '[';
  175. foreach($value as $item) {
  176. switch ($attr) {
  177. case "tags":
  178. $newNote .= $item->name . '(' . $item->type . '),';
  179. break;
  180. case "attendee":
  181. $newNote .= Addressbook_Controller_Contact::getInstance()->get($item->user_id)->n_fn . ',';
  182. break;
  183. case "alarms":
  184. $item = $key === 'modified' ? $item->diff : $item;
  185. if($item->alarm_time){
  186. $item->alarm_time = Tinebase_Translation::dateToStringInTzAndLocaleFormat(new Tinebase_DateTime($item->alarm_time));
  187. $item->minutes_before = ($item->minutes_before !== 'custom') ? $item->minutes_before . ' ' .$translate->_('minutes before') : '';
  188. $newNote .= $item->alarm_time . '(' . $item->minutes_before . '), ';
  189. }
  190. else{
  191. $newNote .= '(), ';
  192. }
  193. break;
  194. }
  195. }
  196. $newNote = substr($newNote,0,-1) . '],';
  197. }
  198. }
  199. }
  200. $newNote = substr($newNote,0,-1);
  201. $note = str_replace($matches[0][0] , $newNote, $note);
  202. }
  203. }
  204. return $note;
  205. }
  206. /************************** get notes ************************/
  207. /**
  208. * search for notes
  209. *
  210. * @param Tinebase_Model_NoteFilter $_filter
  211. * @param Tinebase_Model_Pagination $_pagination
  212. * @return Tinebase_Record_RecordSet subtype Tinebase_Model_Note
  213. */
  214. public function searchNotes(Tinebase_Model_NoteFilter $_filter, Tinebase_Model_Pagination $_pagination = NULL)
  215. {
  216. $select = $this->_db->select()
  217. ->from(array('notes' => SQL_TABLE_PREFIX . 'notes'));
  218. Tinebase_Backend_Sql_Filter_FilterGroup::appendFilters($select, $_filter, $this);
  219. if ($_pagination !== NULL) {
  220. $_pagination->appendPaginationSql($select);
  221. }
  222. //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $select->__toString());
  223. $stmt = $this->_db->query($select);
  224. $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
  225. $result = new Tinebase_Record_RecordSet('Tinebase_Model_Note', $rows, true);
  226. return $result;
  227. }
  228. /**
  229. * count notes
  230. *
  231. * @param Tinebase_Model_NoteFilter $_filter
  232. * @return int notes count
  233. */
  234. public function searchNotesCount(Tinebase_Model_NoteFilter $_filter)
  235. {
  236. $select = $this->_db->select()
  237. ->from(array('notes' => SQL_TABLE_PREFIX . 'notes'), array('count' => 'COUNT(' . $this->_db->quoteIdentifier('id') . ')'));
  238. Tinebase_Backend_Sql_Filter_FilterGroup::appendFilters($select, $_filter, $this);
  239. //$_filter->appendFilterSql($select);
  240. $result = $this->_db->fetchOne($select);
  241. return $result;
  242. }
  243. /**
  244. * get a single note
  245. *
  246. * @param string $_noteId
  247. * @return Tinebase_Model_Note
  248. * @throws Tinebase_Exception_NotFound
  249. */
  250. public function getNote($_noteId)
  251. {
  252. $row = $this->_notesTable->fetchRow($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', (string) $_noteId));
  253. if (!$row) {
  254. throw new Tinebase_Exception_NotFound('Note not found.');
  255. }
  256. return new Tinebase_Model_Note($row->toArray());
  257. }
  258. /**
  259. * get all notes of a given record (calls searchNotes)
  260. *
  261. * @param string $_model model of record
  262. * @param string $_id id of record
  263. * @param string $_backend backend of record
  264. * @param boolean $_onlyNonSystemNotes get only non-system notes per default
  265. * @return Tinebase_Record_RecordSet of Tinebase_Model_Note
  266. */
  267. public function getNotesOfRecord($_model, $_id, $_backend = 'Sql', $_onlyNonSystemNotes = TRUE)
  268. {
  269. $backend = ucfirst(strtolower($_backend));
  270. $filter = $this->_getNotesFilter($_id, $_model, $backend, $_onlyNonSystemNotes);
  271. $pagination = new Tinebase_Model_Pagination(array(
  272. 'limit' => Tinebase_Notes::NUMBER_RECORD_NOTES,
  273. 'sort' => 'creation_time',
  274. 'dir' => 'DESC'
  275. ));
  276. $result = $this->searchNotes($filter, $pagination);
  277. return $result;
  278. }
  279. /**
  280. * get all notes of all given records (calls searchNotes)
  281. *
  282. * @param Tinebase_Record_RecordSet $_records the recordSet
  283. * @param string $_notesProperty the property in the record where the notes are in (defaults: 'notes')
  284. * @param string $_backend backend of record
  285. * @return void
  286. */
  287. public function getMultipleNotesOfRecords($_records, $_notesProperty = 'notes', $_backend = 'Sql', $_onlyNonSystemNotes = TRUE)
  288. {
  289. if (count($_records) == 0) {
  290. return;
  291. }
  292. $modelName = $_records->getRecordClassName();
  293. $filter = $this->_getNotesFilter($_records->getArrayOfIds(), $modelName, $_backend, $_onlyNonSystemNotes);
  294. // search and add index
  295. $notesOfRecords = $this->searchNotes($filter);
  296. $notesOfRecords->addIndices(array('record_id'));
  297. // add notes to records
  298. Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Getting ' . count($notesOfRecords) . ' notes for ' . count($_records) . ' records.');
  299. foreach($_records as $record) {
  300. //$record->notes = Tinebase_Notes::getInstance()->getNotesOfRecord($modelName, $record->getId(), $_backend);
  301. $record->{$_notesProperty} = $notesOfRecords->filter('record_id', $record->getId());
  302. }
  303. }
  304. /************************** set / add / delete notes ************************/
  305. /**
  306. * sets notes of a record
  307. *
  308. * @param Tinebase_Record_Abstract $_record the record object
  309. * @param string $_backend backend (default: 'Sql')
  310. * @param string $_notesProperty the property in the record where the tags are in (default: 'notes')
  311. *
  312. * @todo add update notes ?
  313. */
  314. public function setNotesOfRecord($_record, $_backend = 'Sql', $_notesProperty = 'notes')
  315. {
  316. $model = get_class($_record);
  317. $backend = ucfirst(strtolower($_backend));
  318. //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_record->toArray(), TRUE));
  319. $currentNotesIds = $this->getNotesOfRecord($model, $_record->getId(), $backend)->getArrayOfIds();
  320. $notes = $_record->$_notesProperty;
  321. if ($notes instanceOf Tinebase_Record_RecordSet) {
  322. $notesToSet = $notes;
  323. } else {
  324. if (count($notes) > 0 && $notes[0] instanceOf Tinebase_Record_Abstract) {
  325. // array of notes records given
  326. $notesToSet = new Tinebase_Record_RecordSet('Tinebase_Model_Note', $notes);
  327. } else {
  328. // array of arrays given
  329. $notesToSet = new Tinebase_Record_RecordSet('Tinebase_Model_Note');
  330. foreach($notes as $noteData) {
  331. if (!empty($noteData)) {
  332. $noteArray = (!is_array($noteData)) ? array('note' => $noteData) : $noteData;
  333. if (!isset($noteArray['note_type_id'])) {
  334. // get default note type
  335. $defaultNote = $this->getNoteTypeByName('note');
  336. $noteArray['note_type_id'] = $defaultNote->getId();
  337. }
  338. try {
  339. $note = new Tinebase_Model_Note($noteArray);
  340. $notesToSet->addRecord($note);
  341. } catch (Tinebase_Exception_Record_Validation $terv) {
  342. // discard invalid notes here
  343. Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
  344. . ' Note is invalid! '
  345. . $terv->getMessage()
  346. //. print_r($noteArray, TRUE)
  347. );
  348. }
  349. }
  350. }
  351. }
  352. }
  353. //$toAttach = array_diff($notesToSet->getArrayOfIds(), $currentNotesIds);
  354. $toDetach = array_diff($currentNotesIds, $notesToSet->getArrayOfIds());
  355. // delete detached/deleted notes
  356. $this->deleteNotes($toDetach);
  357. // add new notes
  358. Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Adding ' . count($notesToSet) . ' note(s) to record.');
  359. foreach ($notesToSet as $note) {
  360. //if (in_array($note->getId(), $toAttach)) {
  361. if (!$note->getId()) {
  362. $note->record_model = $model;
  363. $note->record_backend = $backend;
  364. $note->record_id = $_record->getId();
  365. $this->addNote($note);
  366. }
  367. }
  368. }
  369. /**
  370. * add new note
  371. *
  372. * @param Tinebase_Model_Note $_note
  373. */
  374. public function addNote(Tinebase_Model_Note $_note)
  375. {
  376. if (!$_note->getId()) {
  377. $id = $_note->generateUID();
  378. $_note->setId($id);
  379. }
  380. Tinebase_Timemachine_ModificationLog::getInstance()->setRecordMetaData($_note, 'create');
  381. $data = $_note->toArray(FALSE, FALSE);
  382. $this->_notesTable->insert($data);
  383. }
  384. /**
  385. * add new system note
  386. *
  387. * @param Tinebase_Record_Abstract|string $_record
  388. * @param string|Tinebase_Mode_User $_userId
  389. * @param string $_type (created|changed)
  390. * @param Tinebase_Record_RecordSet|string $_mods (Tinebase_Model_ModificationLog)
  391. * @param string $_backend backend of record
  392. * @return Tinebase_Model_Note|boolean
  393. *
  394. * @todo get field translations from application?
  395. * @todo attach modlog record (id) to note instead of saving an ugly string
  396. */
  397. public function addSystemNote($_record, $_userId = NULL, $_type = Tinebase_Model_Note::SYSTEM_NOTE_NAME_CREATED, $_mods = NULL, $_backend = 'Sql', $_modelName = NULL)
  398. {
  399. if (empty($_mods) && $_type === Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED) {
  400. Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .' Nothing changed -> do not add "changed" note.');
  401. return FALSE;
  402. }
  403. $id = ($_record instanceof Tinebase_Record_Abstract) ? $_record->getId() : $_record;
  404. $modelName = ($_modelName !== NULL) ? $_modelName : (($_record instanceof Tinebase_Record_Abstract) ? get_class($_record) : 'unknown');
  405. if (($_userId === NULL)) {
  406. $_userId = Tinebase_Core::getUser();
  407. }
  408. $user = ($_userId instanceof Tinebase_Model_User) ? $_userId : Tinebase_User::getInstance()->getUserById($_userId);
  409. $translate = Tinebase_Translation::getTranslation('Tinebase');
  410. $noteText = $translate->_($_type) . ' ' . $translate->_('by') . ' ' . $user->accountDisplayName;
  411. if ($_mods !== NULL) {
  412. if ($_mods instanceof Tinebase_Record_RecordSet && count($_mods) > 0) {
  413. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  414. .' mods to log: ' . print_r($_mods->toArray(), TRUE));
  415. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
  416. .' Adding "' . $_type . '" system note note to record (id ' . $id . ')');
  417. $noteText .= ' | ' .$translate->_('Changed fields:');
  418. foreach ($_mods as $mod) {
  419. $noteText .= ' ' . $translate->_($mod->modified_attribute) .' (' . $mod->old_value . ' -> ' . $mod->new_value . ')';
  420. }
  421. } else if (is_string($_mods)) {
  422. $noteText = $_mods;
  423. }
  424. }
  425. $noteType = $this->getNoteTypeByName($_type);
  426. $note = new Tinebase_Model_Note(array(
  427. 'note_type_id' => $noteType->getId(),
  428. 'note' => $noteText,
  429. 'record_model' => $modelName,
  430. 'record_backend' => ucfirst(strtolower($_backend)),
  431. 'record_id' => $id,
  432. ));
  433. return $this->addNote($note);
  434. }
  435. /**
  436. * add multiple modification system nodes
  437. *
  438. * @param Tinebase_Record_RecordSet $_mods
  439. * @param string $_userId
  440. */
  441. public function addMultipleModificationSystemNotes($_mods, $_userId)
  442. {
  443. $_mods->addIndices(array('record_id'));
  444. foreach ($_mods->record_id as $recordId) {
  445. $modsOfRecord = $_mods->filter('record_id', $recordId);
  446. $this->addSystemNote($recordId, $_userId, Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $modsOfRecord);
  447. }
  448. }
  449. /**
  450. * delete notes
  451. *
  452. * @param array $_noteIds
  453. */
  454. public function deleteNotes(array $_noteIds)
  455. {
  456. if (!empty($_noteIds)) {
  457. $where = array($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' IN (?)', $_noteIds));
  458. $this->_notesTable->delete($where);
  459. }
  460. }
  461. /**
  462. * delete notes
  463. *
  464. * @param string $_model model of record
  465. * @param string $_backend backend of record
  466. * @param string $_id id of record
  467. */
  468. public function deleteNotesOfRecord($_model, $_backend, $_id)
  469. {
  470. $backend = ucfirst(strtolower($_backend));
  471. $notes = $this->getNotesOfRecord($_model, $_id, $backend);
  472. $this->deleteNotes($notes->getArrayOfIdsAsString());
  473. }
  474. /**
  475. * get note filter
  476. *
  477. * @param string|array $_id
  478. * @param string $_model
  479. * @param string $_backend
  480. * @param boolean|optional $onlyNonSystemNotes
  481. * @return Tinebase_Model_NoteFilter
  482. */
  483. protected function _getNotesFilter($_id, $_model, $_backend, $_onlyNonSystemNotes = TRUE)
  484. {
  485. $backend = ucfirst(strtolower($_backend));
  486. $filter = new Tinebase_Model_NoteFilter(array(
  487. array(
  488. 'field' => 'record_model',
  489. 'operator' => 'equals',
  490. 'value' => $_model
  491. ),
  492. array(
  493. 'field' => 'record_backend',
  494. 'operator' => 'equals',
  495. 'value' => $backend
  496. ),
  497. array(
  498. 'field' => 'record_id',
  499. 'operator' => 'in',
  500. 'value' => (array) $_id
  501. ),
  502. array(
  503. 'field' => 'note_type_id',
  504. 'operator' => 'in',
  505. 'value' => $this->getNoteTypes($_onlyNonSystemNotes, true)
  506. )
  507. ));
  508. return $filter;
  509. }
  510. /************************** note types *******************/
  511. /**
  512. * get all note types
  513. *
  514. * @param boolean|optional $onlyNonSystemNotes
  515. * @param $onlyIds boolean
  516. * @return Tinebase_Record_RecordSet of Tinebase_Model_NoteType
  517. */
  518. public function getNoteTypes($onlyNonSystemNotes = false, $onlyIds = false)
  519. {
  520. $select = $this->_db->select()
  521. ->from(array('note_types' => SQL_TABLE_PREFIX . 'note_types'), ($onlyIds ? 'id' : '*'));
  522. if ($onlyNonSystemNotes) {
  523. $select->where($this->_db->quoteIdentifier('is_user_type') . ' = 1');
  524. }
  525. $stmt = $this->_db->query($select);
  526. if ($onlyIds) {
  527. $types = $stmt->fetchAll(Zend_Db::FETCH_COLUMN);
  528. } else {
  529. $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
  530. $types = new Tinebase_Record_RecordSet('Tinebase_Model_NoteType', $rows, true);
  531. }
  532. return $types;
  533. }
  534. /**
  535. * get note type by name
  536. *
  537. * @param string $_name
  538. * @return Tinebase_Model_NoteType
  539. * @throws Tinebase_Exception_NotFound
  540. */
  541. public function getNoteTypeByName($_name)
  542. {
  543. $row = $this->_noteTypesTable->fetchRow($this->_db->quoteInto($this->_db->quoteIdentifier('name') . ' = ?', $_name));
  544. if (!$row) {
  545. throw new Tinebase_Exception_NotFound('Note type not found.');
  546. }
  547. return new Tinebase_Model_NoteType($row->toArray());
  548. }
  549. /**
  550. * add new note type
  551. *
  552. * @param Tinebase_Model_NoteType $_noteType
  553. */
  554. public function addNoteType(Tinebase_Model_NoteType $_noteType)
  555. {
  556. if (!$_noteType->getId()) {
  557. $id = $_noteType->generateUID();
  558. $_noteType->setId($id);
  559. }
  560. $data = $_noteType->toArray();
  561. $this->_noteTypesTable->insert($data);
  562. }
  563. /**
  564. * update note type
  565. *
  566. * @param Tinebase_Model_NoteType $_noteType
  567. */
  568. public function updateNoteType(Tinebase_Model_NoteType $_noteType)
  569. {
  570. $data = $_noteType->toArray();
  571. $where = array(
  572. $this->_noteTypesTable->getAdapter()->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $_noteType->getId()),
  573. );
  574. $this->_noteTypesTable->update($data, $where);
  575. }
  576. /**
  577. * delete note type
  578. *
  579. * @param integer $_noteTypeId
  580. */
  581. public function deleteNoteType($_noteTypeId)
  582. {
  583. $this->_noteTypesTable->delete($this->_db->quoteInto($this->_db->quoteIdentifier('id') . ' = ?', $_noteTypeId));
  584. }
  585. }