<?php
/**
 * This file is part of the SymfonyCronBundle package.
 *
 * (c) Dries De Peuter <dries@nousefreak.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Cron\CronBundle\Command;

use Cron\CronBundle\Entity\CronJob;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Dries De Peuter <dries@nousefreak.be>
 */
class CronDeleteCommand extends ContainerAwareCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('cron:delete')
            ->setDescription('Delete a cron job')
            ->addArgument('job', InputArgument::REQUIRED, 'The job to delete');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $job = $this->queryJob($input->getArgument('job'));

        if (!$job) {
            throw new \InvalidArgumentException('Unknown job.');
        }

        if ($job->getEnabled()) {
            throw new \InvalidArgumentException('The job should be disabled first.');
        }

        $output->writeln(sprintf('<info>You are about to delete "%s".</info>', $job->getName()));

        $question = new ConfirmationQuestion('<question>Delete this job</question> [N/y]: ', false, '/^(y)/i');

        if (!$this->getQuestionHelper()->ask($input, $output, $question)) {
            return;
        }

        $this->getContainer()->get('cron.manager')
            ->deleteJob($job);

        $output->writeln(sprintf('<info>Cron "%s" was deleted.</info>', $job->getName()));
    }

    /**
     * @param  string  $jobName
     * @return CronJob
     */
    protected function queryJob($jobName)
    {
        return $this->getContainer()->get('cron.manager')
            ->getJobByName($jobName);
    }

    /**
     * @return QuestionHelper
     */
    private function getQuestionHelper()
    {
        return $this->getHelperSet()->get('question');
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Propagation;

/**
 * This makes a given span the current span by placing it in scope.
 *
 *
This type is an SPI, and intended to be used by implementors looking to change thread-local
 * storage, or integrate with other contexts such as logging (MDC).
 *
 *
Design

 *
 * This design was inspired by com.google.instrumentation.trace.ContextUtils,
 * com.google.inject.servlet.RequestScoper and com.github.kristofa.brave.CurrentSpan
 */

final class CurrentTraceContext
{
    private $context;

    private function __construct(TraceContext $currentContext = null)
    {
        $this->context = $currentContext;
    }

    /**
     * Creates a current trace context controller. If there is no context, and consequently no span, it holds null.
     *
     * @param TraceContext|null $currentContext
     * @return CurrentTraceContext
     */
    public static function create(?TraceContext $currentContext = null): self
    {
        return new self($currentContext);
    }

    /**
     * Returns the current span context in scope or null if there isn't one.
     *
     * @return TraceContext|null
     */
    public function getContext(): ?TraceContext
    {
        return $this->context;
    }

    /**
     * Sets the current span in scope until the returned callable is called. It is a programming
     * error to drop or never close the result.
     *
     * @param TraceContext|null $currentContext
     * @return callable The scope closed
     */
    public function createScopeAndRetrieveItsCloser(?TraceContext $currentContext = null): callable
    {
        $previous = $this->context;
        $self = $this;
        $this->context = $currentContext;

        return function () use ($previous, $self) {
            $self->context = $previous;
        };
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Reporters\Http;

use BadFunctionCallException;
use RuntimeException;

final class CurlFactory implements ClientFactory
{
    private function __construct()
    {
    }

    /**
     * @return CurlFactory
     * @throws \BadFunctionCallException if the curl extension is not installed.
     */
    public static function create(): self
    {
        if (!function_exists('curl_init')) {
            throw new BadFunctionCallException('cURL is not enabled');
        }

        return new self();
    }

    /**
     * {@inheritdoc}
     */
    public function build(?array $options = []): callable
    {
        /**
         * @param string $payload
         * @throws RuntimeException
         * @return void
         */
        return static function ($payload) use ($options) {
            $handle = curl_init($options['endpoint_url']);
            if ($handle === false) {
                throw new RuntimeException(
                    sprintf('failed to create the handle for url "%s"', $options['endpoint_url'])
                );
            }

            curl_setopt($handle, CURLOPT_POST, 1);
            curl_setopt($handle, CURLOPT_POSTFIELDS, $payload);
            curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
            $requiredHeaders = [
                'Content-Type' => 'application/json',
                'Content-Length' => strlen($payload),
            ];
            $additionalHeaders = (isset($options['headers']) ? $options['headers'] : []);
            $headers = array_merge($additionalHeaders, $requiredHeaders);
            $formattedHeaders = array_map(function ($key, $value) {
                return $key . ': ' . $value;
            }, array_keys($headers), $headers);
            curl_setopt($handle, CURLOPT_HTTPHEADER, $formattedHeaders);

            if (isset($options['timeout'])) {
                curl_setopt($handle, CURLOPT_TIMEOUT, $options['timeout']);
            }

            if (curl_exec($handle) !== false) {
                $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
                curl_close($handle);

                if ($statusCode !== 202) {
                    throw new RuntimeException(
                        sprintf('Reporting of spans failed, status code %d', $statusCode)
                    );
                }
            } else {
                throw new RuntimeException(sprintf(
                    'Reporting of spans failed: %s, error code %s',
                    curl_error($handle),
                    curl_errno($handle)
                ));
            }
        };
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Propagation;

use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Zipkin\Propagation\Exceptions\InvalidTraceContextArgument;
use Zipkin\Propagation\TraceContext;

/**
 * @see https://github.com/openzipkin/b3-propagation
 */
final class B3 implements Propagation
{
    /**
     * 128 or 64-bit trace ID lower-hex encoded into 32 or 16 characters (required)
     */
    const TRACE_ID_NAME = 'X-B3-TraceId';

    /**
     * 64-bit span ID lower-hex encoded into 16 characters (required)
     */
    const SPAN_ID_NAME = 'X-B3-SpanId';

    /**
     * 64-bit parent span ID lower-hex encoded into 16 characters (absent on root span)
     */
    const PARENT_SPAN_ID_NAME = 'X-B3-ParentSpanId';

    /**
     * '1' means report this span to the tracing system, '0' means do not. (absent means defer the
     * decision to the receiver of this header).
     */
    const SAMPLED_NAME = 'X-B3-Sampled';

    /**
     * '1' implies sampled and is a request to override collection-tier sampling policy.
     */
    const FLAGS_NAME = 'X-B3-Flags';

    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger ?: new NullLogger();
    }

    /**
     * @return array|string[]
     */
    public function getKeys(): array
    {
        return [
            self::TRACE_ID_NAME,
            self::SPAN_ID_NAME,
            self::PARENT_SPAN_ID_NAME,
            self::SAMPLED_NAME,
            self::FLAGS_NAME,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getInjector(Setter $setter): callable
    {
        /**
         * @param TraceContext $traceContext
         * @param &$carrier
         * @return void
         */
        return function (TraceContext $traceContext, &$carrier) use ($setter) {
            $setter->put($carrier, self::TRACE_ID_NAME, $traceContext->getTraceId());
            $setter->put($carrier, self::SPAN_ID_NAME, $traceContext->getSpanId());

            if ($traceContext->getParentId() !== null) {
                $setter->put($carrier, self::PARENT_SPAN_ID_NAME, $traceContext->getParentId());
            }

            if ($traceContext->isSampled() !== null) {
                $setter->put($carrier, self::SAMPLED_NAME, $traceContext->isSampled() ? '1' : '0');
            }

            $setter->put($carrier, self::FLAGS_NAME, $traceContext->isDebug() ? '1' : '0');
        };
    }

    /**
     * {@inheritdoc}
     */
    public function getExtractor(Getter $getter): callable
    {
        /**
         * @param mixed $carrier
         * @return TraceContext|SamplingFlags
         */
        return function ($carrier) use ($getter) {
            $isSampledRaw = $getter->get($carrier, self::SAMPLED_NAME);

            $isSampled = SamplingFlags::EMPTY_SAMPLED;
            if ($isSampledRaw !== null) {
                if ($isSampledRaw === '1' || strtolower($isSampledRaw) === 'true') {
                    $isSampled = true;
                } elseif ($isSampledRaw === '0' || strtolower($isSampledRaw) === 'false') {
                    $isSampled = false;
                }
            }

            $isDebugRaw = $getter->get($carrier, self::FLAGS_NAME);

            /**
             * @var bool|null
             */
            $isDebug = SamplingFlags::EMPTY_DEBUG;
            if ($isDebugRaw !== null) {
                $isDebug = ($isDebugRaw === '1');
            }

            $traceId = $getter->get($carrier, self::TRACE_ID_NAME);

            if ($isSampled === null && $isDebug === null && $traceId === null) {
                return DefaultSamplingFlags::createAsEmpty();
            }

            $spanId = $getter->get($carrier, self::SPAN_ID_NAME);

            if ($spanId === null) {
                return DefaultSamplingFlags::create($isSampled, $isDebug);
            }

            $parentSpanId = $getter->get($carrier, self::PARENT_SPAN_ID_NAME);

            try {
                return TraceContext::create($traceId, $spanId, $parentSpanId, $isSampled, $isDebug);
            } catch (InvalidTraceContextArgument $e) {
                $this->logger->debug(sprintf(
                    'Failed to extract propagated context: %s',
                    $e->getMessage()
                ));

                return DefaultSamplingFlags::createAsEmpty();
            }
        };
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Recording;

use Zipkin\Endpoint;
use Zipkin\Propagation\TraceContext;

final class Span
{
    /**
     * Epoch microseconds of the start of this span, absent if this an incomplete
     * span.
     *
     * This value should be set directly by instrumentation, using the most
     * precise value possible. For example, gettimeofday or syncing nanoTime
     * against a tick of currentTimeMillis.
     *
     * Timestamp is nullable for input only. Spans without a timestamp cannot be
     * presented in a timeline: Span stores should not output spans missing a
     * timestamp.
     *
     * There are two known edge-cases where this could be absent: both cases
     * exist when a collector receives a span in parts and a binary annotation
     * precedes a timestamp. This is possible when..
     *  - The span is in-flight (ex not yet received a timestamp)
     *  - The span's start event was lost
     *
     * @var int
     */
    private $timestamp;

    /**
     * @var bool
     */
    private $finished = false;

    /**
     * Span name in lowercase, rpc method for example. Conventionally, when the
     * span name isn't known, name = "unknown".
     *
     * @var string
     */
    private $name;

    /**
     * @var string
     */
    private $kind;

    /**
     * Unique 8-byte identifier for a trace, set on all spans within it.
     *
     * @var int
     */
    private $traceId;

    /**
     * The parent's Span.id; absent if this the root span in a trace.
     *
     * @var int
     */
    private $parentId;

    /**
     * Unique 8-byte identifier of this span within a trace. A span is uniquely
     * identified in storage by (trace_id, id).
     *
     * @var int
     */
    private $spanId;

    /**
     * True is a request to store this span even if it overrides sampling policy.
     *
     * @var bool
     */
    private $debug;

    /**
     * Associates events that explain latency with a timestamp. Unlike log
     * statements, annotations are often codes: for example SERVER_RECV("sr").
     * Annotations are sorted ascending by timestamp.
     *
     * @var array<array>
     */
    private $annotations = [];

    /**
     * @var array
     */
    private $tags = [];

    /**
     * Measurement in microseconds of the critical path, if known. Durations of
     * less than one microsecond must be rounded up to 1 microsecond.
     *
     * This value should be set directly, as opposed to implicitly via annotation
     * timestamps. Doing so encourages precision decoupled from problems of
     * clocks, such as skew or NTP updates causing time to move backwards.
     *
     * If this field is persisted as unset, zipkin will continue to work, except
     * duration query support will be implementation-specific. Similarly, setting
     * this field non-atomically is implementation-specific.
     *
     * This field is i64 vs i32 to support spans longer than 35 minutes.
     *
     * @var int
     */
    private $duration;

    /**
     * @var Endpoint
     */
    private $remoteEndpoint;

    /**
     * @var Endpoint
     */
    private $localEndpoint;

    private function __construct($traceId, $parentId, $spanId, $debug, Endpoint $localEndpoint)
    {
        $this->traceId = $traceId;
        $this->parentId = $parentId;
        $this->spanId = $spanId;
        $this->debug = $debug;
        $this->localEndpoint = $localEndpoint;
    }

    /**
     * @param TraceContext $context
     * @param Endpoint $localEndpoint
     * @return Span
     */
    public static function createFromContext(TraceContext $context, Endpoint $localEndpoint): Span
    {
        return new self(
            $context->getTraceId(),
            $context->getParentId(),
            $context->getSpanId(),
            $context->isDebug(),
            $localEndpoint
        );
    }

    /**
     * @return int
     */
    public function getTimestamp(): int
    {
        return $this->timestamp;
    }

    /**
     * @param int $timestamp
     * @return void
     */
    public function start(int $timestamp): void
    {
        $this->timestamp = $timestamp;
    }

    /**
     * @param string $name
     * @return void
     */
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    /**
     * @param string $kind
     * @return void
     */
    public function setKind(string $kind): void
    {
        $this->kind = $kind;
    }

    /**
     * @param int $timestamp
     * @param string $value
     */
    public function annotate(int $timestamp, string $value): void
    {
        $this->annotations[] = [
            'value' => $value,
            'timestamp' => $timestamp,
        ];
    }

    /**
     * @param string $key
     * @param string $value
     */
    public function tag(string $key, string $value): void
    {
        $this->tags[$key] = $value;
    }

    /**
     * @param Endpoint $remoteEndpoint
     * @return void
     */
    public function setRemoteEndpoint(Endpoint $remoteEndpoint): void
    {
        $this->remoteEndpoint = $remoteEndpoint;
    }

    /**
     * Completes and reports the span
     *
     * @param int|null $finishTimestamp
     */
    public function finish(?int $finishTimestamp = null): void
    {
        if ($this->finished) {
            return;
        }

        if ($this->timestamp !== null && $finishTimestamp !== null) {
            $this->duration = $finishTimestamp - $this->timestamp;
        }

        $this->finished = true;
    }

    /**
     * @return array
     */
    public function toArray(): array
    {
        $spanAsArray = [
            'id' => (string) $this->spanId,
            'name' => $this->name,
            'traceId' => (string) $this->traceId,
            'parentId' => $this->parentId ? (string) $this->parentId : null,
            'timestamp' => $this->timestamp,
            'duration' => $this->duration,
            'debug' => $this->debug,
            'localEndpoint' => $this->localEndpoint->toArray(),
        ];

        if ($this->kind !== null) {
            $spanAsArray['kind'] = $this->kind;
        }

        if ($this->remoteEndpoint !== null) {
            $spanAsArray['remoteEndpoint'] = $this->remoteEndpoint->toArray();
        }

        if (!empty($this->annotations)) {
            $spanAsArray['annotations'] = $this->annotations;
        }

        if (!empty($this->tags)) {
            $spanAsArray['tags'] = $this->tags;
        }

        return $spanAsArray;
    }

    /**
     * @return string
     */
    public function __toString(): string
    {
        return json_encode($this->toArray());
    }
}


<?php

declare(strict_types=1);

namespace Zipkin;

use RuntimeException;
use Zipkin\Propagation\CurrentTraceContext;
use Zipkin\Propagation\DefaultSamplingFlags;
use Zipkin\Propagation\SamplingFlags;
use Zipkin\Propagation\TraceContext;
use Zipkin\Sampler;

final class Tracer
{
    /**
     * @var Sampler
     */
    private $sampler;

    /**
     * @var bool
     */
    private $isNoop;

    /**
     * @var bool
     */
    private $usesTraceId128bits;

    /**
     * @var Recorder
     */
    private $recorder;

    /**
     * @var CurrentTraceContext
     */
    private $currentTraceContext;

    /**
     * @param Endpoint $localEndpoint
     * @param Reporter $reporter
     * @param Sampler $sampler
     * @param bool $usesTraceId128bits
     * @param CurrentTraceContext $currentTraceContext
     * @param bool $isNoop
     */
    public function __construct(
        Endpoint $localEndpoint,
        Reporter $reporter,
        Sampler $sampler,
        bool $usesTraceId128bits,
        CurrentTraceContext $currentTraceContext,
        bool $isNoop
    ) {
        $this->recorder = new Recorder($localEndpoint, $reporter, $isNoop);
        $this->sampler = $sampler;
        $this->usesTraceId128bits = $usesTraceId128bits;
        $this->currentTraceContext = $currentTraceContext;
        $this->isNoop = $isNoop;
    }

    /**
     * Creates a new trace. If there is an existing trace, use {@link #newChild(TraceContext)}
     * instead.
     *
     *
     * For example, to sample all requests for a specific url:
     *
{@code
     * function newTrace(Request $request) {
     *   $uri = $request->getUri();
     *   $flags = SamplingFlags::createAsEmpty();
     *   if (strpos('/experimental', $uri) !== false) {
     *     $flags = SamplingFlags::createAsSampled();
     *   } else if (strpos('/static', $uri) !== false) {
     *     $flags = SamplingFlags::createAsNotSampled();
     *   }
     *   return $this->tracer->newTrace($flags);
     * }
     * }

     *
     * @param SamplingFlags $samplingFlags
     * @return Span
     */
    public function newTrace(SamplingFlags $samplingFlags = null): Span
    {
        if ($samplingFlags === null) {
            $samplingFlags = DefaultSamplingFlags::createAsEmpty();
        }

        return $this->ensureSampled($this->newRootContext($samplingFlags));
    }

    /**
     * Creates a new span within an existing trace. If there is no existing trace, use {@link
     * #newTrace()} instead.
     *
     * @param TraceContext $parent
     * @return Span
     * @throws \RuntimeException
     */
    public function newChild(TraceContext $parent): Span
    {
        return $this->nextSpan($parent);
    }

    /**
     * Joining is re-using the same trace and span ids extracted from an incoming request. Here, we
     * ensure a sampling decision has been made. If the span passed sampling, we assume this is a
     * shared span, one where the caller and the current tracer report to the same span IDs. If no
     * sampling decision occurred yet, we have exclusive access to this span ID.
     *
     *
Here's an example of conditionally joining a span, depending on if a trace context was
     * extracted from an incoming request.
     *
     *
{@code
     * $contextOrFlags = $extractor->extract($request->headers);
     * span = ($contextOrFlags instanceof TraceContext)
     *          ? $tracer->joinSpan($contextOrFlags)
     *          : $tracer->newTrace($contextOrFlags);
     * }

     *
     * @see Propagation
     * @see Extractor#extract(Object)
     *
     * @param TraceContext $context
     * @return Span
     */
    public function joinSpan(TraceContext $context): Span
    {
        return $this->toSpan($context);
    }

    /**
     * Makes the given span the "current span" and returns the closer that exits that scope.
     * The span provided will be returned by {@link #currentSpan()} until the return value is closed.
     *
     *
Note: While downstream code might affect the span, calling this method, and calling the closer on
     * the result have no effect on the input. For example, calling closer on the result does not
     * finish the span. Not only is it safe to call the closer, you must call the closer to end the scope, or
     * risk leaking resources associated with the scope.
     *
     * @param Span $span to place into scope or null to clear the scope
     *
     * @return callable The scope closer
     */
    public function openScope(?Span $span = null): callable
    {
        return $this->currentTraceContext->createScopeAndRetrieveItsCloser(
            $span === null ? null : $span->getContext()
        );
    }

    /**
     * Returns the current span in scope or null if there isn't one.
     *
     * @return Span|null
     */
    public function getCurrentSpan(): ?Span
    {
        $currentContext = $this->currentTraceContext->getContext();
        return $currentContext === null ? null : $this->toSpan($currentContext);
    }

    /**
     * Calling this will flush any pending spans to the transport.
     *
     * Make sure this method is called after the request is finished.
     *
     * @see register_shutdown_function()
     * @see fastcgi_finish_request()
     */
    public function flush()
    {
        $this->recorder->flushAll();
    }

    /**
     * This creates a new span based on parameters extracted from an incoming request. This will
     * always result in a new span. If no trace identifiers were extracted, a span will be created
     * based on the implicit context in the same manner as {@link #nextSpan()}.
     *
     *
Ex.
     *
{@code
     * $extracted = $extractor->extract($headers);
     * $span = $tracer->nextSpan($extracted);
     * }

     *
     *
Note: Unlike {@link #joinSpan(TraceContext)}, this does not attempt to re-use
     * extracted span IDs. This means the extracted context (if any) is the parent of the span
     * returned.
     *
     *
Note: If a context could be extracted from the input, that trace is resumed, not
     * whatever the {@link #currentSpan()} was. Make sure you re-apply {@link #withSpanInScope(Span)}
     * so that data is written to the correct trace.
     *
     * @param SamplingFlags|TraceContext $contextOrFlags
     * @return Span
     * @throws \RuntimeException
     */
    public function nextSpan(?SamplingFlags $contextOrFlags = null): Span
    {
        if ($contextOrFlags === null) {
            $parent = $this->currentTraceContext->getContext();
            return $parent === null ? $this->newTrace() : $this->newChild($parent);
        }

        if ($contextOrFlags instanceof TraceContext) {
            return $this->toSpan(TraceContext::createFromParent($contextOrFlags));
        }

        if ($contextOrFlags instanceof SamplingFlags) {
            $implicitParent = $this->currentTraceContext->getContext();
            if ($implicitParent === null) {
                return $this->toSpan($this->newRootContext($contextOrFlags));
            }
        }

        throw new RuntimeException('Context or flags for next span is invalid.');
    }

    /**
     * @param SamplingFlags|TraceContext $contextOrFlags
     * @return TraceContext
     */
    private function newRootContext(SamplingFlags $contextOrFlags): TraceContext
    {
        $context = TraceContext::createAsRoot($contextOrFlags, $this->usesTraceId128bits);

        if ($context->isSampled() === null) {
            $context = $context->withSampled($this->sampler->isSampled($context->getTraceId()));
        }

        return $context;
    }

    /**
     * @param TraceContext $context
     * @return Span
     */
    private function ensureSampled(TraceContext $context): Span
    {
        if ($context->isSampled() === null) {
            $context = $context->withSampled($this->sampler->isSampled($context->getTraceId()));
        }

        return $this->toSpan($context);
    }

    /**
     * Converts the context as-is to a Span object
     *
     * @param TraceContext $context
     * @return Span
     */
    private function toSpan(TraceContext $context): Span
    {
        if (!$this->isNoop && $context->isSampled()) {
            return RealSpan::create($context, $this->recorder);
        }

        return NoopSpan::create($context);
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Propagation;

use ArrayAccess;
use Zipkin\Propagation\Exceptions\InvalidPropagationCarrier;
use Zipkin\Propagation\Exceptions\InvalidPropagationKey;

final class Map implements Getter, Setter
{
    /**
     * {@inheritdoc}
     * @param array|ArrayAccess $carrier
     */
    public function get($carrier, string $key): ?string
    {
        $lKey = strtolower($key);

        if ($carrier instanceof ArrayAccess) {
            return $carrier->offsetExists($lKey) ? $carrier->offsetGet($lKey) : null;
        }

        if (is_array($carrier)) {
            return array_key_exists($lKey, $carrier) ? $carrier[$lKey] : null;
        }

        throw InvalidPropagationCarrier::forCarrier($carrier);
    }

    /**
     * {@inheritdoc}
     * @param array|ArrayAccess $carrier
     */
    public function put(&$carrier, string $key, string $value): void
    {
        if ($key === '') {
            throw InvalidPropagationKey::forEmptyKey();
        }

        $lKey = strtolower($key);

        if ($carrier instanceof ArrayAccess || is_array($carrier)) {
            $carrier[$lKey] = $value;
            return;
        }

        throw InvalidPropagationCarrier::forCarrier($carrier);
    }
}


<?php

declare(strict_types=1);

namespace Zipkin;

use InvalidArgumentException;
use Zipkin\Propagation\TraceContext;
use function Zipkin\Timestamp\now;
use function Zipkin\Timestamp\isValid;

final class RealSpan implements Span
{
    /**
     * @var Recorder
     */
    private $recorder;

    /**
     * @var TraceContext
     */
    private $traceContext;

    private function __construct(TraceContext $context, Recorder $recorder)
    {
        $this->traceContext = $context;
        $this->recorder = $recorder;
    }

    /**
     * @param TraceContext $context
     * @param Recorder $recorder
     * @return RealSpan
     */
    public static function create(TraceContext $context, Recorder $recorder): self
    {
        return new self($context, $recorder);
    }

    /**
     * When true, no recording is done and nothing is reported to zipkin. However, this span should
     * still be injected into outgoing requests. Use this flag to avoid performing expensive
     * computation.
     *
     * @return bool
     */
    public function isNoop(): bool
    {
        return false;
    }

    /**
     * @return TraceContext
     */
    public function getContext(): TraceContext
    {
        return $this->traceContext;
    }

    /**
     * Starts the span with an implicit timestamp.
     *
     * Spans can be modified before calling start. For example, you can add tags to the span and
     * set its name without lock contention.
     *
     * @param int $timestamp
     * @return void
     * @throws \InvalidArgumentException
     */
    public function start(?int $timestamp = null): void
    {
        if ($timestamp === null) {
            $timestamp = now();
        } else {
            if (!isValid($timestamp)) {
                throw new InvalidArgumentException(
                    sprintf('Invalid timestamp. Expected int, got %s', $timestamp)
                );
            }
        }

        $this->recorder->start($this->traceContext, $timestamp);
    }

    /**
     * Sets the string name for the logical operation this span represents.
     *
     * @param string $name
     * @return void
     */
    public function setName(string $name): void
    {
        $this->recorder->setName($this->traceContext, $name);
    }

    /**
     * The kind of span is optional. When set, it affects how a span is reported. For example, if the
     * kind is {@link Zipkin\Kind\SERVER}, the span's start timestamp is implicitly annotated as "sr"
     * and that plus its duration as "ss".
     *
     * @param string $kind
     * @return void
     */
    public function setKind(string $kind): void
    {
        $this->recorder->setKind($this->traceContext, $kind);
    }

    /**
     * Tags give your span context for search, viewing and analysis. For example, a key
     * "your_app.version" would let you lookup spans by version. A tag {@link Zipkin\Tags\SQL_QUERY}
     * isn't searchable, but it can help in debugging when viewing a trace.
     *
     * @param string $key Name used to lookup spans, such as "your_app.version". See {@link Zipkin\Tags} for
     * standard ones.
     * @param string $value, cannot be null.
     * @return void
     */
    public function tag(string $key, string $value): void
    {
        $this->recorder->tag($this->traceContext, $key, $value);
    }

    /**
     * Associates an event that explains latency with the current system time.
     *
     * @param string $value A short tag indicating the event, like "finagle.retry"
     * @param int|null $timestamp
     * @return void
     * @throws \InvalidArgumentException
     * @see Zipkin\Annotations
     */
    public function annotate(string $value, ?int $timestamp = null): void
    {
        if (!isValid($timestamp)) {
            throw new InvalidArgumentException(
                sprintf('Valid timestamp represented microtime expected, got \'%s\'', $timestamp)
            );
        }

        $this->recorder->annotate($this->traceContext, $timestamp, $value);
    }

    /**
     * For a client span, this would be the server's address.
     *
     * It is often expensive to derive a remote address: always check {@link #isNoop()} first!
     *
     * @param Endpoint $remoteEndpoint
     * @return void
     */
    public function setRemoteEndpoint(Endpoint $remoteEndpoint): void
    {
        $this->recorder->setRemoteEndpoint($this->traceContext, $remoteEndpoint);
    }

    /**
     * Throws away the current span without reporting it.
     *
     * @return void
     */
    public function abandon(): void
    {
        $this->recorder->abandon($this->traceContext);
    }

    /**
     * Like {@link #finish()}, except with a given timestamp in microseconds.
     *
     * {@link zipkin.Span#duration Zipkin's span duration} is derived by subtracting the start
     * timestamp from this, and set when appropriate.
     *
     * @param int|null $timestamp
     * @return void
     * @throws \InvalidArgumentException
     */
    public function finish(?int $timestamp = null): void
    {
        if ($timestamp !== null && !Timestamp\isValid($timestamp)) {
            throw new InvalidArgumentException('Invalid timestamp');
        }

        if ($timestamp === null) {
            $timestamp = now();
        }

        $this->recorder->finish($this->traceContext, $timestamp);
    }

    /**
     * Reports the span, even if unfinished. Most users will not call this method.
     *
     * This primarily supports two use cases: one-way spans and orphaned spans.
     * For example, a one-way span can be modeled as a span where one flusher calls start and another
     * calls finish. In order to report that span from its origin, flush must be called.
     *
     * Another example is where a user didn't call finish within a deadline or before a shutdown
     * occurs. By flushing, you can report what was in progress.
     *
     * @return void
     */
    public function flush(): void
    {
        $this->recorder->flush($this->traceContext);
    }
}


<?php

declare(strict_types=1);

namespace Zipkin\Propagation;

use Psr\Http\Message\RequestInterface;

final class RequestHeaders implements Getter, Setter
{
    /**
     * {@inheritdoc}
     *
     * @param RequestInterface $carrier
     */
    public function get($carrier, string $key): ?string
    {
        $lKey = strtolower($key);
        return $carrier->hasHeader($lKey) ? $carrier->getHeader($lKey)[0] : null;
    }

    /**
     * {@inheritdoc}
     *
     * @param RequestInterface $carrier
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function put(&$carrier, string $key, string $value): void
    {
        $lKey = strtolower($key);
        $carrier = $carrier->withAddedHeader($lKey, $value);
    }
}


<?php
namespace Slim\Csrf;

use ArrayAccess;
use Countable;
use Traversable;
use IteratorAggregate;
use RuntimeException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * CSRF protection middleware.
 */
class Guard
{
    /**
     * Prefix for CSRF parameters (omit trailing "_" underscore)
     *
     * @var string
     */
    protected $prefix;

    /**
     * CSRF storage
     *
     * Should be either an array or an object. If an object is used, then it must
     * implement ArrayAccess and should implement Countable and Iterator (or
     * IteratorAggregate) if storage limit enforcement is required.
     *
     * @var array|ArrayAccess
     */
    protected $storage;

    /**
     * Number of elements to store in the storage array
     *
     * Default is 200, set via constructor
     *
     * @var integer
     */
    protected $storageLimit;

    /**
     * CSRF Strength
     *
     * @var int
     */
     protected $strength;

    /**
     * Callable to be executed if the CSRF validation fails
     *
     * Signature of callable is:
     *    function($request, $response, $next)
     * and a $response must be returned.
     *
     * @var callable
     */
    protected $failureCallable;

    /**
     * Determines whether or not we should persist the token throughout the duration of the user's session.
     *
     * For security, Slim-Csrf will *always* reset the token if there is a validation error.
     * @var bool True to use the same token throughout the session (unless there is a validation error),
     * false to get a new token with each request.
     */
    protected $persistentTokenMode;

    /**
     * Stores the latest key-pair generated by the class
     *
     * @var array
     */
    protected $keyPair;

    /**
     * Create new CSRF guard
     *
     * @param string                 $prefix
     * @param null|array|ArrayAccess $storage
     * @param null|callable          $failureCallable
     * @param integer                $storageLimit
     * @param integer                $strength
     * @param boolean                $persistentTokenMode
     * @throws RuntimeException if the session cannot be found
     */
    public function __construct(
        $prefix = 'csrf',
        &$storage = null,
        callable $failureCallable = null,
        $storageLimit = 200,
        $strength = 16,
        $persistentTokenMode = false
    ) {
        $this->prefix = rtrim($prefix, '_');
        if ($strength < 16) {
            throw new RuntimeException('CSRF middleware failed. Minimum strength is 16.');
        }
        $this->strength = $strength;
        $this->storage = &$storage;

        $this->setFailureCallable($failureCallable);
        $this->setStorageLimit($storageLimit);

        $this->setPersistentTokenMode($persistentTokenMode);

        $this->keyPair = null;
    }

    /**
     * Retrieve token name key
     *
     * @return string
     */
    public function getTokenNameKey()
    {
        return $this->prefix . '_name';
    }

    /**
     * Retrieve token value key
     *
     * @return string
     */
    public function getTokenValueKey()
    {
        return $this->prefix . '_value';
    }

    /**
     * Invoke middleware
     *
     * @param  ServerRequestInterface  $request  PSR7 request object
     * @param  ResponseInterface $response PSR7 response object
     * @param  callable          $next     Next middleware callable
     *
     * @return ResponseInterface PSR7 response object
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
    {
        $this->validateStorage();

        // Validate POST, PUT, DELETE, PATCH requests
        if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
            $body = $request->getParsedBody();
            $body = $body ? (array)$body : [];
            $name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
            $value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
            if (!$name || !$value || !$this->validateToken($name, $value)) {
                // Need to regenerate a new token, as the validateToken removed the current one.
                $request = $this->generateNewToken($request);

                $failureCallable = $this->getFailureCallable();
                return $failureCallable($request, $response, $next);
            }
        }

        // Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
        if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
            $request = $this->generateNewToken($request);
        } elseif ($this->persistentTokenMode) {
            $pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken();
            $request = $this->attachRequestAttributes($request, $pair);
        }

        // Enforce the storage limit
        $this->enforceStorageLimit();

        return $next($request, $response);
    }

    /**
     * @param $prefix
     * @param $storage
     * @return mixed
     */
    public function validateStorage()
    {
        if (is_array($this->storage)) {
            return $this->storage;
        }

        if ($this->storage instanceof ArrayAccess) {
            return $this->storage;
        }

        if (!isset($_SESSION)) {
            throw new RuntimeException('CSRF middleware failed. Session not found.');
        }
        if (!array_key_exists($this->prefix, $_SESSION) || !\is_array($_SESSION[$this->prefix])) {
            $_SESSION[$this->prefix] = [];
        }
        $this->storage = &$_SESSION[$this->prefix];
        return $this->storage;
    }

    /**
     * Generates a new CSRF token
     *
     * @return array
     */
    public function generateToken()
    {
        // Generate new CSRF token
        $name = uniqid($this->prefix);
        $value = $this->createToken();
        $this->saveToStorage($name, $value);

        $this->keyPair = [
            $this->prefix . '_name' => $name,
            $this->prefix . '_value' => $value
        ];

        return $this->keyPair;
    }

    /**
     * Generates a new CSRF token and attaches it to the Request Object
     *
     * @param  ServerRequestInterface $request PSR7 response object.
     *
     * @return ServerRequestInterface PSR7 response object.
     */
    public function generateNewToken(ServerRequestInterface $request)
    {

        $pair = $this->generateToken();

        $request = $this->attachRequestAttributes($request, $pair);

        return $request;
    }

    /**
     * Validate CSRF token from current request
     * against token value stored in $_SESSION
     *
     * @param  string $name  CSRF name
     * @param  string $value CSRF token value
     *
     * @return bool
     */
    public function validateToken($name, $value)
    {
        $token = $this->getFromStorage($name);
        if (function_exists('hash_equals')) {
            $result = ($token !== false && hash_equals($token, $value));
        } else {
            $result = ($token !== false && $token === $value);
        }

        // If we're not in persistent token mode, delete the token.
        if (!$this->persistentTokenMode) {
            $this->removeFromStorage($name);
        }

        return $result;
    }

    /**
     * Create CSRF token value
     *
     * @return string
     */
    protected function createToken()
    {
        return bin2hex(random_bytes($this->strength));
    }

    /**
     * Save token to storage
     *
     * @param  string $name  CSRF token name
     * @param  string $value CSRF token value
     */
    protected function saveToStorage($name, $value)
    {
        $this->storage[$name] = $value;
    }

    /**
     * Get token from storage
     *
     * @param  string      $name CSRF token name
     *
     * @return string|bool CSRF token value or `false` if not present
     */
    protected function getFromStorage($name)
    {
        return isset($this->storage[$name]) ? $this->storage[$name] : false;
    }

    /**
     * Get the most recent key pair from storage.
     *
     * @return string[]|null Array containing name and value if found, null otherwise
     */
    protected function getLastKeyPair()
    {
        // Use count, since empty ArrayAccess objects can still return false for `empty`
        if (count($this->storage) < 1) {
            return null;
        }

        foreach ($this->storage as $name => $value) {
            continue;
        }

        $keyPair = [
            $this->prefix . '_name' => $name,
            $this->prefix . '_value' => $value
        ];

        return $keyPair;
    }

    /**
     * Load the most recent key pair in storage.
     *
     * @return bool `true` if there was a key pair to load in storage, false otherwise.
     */
    protected function loadLastKeyPair()
    {
        $this->keyPair = $this->getLastKeyPair();

        if ($this->keyPair) {
            return true;
        }

        return false;
    }

    /**
     * Remove token from storage
     *
     * @param  string $name CSRF token name
     */
    protected function removeFromStorage($name)
    {
        $this->storage[$name] = ' ';
        unset($this->storage[$name]);
    }

    /**
     * Remove the oldest tokens from the storage array so that there
     * are never more than storageLimit tokens in the array.
     *
     * This is required as a token is generated every request and so
     * most will never be used.
     */
    protected function enforceStorageLimit()
    {
        if ($this->storageLimit < 1) {
            return;
        }

        // $storage must be an array or implement Countable and Traversable
        if (!is_array($this->storage)
            && !($this->storage instanceof Countable && $this->storage instanceof Traversable)
        ) {
            return;
        }

        if (is_array($this->storage)) {
            while (count($this->storage) > $this->storageLimit) {
                array_shift($this->storage);
            }
        } else {
            // array_shift() doesn't work for ArrayAccess, so we need an iterator in order to use rewind()
            // and key(), so that we can then unset
            $iterator = $this->storage;
            if ($this->storage instanceof \IteratorAggregate) {
                $iterator = $this->storage->getIterator();
            }
            while (count($this->storage) > $this->storageLimit) {
                $iterator->rewind();
                unset($this->storage[$iterator->key()]);
            }
        }
    }

    /**
     * @param ServerRequestInterface $request
     * @param $pair
     * @return static
     */
    protected function attachRequestAttributes(ServerRequestInterface $request, $pair)
    {
        return $request->withAttribute($this->prefix . '_name', $pair[$this->prefix . '_name'])
            ->withAttribute($this->prefix . '_value', $pair[$this->prefix . '_value']);
    }

    /**
     * Getter for failureCallable
     *
     * @return callable|\Closure
     */
    public function getFailureCallable()
    {
        if (is_null($this->failureCallable)) {
            $this->failureCallable = function (ServerRequestInterface $request, ResponseInterface $response, $next) {
                $body = new \Slim\Http\Body(fopen('php://temp', 'r+'));
                $body->write('Failed CSRF check!');
                return $response->withStatus(400)->withHeader('Content-type', 'text/plain')->withBody($body);
            };
        }
        return $this->failureCallable;
    }

    /**
     * Setter for failureCallable
     *
     * @param mixed $failureCallable Value to set
     * @return $this
     */
    public function setFailureCallable($failureCallable)
    {
        $this->failureCallable = $failureCallable;
        return $this;
    }

    /**
     * Setter for persistentTokenMode
     *
     * @param bool $persistentTokenMode True to use the same token throughout the session (unless there is a validation error),
     * false to get a new token with each request.
     * @return $this
     */
    public function setPersistentTokenMode($persistentTokenMode)
    {
        $this->persistentTokenMode = $persistentTokenMode;
        return $this;
    }

    /**
     * Setter for storageLimit
     *
     * @param integer $storageLimit Value to set
     * @return $this
     */
    public function setStorageLimit($storageLimit)
    {
        $this->storageLimit = (int)$storageLimit;
        return $this;
    }

    /**
     * Getter for persistentTokenMode
     *
     * @return bool
     */
    public function getPersistentTokenMode()
    {
        return $this->persistentTokenMode;
    }

    /**
     * @return string
     */
    public function getTokenName()
    {
        return isset($this->keyPair[$this->getTokenNameKey()]) ? $this->keyPair[$this->getTokenNameKey()] : null;
    }

    /**
     * @return string
     */
    public function getTokenValue()
    {
        return isset($this->keyPair[$this->getTokenValueKey()]) ? $this->keyPair[$this->getTokenValueKey()] : null;
    }
}


<?php

/*
 * This file is part of the HTML sanitizer project.
 *
 * (c) Titouan Galopin <galopintitouan@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace HtmlSanitizer;

use HtmlSanitizer\Extension\Basic\BasicExtension;
use HtmlSanitizer\Extension\Code\CodeExtension;
use HtmlSanitizer\Extension\Details\DetailsExtension;
use HtmlSanitizer\Extension\Extra\ExtraExtension;
use HtmlSanitizer\Extension\Iframe\IframeExtension;
use HtmlSanitizer\Extension\Image\ImageExtension;
use HtmlSanitizer\Extension\Listing\ListExtension;
use HtmlSanitizer\Extension\Table\TableExtension;
use HtmlSanitizer\Parser\MastermindsParser;
use HtmlSanitizer\Parser\ParserInterface;
use Psr\Log\LoggerInterface;

/**
 * @author Titouan Galopin <galopintitouan@gmail.com>
 *
 * @final
 */
class Sanitizer implements SanitizerInterface
{
    /**
     * @var DomVisitorInterface
     */
    private $domVisitor;

    /**
     * @var int
     */
    private $maxInputLength;

    /**
     * @var ParserInterface
     */
    private $parser;

    /**
     * @var LoggerInterface|null
     */
    private $logger;

    public function __construct(DomVisitorInterface $domVisitor, int $maxInputLength, ParserInterface $parser = null, LoggerInterface $logger = null)
    {
        $this->domVisitor = $domVisitor;
        $this->maxInputLength = $maxInputLength;
        $this->parser = $parser ?: new MastermindsParser();
        $this->logger = $logger;
    }

    /**
     * Quickly create an already configured sanitizer using the default builder.
     *
     * @param array $config
     *
     * @return SanitizerInterface
     */
    public static function create(array $config): SanitizerInterface
    {
        $builder = new SanitizerBuilder();
        $builder->registerExtension(new BasicExtension());
        $builder->registerExtension(new ListExtension());
        $builder->registerExtension(new ImageExtension());
        $builder->registerExtension(new CodeExtension());
        $builder->registerExtension(new TableExtension());
        $builder->registerExtension(new IframeExtension());
        $builder->registerExtension(new DetailsExtension());
        $builder->registerExtension(new ExtraExtension());

        return $builder->build($config);
    }

    public function sanitize(string $html): string
    {
        $sanitized = $this->doSanitize($html);

        if ($this->logger) {
            $this->logger->debug('Sanitized given input to "{output}".', [
                'output' => mb_substr($sanitized, 0, 50).(mb_strlen($sanitized) > 50 ? '...' : ''),
            ]);
        }

        return $sanitized;
    }

    private function doSanitize(string $html): string
    {
        // Prevent DOS attack induced by extremely long HTML strings
        if (mb_strlen($html) > $this->maxInputLength) {
            $html = mb_substr($html, 0, $this->maxInputLength);
        }

        /*
         * Only operate on valid UTF-8 strings. This is necessary to prevent cross
         * site scripting issues on Internet Explorer 6. Idea from Drupal (filter_xss).
         */
        if (!$this->isValidUtf8($html)) {
            return '';
        }

        // Remove NULL character
        $html = str_replace(\chr(0), '', $html);

        try {
            $parsed = $this->parser->parse($html);
        } catch (\Exception $exception) {
            return '';
        }

        return $this->domVisitor->visit($parsed)->render();
    }

    /**
     * @param string $html
     *
     * @return bool
     */
    private function isValidUtf8(string $html): bool
    {
        // preg_match() fails silently on strings containing invalid UTF-8.
        return '' === $html || 1 === preg_match('/^./us', $html);
    }
}


<?php

/*
 * This file is part of the HTML sanitizer project.
 *
 * (c) Titouan Galopin <galopintitouan@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace HtmlSanitizer\Visitor;

use HtmlSanitizer\Model\Cursor;
use HtmlSanitizer\Node\TagNodeInterface;

/**
 * @author Titouan Galopin <galopintitouan@gmail.com>
 *
 * @internal
 */
trait TagVisitorTrait
{
    abstract protected function getDomNodeName(): string;

    public function getSupportedNodeNames(): array
    {
        return [$this->getDomNodeName()];
    }

    public function supports(\DOMNode $domNode, Cursor $cursor): bool
    {
        return $this->getDomNodeName() === $domNode->nodeName;
    }

    /**
     * Set attributes from a DOM node to a sanitized node.
     *
     * @param \DOMNode         $domNode
     * @param TagNodeInterface $node
     * @param array            $allowedAttributes
     */
    private function setAttributes(\DOMNode $domNode, TagNodeInterface $node, array $allowedAttributes = [])
    {
        if (!\count($domNode->attributes)) {
            return;
        }

        /** @var \DOMAttr $attribute */
        foreach ($domNode->attributes as $attribute) {
            $name = strtolower($attribute->name);

            if (\in_array($name, $allowedAttributes, true)) {
                $node->setAttribute($name, $attribute->value);
            }
        }
    }

    /**
     * Read the value of a DOMNode attribute.
     *
     * @param \DOMNode $domNode
     * @param string   $name
     *
     * @return null|string
     */
    private function getAttribute(\DOMNode $domNode, string $name): ?string
    {
        if (!\count($domNode->attributes)) {
            return null;
        }

        /** @var \DOMAttr $attribute */
        foreach ($domNode->attributes as $attribute) {
            if ($attribute->name === $name) {
                return $attribute->value;
            }
        }

        return null;
    }
}


<?php

namespace BeyondCode\QueryDetector;

use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\ServiceProvider;

class QueryDetectorServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../config/config.php' => config_path('querydetector.php'),
            ], 'config');
        }

        $this->registerMiddleware(QueryDetectorMiddleware::class);
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        $this->app->singleton(QueryDetector::class);

        $this->app->alias(QueryDetector::class, 'querydetector');

        $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'querydetector');
    }

    /**
     * Register the middleware
     *
     * @param  string $middleware
     */
    protected function registerMiddleware($middleware)
    {
        $kernel = $this->app[Kernel::class];
        $kernel->pushMiddleware($middleware);
    }
}


<?php

namespace BeyondCode\QueryDetector;

use Closure;

class QueryDetectorMiddleware
{
    /** @var QueryDetector */
    private $detector;

    public function __construct(QueryDetector $detector)
    {
        $this->detector = $detector;
    }

    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param  Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (! $this->detector->isEnabled()) {
            return $next($request);
        }

        $this->detector->boot();

        /** @var \Illuminate\Http\Response $response */
        $response = $next($request);

        // Modify the response to add the Debugbar
        $this->detector->output($request, $response);

        return $response;
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Configuration;

/**
 * Processes default values for some backend configuration options.
 *
 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
 */
class DefaultConfigPass implements ConfigPassInterface
{
    public function process(array $backendConfig)
    {
        $backendConfig = $this->processDefaultEntity($backendConfig);
        $backendConfig = $this->processDefaultMenuItem($backendConfig);
        $backendConfig = $this->processDefaultHomepage($backendConfig);

        return $backendConfig;
    }

    /**
     * Finds the default entity to display when the backend index is not
     * defined explicitly.
     *
     * @param array $backendConfig
     *
     * @return array
     */
    private function processDefaultEntity(array $backendConfig)
    {
        $entityNames = \array_keys($backendConfig['entities']);
        $firstEntityName = $entityNames[0] ?? null;
        $backendConfig['default_entity_name'] = $firstEntityName;

        return $backendConfig;
    }

    /**
     * Finds the default menu item to display when browsing the backend index.
     *
     * @param array $backendConfig
     *
     * @return array
     */
    private function processDefaultMenuItem(array $backendConfig)
    {
        $defaultMenuItem = $this->findDefaultMenuItem($backendConfig['design']['menu']);

        if ('empty' === $defaultMenuItem['type']) {
            throw new \RuntimeException(\sprintf('The "menu" configuration sets "%s" as the default item, which is not possible because its type is "empty" and it cannot redirect to a valid URL.', $defaultMenuItem['label']));
        }

        $backendConfig['default_menu_item'] = $defaultMenuItem;

        return $backendConfig;
    }

    /**
     * Finds the first menu item whose 'default' option is 'true' (if any).
     * It looks for the option both in the first level items and in the
     * submenu items.
     *
     * @param array $menuConfig
     *
     * @return mixed
     */
    private function findDefaultMenuItem(array $menuConfig)
    {
        foreach ($menuConfig as $itemConfig) {
            if (true === $itemConfig['default']) {
                return $itemConfig;
            }

            foreach ($itemConfig['children'] as $subitemConfig) {
                if (true === $subitemConfig['default']) {
                    return $subitemConfig;
                }
            }
        }
    }

    /**
     * Processes the backend config to define the URL or the route/params to
     * use as the default backend homepage when none is defined explicitly.
     * (Note: we store the route/params instead of generating the URL because
     * the 'router' service cannot be used inside a compiler pass).
     *
     * @param array $backendConfig
     *
     * @return array
     */
    private function processDefaultHomepage(array $backendConfig)
    {
        $backendHomepage = [];

        // if no menu item has been set as "default", use the "list"
        // action of the first configured entity as the backend homepage
        if (null === $menuItemConfig = $backendConfig['default_menu_item']) {
            $defaultEntityName = $backendConfig['default_entity_name'];
            $backendHomepage['route'] = 'easyadmin';
            $backendHomepage['params'] = ['action' => 'list', 'entity' => $defaultEntityName];

            // if the default entity defines a custom sorting, use it
            $defaultEntityConfig = isset($backendConfig['entities'][$defaultEntityName]) ? $backendConfig['entities'][$defaultEntityName] : [];
            if (isset($defaultEntityConfig['list']['sort'])) {
                $backendHomepage['params'] = \array_merge($backendHomepage['params'], [
                    'sortField' => $defaultEntityConfig['list']['sort']['field'],
                    'sortDirection' => $defaultEntityConfig['list']['sort']['direction'],
                ]);
            }
        } else {
            $routeParams = ['menuIndex' => $menuItemConfig['menu_index'], 'submenuIndex' => $menuItemConfig['submenu_index']];

            if ('entity' === $menuItemConfig['type']) {
                $backendHomepage['route'] = 'easyadmin';
                $backendHomepage['params'] = \array_merge(['action' => 'list', 'entity' => $menuItemConfig['entity']], $routeParams, $menuItemConfig['params']);
            } elseif ('route' === $menuItemConfig['type']) {
                $backendHomepage['route'] = $menuItemConfig['route'];
                $backendHomepage['params'] = \array_merge($routeParams, $menuItemConfig['params']);
            } elseif ('link' === $menuItemConfig['type']) {
                $backendHomepage['url'] = $menuItemConfig['url'];
            }
        }

        $backendConfig['homepage'] = $backendHomepage;

        return $backendConfig;
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Type\Configurator;

use Symfony\Component\Form\FormConfigInterface;

/**
 * This configurator is applied to any form field of type 'fos_ckeditor' available
 * when using the FOSCKEditorBundle. It's used to provide a better default
 * configuration for the WYSIWYG editors created with this bundle.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FOSCKEditorTypeConfigurator implements TypeConfiguratorInterface
{
    /**
     * {@inheritdoc}
     */
    public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig)
    {
        // when the IvoryCKEditor doesn't define the toolbar to use, EasyAdmin uses a simple toolbar
        $options['config']['toolbar'] = [
            ['name' => 'styles', 'items' => ['Bold', 'Italic', 'Strike', 'Link']],
            ['name' => 'lists', 'items' => ['BulletedList', 'NumberedList', '-', 'Outdent', 'Indent']],
            ['name' => 'clipboard', 'items' => ['Copy', 'Paste', 'PasteFromWord', '-', 'Undo', 'Redo']],
            ['name' => 'advanced', 'items' => ['Source']],
        ];

        return $options;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($type, array $options, array $metadata)
    {
        $isFosCkeditorField = \in_array($type, ['fos_ckeditor', 'FOS\\CKEditorBundle\\Form\\Type\\CKEditorType'], true);

        return $isFosCkeditorField && !isset($options['config']['toolbar']) && !isset($options['config_name']);
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Type\Configurator;

use Symfony\Component\Form\FormConfigInterface;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

/**
 * This configurator is applied to any form field of type 'textarea'.
 *
 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
 */
class TextareaTypeConfigurator implements TypeConfiguratorInterface
{
    /**
     * {@inheritdoc}
     */
    public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig)
    {
        // if <textarea> doesn't define its rows, use a default number to make it look good
        $options['attr']['rows'] = 5;

        return $options;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($type, array $options, array $metadata)
    {
        $isTextareaField = \in_array($type, ['textarea', TextareaType::class], true);

        return $isTextareaField && !isset($options['attr']['rows']);
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Util;

use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminAutocompleteType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminDividerType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminFormType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminGroupType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminSectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\DateIntervalType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\LocaleType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\PercentType;
use Symfony\Component\Form\Extension\Core\Type\RadioType;
use Symfony\Component\Form\Extension\Core\Type\RangeType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;

/*
 * Utility class to map shortcut form types (e.g. `text` or `submit`) to its
 * associated FQCN type.
 *
 * @author Yonel Ceruto <yonelceruto@gmail.com>
 *
 * @internal
 */
final class FormTypeHelper
{
    private static $nameToClassMap = [
        // Symfony's built-in types
        'birthday' => BirthdayType::class,
        'button' => ButtonType::class,
        'checkbox' => CheckboxType::class,
        'choice' => ChoiceType::class,
        'collection' => CollectionType::class,
        'color' => ColorType::class,
        'country' => CountryType::class,
        'currency' => CurrencyType::class,
        'datetime' => DateTimeType::class,
        'datetime_immutable' => DateTimeType::class,
        'date' => DateType::class,
        'date_immutable' => DateType::class,
        'date_interval' => DateIntervalType::class,
        'email' => EmailType::class,
        'entity' => EntityType::class,
        'file' => FileType::class,
        'form' => FormType::class,
        'hidden' => HiddenType::class,
        'integer' => IntegerType::class,
        'language' => LanguageType::class,
        'locale' => LocaleType::class,
        'money' => MoneyType::class,
        'number' => NumberType::class,
        'password' => PasswordType::class,
        'percent' => PercentType::class,
        'radio' => RadioType::class,
        'range' => RangeType::class,
        'repeated' => RepeatedType::class,
        'reset' => ResetType::class,
        'search' => SearchType::class,
        'submit' => SubmitType::class,
        'tel' => TelType::class,
        'textarea' => TextareaType::class,
        'text' => TextType::class,
        'time' => TimeType::class,
        'time_immutable' => TimeType::class,
        'timezone' => TimezoneType::class,
        'url' => UrlType::class,
        // EasyAdmin custom types
        'easyadmin' => EasyAdminFormType::class,
        'easyadmin_autocomplete' => EasyAdminAutocompleteType::class,
        'easyadmin_divider' => EasyAdminDividerType::class,
        'easyadmin_group' => EasyAdminGroupType::class,
        'easyadmin_section' => EasyAdminSectionType::class,
        // Popular third-party bundles types
        'ckeditor' => 'Ivory\\CKEditorBundle\\Form\\Type\\CKEditorType',
        'fos_ckeditor' => 'FOS\\CKEditorBundle\\Form\\Type\\CKEditorType',
        'vich_file' => 'Vich\\UploaderBundle\\Form\\Type\\VichFileType',
        'vich_image' => 'Vich\\UploaderBundle\\Form\\Type\\VichImageType',
    ];

    /**
     * It returns the FQCN of the given short type name. If the FQCN is not
     * found, it returs the given value.
     *
     * @param string $typeName
     *
     * @return string
     */
    public static function getTypeClass($typeName)
    {
        return self::$nameToClassMap[$typeName] ?? $typeName;
    }

    /**
     * It returns the short type name of the given FQCN. If the type name is not
     * found, it returns the given value.
     *
     * @param string $typeFqcn
     *
     * @return string
     */
    public static function getTypeName($typeFqcn)
    {
        // needed to avoid collisions between immutable and non-immutable date types,
        // which are mapped to the same Symfony Form type classes
        $filteredNameToClassMap = \array_filter(self::$nameToClassMap, function ($typeName) {
            return !\in_array($typeName, ['datetime_immutable', 'date_immutable', 'time_immutable']);
        }, ARRAY_FILTER_USE_KEY);
        $classToNameMap = \array_flip($filteredNameToClassMap);

        return $classToNameMap[$typeFqcn] ?? $typeFqcn;
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Type\Configurator;

use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormConfigInterface;

/**
 * This configurator is applied to any form field of type 'association' and is
 * used to configure lots of their features (for example whether we should use
 * a JavaScript widget to display their contents).
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class EntityTypeConfigurator implements TypeConfiguratorInterface
{
    /**
     * {@inheritdoc}
     */
    public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig)
    {
        if (!isset($options['multiple']) && $metadata['associationType'] & ClassMetadata::TO_MANY) {
            $options['multiple'] = true;
        }

        // Supported associations are displayed using advanced JavaScript widgets
        $options['attr']['data-widget'] = 'select2';

        // Configure "placeholder" option for entity fields
        if (($metadata['associationType'] & ClassMetadata::TO_ONE)
            && !isset($options['placeholder'])
            && isset($options['required']) && false === $options['required']
        ) {
            $options['placeholder'] = 'label.form.empty_value';
        }

        return $options;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($type, array $options, array $metadata)
    {
        $isEntityType = \in_array($type, ['entity', EntityType::class], true);

        return $isEntityType && 'association' === $metadata['dataType'];
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Extension that injects EasyAdmin related information in the view used to
 * render the form.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class EasyAdminExtension extends AbstractTypeExtension
{
    /** @var RequestStack|null */
    private $requestStack;

    /**
     * @param RequestStack|null $requestStack
     */
    public function __construct(RequestStack $requestStack = null)
    {
        $this->requestStack = $requestStack;
    }

    /**
     * {@inheritdoc}
     */
    public function finishView(FormView $view, FormInterface $form, array $options)
    {
        $request = null;
        if (null !== $this->requestStack) {
            $request = $this->requestStack->getCurrentRequest();
        }

        if (null === $request) {
            return;
        }

        if ($request->attributes->has('easyadmin')) {
            $easyadmin = $request->attributes->get('easyadmin');
            $entity = $easyadmin['entity'];
            $action = $easyadmin['view'];
            $fields = $entity[$action]['fields'] ?? [];
            $view->vars['easyadmin'] = [
                'entity' => $entity,
                'view' => $action,
                'item' => $easyadmin['item'],
                'field' => null,
                'form_group' => $form->getConfig()->getAttribute('easyadmin_form_group'),
                'form_tab' => $form->getConfig()->getAttribute('easyadmin_form_tab'),
            ];

            /*
             * Checks if current form view is direct child on the topmost form
             * (ie. this form view`s field exists in easyadmin configuration)
             */
            if (null !== $view->parent && null === $view->parent->parent) {
                $view->vars['easyadmin']['field'] = $fields[$view->vars['name']] ?? null;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public static function getExtendedTypes()
    {
        return [FormType::class];
    }

    /**
     * This legacy method can be removed when the minimum supported version is Symfony 4.2.
     */
    public function getExtendedType()
    {
        return FormType::class;
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Search;

use Pagerfanta\Pagerfanta;

/**
 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
 */
class Finder
{
    private const MAX_RESULTS = 15;

    /** @var QueryBuilder */
    private $queryBuilder;

    /** @var Paginator */
    private $paginator;

    public function __construct(QueryBuilder $queryBuilder, Paginator $paginator)
    {
        $this->queryBuilder = $queryBuilder;
        $this->paginator = $paginator;
    }

    /**
     * @param array  $entityConfig
     * @param string $searchQuery
     * @param int    $page
     * @param int    $maxResults
     * @param string $sortField
     * @param string $sortDirection
     *
     * @return Pagerfanta
     */
    public function findByAllProperties(array $entityConfig, $searchQuery, $page = 1, $maxResults = self::MAX_RESULTS, $sortField = null, $sortDirection = null)
    {
        $queryBuilder = $this->queryBuilder->createSearchQueryBuilder($entityConfig, $searchQuery, $sortField, $sortDirection);

        return $this->paginator->createOrmPaginator($queryBuilder, $page, $maxResults);
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Configuration;

use EasyCorp\Bundle\EasyAdminBundle\Form\Util\FormTypeHelper;
use Symfony\Component\Form\FormRegistryInterface;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess;

/**
 * Processes the entity fields to complete their configuration and to treat
 * some fields in a special way.
 *
 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
 */
class PropertyConfigPass implements ConfigPassInterface
{
    private $defaultEntityFieldConfig = [
        // CSS class or classes applied to form field or list/show property
        'css_class' => '',
        // date/time/datetime/number format applied to form field value
        'format' => null,
        // form field help message
        'help' => null,
        // form field label (if 'null', autogenerate it; if 'false', hide it)
        'label' => null,
        // its value matches the value of 'dataType' for list/show and the value of 'fieldType' for new/edit
        'type' => null,
        // Symfony form field type (text, date, number, choice, ...) used to display the field
        'fieldType' => null,
        // Data type (text, date, integer, boolean, ...) of the Doctrine property associated with the field
        'dataType' => null,
        // is a virtual field or a real Doctrine entity property?
        'virtual' => false,
        // listings can be sorted according to the values of this field
        'sortable' => true,
        // the path of the template used to render the field in 'show' and 'list' views
        'template' => null,
        // the options passed to the Symfony Form type used to render the form field
        'type_options' => [],
        // the name of the group where this form field is displayed (used only for complex form layouts)
        'form_group' => null,
    ];

    private $defaultVirtualFieldMetadata = [
        'columnName' => 'virtual',
        'fieldName' => 'virtual',
        'id' => false,
        'length' => null,
        'nullable' => false,
        'precision' => 0,
        'scale' => 0,
        'sortable' => false,
        'type' => 'text',
        'type_options' => [
            'required' => false,
        ],
        'unique' => false,
        'virtual' => true,
    ];

    private $formRegistry;

    public function __construct(FormRegistryInterface $formRegistry)
    {
        $this->formRegistry = $formRegistry;
    }

    public function process(array $backendConfig)
    {
        $backendConfig = $this->processMetadataConfig($backendConfig);
        $backendConfig = $this->processFieldConfig($backendConfig);

        return $backendConfig;
    }

    /**
     * $entityConfig['properties'] stores the raw metadata provided by Doctrine.
     * This method adds some other options needed for EasyAdmin backends. This is
     * required because $entityConfig['properties'] will be used as the fields of
     * the views that don't define their fields.
     *
     * @param array $backendConfig
     *
     * @return array
     */
    private function processMetadataConfig(array $backendConfig)
    {
        foreach ($backendConfig['entities'] as $entityName => $entityConfig) {
            $properties = [];
            foreach ($entityConfig['properties'] as $propertyName => $propertyMetadata) {
                $typeGuess = $this->getFormTypeGuessOfProperty($entityConfig['class'], $propertyName);
                $requiredGuess = $this->getFormRequiredGuessOfProperty($entityConfig['class'], $propertyName);

                $guessedType = null !== $typeGuess
                    ? FormTypeHelper::getTypeName($typeGuess->getType())
                    : $propertyMetadata['type'];

                $guessedTypeOptions = null !== $typeGuess
                    ? $typeGuess->getOptions()
                    : [];

                if (null !== $requiredGuess) {
                    $guessedTypeOptions['required'] = $requiredGuess->getValue();
                }

                $properties[$propertyName] = \array_replace(
                    $this->defaultEntityFieldConfig,
                    $propertyMetadata,
                    [
                        'property' => $propertyName,
                        'dataType' => $propertyMetadata['type'],
                        'fieldType' => $guessedType,
                        'type_options' => $guessedTypeOptions,
                    ]
                );

                // 'boolean' properties are displayed by default as toggleable
                // flip switches (if the 'edit' action is enabled for the entity)
                if ('boolean' === $properties[$propertyName]['dataType'] && !\in_array('edit', $entityConfig['disabled_actions'])) {
                    $properties[$propertyName]['dataType'] = 'toggle';
                }
            }

            $backendConfig['entities'][$entityName]['properties'] = $properties;
        }

        return $backendConfig;
    }

    /**
     * Completes the configuration of each field/property with the metadata
     * provided by Doctrine for each entity property.
     *
     * @param array $backendConfig
     *
     * @return array
     */
    private function processFieldConfig(array $backendConfig)
    {
        foreach ($backendConfig['entities'] as $entityName => $entityConfig) {
            foreach (['edit', 'list', 'new', 'search', 'show'] as $view) {
                $originalViewConfig = $backendConfig['entities'][$entityName][$view];
                foreach ($entityConfig[$view]['fields'] as $fieldName => $fieldConfig) {
                    $originalFieldConfig = $originalViewConfig['fields'][$fieldName] ?? null;

                    if (\array_key_exists($fieldName, $entityConfig['properties'])) {
                        $fieldMetadata = \array_merge(
                            $entityConfig['properties'][$fieldName],
                            ['virtual' => false]
                        );
                    } else {
                        // this is a virtual field which doesn't exist as a property of
                        // the related entity. That's why Doctrine can't provide metadata for it
                        $fieldMetadata = \array_merge(
                            $this->defaultVirtualFieldMetadata,
                            ['columnName' => $fieldName, 'fieldName' => $fieldName]
                        );
                    }

                    $normalizedConfig = \array_replace_recursive(
                        $this->defaultEntityFieldConfig,
                        $fieldMetadata,
                        $fieldConfig
                    );

                    // 'list', 'search' and 'show' views: use the value of the 'type' option
                    // as the 'dataType' option because the previous code has already
                    // prioritized end-user preferences over Doctrine and default values
                    if (\in_array($view, ['list', 'search', 'show'])) {
                        $normalizedConfig['dataType'] = $normalizedConfig['type'];
                    }

                    // 'new' and 'edit' views: if the user has defined the 'type' option
                    // for the field, use it as 'fieldType'. Otherwise, use the guessed
                    // form type of the property data type.
                    if (\in_array($view, ['edit', 'new'])) {
                        $normalizedConfig['fieldType'] = isset($originalFieldConfig['type'])
                            ? $originalFieldConfig['type']
                            : $normalizedConfig['fieldType'];

                        if (null === $normalizedConfig['fieldType']) {
                            // this is a virtual field which doesn't exist as a property of
                            // the related entity. Textarea is used as a default form type.
                            $normalizedConfig['fieldType'] = 'textarea';
                        }

                        $normalizedConfig['type_options'] = $this->getFormTypeOptionsOfProperty(
                            $normalizedConfig, $fieldMetadata, $originalFieldConfig
                        );
                    }

                    // special case for the 'list' view: 'boolean' properties are displayed
                    // as toggleable flip switches when certain conditions are met
                    if ('list' === $view && 'boolean' === $normalizedConfig['dataType']) {
                        // conditions:
                        //   1) the end-user hasn't configured the field type explicitly
                        //   2) the 'edit' action is enabled for the 'list' view of this entity
                        if (!isset($originalFieldConfig['type']) && !\in_array('edit', $entityConfig['disabled_actions'])) {
                            $normalizedConfig['dataType'] = 'toggle';
                        }
                    }

                    if (null === $normalizedConfig['format']) {
                        $normalizedConfig['format'] = $this->getFieldFormat($normalizedConfig['type'], $backendConfig);
                    }

                    $backendConfig['entities'][$entityName][$view]['fields'][$fieldName] = $normalizedConfig;
                }
            }
        }

        return $backendConfig;
    }

    /**
     * Resolves from type options of field
     *
     * @param array $mergedConfig
     * @param array $guessedConfig
     * @param array $userDefinedConfig
     *
     * @return array
     */
    private function getFormTypeOptionsOfProperty(array $mergedConfig, array $guessedConfig, array $userDefinedConfig)
    {
        $resolvedFormOptions = $mergedConfig['type_options'];

        // if the user has defined a 'type', the type options
        // must be reset so they don't get mixed with the form components guess.
        // Only the 'required' and user defined option are kept
        if (
            isset($userDefinedConfig['type'], $guessedConfig['fieldType'])
            && $userDefinedConfig['type'] !== $guessedConfig['fieldType']
        ) {
            $resolvedFormOptions = \array_merge(
                \array_intersect_key($resolvedFormOptions, ['required' => null]),
                isset($userDefinedConfig['type_options']) ? $userDefinedConfig['type_options'] : []
            );
        }
        // if the user has defined the "type" or "type_options"
        // AND the "type" is the same as the default one
        elseif (
            (
                isset($userDefinedConfig['type'])
                && isset($guessedConfig['fieldType'])
                && $userDefinedConfig['type'] === $guessedConfig['fieldType']
            ) || (
                !isset($userDefinedConfig['type']) && isset($userDefinedConfig['type_options'])
            )
        ) {
            $resolvedFormOptions = \array_merge(
                $resolvedFormOptions,
                isset($userDefinedConfig['type_options']) ? $userDefinedConfig['type_options'] : []
            );
        }

        return $resolvedFormOptions;
    }

    /**
     * Guesses what Form Type a property of a class has.
     *
     * @param string $class
     * @param string $property
     *
     * @return TypeGuess|null
     */
    private function getFormTypeGuessOfProperty($class, $property)
    {
        return $this->formRegistry->getTypeGuesser()->guessType($class, $property);
    }

    /**
     * Guesses if a property of a class should be a required field in a Form.
     *
     * @param string $class
     * @param string $property
     *
     * @return ValueGuess|null
     */
    private function getFormRequiredGuessOfProperty($class, $property)
    {
        return $this->formRegistry->getTypeGuesser()->guessRequired($class, $property);
    }

    /**
     * Returns the date/time/datetime/number format for the given field
     * according to its type and the default formats defined for the backend.
     *
     * @param string $fieldType
     * @param array  $backendConfig
     *
     * @return string The format that should be applied to the field value
     */
    private function getFieldFormat($fieldType, array $backendConfig)
    {
        if (\in_array($fieldType, ['date', 'date_immutable', 'dateinterval', 'time', 'time_immutable', 'datetime', 'datetime_immutable', 'datetimetz'])) {
            // make 'datetimetz' use the same format as 'datetime'
            $fieldType = ('datetimetz' === $fieldType) ? 'datetime' : $fieldType;
            $fieldType = ('_immutable' === \mb_substr($fieldType, -10)) ? \mb_substr($fieldType, 0, -10) : $fieldType;

            return $backendConfig['formats'][$fieldType];
        }

        if (\in_array($fieldType, ['bigint', 'integer', 'smallint', 'decimal', 'float'])) {
            return isset($backendConfig['formats']['number']) ? $backendConfig['formats']['number'] : null;
        }
    }
}


<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Type\Configurator;

use EasyCorp\Bundle\EasyAdminBundle\Configuration\ConfigManager;
use Symfony\Component\Form\FormConfigInterface;

/**
 * @author Konstantin Grachev <me@grachevko.ru>
 */
final class TypeConfigurator implements TypeConfiguratorInterface
{
    /**
     * @var ConfigManager
     */
    private $configManager;

    public function __construct(ConfigManager $configManager)
    {
        $this->configManager = $configManager;
    }

    /**
     * {@inheritdoc}
     */
    public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig)
    {
        if (!\array_key_exists('label', $options) && \array_key_exists('label', $metadata)) {
            $options['label'] = $metadata['label'];
        }

        if (empty($options['translation_domain'])) {
            $entityConfig = $this->configManager->getEntityConfig($parentConfig->getOption('entity'));

            if (!empty($entityConfig['translation_domain'])) {
                $options['translation_domain'] = $entityConfig['translation_domain'];
            }
        }

        return $options;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($type, array $options, array $metadata)
    {
        return true;
    }
}