Completed
Push — master ( 4f1190...3b2a6f )
by Nikolas
03:34
created

ExecuteResource::assembleResult()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 30
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 30
rs 6.7273
cc 7
eloc 25
nc 7
nop 2
1
<?php
2
namespace rtens\domin\delivery\web\root;
3
4
use rtens\domin\Action;
5
use rtens\domin\delivery\ParameterReader;
6
use rtens\domin\delivery\web\HeadElements;
7
use rtens\domin\delivery\web\RequestParameterReader;
8
use rtens\domin\delivery\web\WebApplication;
9
use rtens\domin\delivery\web\WebExecutor;
10
use rtens\domin\delivery\web\WebField;
11
use rtens\domin\execution\ExecutionResult;
12
use rtens\domin\execution\FailedResult;
13
use rtens\domin\execution\MissingParametersResult;
14
use rtens\domin\execution\NoResult;
15
use rtens\domin\execution\NotPermittedResult;
16
use rtens\domin\execution\RedirectResult;
17
use rtens\domin\execution\RenderedResult;
18
use rtens\domin\Parameter;
19
use watoki\collections\Map;
20
use watoki\curir\cookie\Cookie;
21
use watoki\curir\cookie\CookieStore;
22
use watoki\curir\delivery\WebRequest;
23
use watoki\curir\rendering\PhpRenderer;
24
use watoki\curir\Resource;
25
use watoki\factory\Factory;
26
use watoki\reflect\type\ClassType;
27
28
class ExecuteResource extends Resource {
29
30
    const ACTION_ARG = '__action';
31
    const BREADCRUMB_COOKIE = 'domin_trail';
32
33
    /** @var CookieStore */
34
    private $cookies;
35
36
    /**
37
     * @param Factory $factory <-
38
     * @param WebApplication $app <-
39
     * @param CookieStore $cookies <-
40
     */
41
    public function __construct(Factory $factory, WebApplication $app, CookieStore $cookies) {
42
        parent::__construct($factory);
43
        $this->app = $app;
0 ignored issues
show
Bug introduced by
The property app does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
44
        $this->cookies = $cookies;
45
    }
46
47
    private static function baseHeadElements() {
48
        return [
49
            HeadElements::jquery(),
50
            HeadElements::jqueryUi(), // not actually needed but it needs to be included before bootstrap.js too avoid conflicts
51
            HeadElements::bootstrap(),
52
            HeadElements::bootstrapJs(),
53
        ];
54
    }
55
56
    /**
57
     * @param string $__action
58
     * @param WebRequest $__request <-
59
     * @return array
60
     */
61
    public function doPost($__action, WebRequest $__request) {
62
        return $this->doGet($__action, $__request);
63
    }
64
65
    /**
66
     * @param string $__action
67
     * @param WebRequest $__request <-
68
     * @return array
69
     * @throws \Exception
70
     */
71
    public function doGet($__action, WebRequest $__request) {
72
        $headElements = self::baseHeadElements();
73
        $renderedAction = null;
74
        $caption = 'Error';
75
        $crumbs = [];
76
77
        $reader = new RequestParameterReader($__request);
78
79
        try {
80
            $action = $this->app->actions->getAction($__action);
81
            $caption = $action->caption();
82
83
            $executor = new WebExecutor($this->app->actions, $this->app->fields, $this->app->renderers, $reader);
84
            $executor->restrictAccess($this->app->getAccessControl($__request));
85
            $result = $executor->execute($__action);
86
87
            if (!($result instanceof RedirectResult)) {
88
                $crumbs = $this->updateCrumbs($__action, $result, $__request, $reader);
89
                $headElements = array_merge($headElements, $executor->getHeadElements());
90
91
                $actionParameter = new Parameter($__action, new ClassType(get_class($action)));
92
                $actionField = $this->app->fields->getField($actionParameter);
93
                if (!($actionField instanceof WebField)) {
94
                    throw new \Exception(get_class($actionField) . " must implement WebField");
95
                }
96
97
                $renderedAction = $actionField->render($actionParameter, $this->readParameters($action, $reader));
98
                $headElements = array_merge($headElements, $actionField->headElements($actionParameter));
99
            }
100
        } catch (\Exception $e) {
101
            $result = new FailedResult($e);
102
        }
103
104
        $resultModel = $this->assembleResult($result, $__request);
105
106
        return array_merge(
107
            [
108
                'name' => $this->app->name,
109
                'menu' => $this->app->menu->render($__request),
110
                'breadcrumbs' => $crumbs ? array_slice($crumbs, 0, -1) : null,
111
                'current' => $crumbs ? array_slice($crumbs, -1)[0]['target'] : null,
112
                'caption' => $caption,
113
                'action' => $renderedAction,
114
                'headElements' => HeadElements::filter($headElements)
115
            ],
116
            $resultModel
117
        );
118
    }
119
120
    private function assembleResult(ExecutionResult $result, WebRequest $request) {
121
        $model = [
122
            'error' => null,
123
            'missing' => null,
124
            'success' => null,
125
            'redirect' => null,
126
            'output' => null
127
        ];
128
129
        if ($result instanceof FailedResult) {
130
            $model['error'] = htmlentities($result->getMessage());
131
        } else if ($result instanceof NoResult) {
132
            $model['success'] = true;
133
            $model['redirect'] = $this->getLastCrumb();
134
        } else if ($result instanceof RenderedResult) {
135
            $model['output'] = $result->getOutput();
136
        } else if ($result instanceof MissingParametersResult) {
137
            $model['missing'] = $result->getParameters();
138
        } else if ($result instanceof RedirectResult) {
139
            $model['success'] = true;
140
            $model['redirect'] = $request->getContext()
141
                ->appended($result->getActionId())
142
                ->withParameters(new Map($result->getParameters()));
143
        } else if ($result instanceof NotPermittedResult) {
144
            $model['error'] = 'You are not permitted to execute this action.';
145
            $model['redirect'] = $this->app->getAccessControl($request)->acquirePermission();
146
        }
147
148
        return $model;
149
    }
150
151
    private function readParameters(Action $action, ParameterReader $reader) {
152
        $values = [];
153
154
        foreach ($action->parameters() as $parameter) {
155
            if ($reader->has($parameter)) {
156
                $field = $this->app->fields->getField($parameter);
157
                $values[$parameter->getName()] = $field->inflate($parameter, $reader->read($parameter));
158
            }
159
        }
160
        return $values;
161
    }
162
163
    private function updateCrumbs($actionId, ExecutionResult $result, WebRequest $request, ParameterReader $reader) {
164
        $action = $this->app->actions->getAction($actionId);
165
        $crumbs = $this->readCrumbs();
166
167
        $current = [
168
            'target' => (string)$request->getContext()
169
                ->appended($actionId)
170
                ->withParameters(new Map($this->readRawParameters($action, $reader))),
171
            'caption' => $action->caption()
172
        ];
173
        $newCrumbs = [];
174
        foreach ($crumbs as $crumb) {
175
            if ($crumb == $current) {
176
                break;
177
            }
178
            $newCrumbs[] = $crumb;
179
        }
180
        $newCrumbs[] = $current;
181
        if ($result instanceof RenderedResult) {
182
            $this->saveCrumbs($newCrumbs);
183
        }
184
        return $newCrumbs;
185
    }
186
187
    private function readRawParameters(Action $action, ParameterReader $reader) {
188
        $values = [];
189
190
        foreach ($action->parameters() as $parameter) {
191
            if ($reader->has($parameter)) {
192
                $values[$parameter->getName()] = $reader->read($parameter);
193
            }
194
        }
195
        return $values;
196
    }
197
198
    private function getLastCrumb() {
199
        $crumbs = $this->readCrumbs();
200
        if (!$crumbs) {
201
            return null;
202
        }
203
        return end($crumbs)['target'];
204
    }
205
206
    private function readCrumbs() {
207
        if ($this->cookies->hasKey(self::BREADCRUMB_COOKIE)) {
208
            return $this->cookies->read(self::BREADCRUMB_COOKIE)->payload;
209
        }
210
        return [];
211
    }
212
213
    private function saveCrumbs($crumbs) {
214
        $this->cookies->create(new Cookie($crumbs), self::BREADCRUMB_COOKIE);
215
    }
216
217
    protected function createDefaultRenderer() {
218
        return new PhpRenderer();
219
    }
220
}