PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Http/Route.php +124 -35 2.4.012.11.0 View file →
@@ -19,8 +19,10 @@
19 19 use FluentCommunity\Framework\Http\SubstituteParameters;
20 20 use FluentCommunity\Framework\Http\Middleware\RateLimiter;
21 21 use FluentCommunity\Framework\Validator\ValidationException;
22 22 use FluentCommunity\Framework\Database\Orm\ModelNotFoundException;
23 +use FluentCommunity\Framework\Foundation\Exceptions\HttpException;
24 +use FluentCommunity\Framework\Foundation\Exceptions\ExceptionHandler;
23 25 use FluentCommunity\Framework\Http\Response\Response as WPFluentResponse;
24 26
25 27 class Route
26 28 {
@@ -203,9 +205,8 @@
203 205
204 206 /**
205 207 * Map the route to be used in front-end.
206 208 *
207 - * @param mixed $handler
208 209 * @return self
209 210 */
210 211 public function preparefrontendHandlers()
211 212 {
@@ -909,52 +910,83 @@
909 910 } catch (ModelNotFoundException $e) {
910 911 return $this->app->response->sendError([
911 912 'message' => $e->getMessage()
912 913 ], 404);
914 + } catch (HttpException $e) {
915 + return $this->renderHttpException($e);
913 916 } catch (Throwable $e) {
914 - return $this->handleUnknownException(
915 - $e, $this->response ? $this->response->get_headers() : []
916 - );
917 + $headers = $this->response ? $this->response->get_headers() : [];
918 +
919 + // Consult the plugin's ExceptionHandler registry BEFORE the
920 + // production sanitizer. A registered renderable may return
921 + // either an HttpException (rendered with full status + safe
922 + // message) or a WP_REST_Response (returned verbatim). Null /
923 + // no-match falls through to handleUnknownException — the
924 + // sanitization default is preserved for any exception not
925 + // explicitly opted in.
926 + if ($mapped = $this->mapToHandlerResponse($e)) {
927 + return $mapped;
928 + }
929 +
930 + return $this->handleUnknownException($e, $headers);
917 931 }
918 932 }
919 933
920 934 /**
921 - * Handle response from route.
922 - *
923 - * @param \WP_REST_Response $response
924 - * @return \WP_REST_Response
935 + * Run the bound `ExceptionHandler` over `$e` and convert its result
936 + * to a `WP_REST_Response`, or `null` if the handler has nothing for
937 + * this exception (in which case the caller falls through to the
938 + * sanitizer).
939 + *
940 + * Returns an `HttpException` result through `renderHttpException()`
941 + * so observability + headers + the `{code, message, data}` shape
942 + * stay consistent with the dedicated `HttpException` catch arm.
943 + * A `WP_REST_Response` is returned verbatim — the renderer claimed
944 + * full control over the response shape; we still fire
945 + * `fluent_exception` so observability listeners see the original
946 + * exception.
947 + *
948 + * @param \Throwable $e
949 + * @return \WP_REST_Response|null
925 950 */
926 - protected function handleResponse($response)
951 + protected function mapToHandlerResponse(Throwable $e)
927 952 {
928 - return $response;
929 - }
953 + if (!$this->app->bound(ExceptionHandler::class)) {
954 + return null;
955 + }
930 956
931 - /**
932 - * Extract error message from response data.
933 - *
934 - * @param \WP_REST_Response $response
935 - * @return string
936 - */
937 - protected function extractErrorMessage($response)
938 - {
939 - $data = $response->get_data();
957 + $handler = $this->app->make(ExceptionHandler::class);
940 958
941 - if (is_string($data)) {
942 - return $data;
959 + if (!$handler instanceof ExceptionHandler) {
960 + return null;
943 961 }
944 962
945 - if (is_array($data) && isset($data['message'])) {
946 - return $data['message'];
963 + $result = $handler->render($e, $this->app);
964 +
965 + if ($result instanceof HttpException) {
966 + return $this->renderHttpException($result);
947 967 }
948 968
949 - if ($data instanceof WP_Error) {
950 - return $data->get_error_message();
969 + if ($result instanceof WP_REST_Response) {
970 + $this->fireExceptionEvent($e);
971 + return $result;
951 972 }
952 973
953 - return 'Unknown error';
974 + return null;
954 975 }
955 976
956 977 /**
978 + * Handle response from route.
979 + *
980 + * @param \WP_REST_Response $response
981 + * @return \WP_REST_Response
982 + */
983 + protected function handleResponse($response)
984 + {
985 + return $response;
986 + }
987 +
988 + /**
957 989 * Throw an exception based on the status code.
958 990 *
959 991 * @param string $message
960 992 * @param int $status
@@ -985,20 +1017,50 @@
985 1017 $data = [];
986 1018
987 1019 $this->fireExceptionEvent($e);
988 1020
1021 + // Production sanitization: client-facing message must not leak
1022 + // PDO / HTTP-client / file-system internals. The real message
1023 + // ships to fluent_exception listeners (Night Watcher / bridge)
1024 + // via fireExceptionEvent above, so observability is preserved.
989 1025 if ($this->app->isDebugOn()) {
990 1026 $data = [
991 1027 'file' => $e->getFile(),
992 1028 'line' => $e->getLine(),
993 1029 ];
1030 +
1031 + $message = $e->getMessage();
1032 + } else {
1033 + $message = 'An internal error occurred.';
994 1034 }
995 1035
996 1036 return $this->app->response->sendError([
997 1037 'code' => 'plugin_exception',
998 1038 'data' => $data,
1039 + 'message' => $message,
1040 + ], $e->getCode() ?: 500, $headers);
1041 + }
1042 +
1043 + /**
1044 + * Render an HttpException to a sanitization-free response.
1045 + *
1046 + * HttpException is the opt-in contract for "I authored this message,
1047 + * it is safe to ship to the client". Bypasses handleUnknownException's
1048 + * production sanitization but still fires fluent_exception for
1049 + * observability so listeners see every thrown HttpException.
1050 + *
1051 + * @param HttpException $e
1052 + * @return \WP_REST_Response
1053 + */
1054 + protected function renderHttpException(HttpException $e)
1055 + {
1056 + $this->fireExceptionEvent($e);
1057 +
1058 + return $this->app->response->sendError([
1059 + 'code' => $e->getErrorCode(),
999 1060 'message' => $e->getMessage(),
1000 - ], $e->getCode() ?: 500, $headers);
1061 + 'data' => $e->getData(),
1062 + ], $e->getStatusCode(), $e->getHeaders());
1001 1063 }
1002 1064
1003 1065 /**
1004 1066 * Dispatch the route action.
@@ -1072,8 +1134,17 @@
1072 1134 * @return void
1073 1135 */
1074 1136 protected function fireExceptionEvent($exception)
1075 1137 {
1138 + // Reentrancy guard: a fluent_exception listener that itself triggers
1139 + // an exception path must not re-enter this method and recurse. Reset
1140 + // in finally so subsequent (sequential) calls proceed normally.
1141 + static $firing = false;
1142 +
1143 + if ($firing) {
1144 + return;
1145 + }
1146 +
1076 1147 if ($this->app->isDebugOn() || defined('FLUENT_BRIDGE_SECRET')) {
1077 1148 $message = sprintf(
1078 1149 "%s in %s:%d\nStack trace:\n%s\n",
1079 1150 $exception->getMessage(),
@@ -1080,12 +1151,28 @@
1080 1151 $exception->getFile(),
1081 1152 $exception->getLine(),
1082 1153 $exception->getTraceAsString()
1083 1154 );
1084 -
1155 +
1085 1156 error_log($message);
1086 -
1157 + }
1158 +
1159 + $firing = true;
1160 +
1161 + try {
1087 1162 $this->app->doAction('fluent_exception', $exception);
1163 + } catch (Throwable $listenerError) {
1164 + // Listener-throw isolation: a buggy fluent_exception listener
1165 + // (DB down, disk full) must not escape and crash the response.
1166 + // Log under the same gate; never re-fire fluent_exception here
1167 + // — that would be the cascade we are protecting against.
1168 + if ($this->app->isDebugOn() || defined('FLUENT_BRIDGE_SECRET')) {
1169 + error_log(
1170 + 'fluent_exception listener failed: ' . $listenerError->getMessage()
1171 + );
1172 + }
1173 + } finally {
1174 + $firing = false;
1088 1175 }
1089 1176 }
1090 1177
1091 1178 /**
@@ -1133,9 +1220,9 @@
1133 1220 }
1134 1221
1135 1222 return $response;
1136 1223
1137 - } catch (Exception $e) {
1224 + } catch (Throwable $e) {
1138 1225 return new WP_Error(
1139 1226 'Permission Callback Error',
1140 1227 $e->getMessage(), [
1141 1228 'status' => $e->getCode() ?: 403
@@ -1261,9 +1348,11 @@
1261 1348 * @return array
1262 1349 */
1263 1350 protected function collectMiddleWare($type = 'before')
1264 1351 {
1265 - $middleware = $this->app['config']->get('middleware', []);
1352 + $middleware = $this->app->bound('http.middleware')
1353 + ? $this->app['http.middleware']
1354 + : [];
1266 1355
1267 1356 $callableMiddleware = Arr::get($middleware, "global.{$type}", []);
1268 1357
1269 1358 $routeArray = [];
@@ -1292,9 +1381,9 @@
1292 1381 if (isset($handler)) {
1293 1382 $this->addMiddlewareInTheStack($callableMiddleware, $handler);
1294 1383 } else {
1295 1384 if (isset($key)) {
1296 - $mpath = 'config.middleware.route.' . $type;
1385 + $mpath = 'app/Http/middleware.php route.' . $type;
1297 1386 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
1298 1387 } else {
1299 1388 $msg = "Could't resolve middleware.";
1300 1389 }
@@ -1459,10 +1548,10 @@
1459 1548 * @return boolean
1460 1549 */
1461 1550 protected function isPolicyHandlerParseable($policyHandler)
1462 1551 {
1463 - return (strpos($policyHandler, '@') === true
1464 - || strpos($policyHandler, '::') === true);
1552 + return (strpos($policyHandler, '@') !== false
1553 + || strpos($policyHandler, '::') !== false);
1465 1554 }
1466 1555
1467 1556 /**
1468 1557 * Default/Fallback policy handler for the route