upskill-event-manager/wordpress-dev/vendor/antecedent/patchwork/src/CallRerouting.php
bengizmo cdef12ee80 feat(events): Implement fallback logic and UI for Create/Modify Event page
- Refactored fallback submission logic in `class-event-handler.php` to remove `wp_die`/`exit` calls and use redirects for error handling, enabling proper unit testing.
- Implemented meta-data saving (dates, venue, organizer) in the fallback logic using `update_post_meta`.
- Updated unit tests (`test-event-management.php`) to remove `markTestIncomplete` calls related to handler errors and uncommented meta assertions. Unit tests for fallback logic now pass.
- Added Instructions section and Return to Dashboard button to the event form shortcode (`display_event_form_shortcode`).
- Applied basic theme styling classes (`ast-container`, `notice`, `ast-button`) to the event form.
- Updated `docs/implementation_plan.md` to reflect completion of tasks 4.1-4.5 and set focus to Task 5.

Refs: Task 4.1, 4.2, 4.3, 4.4, 4.5
2025-04-01 11:46:24 -03:00

604 lines
22 KiB
PHP

<?php
/**
* @link http://patchwork2.org/
* @author Ignas Rudaitis <ignas.rudaitis@gmail.com>
* @copyright 2010-2018 Ignas Rudaitis
* @license http://www.opensource.org/licenses/mit-license.html
*/
namespace Patchwork\CallRerouting;
require __DIR__ . '/CallRerouting/Handle.php';
require __DIR__ . '/CallRerouting/Decorator.php';
use Patchwork\Utils;
use Patchwork\Stack;
use Patchwork\Config;
use Patchwork\Exceptions;
use Patchwork\CodeManipulation;
use Patchwork\CodeManipulation\Actions\RedefinitionOfLanguageConstructs;
use Patchwork\CodeManipulation\Actions\RedefinitionOfNew;
const INTERNAL_REDEFINITION_NAMESPACE = 'Patchwork\Redefinitions';
const EVALUATED_CODE_FILE_NAME_SUFFIX = '/\(\d+\) : eval\(\)\'d code$/';
const INSTANTIATOR_NAMESPACE = 'Patchwork\Instantiators';
const INSTANTIATOR_DEFAULT_ARGUMENT = 'Patchwork\CallRerouting\INSTANTIATOR_DEFAULT_ARGUMENT';
const INTERNAL_STUB_CODE = '
namespace @ns_for_redefinitions;
function @name(@signature) {
$__pwArgs = \array_slice(\debug_backtrace()[0]["args"], 1);
if (!empty($__pwNamespace) && \function_exists($__pwNamespace . "\\\\@name")) {
return \call_user_func_array($__pwNamespace . "\\\\@name", $__pwArgs);
}
@interceptor;
return \call_user_func_array("@name", $__pwArgs);
}
';
const INSTANTIATOR_CODE = '
namespace @namespace;
class @instantiator {
function instantiate(@parameters) {
$__pwArgs = \debug_backtrace()[0]["args"];
foreach ($__pwArgs as $__pwOffset => $__pwValue) {
if ($__pwValue === \Patchwork\CallRerouting\INSTANTIATOR_DEFAULT_ARGUMENT) {
unset($__pwArgs[$__pwOffset]);
}
}
switch (count($__pwArgs)) {
case 0:
return new \@class;
case 1:
return new \@class($__pwArgs[0]);
case 2:
return new \@class($__pwArgs[0], $__pwArgs[1]);
case 3:
return new \@class($__pwArgs[0], $__pwArgs[1], $__pwArgs[2]);
case 4:
return new \@class($__pwArgs[0], $__pwArgs[1], $__pwArgs[2], $__pwArgs[3]);
case 5:
return new \@class($__pwArgs[0], $__pwArgs[1], $__pwArgs[2], $__pwArgs[3], $__pwArgs[4]);
default:
$__pwReflector = new \ReflectionClass(\'@class\');
return $__pwReflector->newInstanceArgs($__pwArgs);
}
}
}
';
function connect($source, callable $target, ?Handle $handle = null, $partOfWildcard = false)
{
$source = translateIfLanguageConstruct($source);
$handle = $handle ?: new Handle;
list($class, $method) = Utils\interpretCallable($source);
if (constitutesWildcard($source)) {
return applyWildcard($source, $target, $handle);
}
if (Utils\isOwnName($class) || Utils\isOwnName($method)) {
return $handle;
}
validate($source, $partOfWildcard);
if (empty($class)) {
if (Utils\callableDefined($source) && (new \ReflectionFunction($method))->isInternal()) {
$stub = INTERNAL_REDEFINITION_NAMESPACE . '\\' . $source;
return connect($stub, $target, $handle, $partOfWildcard);
}
$handle = connectFunction($method, $target, $handle);
} else {
if (Utils\callableDefined($source)) {
if ($method === 'new') {
$handle = connectInstantiation($class, $target, $handle);
} elseif ((new \ReflectionMethod($class, $method))->isUserDefined()) {
$handle = connectMethod($source, $target, $handle);
} else {
throw new InternalMethodsNotSupported($source);
}
} else {
$handle = queueConnection($source, $target, $handle);
}
}
attachExistenceAssertion($handle, $source);
return $handle;
}
function constitutesWildcard($source)
{
$source = Utils\interpretCallable($source);
$source = Utils\callableToString($source);
return strcspn($source, '*{,}') != strlen($source);
}
function applyWildcard($wildcard, callable $target, ?Handle $handle = null)
{
$handle = $handle ?: new Handle;
list($class, $method, $instance) = Utils\interpretCallable($wildcard);
if (!empty($instance)) {
foreach (Utils\matchWildcard($method, get_class_methods($instance)) as $item) {
if (!$handle->hasTag($item)) {
connect([$instance, $item], $target, $handle);
$handle->tag($item);
}
}
return $handle;
}
$callables = Utils\matchWildcard($wildcard, Utils\getRedefinableCallables());
foreach ($callables as $callable) {
if (!inPreprocessedFile($callable) || $handle->hasTag($callable)) {
continue;
}
if (function_exists($callable)) {
# Restore lower/upper case distinction
$callable = (new \ReflectionFunction($callable))->getName();
}
connect($callable, $target, $handle, true);
$handle->tag($callable);
}
if (!isset($class) || !class_exists($class, false)) {
queueConnection($wildcard, $target, $handle);
}
return $handle;
}
function attachExistenceAssertion(Handle $handle, $function)
{
$handle->addExpirationHandler(function() use ($function) {
if (!Utils\callableDefined($function)) {
# Not using exceptions because this might happen during PHP shutdown
$message = '%s() was never defined during the lifetime of its redefinition';
trigger_error(sprintf($message, Utils\callableToString($function)), E_USER_WARNING);
}
});
}
function validate($function, $partOfWildcard = false)
{
list($class, $method) = Utils\interpretCallable($function);
if (!Utils\callableDefined($function) || $method === 'new') {
return;
}
$reflection = Utils\reflectCallable($function);
$name = Utils\callableToString($function);
if ($reflection->isInternal() && !in_array($name, Config\getRedefinableInternals())) {
throw new Exceptions\NotUserDefined($function);
}
if (!$reflection->isInternal() && !inPreprocessedFile($function) && !$partOfWildcard) {
throw new Exceptions\DefinedTooEarly($function);
}
}
function inPreprocessedFile($callable)
{
if (Utils\isOwnName(Utils\callableToString($callable))) {
return false;
}
$file = Utils\reflectCallable($callable)->getFileName();
$evaluated = preg_match(EVALUATED_CODE_FILE_NAME_SUFFIX, $file);
return $evaluated || !empty(State::$preprocessedFiles[$file]);
}
function connectFunction($function, callable $target, ?Handle $handle = null)
{
$handle = $handle ?: new Handle;
$routes = &State::$routes[null][$function];
$offset = Utils\append($routes, [$target, $handle]);
$handle->addReference($routes[$offset]);
return $handle;
}
function queueConnection($source, callable $target, ?Handle $handle = null)
{
$handle = $handle ?: new Handle;
$offset = Utils\append(State::$queue, [$source, $target, $handle]);
$handle->addReference(State::$queue[$offset]);
return $handle;
}
function deployQueue()
{
foreach (State::$queue as $offset => $item) {
if (empty($item)) {
unset(State::$queue[$offset]);
continue;
}
list($source, $target, $handle) = $item;
if (Utils\callableDefined($source) || constitutesWildcard($source)) {
connect($source, $target, $handle);
unset(State::$queue[$offset]);
}
}
}
function connectMethod($function, callable $target, ?Handle $handle = null)
{
$handle = $handle ?: new Handle;
list($class, $method, $instance) = Utils\interpretCallable($function);
$target = new Decorator($target);
$target->superclass = $class;
$target->method = $method;
$target->instance = $instance;
$reflection = Utils\reflectCallable($function);
$declaringClass = $reflection->getDeclaringClass();
$class = $declaringClass->getName();
$aliases = $declaringClass->getTraitAliases();
if (isset($aliases[$method])) {
list($trait, $method) = explode('::', $aliases[$method]);
}
$routes = &State::$routes[$class][$method];
$offset = Utils\append($routes, [$target, $handle]);
$handle->addReference($routes[$offset]);
return $handle;
}
function connectInstantiation($class, callable $target, ?Handle $handle = null)
{
if (!Config\isNewKeywordRedefinable()) {
throw new Exceptions\NewKeywordNotRedefinable;
}
$handle = $handle ?: new Handle;
$class = strtr($class, ['\\' => '__']);
$routes = &State::$routes["Patchwork\\Instantiators\\$class"]['instantiate'];
$offset = Utils\append($routes, [$target, $handle]);
$handle->addReference($routes[$offset]);
return $handle;
}
function disconnectAll()
{
foreach (State::$routes as $class => $routesByClass) {
foreach ($routesByClass as $method => $routes) {
foreach ($routes as $route) {
list($callback, $handle) = $route;
if ($handle !== null) {
$handle->expire();
}
}
}
}
State::$routes = [];
connectDefaultInternals();
}
function dispatchTo(callable $target)
{
return call_user_func_array($target, Stack\top('args'));
}
function dispatch($class, $calledClass, $method, $frame, &$result, ?array $args = null)
{
$trace = debug_backtrace();
$isInternalStub = strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0;
$isLanguageConstructStub = strpos($method, RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX) === 0;
$isInstantiator = strpos($method, INSTANTIATOR_NAMESPACE) === 0;
if ($isInternalStub && !$isLanguageConstructStub && $args === null) {
# Mind the namespace-of-origin argument
$args = array_reverse($trace)[$frame - 1]['args'];
array_shift($args);
}
if ($isInstantiator) {
$args = $args ?: array_reverse($trace)[$frame - 1]['args'];
foreach ($args as $offset => $value) {
if ($value === INSTANTIATOR_DEFAULT_ARGUMENT) {
unset($args[$offset]);
}
}
}
$success = false;
Stack\pushFor($frame, $calledClass, function() use ($class, $method, &$result, &$success) {
foreach (getRoutesFor($class, $method) as $offset => $route) {
if (empty($route)) {
unset(State::$routes[$class][$method][$offset]);
continue;
}
State::$routeStack[] = [$class, $method, $offset];
try {
$result = dispatchTo(reset($route));
$success = true;
} catch (Exceptions\NoResult $e) {
array_pop(State::$routeStack);
continue;
}
array_pop(State::$routeStack);
if ($success) {
break;
}
}
}, $args);
return $success;
}
function relay(?array $args = null)
{
list($class, $method, $offset) = end(State::$routeStack);
$route = &State::$routes[$class][$method][$offset];
$backup = $route;
$route = ['Patchwork\fallBack', new Handle];
$top = Stack\top();
if ($args === null) {
$args = $top['args'];
}
$isInternalStub = strpos($method, INTERNAL_REDEFINITION_NAMESPACE) === 0;
$isLanguageConstructStub = strpos($method, RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX) === 0;
if ($isInternalStub && !$isLanguageConstructStub) {
array_unshift($args, '');
}
try {
if (isset($top['class'])) {
$reflection = new \ReflectionMethod(Stack\topCalledClass(), $top['function']);
$reflection->setAccessible(true);
$result = $reflection->invokeArgs(Stack\top('object'), $args);
} else {
$result = call_user_func_array($top['function'], $args);
}
} catch (\Exception $e) {
$exception = $e;
}
$route = $backup;
if (isset($exception)) {
throw $exception;
}
return $result;
}
/**
* @deprecated 2.2.0
*/
function connectOnHHVM($function, Handle $handle)
{
fb_intercept($function, function($name, $obj, $args, $data, &$done) {
deployQueue();
list($class, $method) = Utils\interpretCallable($name);
$calledClass = null;
if (is_string($obj)) {
$calledClass = $obj;
} elseif (is_object($obj)) {
$calledClass = get_class($obj);
}
$frame = count(debug_backtrace(0)) - 1;
$result = null;
$done = dispatch($class, $calledClass, $method, $frame, $result, $args);
return $result;
});
$handle->addExpirationHandler(getHHVMExpirationHandler($function));
}
/**
* @deprecated 2.2.0
*/
function getHHVMExpirationHandler($function)
{
return function() use ($function) {
list($class, $method) = Utils\interpretCallable($function);
$empty = true;
foreach (getRoutesFor($class, $method) as $offset => $route) {
if (!empty($route)) {
$empty = false;
break;
} else {
unset(State::$routes[$class][$method][$offset]);
}
}
if ($empty) {
fb_intercept($function, null);
}
};
}
function getRoutesFor($class, $method)
{
if (!isset(State::$routes[$class][$method])) {
return [];
}
return array_reverse(State::$routes[$class][$method], true);
}
function dispatchDynamic($callable, array $arguments)
{
list($class, $method) = Utils\interpretCallable($callable);
$translation = INTERNAL_REDEFINITION_NAMESPACE . '\\' . $method;
if ($class === null && function_exists($translation)) {
$callable = $translation;
# Mind the namespace-of-origin argument
array_unshift($arguments, '');
}
return call_user_func_array($callable, $arguments);
}
function createStubsForInternals()
{
$namespace = INTERNAL_REDEFINITION_NAMESPACE;
foreach (Config\getRedefinableInternals() as $name) {
if (function_exists($namespace . '\\' . $name)) {
continue;
}
$signature = ['$__pwNamespace'];
foreach ((new \ReflectionFunction($name))->getParameters() as $offset => $argument) {
$formal = '';
if ($argument->isPassedByReference()) {
$formal .= '&';
}
$formal .= '$' . $argument->getName();
$isVariadic = is_callable([$argument, 'isVariadic']) ? $argument->isVariadic() : false;
if ($argument->isOptional() || $isVariadic || ($name === 'define' && $offset === 2)) {
continue;
}
$signature[] = $formal;
}
$refs = sprintf('[%s]', join(', ', $signature));
$interceptor = sprintf(
str_replace(
'$__pwRefOffset = 0;',
'$__pwRefOffset = 1;',
\Patchwork\CodeManipulation\Actions\CallRerouting\CALL_INTERCEPTION_CODE
),
$refs
);
eval(strtr(INTERNAL_STUB_CODE, [
'@name' => $name,
'@signature' => join(', ', $signature),
'@interceptor' => $interceptor,
'@ns_for_redefinitions' => INTERNAL_REDEFINITION_NAMESPACE,
]));
}
}
/**
* This is needed, for instance, to intercept the time() call in call_user_func('time').
*
* For that to happen, we require that if at least one internal function is redefinable, then
* call_user_func, preg_replace_callback and other callback-taking internal functions also be
* redefinable: see Patchwork\Config.
*
* Here, we go through the callback-taking internals and add argument-inspecting patches
* (redefinitions) to them.
*
* The patches are then expected to find the "nested" internal calls, such as the 'time' argument
* in call_user_func('time'), and invoke their respective redefinitions, if any.
*/
function connectDefaultInternals()
{
# call_user_func() etc. are not a problem if no other internal functions are redefined
if (Config\getRedefinableInternals() === []) {
return;
}
foreach (Config\getDefaultRedefinableInternals() as $function) {
# Which arguments are callbacks? Store their offsets in the following array.
$offsets = [];
foreach ((new \ReflectionFunction($function))->getParameters() as $offset => $argument) {
$name = $argument->getName();
if (strpos($name, 'call') !== false || strpos($name, 'func') !== false) {
$offsets[] = $offset;
}
}
connect($function, function() use ($function, $offsets) {
# This is the argument-inspecting patch.
$args = Stack\top('args');
$caller = Stack\all()[1];
foreach ($offsets as $offset) {
# Callback absent
if (!isset($args[$offset])) {
continue;
}
$callable = $args[$offset];
# Callback is a closure => definitely not internal
if ($callable instanceof \Closure) {
continue;
}
list($class, $method, $instance) = Utils\interpretCallable($callable);
if (empty($class)) {
# Callback is global function, which might be internal too.
$args[$offset] = function() use ($callable) {
return dispatchDynamic($callable, func_get_args());
};
}
# Callback involves a class => not internal either, since the only internals that
# Patchwork can handle as of 2.0 are global functions.
# However, we must handle all kinds of opaque access here too, such as self:: and
# private methods, because we're actually patching a stub (see INTERNAL_STUB_CODE)
# and not directly call_user_func itself (or usort, or any other of those).
# We must compensate for scope that is lost, and that callback-taking functions
# can make use of.
if (!empty($class)) {
if ($class === 'self' || $class === 'static' || $class === 'parent') {
# We do not discriminate between early and late static binding here: FIXME.
$actualClass = $caller['class'];
if ($class === 'parent') {
$actualClass = get_parent_class($actualClass);
}
$class = $actualClass;
}
# When calling a parent constructor, the reference to the object being
# constructed needs to be extracted from the stack info.
# Also turned out to be necessary to solve this, without any parent
# constructors involved: https://github.com/antecedent/patchwork/issues/99
if (is_null($instance) && isset($caller['object'])) {
$instance = $caller['object'];
}
try {
$reflection = new \ReflectionMethod($class, $method);
$reflection->setAccessible(true);
$args[$offset] = function() use ($reflection, $instance) {
return $reflection->invokeArgs($instance, func_get_args());
};
} catch (\ReflectionException $e) {
# If it's an invalid callable, then just prevent the unexpected propagation
# of ReflectionExceptions.
}
}
}
# Give the inspected arguments back to the *original* definition of the
# callback-taking function, e.g. \array_map(). This works given that the
# present patch is the innermost.
return call_user_func_array($function, $args);
});
}
}
/**
* @since 2.0.5
*
* As of version 2.0.5, this is used to accommodate language constructs
* (echo, eval, exit and others) within the concept of callable.
*/
function translateIfLanguageConstruct($callable)
{
if (!is_string($callable)) {
return $callable;
}
if (in_array($callable, Config\getRedefinableLanguageConstructs())) {
return RedefinitionOfLanguageConstructs\LANGUAGE_CONSTRUCT_PREFIX . $callable;
} elseif (in_array($callable, Config\getSupportedLanguageConstructs())) {
throw new Exceptions\NotUserDefined($callable);
} else {
return $callable;
}
}
function resolveClassToInstantiate($class, $calledClass)
{
$pieces = explode('\\', $class);
$last = array_pop($pieces);
if (in_array($last, ['self', 'static', 'parent'])) {
$frame = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
if ($last == 'self') {
$class = $frame['class'];
} elseif ($last == 'parent') {
$class = get_parent_class($frame['class']);
} elseif ($last == 'static') {
$class = $calledClass;
}
}
return ltrim($class, '\\');
}
function getInstantiator($class, $calledClass)
{
$namespace = INSTANTIATOR_NAMESPACE;
$class = resolveClassToInstantiate($class, $calledClass);
$adaptedName = strtr($class, ['\\' => '__']);
if (!class_exists("$namespace\\$adaptedName")) {
$constructor = (new \ReflectionClass($class))->getConstructor();
list($parameters, $arguments) = Utils\getParameterAndArgumentLists($constructor);
$code = strtr(INSTANTIATOR_CODE, [
'@namespace' => INSTANTIATOR_NAMESPACE,
'@instantiator' => $adaptedName,
'@class' => $class,
'@parameters' => $parameters,
]);
RedefinitionOfNew\suspendFor(function() use ($code) {
eval(CodeManipulation\transformForEval($code));
});
}
$instantiator = "$namespace\\$adaptedName";
return new $instantiator;
}
class State
{
static $routes = [];
static $queue = [];
static $preprocessedFiles = [];
static $routeStack = [];
}