EventMerger.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
  4. *
  5. * @author Joas Schilling <coding@schilljs.com>
  6. *
  7. * @license GNU AGPL version 3 or any later version
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as
  11. * published by the Free Software Foundation, either version 3 of the
  12. * License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace OC\Activity;
  24. use OCP\Activity\IEvent;
  25. use OCP\Activity\IEventMerger;
  26. use OCP\IL10N;
  27. class EventMerger implements IEventMerger {
  28. /** @var IL10N */
  29. protected $l10n;
  30. /**
  31. * @param IL10N $l10n
  32. */
  33. public function __construct(IL10N $l10n) {
  34. $this->l10n = $l10n;
  35. }
  36. /**
  37. * Combines two events when possible to have grouping:
  38. *
  39. * Example1: Two events with subject '{user} created {file}' and
  40. * $mergeParameter file with different file and same user will be merged
  41. * to '{user} created {file1} and {file2}' and the childEvent on the return
  42. * will be set, if the events have been merged.
  43. *
  44. * Example2: Two events with subject '{user} created {file}' and
  45. * $mergeParameter file with same file and same user will be merged to
  46. * '{user} created {file1}' and the childEvent on the return will be set, if
  47. * the events have been merged.
  48. *
  49. * The following requirements have to be met, in order to be merged:
  50. * - Both events need to have the same `getApp()`
  51. * - Both events must not have a message `getMessage()`
  52. * - Both events need to have the same subject `getSubject()`
  53. * - Both events need to have the same object type `getObjectType()`
  54. * - The time difference between both events must not be bigger then 3 hours
  55. * - Only up to 5 events can be merged.
  56. * - All parameters apart from such starting with $mergeParameter must be
  57. * the same for both events.
  58. *
  59. * @param string $mergeParameter
  60. * @param IEvent $event
  61. * @param IEvent|null $previousEvent
  62. * @return IEvent
  63. */
  64. public function mergeEvents($mergeParameter, IEvent $event, IEvent $previousEvent = null) {
  65. // No second event => can not combine
  66. if (!$previousEvent instanceof IEvent) {
  67. return $event;
  68. }
  69. // Different app => can not combine
  70. if ($event->getApp() !== $previousEvent->getApp()) {
  71. return $event;
  72. }
  73. // Message is set => can not combine
  74. if ($event->getMessage() !== '' || $previousEvent->getMessage() !== '') {
  75. return $event;
  76. }
  77. // Different subject => can not combine
  78. if ($event->getSubject() !== $previousEvent->getSubject()) {
  79. return $event;
  80. }
  81. // Different object type => can not combine
  82. if ($event->getObjectType() !== $previousEvent->getObjectType()) {
  83. return $event;
  84. }
  85. // More than 3 hours difference => can not combine
  86. if (abs($event->getTimestamp() - $previousEvent->getTimestamp()) > 3 * 60 * 60) {
  87. return $event;
  88. }
  89. // Other parameters are not the same => can not combine
  90. try {
  91. list($combined, $parameters) = $this->combineParameters($mergeParameter, $event, $previousEvent);
  92. } catch (\UnexpectedValueException $e) {
  93. return $event;
  94. }
  95. try {
  96. $newSubject = $this->getExtendedSubject($event->getRichSubject(), $mergeParameter, $combined);
  97. $parsedSubject = $this->generateParsedSubject($newSubject, $parameters);
  98. $event->setRichSubject($newSubject, $parameters)
  99. ->setParsedSubject($parsedSubject)
  100. ->setChildEvent($previousEvent);
  101. } catch (\UnexpectedValueException $e) {
  102. return $event;
  103. }
  104. return $event;
  105. }
  106. /**
  107. * @param string $mergeParameter
  108. * @param IEvent $event
  109. * @param IEvent $previousEvent
  110. * @return array
  111. * @throws \UnexpectedValueException
  112. */
  113. protected function combineParameters($mergeParameter, IEvent $event, IEvent $previousEvent) {
  114. $params1 = $event->getRichSubjectParameters();
  115. $params2 = $previousEvent->getRichSubjectParameters();
  116. $params = [];
  117. $combined = 0;
  118. // Check that all parameters from $event exist in $previousEvent
  119. foreach ($params1 as $key => $parameter) {
  120. if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) {
  121. if (!$this->checkParameterAlreadyExits($params, $mergeParameter, $parameter)) {
  122. $combined++;
  123. $params[$mergeParameter . $combined] = $parameter;
  124. }
  125. continue;
  126. }
  127. if (!isset($params2[$key]) || $params2[$key] !== $parameter) {
  128. // Parameter missing on $previousEvent or different => can not combine
  129. throw new \UnexpectedValueException();
  130. }
  131. $params[$key] = $parameter;
  132. }
  133. // Check that all parameters from $previousEvent exist in $event
  134. foreach ($params2 as $key => $parameter) {
  135. if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) {
  136. if (!$this->checkParameterAlreadyExits($params, $mergeParameter, $parameter)) {
  137. $combined++;
  138. $params[$mergeParameter . $combined] = $parameter;
  139. }
  140. continue;
  141. }
  142. if (!isset($params1[$key]) || $params1[$key] !== $parameter) {
  143. // Parameter missing on $event or different => can not combine
  144. throw new \UnexpectedValueException();
  145. }
  146. $params[$key] = $parameter;
  147. }
  148. return [$combined, $params];
  149. }
  150. /**
  151. * @param array[] $parameters
  152. * @param string $mergeParameter
  153. * @param array $parameter
  154. * @return bool
  155. */
  156. protected function checkParameterAlreadyExits($parameters, $mergeParameter, $parameter) {
  157. foreach ($parameters as $key => $param) {
  158. if (preg_match('/^' . $mergeParameter . '(\d+)?$/', $key)) {
  159. if ($param === $parameter) {
  160. return true;
  161. }
  162. }
  163. }
  164. return false;
  165. }
  166. /**
  167. * @param string $subject
  168. * @param string $parameter
  169. * @param int $counter
  170. * @return mixed
  171. */
  172. protected function getExtendedSubject($subject, $parameter, $counter) {
  173. switch ($counter) {
  174. case 1:
  175. $replacement = '{' . $parameter . '1}';
  176. break;
  177. case 2:
  178. $replacement = $this->l10n->t(
  179. '%1$s and %2$s',
  180. ['{' . $parameter . '2}', '{' . $parameter . '1}']
  181. );
  182. break;
  183. case 3:
  184. $replacement = $this->l10n->t(
  185. '%1$s, %2$s and %3$s',
  186. ['{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}']
  187. );
  188. break;
  189. case 4:
  190. $replacement = $this->l10n->t(
  191. '%1$s, %2$s, %3$s and %4$s',
  192. ['{' . $parameter . '4}', '{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}']
  193. );
  194. break;
  195. case 5:
  196. $replacement = $this->l10n->t(
  197. '%1$s, %2$s, %3$s, %4$s and %5$s',
  198. ['{' . $parameter . '5}', '{' . $parameter . '4}', '{' . $parameter . '3}', '{' . $parameter . '2}', '{' . $parameter . '1}']
  199. );
  200. break;
  201. default:
  202. throw new \UnexpectedValueException();
  203. }
  204. return str_replace(
  205. '{' . $parameter . '}',
  206. $replacement,
  207. $subject
  208. );
  209. }
  210. /**
  211. * @param string $subject
  212. * @param array[] $parameters
  213. * @return string
  214. */
  215. protected function generateParsedSubject($subject, $parameters) {
  216. $placeholders = $replacements = [];
  217. foreach ($parameters as $placeholder => $parameter) {
  218. $placeholders[] = '{' . $placeholder . '}';
  219. if ($parameter['type'] === 'file') {
  220. $replacements[] = trim($parameter['path'], '/');
  221. } else if (isset($parameter['name'])) {
  222. $replacements[] = $parameter['name'];
  223. } else {
  224. $replacements[] = $parameter['id'];
  225. }
  226. }
  227. return str_replace($placeholders, $replacements, $subject);
  228. }
  229. }