EventMerger.php 7.7 KB

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