handler.php
186 lines
| 1 | <?php |
| 2 | /** |
| 3 | * @package VikWP - Libraries |
| 4 | * @subpackage adapter.session |
| 5 | * @author E4J s.r.l. |
| 6 | * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved. |
| 7 | * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL |
| 8 | * @link https://vikwp.com |
| 9 | */ |
| 10 | |
| 11 | // No direct access |
| 12 | defined('ABSPATH') or die('No script kiddies please!'); |
| 13 | |
| 14 | /** |
| 15 | * Class used to handle the PHP session. |
| 16 | * Provides the methods to begin a new session and to destroy it. |
| 17 | * |
| 18 | * @since 10.0 |
| 19 | */ |
| 20 | class JSessionHandler |
| 21 | { |
| 22 | /** |
| 23 | * Starts a new session, only if it doesn't already exist. |
| 24 | * |
| 25 | * @return void |
| 26 | */ |
| 27 | public static function start() |
| 28 | { |
| 29 | /** |
| 30 | * Prevent the handler from starting a new session in case the process was launched |
| 31 | * from wp-cron.php, which manually sends some headers at the beginning of the file. |
| 32 | * |
| 33 | * @since 10.1.65 |
| 34 | */ |
| 35 | if (defined('DOING_CRON') && DOING_CRON) |
| 36 | { |
| 37 | return; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * Do not go ahead in case the session has been already started, otherwise duplicate |
| 42 | * calls to this method risks to register the same hook more than once. |
| 43 | * |
| 44 | * @since 10.1.70 |
| 45 | */ |
| 46 | if (self::isStarted()) |
| 47 | { |
| 48 | return; |
| 49 | } |
| 50 | |
| 51 | /** |
| 52 | * In case the headers have been already sent, trigger warning and abort. |
| 53 | * |
| 54 | * @since 10.1.70 |
| 55 | */ |
| 56 | if (headers_sent()) |
| 57 | { |
| 58 | trigger_error('Session cannot be started after headers have already been sent', E_USER_WARNING); |
| 59 | return; |
| 60 | } |
| 61 | |
| 62 | session_start(); |
| 63 | |
| 64 | /** |
| 65 | * Filters whether to preempt an HTTP request's return value. |
| 66 | * It is needed to support concurrent cURL requests (see Site Health). |
| 67 | * |
| 68 | * Returning a non-false value from the filter will short-circuit the HTTP request and return |
| 69 | * early with that value. A filter should return either: |
| 70 | * |
| 71 | * - An array containing 'headers', 'body', 'response', 'cookies', and 'filename' elements |
| 72 | * - A WP_Error instance |
| 73 | * - boolean false (to avoid short-circuiting the response) |
| 74 | * |
| 75 | * Returning any other value may result in unexpected behaviour. |
| 76 | * |
| 77 | * @since 2.9.0 |
| 78 | * |
| 79 | * @param false|array|WP_Error $preempt Whether to preempt an HTTP request's return value. Default false. |
| 80 | * @param array $r HTTP request arguments. |
| 81 | * @param string $url The request URL. |
| 82 | */ |
| 83 | add_filter('pre_http_request', function($preempt, $r, $url) |
| 84 | { |
| 85 | $input = JFactory::getApplication()->input; |
| 86 | |
| 87 | $httpUrl = new JUri($url); |
| 88 | $baseUrl = new JUri(site_url()); |
| 89 | |
| 90 | /** |
| 91 | * As WP Site Health suggests, an active session might close timeouts for loopback connection. |
| 92 | * For this reason, every time an HTTP request is made, we should try to detect whether the |
| 93 | * provided end-point has the same root of the current website, meaning that we are performing |
| 94 | * a self connection, probably through the REST API. |
| 95 | * |
| 96 | * @since 10.1.39 |
| 97 | */ |
| 98 | if (strpos($httpUrl->toString(['host', 'path']), $baseUrl->toString(['host', 'path'])) !== false) |
| 99 | { |
| 100 | // loopback detected, terminate the session before starting a request |
| 101 | session_write_close(); |
| 102 | } |
| 103 | |
| 104 | return $preempt; |
| 105 | }, 10, 3); |
| 106 | |
| 107 | /** |
| 108 | * Suppress "session active" critical error when Site Health performs its tests. |
| 109 | * |
| 110 | * Even if the session is active on Site Health, the plugin always call the |
| 111 | * `session_write_close` method before making any HTTP requests. |
| 112 | * |
| 113 | * @since 5.5.0 |
| 114 | */ |
| 115 | add_action('init', function() { |
| 116 | global $pagenow; |
| 117 | |
| 118 | // check if the current page is Site Health |
| 119 | if (preg_match("/^site-health/i", $pagenow)) |
| 120 | { |
| 121 | // always write session to avoid receiving a critical issue |
| 122 | session_write_close(); |
| 123 | } |
| 124 | }); |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Destroys the current active session, only if it already exists. |
| 129 | * |
| 130 | * @param boolean $restart True to immediately restart a new session. |
| 131 | * |
| 132 | * @return void |
| 133 | */ |
| 134 | public static function destroy($restart = true) |
| 135 | { |
| 136 | if (!self::isStarted()) |
| 137 | { |
| 138 | return; |
| 139 | } |
| 140 | |
| 141 | session_destroy(); |
| 142 | |
| 143 | if ($restart) |
| 144 | { |
| 145 | self::start(); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Checks if a session has been started. |
| 151 | * |
| 152 | * @return boolean True if active, otherwise false. |
| 153 | */ |
| 154 | public static function isStarted() |
| 155 | { |
| 156 | /** |
| 157 | * Make sure the session status is currently active. This because, |
| 158 | * in case someone had manually closed the session, this method would |
| 159 | * have improperly returned true. |
| 160 | * |
| 161 | * @since 10.1.43 |
| 162 | */ |
| 163 | return session_status() === PHP_SESSION_ACTIVE && self::getId(); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Returns the session ID, if any. |
| 168 | * |
| 169 | * @return string The unique session ID if active, otherwise an empty string. |
| 170 | */ |
| 171 | public static function getId() |
| 172 | { |
| 173 | return session_id(); |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * Returns the session name, if any. |
| 178 | * |
| 179 | * @return string The session name. |
| 180 | */ |
| 181 | public static function getName() |
| 182 | { |
| 183 | return session_name(); |
| 184 | } |
| 185 | } |
| 186 |