Skip to content

Commit f281cfd

Browse files
author
Benno Weinzierl
committed
[INIT] Initial Commit
1 parent 2e8af83 commit f281cfd

5 files changed

Lines changed: 291 additions & 1 deletion

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace FormatD\UserActionHistory\Controller;
4+
5+
use Neos\Flow\Annotations as Flow;
6+
use \Neos\Utility\TypeHandling;
7+
8+
/**
9+
* Controller for redirecting to history entries
10+
*
11+
* @package FormatD\UserActionHistory\Controller
12+
*/
13+
class HistoryController extends \Neos\Flow\Mvc\Controller\ActionController {
14+
15+
/**
16+
* @Flow\Inject
17+
* @var \FormatD\UserActionHistory\Domain\Model\UserActionHistory
18+
*/
19+
protected $userActionHistory;
20+
21+
/**
22+
* @param string $entryId
23+
*/
24+
public function redirectToActionHistoryEntryAction($entryId)
25+
{
26+
$this->redirectToRequest($this->userActionHistory->getActionRequestByEntryId($entryId));
27+
}
28+
29+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
namespace FormatD\UserActionHistory\Domain\Model;
3+
4+
5+
use Neos\Flow\Annotations as Flow;
6+
7+
/**
8+
* A history in session of controller actions visited by a user
9+
*
10+
* @Flow\Scope("session")
11+
*/
12+
class UserActionHistory {
13+
14+
/**
15+
* @var \Doctrine\Common\Collections\Collection
16+
*/
17+
protected $historyEntries;
18+
19+
/**
20+
* @Flow\Inject
21+
* @var \Neos\Flow\Persistence\PersistenceManagerInterface
22+
*/
23+
protected $persistenceManager;
24+
25+
/**
26+
* Constructor
27+
*/
28+
public function __construct()
29+
{
30+
$this->historyEntries = new \Doctrine\Common\Collections\ArrayCollection();
31+
}
32+
33+
/**
34+
* @param string $description
35+
* @param \Neos\Flow\Mvc\ActionRequest $request
36+
* @param object $entity
37+
* @param array $requestOverride
38+
*/
39+
public function addEntry($description, \Neos\Flow\Mvc\ActionRequest $request, $entity = NULL, $requestOverride = NULL)
40+
{
41+
$lastEntry = $this->historyEntries->last();
42+
43+
if ($lastEntry['description'] !== $description || $lastEntry['entity'] !== $entity) {
44+
if ($requestOverride) {
45+
if (isset($requestOverride['controller'])) $request->setControllerName($requestOverride['controller']);
46+
if (isset($requestOverride['action'])) $request->setControllerActionName($requestOverride['action']);
47+
if (isset($requestOverride['package'])) $request->setControllerPackageKey($requestOverride['package']);
48+
if (isset($requestOverride['subpackage'])) $request->setControllerSubpackageKey($requestOverride['subpackage']);
49+
if (isset($requestOverride['arguments'])) $request->setArguments($this->persistenceManager->convertObjectsToIdentityArrays($requestOverride['arguments']));
50+
}
51+
$this->historyEntries->add(array('id' => uniqid('ID'), 'description' => $description, 'request' => $request, 'entity' => $entity));
52+
}
53+
}
54+
55+
/**
56+
* @param integer $limit
57+
* @param string $skipControllerActions
58+
* @param boolean $skipDuplicateDescriptions Skip consecutive entries if description is the same
59+
*/
60+
public function getLastEntries($limit, $skipControllerActions = '', $skipDuplicateDescriptions = FALSE)
61+
{
62+
$entries = array();
63+
$count = 0;
64+
$historyEntries = clone $this->historyEntries;
65+
$lastEntry = NULL;
66+
while ($entry = $this->getAndRemoveLastEntry($historyEntries, $skipControllerActions)) {
67+
if ($lastEntry['description'] === $entry['description'] &&
68+
($skipDuplicateDescriptions || ($lastEntry['entity'] && $entry['entity'] && $lastEntry['entity'] === $entry['entity']))
69+
) {
70+
continue;
71+
}
72+
73+
$entries[] = $entry;
74+
$lastEntry = $entry;
75+
$count++;
76+
77+
if ($count >= $limit) {
78+
break;
79+
}
80+
}
81+
return $entries;
82+
}
83+
84+
/**
85+
* @param string $skipControllerActions
86+
* @return \Neos\Flow\Mvc\ActionRequest
87+
*/
88+
public function getLastActionRequest($skipControllerActions = '')
89+
{
90+
$historyEntries = clone $this->historyEntries;
91+
$entry = $this->getAndRemoveLastEntry($historyEntries, $skipControllerActions);
92+
if ($entry) {
93+
return $entry['request'];
94+
}
95+
return NULL;
96+
}
97+
98+
/**
99+
* @param string $entryId
100+
* @return \Neos\Flow\Mvc\ActionRequest
101+
*/
102+
public function getActionRequestByEntryId($entryId)
103+
{
104+
$criteria = \Doctrine\Common\Collections\Criteria::create()
105+
->where(\Doctrine\Common\Collections\Criteria::expr()->eq("id", $entryId));
106+
107+
$entry = $this->historyEntries->matching($criteria)->last();
108+
if (!$entry) {
109+
throw new \FormatD\UserActionHistory\Exception('Entry "' . $entryId . '" not found.', 1524142543);
110+
}
111+
return $entry['request'];
112+
}
113+
114+
/**
115+
* Get last entry and remove it
116+
*
117+
* You can provide controller-actions to exclude with $skipControllerActions
118+
* Format: PackageKey:ControllerName->ActionName
119+
*
120+
* Examples:
121+
* - My.Package:Standard->index
122+
* - User->edit
123+
* - User->edit,MyOtherEntity->edit
124+
*
125+
* @param \Doctrine\Common\Collections\Collection $historyEntries
126+
* @param string $skipControllerActions
127+
* @return array
128+
*/
129+
protected function getAndRemoveLastEntry($historyEntries, $skipControllerActions = '')
130+
{
131+
if ($skipControllerActions !== '') {
132+
$skipSets = explode(',', $skipControllerActions);
133+
134+
while ($entry = $historyEntries->last()) {
135+
$historyEntries->removeElement($entry);
136+
137+
$skipFlag = FALSE;
138+
foreach ($skipSets as $skipSet) {
139+
$parts = explode('->', $skipSet);
140+
141+
if (count($controllerParts = explode(':', $parts[0])) > 1) {
142+
$packageName = $controllerParts[0];
143+
$controllerName = $controllerParts[1];
144+
} else {
145+
$packageName = '*';
146+
$controllerName = $parts[0];
147+
}
148+
149+
$actionName = $parts[1];
150+
151+
if ($packageName === '*' || $packageName === $entry['request']->getControllerPackageKey()) {
152+
if ($controllerName === '*' || $controllerName === $entry['request']->getControllerName()) {
153+
if ($actionName === '*' || $actionName === $entry['request']->getControllerActionName()) {
154+
$skipFlag = TRUE;
155+
break;
156+
}
157+
}
158+
}
159+
}
160+
if (!$skipFlag) {
161+
return $entry;
162+
}
163+
}
164+
} else {
165+
$entry = $historyEntries->last();
166+
$historyEntries->removeElement($entry);
167+
}
168+
169+
return $entry;
170+
}
171+
172+
}

Classes/Exception.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace FormatD\UserActionHistory;
3+
4+
/*
5+
* This file is part of the FormatD.UserActionHistory package.
6+
*/
7+
8+
use Neos\Flow\Annotations as Flow;
9+
10+
/**
11+
* Base FormatD UserActionHistory Exception
12+
*/
13+
class Exception extends \Neos\Flow\Exception {
14+
15+
}
16+
17+
?>

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,55 @@
1+
12
# FormatD.UserActionHistory
2-
A session scope class to record requests of a user. Handy for example to provide a list of recently edited records or handle redirects in a backend interface.
3+
4+
A request history for Neos FLow applications.
5+
This package was mainly build for CRUD like backend interfaces but can be used for a lot of use cases.
6+
7+
## What does it do?
8+
9+
It provides a session scope object with the possibility to add requests into a history stack.
10+
With this history it is possible for example to handle back-links or redirects back to the previous page after some action.
11+
12+
## Adding Entries
13+
14+
Inject the UserActionHistory into your controller and add entries like that. You should not add unsafe requests (e.g. POST) to the history because if you redirect to them later it will have unexpected results.
15+
16+
```
17+
$this->userActionHistory->addEntry('Edit Backend-User: ' . $user->getName(), $this->request, $user);
18+
```
19+
20+
## Redirecting to previous request
21+
22+
If you want to redirect to the previous request for example in an updateAction you can do the following.
23+
Notice that you can provide action patterns to skip (for example if you do not want to redirect to the editAction but to the request previous to that).
24+
25+
```
26+
if ($lastRequest = $this->userActionHistory->getLastActionRequest('UserManagement->edit')) {
27+
$this->redirectToRequest($lastRequest);
28+
} else {
29+
$this->redirect('index');
30+
}
31+
```
32+
33+
34+
## Displaying a linked list of history items
35+
36+
If you want to build a menu of your last visited pages (or last edited records in a CRUD application) you would to it like that.
37+
38+
In Controller:
39+
```
40+
$this->view->assign('userActionHistoryEntries', $this->userActionHistory->getLastEntries(15, '*->index'));
41+
```
42+
43+
In Template:
44+
```
45+
<ul>
46+
<f:for each="{userActionHistoryEntries}" as="entry">
47+
<li>
48+
<f:link.action controller="History" action="redirectToActionHistoryEntry" package="FormatD.UserActionHistory" arguments="{entryId: entry.id}">
49+
{entry.description}
50+
</f:link.action>
51+
</li>
52+
</f:for>
53+
</ul>
54+
```
55+
(Don't forget to make the HistoryController accessable in your routes configuration and if necessary in your policy configuration)

composer.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "formatd/useractionhistory",
3+
"description": "A request history for Neos FLow applications.",
4+
"type": "neos-package",
5+
"license": "MIT",
6+
"require": {
7+
"neos/flow": ">=4.0"
8+
},
9+
"autoload": {
10+
"psr-4": {
11+
"FormatD\\UserActionHistory\\": "Classes/"
12+
}
13+
},
14+
"extra": {
15+
"neos": {
16+
"package-key": "FormatD.UserActionHistory"
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)