Passed
Pull Request — 4 (#8209)
by Ingo
09:07
created

PjaxResponseNegotiator::respond()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 11
nop 2
dl 0
loc 35
rs 8.1155
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\Core\Convert;
6
use InvalidArgumentException;
7
8
/**
9
 * Handle the X-Pjax header that AJAX responses may provide, returning the
10
 * fragment, or, in the case of non-AJAX form submissions, redirecting back
11
 * to the submitter.
12
 *
13
 * X-Pjax ensures that users won't end up seeing the unstyled form HTML in
14
 * their browser.
15
 *
16
 * If a JS error prevents the Ajax overriding of form submissions from happening.
17
 *
18
 * It also provides better non-JS operation.
19
 *
20
 * Caution: This API is volatile, and might eventually be replaced by a generic
21
 * action helper system for controllers.
22
 */
23
class PjaxResponseNegotiator
24
{
25
26
    /**
27
     * See {@link respond()}
28
     *
29
     * @var array
30
     */
31
    protected $callbacks = array();
32
33
    protected $response = null;
34
35
    /**
36
     * Overriden fragments (if any). Otherwise uses fragments from the request.
37
     */
38
    protected $fragmentOverride = null;
39
40
    /**
41
     * @param array $callbacks
42
     * @param HTTPResponse $response An existing response to reuse (optional)
43
     */
44
    public function __construct($callbacks = array(), $response = null)
45
    {
46
        $this->callbacks = $callbacks;
47
        $this->response = $response;
48
    }
49
50
    public function getResponse()
51
    {
52
        if (!$this->response) {
53
            $this->response = new HTTPResponse();
54
        }
55
        return $this->response;
56
    }
57
58
    public function setResponse($response)
59
    {
60
        $this->response = $response;
61
    }
62
63
    /**
64
     * Out of the box, the handler "CurrentForm" value, which will return the rendered form.
65
     * Non-Ajax calls will redirect back.
66
     *
67
     * @param HTTPRequest $request
68
     * @param array $extraCallbacks List of anonymous functions or callables returning either a string
69
     * or HTTPResponse, keyed by their fragment identifier. The 'default' key can
70
     * be used as a fallback for non-ajax responses.
71
     * @return HTTPResponse
72
     * @throws HTTPResponse_Exception
73
     */
74
    public function respond(HTTPRequest $request, $extraCallbacks = array())
75
    {
76
        // Prepare the default options and combine with the others
77
        $callbacks = array_merge($this->callbacks, $extraCallbacks);
78
        $response = $this->getResponse();
79
80
        $responseParts = array();
81
82
        if (isset($this->fragmentOverride)) {
83
            $fragments = $this->fragmentOverride;
84
        } elseif ($fragmentStr = $request->getHeader('X-Pjax')) {
85
            $fragments = explode(',', $fragmentStr);
86
        } else {
87
            if ($request->isAjax()) {
88
                throw new HTTPResponse_Exception("Ajax requests to this URL require an X-Pjax header.", 400);
89
            } elseif (empty($callbacks['default'])) {
90
                throw new HTTPResponse_Exception("Missing default response handler for this URL", 400);
91
            }
92
            $response->setBody(call_user_func($callbacks['default']));
93
            return $response;
94
        }
95
96
        // Execute the fragment callbacks and build the response.
97
        foreach ($fragments as $fragment) {
98
            if (isset($callbacks[$fragment])) {
99
                $res = call_user_func($callbacks[$fragment]);
100
                $responseParts[$fragment] = $res ? (string) $res : $res;
101
            } else {
102
                throw new HTTPResponse_Exception("X-Pjax = '$fragment' not supported for this URL.", 400);
103
            }
104
        }
105
        $response->setBody(Convert::raw2json($responseParts));
106
        $response->addHeader('Content-Type', 'text/json');
107
108
        return $response;
109
    }
110
111
    /**
112
     * @param string   $fragment
113
     * @param Callable $callback
114
     */
115
    public function setCallback($fragment, $callback)
116
    {
117
        $this->callbacks[$fragment] = $callback;
118
    }
119
120
    /**
121
     * Set up fragment overriding - will completely replace the incoming fragments.
122
     *
123
     * @param array $fragments Fragments to insert.
124
     * @return $this
125
     */
126
    public function setFragmentOverride($fragments)
127
    {
128
        if (!is_array($fragments)) {
0 ignored issues
show
introduced by
The condition is_array($fragments) is always true.
Loading history...
129
            throw new InvalidArgumentException("fragments must be an array");
130
        }
131
132
        $this->fragmentOverride = $fragments;
133
        return $this;
134
    }
135
136
    /**
137
     * @return array
138
     */
139
    public function getFragmentOverride()
140
    {
141
        return $this->fragmentOverride;
142
    }
143
}
144