Completed
Branch sideload-incorrect-upload-from (81f0fb)
by
unknown
24:19 queued 16:46
created

initializeSessionSaveHandlerStatus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\services\session;
4
5
use EE_Error;
6
use EEH_URL;
7
use EventEspresso\core\services\request\RequestInterface;
8
9
/**
10
 * Class SessionStartHandler
11
 * Handles a fatal error that can occur while starting the session because of an invalid session handler.
12
 * This fatal error happens regularly on Pantheon's servers,
13
 * see https://github.com/eventespresso/event-espresso-core/issues/494.
14
 * This function keeps track of whether we know the custom session handler works or not.
15
 * If it does, uses the session normally.
16
 * If we don't know if the custom session save handler works, sets an option before starting the session,
17
 * and removes it afterwards.
18
 * This way, if there is a fatal error starting the session, we'll know during the next request,
19
 * and avoid calling the session. Instead, we'll give a helpful error message and suggest how to fix the problem.
20
 * Lastly, we need to allow the admin to indicate when they've fixed the problem, so we can try using EE's session
21
 * again. For that, we give them a link with "ee_retry_session" in it. When they visit a page with that querystring
22
 * variable, we set ourselves up to retry using the session on the next request, and do a redirect.
23
 *
24
 * @package EventEspresso\core\services\session
25
 * @author  Mike Nelson
26
 * @since   4.9.68.p
27
 */
28
class SessionStartHandler
29
{
30
    const OPTION_NAME_SESSION_SAVE_HANDLER_STATUS = 'ee_session_save_handler_status';
31
    const REQUEST_PARAM_RETRY_SESSION = 'ee_retry_session';
32
    const SESSION_SAVE_HANDLER_STATUS_FAILED = 'session_save_handler_failed';
33
    const SESSION_SAVE_HANDLER_STATUS_SUCCESS = 'session_save_handler_success';
34
    const SESSION_SAVE_HANDLER_STATUS_UNKNOWN = 'session_save_handler_untested';
35
36
    /**
37
     * @var RequestInterface $request
38
     */
39
    protected $request;
40
41
    /**
42
     * StartSession constructor.
43
     *
44
     * @param RequestInterface $request
45
     */
46
    public function __construct(RequestInterface $request)
47
    {
48
        $this->request = $request;
49
    }
50
51
    /**
52
     * Check if a custom session save handler is in play
53
     * and attempt to start the PHP session
54
     *
55
     * @since 4.9.68.p
56
     */
57
    public function startSession()
58
    {
59
        // check that session has started
60
        if (session_id() === '') {
61
            // starts a new session if one doesn't already exist, or re-initiates an existing one
62
            if ($this->hasKnownCustomSessionSaveHandler()) {
63
                $this->checkCustomSessionSaveHandler();
64
            } else {
65
                session_start();
66
            }
67
        }
68
    }
69
70
    /**
71
     * Returns `true` if the 'session.save_handler' ini setting matches a known custom handler
72
     *
73
     * @since 4.9.68.p
74
     * @return bool
75
     */
76
    private function hasKnownCustomSessionSaveHandler()
77
    {
78
        return in_array(
79
            ini_get('session.save_handler'),
80
            array(
81
                'user',
82
            ),
83
            true
84
        );
85
    }
86
87
    /**
88
     * Attempt to start the PHP session when a custom Session Save Handler is known to be set.
89
     *
90
     * @since 4.9.68.p
91
     */
92
    private function checkCustomSessionSaveHandler()
93
    {
94
        // If we've already successfully tested the session save handler
95
        // on a previous request then just start the session
96
        if ($this->sessionSaveHandlerIsValid()) {
97
            session_start();
98
            return;
99
        }
100
        // If not, then attempt to deal with any errors,
101
        // otherwise, try to hobble along without the session
102
        if (! $this->handleSessionSaveHandlerErrors()) {
103
            return;
104
        }
105
        // there is no record of a fatal error while trying to start the session
106
        // so let's see if there's a custom session save handler. Proceed with caution
107
        $this->initializeSessionSaveHandlerStatus();
108
        // hold your breath, the custom session save handler might cause a fatal here...
109
        session_start();
110
        // phew! we made it! the custom session handler is a-ok
111
        $this->setSessionSaveHandlerStatusToValid();
112
    }
113
114
115
    /**
116
     * retrieves the value for the 'ee_session_save_handler_status' WP option.
117
     * default value = 'session_save_handler_untested'
118
     *
119
     * @since 4.9.68.p
120
     * @return string
121
     */
122
    private function getSessionSaveHandlerStatus()
123
    {
124
        return get_option(
125
            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
126
            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
127
        );
128
    }
129
130
    /**
131
     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_failed'
132
     * which can then be upgraded is everything works correctly
133
     *
134
     * @since 4.9.68.p
135
     * @return bool
136
     */
137
    private function initializeSessionSaveHandlerStatus()
138
    {
139
        return update_option(
140
            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
141
            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED
142
        );
143
    }
144
145
    /**
146
     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_success'
147
     *
148
     * @since 4.9.68.p
149
     * @return bool
150
     */
151
    private function setSessionSaveHandlerStatusToValid()
152
    {
153
        return update_option(
154
            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
155
            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS
156
        );
157
    }
158
159
    /**
160
     * Sets the 'ee_session_save_handler_status' WP option value to 'session_save_handler_untested'
161
     *
162
     * @since 4.9.68.p
163
     * @return bool
164
     */
165
    private function resetSessionSaveHandlerStatus()
166
    {
167
        return update_option(
168
            SessionStartHandler::OPTION_NAME_SESSION_SAVE_HANDLER_STATUS,
169
            SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_UNKNOWN
170
        );
171
    }
172
173
    /**
174
     * Returns `true` if the 'ee_session_save_handler_status' WP option value
175
     * is equal to 'session_save_handler_success'
176
     *
177
     * @since 4.9.68.p
178
     * @return bool
179
     */
180
    private function sessionSaveHandlerIsValid()
181
    {
182
        return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_SUCCESS;
183
    }
184
185
    /**
186
     * Returns `true` if the 'ee_session_save_handler_status' WP option value
187
     * is equal to 'session_save_handler_failed'
188
     *
189
     * @since 4.9.68.p
190
     * @return bool
191
     */
192
    private function sessionSaveHandlerFailed()
193
    {
194
        return $this->getSessionSaveHandlerStatus() === SessionStartHandler::SESSION_SAVE_HANDLER_STATUS_FAILED;
195
    }
196
197
    /**
198
     * Returns `true` if no errors were detected with the session save handler,
199
     * otherwise attempts to work notify the appropriate authorities
200
     * with a suggestion for how to fix the issue, and returns `false`.
201
     *
202
     *
203
     * @since 4.9.68.p
204
     * @return bool
205
     */
206
    private function handleSessionSaveHandlerErrors()
207
    {
208
        // Check if we had a fatal error last time while trying to start the session
209
        if ($this->sessionSaveHandlerFailed()) {
210
            // apparently, last time we tried using the custom session save handler there was a fatal
211
            if ($this->request->requestParamIsSet(SessionStartHandler::REQUEST_PARAM_RETRY_SESSION)) {
212
                $this->resetSessionSaveHandlerStatus();
213
                // remove "ee_retry_session", otherwise if the problem still isn't fixed,
214
                // we'll just keep getting the fatal error over and over.
215
                // Better to remove it and redirect, and try on the next request
216
                EEH_URL::safeRedirectAndExit(
217
                    remove_query_arg(
218
                        array(SessionStartHandler::REQUEST_PARAM_RETRY_SESSION),
219
                        EEH_URL::current_url()
220
                    )
221
                );
222
            }
223
            // so the session is broken, don't try it again,
224
            // just show a message to users that can fix it
225
            $this->displaySessionSaveHandlerErrorNotice();
226
            return false;
227
        }
228
        return true;
229
    }
230
231
    /**
232
     * Generates an EE_Error notice regarding the current session woes
233
     * but only if the current user is an admin with permission to 'install_plugins'.
234
     *
235
     * @since 4.9.68.p
236
     */
237
    private function displaySessionSaveHandlerErrorNotice()
238
    {
239
        if (current_user_can('install_plugins')) {
240
            $retry_session_url = add_query_arg(
241
                array(SessionStartHandler::REQUEST_PARAM_RETRY_SESSION => true),
242
                EEH_URL::current_url()
243
            );
244
            EE_Error::add_error(
245
                sprintf(
246
                    esc_html__(
247
                        'It appears there was a fatal error while starting the session, so Event Espresso is not able to process registrations normally. Some hosting companies, like Pantheon, require an extra plugin for Event Espresso to work. Please install the %1$sWordPress Native PHP Sessions plugin%2$s, then %3$sclick here to check if the problem is resolved.%2$s',
248
                        'event_espresso'
249
                    ),
250
                    '<a href="https://wordpress.org/plugins/wp-native-php-sessions/">',
251
                    '</a>',
252
                    '<a href="' . $retry_session_url . '">'
253
                ),
254
                __FILE__,
255
                __FUNCTION__,
256
                __LINE__
257
            );
258
        }
259
    }
260
}
261