PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 All 167 releases
← All changes | includes/API/Controller_Registry.php +44 -14 1.10.19 → 1.10.21 View file →
@@ -216,10 +216,9 @@
216 216 // Any controller that keeps a namespace is stamped, not only a
217 217 // WP_REST_Controller subclass: the v2 map takes a class name, so a
218 218 // controller written against WP_REST_Server directly is as entitled to
219 219 // the promotion as one that extends core's base.
220 - $scope = self::namespace_scope( $controller );
221 - if ( null !== $scope ) {
220 + foreach ( self::namespace_scopes( $controller ) as $scope ) {
222 221 self::stamp_namespace( $controller, $scope, $lane );
223 222 }
224 223 }
225 224 $this->controllers[ $registry_key ] = $controller;
@@ -337,29 +336,60 @@
337 336 return $this->controllers;
338 337 }
339 338
340 339 /**
341 - * The class that declares this controller's namespace, if any declares one.
340 + * Every scope holding a namespace slot on this controller, outermost first.
342 341 *
343 - * Walked rather than asked, because property_exists() answers false for a
344 - * property a BASE class keeps private — the shape where a naive write would
345 - * quietly add a dynamic property to the subclass while the inherited
346 - * register_routes() went on reading the original value. A controller that
347 - * declares no namespace anywhere has nothing to stamp and registers where its
348 - * own register_routes() says, as it did before the map was derived.
342 + * The ancestry is walked rather than asked, because property_exists() answers
343 + * false for a property a BASE class keeps private, and a private declaration
344 + * is a slot of its own: a subclass that declares its own namespace alongside
345 + * one the base keeps private has two, and an inherited register_routes() reads
346 + * the base's. Writing from the wrong scope would quietly add a dynamic
347 + * property while the controller went on reading the old value, so each slot is
348 + * written where it lives and all of them are written.
349 349 *
350 + * A namespace a constructor assigned without declaring the property belongs to
351 + * no class, but the instance holds it and the controller reads it, so the
352 + * runtime class is the scope for that one.
353 + *
354 + * A controller that keeps no namespace at all has nothing to stamp and
355 + * registers where its own register_routes() says, as it did before the map was
356 + * derived.
357 + *
350 358 * @param object $controller Controller instance.
351 359 *
352 - * @return string|null The declaring class name, or null when there is none.
360 + * @return string[] Class names, empty when the controller holds no namespace.
353 361 */
354 - private static function namespace_scope( object $controller ): ?string {
362 + private static function namespace_scopes( object $controller ): array {
363 + $scopes = array();
364 +
355 365 for ( $class = new \ReflectionClass( $controller ); false !== $class; $class = $class->getParentClass() ) {
356 - if ( $class->hasProperty( 'namespace' ) ) {
357 - return $class->getName();
366 + if ( ! $class->hasProperty( 'namespace' ) ) {
367 + continue;
358 368 }
369 + // Ask where the slot this class can see actually lives, rather than
370 + // assuming this class owns it: an inherited protected property is ONE
371 + // slot visible from every descendant, so naming each descendant would
372 + // write it several times — harmless for a plain property, not for one
373 + // with a set hook. A private declaration a subclass shadows really is
374 + // a second slot, and this reports it as such.
375 + $scopes[] = $class->getProperty( 'namespace' )->getDeclaringClass()->getName();
359 376 }
360 377
361 - return null;
378 + // A namespace a constructor assigned without declaring the property belongs
379 + // to no class, but the instance holds it and the controller reads it. It
380 + // can also sit on top of a private one a base declares, so this is asked
381 + // whether or not the walk above found anything. The property list is read
382 + // rather than hasProperty(), which answers false for exactly this case
383 + // (measured on PHP 8.3) while getProperties() still returns the property.
384 + foreach ( ( new \ReflectionObject( $controller ) )->getProperties() as $property ) {
385 + if ( 'namespace' === $property->getName() && ! $property->isDefault() ) {
386 + $scopes[] = \get_class( $controller );
387 + break;
388 + }
389 + }
390 +
391 + return array_values( array_unique( $scopes ) );
362 392 }
363 393
364 394 /**
365 395 * WP_REST_Controller::$namespace is protected with no setter (Paul, 2026-09-18: stamp