<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Security\Firewall;

use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Psr\Log\LoggerInterface;

/**
 * The form login listener for LDAP authentication.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapFormLoginListener extends AbstractAuthenticationListener
{
    /**
     * @var CsrfTokenManagerInterface|null
     */
    private $csrfTokenManager;

    /**
     * @var array
     */
    private $defaultListOpts = [
        'username_parameter' => '_username',
        'password_parameter' => '_password',
        'domain_parameter' => '_ldap_domain',
        'csrf_parameter' => '_csrf_token',
        'intention' => 'authenticate',
        'post_only' => true,
    ];

    /**
     * @param TokenStorageInterface $tokenStorage
     * @param AuthenticationManagerInterface $authenticationManager
     * @param SessionAuthenticationStrategyInterface $sessionStrategy
     * @param HttpUtils $httpUtils
     * @param string $providerKey
     * @param AuthenticationSuccessHandlerInterface $successHandler
     * @param AuthenticationFailureHandlerInterface $failureHandler
     * @param array $options
     * @param LoggerInterface $logger
     * @param EventDispatcherInterface $dispatcher
     * @param null|CsrfTokenManagerInterface $csrfTokenManager
     */
    public function __construct(
        TokenStorageInterface $tokenStorage,
        AuthenticationManagerInterface $authenticationManager,
        SessionAuthenticationStrategyInterface $sessionStrategy,
        HttpUtils $httpUtils,
        $providerKey,
        AuthenticationSuccessHandlerInterface $successHandler,
        AuthenticationFailureHandlerInterface $failureHandler,
        array $options = [],
        LoggerInterface $logger = null,
        EventDispatcherInterface $dispatcher = null,
        CsrfTokenManagerInterface $csrfTokenManager = null
    ) {
        $this->csrfTokenManager = $csrfTokenManager;

        parent::__construct(
            $tokenStorage,
            $authenticationManager,
            $sessionStrategy,
            $httpUtils,
            $providerKey,
            $successHandler,
            $failureHandler,
            array_merge($this->defaultListOpts, $options),
            $logger,
            $dispatcher
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function requiresAuthentication(Request $request)
    {
        if ($this->options['post_only'] && !$request->isMethod('POST')) {
            return false;
        }

        return parent::requiresAuthentication($request);
    }

    /**
     * {@inheritdoc}
     */
    protected function attemptAuthentication(Request $request)
    {
        $this->validateCsrfToken($request);

        return $this->authenticationManager->authenticate($this->getUsernamePasswordToken($request));
    }

    /**
     * Get the UsernamePasswordToken based off the Request parameters.
     *
     * @param Request $request
     * @return UsernamePasswordToken
     */
    protected function getUsernamePasswordToken(Request $request)
    {
        if ($this->options['post_only']) {
            $username = trim($this->getParameterFromBag($request->request, $this->options['username_parameter']));
            $password = $this->getParameterFromBag($request->request, $this->options['password_parameter']);
        } else {
            $username = trim($this->getParameterFromRequest($request, $this->options['username_parameter']));
            $password = $this->getParameterFromRequest($request, $this->options['password_parameter']);
        }
        $request->getSession()->set(Security::LAST_USERNAME, $username);

        $token = new UsernamePasswordToken($username, $password, $this->providerKey);
        $this->addDomainToTokenIfPresent($request, $token);

        return $token;
    }

    /**
     * Add the domain name for the login request to the token if specified.
     *
     * @param Request $request
     * @param UsernamePasswordToken $token
     */
    protected function addDomainToTokenIfPresent(Request $request, UsernamePasswordToken $token)
    {
        if ($this->options['post_only'] && $request->request->has($this->options['domain_parameter'])) {
            $token->setAttribute(
                'ldap_domain',
                trim($this->getParameterFromBag($request->request, $this->options['domain_parameter']))
            );
        } elseif ($domain = trim($this->getParameterFromRequest($request, $this->options['domain_parameter']))) {
            $token->setAttribute('ldap_domain', $domain);
        }
    }

    /**
     * Provide a BC wrapper for CSRF token manager/provider compatibility between versions.
     *
     * @param Request $request
     */
    protected function validateCsrfToken(Request $request)
    {
        if (is_null($this->csrfTokenManager)) {
            return;
        }
        $csrfToken = $this->getParameterFromRequest($request, $this->options['csrf_parameter']);

        if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) {
            throw new InvalidCsrfTokenException('Invalid CSRF token.');
        }
    }

    /**
     * Provide a BC wrapper for deep item finding deprecation.
     *
     * @param Request $request
     * @param string $param
     * @return mixed
     */
    protected function getParameterFromRequest(Request $request, $param)
    {
        if (!$this->useParameterBagUtils()) {
            return $request->get($param, null, true);
        }

        return ParameterBagUtils::getRequestParameterValue($request, $param);
    }

    /**
     * Provide a BC wrapper for deep item finding deprecation.
     *
     * @param ParameterBag $bag
     * @param string $param
     * @return mixed
     */
    protected function getParameterFromBag($bag, $param)
    {
        if (!$this->useParameterBagUtils()) {
            return $bag->get($param, null, true);
        }

        return ParameterBagUtils::getParameterBagValue($bag, $param);
    }

    /**
     * Whether or not the ParameterBagUtils class exists (2.8 and above...)
     *
     * @return bool
     */
    protected function useParameterBagUtils()
    {
        return class_exists('Symfony\Component\Security\Http\ParameterBagUtils');
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Doctrine\Subscriber;

use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\ObjectManager;
use LdapTools\Bundle\LdapToolsBundle\Annotation\LdapObject as LdapObjectAnnotation;
use Doctrine\ORM\Event\LifecycleEventArgs;
use LdapTools\LdapManager;
use LdapTools\Object\LdapObject;
use LdapTools\Object\LdapObjectCollection;

/**
 * Doctrine Lifecycle Events to load/save LDAP objects to an entity property.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapObjectSubscriber implements EventSubscriber
{
    /**
     * The annotation class to check for.
     */
    const ANNOTATION = 'LdapTools\Bundle\LdapToolsBundle\Annotation\LdapObject';

    /**
     * @var Reader
     */
    protected $reader;

    /**
     * @var LdapManager
     */
    protected $ldap;

    /**
     * @param Reader $reader
     * @param LdapManager $ldap
     */
    public function __construct(Reader $reader, LdapManager $ldap)
    {
        $this->reader = $reader;
        $this->ldap = $ldap;
    }

    /**
     * @return array
     */
    public function getSubscribedEvents()
    {
        return [
            'prePersist',
            'preUpdate',
            'postLoad',
        ];
    }

    /**
     * @param LifecycleEventArgs $args
     */
    public function preUpdate(LifecycleEventArgs $args)
    {
        $this->transformValueForDb($args);
    }

    /**
     * @param LifecycleEventArgs $args
     */
    public function prePersist(LifecycleEventArgs $args)
    {
        $this->transformValueForDb($args);
    }

    /**
     * @param LifecycleEventArgs $args
     */
    public function postLoad(LifecycleEventArgs $args)
    {
        $entity = $this->getObjectFromLifeCycleArgs($args);
        $om = $this->getOmFromLifeCycleArgs($args);

        $properties = $this->getLdapObjectAnnotationProperties($entity, $om);
        foreach ($properties as $info) {
            if ($info['property']->getValue($entity)) {
                $this->setLdapObjectForProperty($info['property'], $info['annotation'], $entity);
            }
        }
    }

    /**
     * Handles transforming the value as it currently is to the value the DB expects.
     *
     * @param LifecycleEventArgs $args
     */
    protected function transformValueForDb(LifecycleEventArgs $args)
    {
        $entity = $this->getObjectFromLifeCycleArgs($args);
        $om = $this->getOmFromLifeCycleArgs($args);

        $properties = $this->getLdapObjectAnnotationProperties($entity, $om);
        foreach ($properties as $info) {
            if ($info['property']->getValue($entity)) {
                $this->setLdapValueForProperty($info['property'], $info['annotation'], $entity);
            }
        }
    }

    /**
     * Get all the properties from the entity that have a LdapObject annotation defined.
     *
     * @param object $entity
     * @param ObjectManager $om
     * @return array
     */
    protected function getLdapObjectAnnotationProperties($entity, $om)
    {
        $properties = $om->getClassMetadata(get_class($entity))->getReflectionProperties();

        $ldapObjectProps = [];
        foreach ($properties as $prop) {
            $annotation = $this->reader->getPropertyAnnotation($prop, self::ANNOTATION);
            if (!empty($annotation)) {
                $ldapObjectProps[] = ['annotation' => $annotation, 'property' => $prop];
            }
        }

        return $ldapObjectProps;
    }

    /**
     * Based on an array of IDs for LDAP objects, set the property to either a LdapObject for LdapObjectCollection.
     *
     * @param \ReflectionProperty $property
     * @param LdapObjectAnnotation $annotation
     * @param $entity
     */
    protected function setLdapObjectForProperty(\ReflectionProperty $property, LdapObjectAnnotation $annotation, $entity)
    {
        if (empty($property->getValue($entity))) {
            return;
        }
        $domain = $this->ldap->getDomainContext();
        $switchDomain = $annotation->domain ?: null;
        if ($switchDomain) {
            $this->ldap->switchDomain($annotation->domain);
        }

        $results = $this->queryLdapForObjects($property, $annotation, $entity);
        $property->setValue($entity, $results);

        if ($switchDomain) {
            $this->ldap->switchDomain($domain);
        }
    }

    /**
     * Get the specified ID for each LDAP object and save it to an array.
     *
     * @param \ReflectionProperty $property
     * @param LdapObjectAnnotation $annotation
     * @param $entity
     */
    protected function setLdapValueForProperty(\ReflectionProperty $property, LdapObjectAnnotation $annotation, $entity)
    {
        $value = $property->getValue($entity);

        if ($value instanceof LdapObject) {
            $ldapValues = $value->get($annotation->id);
        } elseif ($value instanceof LdapObjectCollection) {
            $ldapValues = [];
            foreach ($value->toArray() as $ldapObject) {
                $ldapValues[] = $ldapObject->get($annotation->id);
            }
        } else {
            throw new \InvalidArgumentException(sprintf(
                'Class "%s" is not valid. Expected a LdapObject or LdapObjectCollection',
                get_class($value)
            ));
        }

        $property->setValue($entity, $ldapValues);
    }

    /**
     * @param LdapObjectAnnotation $annotation
     * @return array
     */
    protected function getLdapAttributesToSelect(LdapObjectAnnotation $annotation)
    {
        $attributes = $annotation->attributes;
        if (empty($attributes)) {
            $schemaFactory = $this->ldap->getSchemaFactory();
            $schema = $schemaFactory->get(
                $this->ldap->getConnection()->getConfig()->getSchemaName(),
                $annotation->type
            );
            $attributes = $schema->getAttributesToSelect();
        }

        if (!in_array($annotation->id, $attributes)) {
            $attributes[] = $annotation->id;
        }

        return $attributes;
    }

    /**
     * @param \ReflectionProperty $property
     * @param LdapObjectAnnotation $annotation
     * @param $entity
     * @return LdapObject|LdapObjectCollection|null
     */
    protected function queryLdapForObjects(\ReflectionProperty $property, LdapObjectAnnotation $annotation, $entity)
    {
        $query = $this->ldap->buildLdapQuery()
            ->select($this->getLdapAttributesToSelect($annotation))
            ->from($annotation->type);
        $values = $property->getValue($entity);

        // A single LdapObject type...
        if (is_string($values) && !empty($values)) {
            $query->where([$annotation->id => $values]);
        // A LdapObjectCollection type...
        } elseif (is_array($values) && !empty($values)) {
            foreach ($values as $value) {
                $query->orWhere([$annotation->id => $value]);
            }
        // A currently null/empty value?
        } else {
            return null;
        }

        if ($annotation->collection) {
            $results = $query->getLdapQuery()->getResult();
        } else {
            $results = $query->getLdapQuery()->getOneOrNullResult();
        }

        return $results;
    }

    /**
     * Avoid calling deprecated methods if possible.
     *
     * @param LifecycleEventArgs $args
     * @return object
     */
    protected function getObjectFromLifeCycleArgs(LifecycleEventArgs $args)
    {
        $rc = new \ReflectionClass('Doctrine\Common\Persistence\Event\LifecycleEventArgs');

        if ($rc->hasMethod('getObject')) {
            return $args->getObject();
        } else {
            return $args->getEntity();
        }
    }

    /**
     * Avoid calling deprecated methods if possible.
     *
     * @param LifecycleEventArgs $args
     * @return \Doctrine\Common\Persistence\ObjectManager|\Doctrine\ORM\EntityManager
     */
    protected function getOmFromLifeCycleArgs(LifecycleEventArgs $args)
    {
        $rc = new \ReflectionClass('Doctrine\Common\Persistence\Event\LifecycleEventArgs');

        if ($rc->hasMethod('getObjectManager')) {
            return $args->getObjectManager();
        } else {
            return $args->getEntityManager();
        }
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle;

use LdapTools\Bundle\LdapToolsBundle\DependencyInjection\Compiler\EventRegisterPass;
use LdapTools\Bundle\LdapToolsBundle\DependencyInjection\Compiler\LdifUrlLoaderPass;
use LdapTools\Bundle\LdapToolsBundle\DependencyInjection\Security\Factory\LdapFormLoginFactory;
use LdapTools\Bundle\LdapToolsBundle\Doctrine\Type\LdapObjectCollectionType;
use LdapTools\Bundle\LdapToolsBundle\Doctrine\Type\LdapObjectType;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Doctrine\DBAL\Types\Type;

/**
 * The Bundle class.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapToolsBundle extends Bundle
{
    public function __construct()
    {
        // It's useful to auto-register the types, but we should not assume they are using doctrine...
        if (!class_exists('\Doctrine\DBAL\Types\Type')) {
            return;
        }

        if (!Type::hasType(LdapObjectType::TYPE)) {
            Type::addType(
                LdapObjectType::TYPE,
                '\LdapTools\Bundle\LdapToolsBundle\Doctrine\Type\LdapObjectType'
            );
        }
        if (!Type::hasType(LdapObjectCollectionType::TYPE)) {
            Type::addType(
                LdapObjectCollectionType::TYPE,
                '\LdapTools\Bundle\LdapToolsBundle\Doctrine\Type\LdapObjectCollectionType'
            );
        }
    }

    /**
     * Make Symfony aware of the LDAP listener factory and add the compiler pass.
     *
     * @param ContainerBuilder $container
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new LdapFormLoginFactory());
        $container->addCompilerPass(new EventRegisterPass());
        $container->addCompilerPass(new LdifUrlLoaderPass());
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Form\ChoiceLoader;

use LdapTools\LdapManager;
use LdapTools\Object\LdapObject;
use LdapTools\Query\LdapQueryBuilder;

/**
 * Removes duplication between the Choice List and the Choice Loader.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
trait LdapObjectChoiceTrait
{
    /**
     * @var \Closure|null
     */
    protected $queryCallback;

    /**
     * @var LdapQueryBuilder|null
     */
    protected $ldapQueryBuilder;

    /**
     * @var LdapManager
     */
    protected $ldap;

    /**
     * @var string
     */
    protected $id;

    /**
     * @var string
     */
    protected $type;

    /**
     * @var string
     */
    protected $labelAttribute;

    /**
     * @param LdapQueryBuilder|\Closure|null $query
     */
    protected function setClosureOrQuery($query)
    {
        if ($query instanceof \Closure) {
            $this->queryCallback = $query;
        } elseif ($query instanceof LdapQueryBuilder) {
            $this->ldapQueryBuilder = $query;
        }
    }

    /**
     * Get the values of a set of choices.
     *
     * @param LdapObject[] $choices
     * @return array
     */
    protected function getLdapValuesForChoices(LdapObject ...$choices)
    {
        $values = [];

        foreach ($choices as $i => $ldapObject) {
            $values[$i] = (string) $ldapObject->get($this->id);
        }

        return $values;
    }

    /**
     * Get the LDAP objects from LDAP. Optionally only get those specified by the passed values.
     *
     * @param array $values The values used to narrow the LDAP query.
     * @return \LdapTools\Object\LdapObjectCollection
     */
    protected function getLdapObjectsByQuery($values = [])
    {
        if (!$this->ldapQueryBuilder) {
            $query = $this->ldap->buildLdapQuery()
                ->select([$this->id, $this->labelAttribute])
                ->from($this->type);
        } else {
            $query = clone $this->ldapQueryBuilder;
        }

        if (!empty($values)) {
            foreach ($values as $value) {
                $query->orWhere([$this->id => $value]);
            }
        }
        if ($this->queryCallback) {
            $closure = $this->queryCallback;
            $closure($query);
        }

        return $query->getLdapQuery()->getResult();
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\CacheWarmer;

use LdapTools\Configuration;
use LdapTools\Factory\LdapObjectSchemaFactory;
use LdapTools\LdapManager;
use LdapTools\Schema\LdapObjectSchema;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;

/**
 * A cache warmer for the LDAP schema types.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapToolsCacheWarmer implements CacheWarmerInterface
{
    /**
     * @var LdapManager
     */
    protected $ldap;

    /**
     * @var Configuration
     */
    protected $config;

    /**
     * @param LdapManager $ldap
     * @param Configuration $config
     */
    public function __construct(LdapManager $ldap, Configuration $config)
    {
        $this->ldap = $ldap;
        $this->config = $config;
    }

    /**
     * {@inheritdoc}
     */
    public function warmUp($cacheDir)
    {
        $domain = $this->ldap->getDomainContext();
        foreach ($this->config->getDomainConfiguration() as $domainConfig) {
            $this->ldap->switchDomain($domainConfig->getDomainName());
            $schemaFactory = $this->ldap->getSchemaFactory();
            $parser = $this->ldap->getSchemaParser();
            $schema = empty($domainConfig->getSchemaName()) ? $domainConfig->getLdapType() : $domainConfig->getSchemaName();
            $ldapObjects = $parser->parseAll($schema);

            $this->cacheAllLdapSchemaObjects($schemaFactory, ...$ldapObjects);
        }
        $this->ldap->switchDomain($domain);
    }

    /**
     * {@inheritdoc}
     */
    public function isOptional()
    {
        return true;
    }

    /**
     * @param LdapObjectSchemaFactory $schemaFactory
     * @param LdapObjectSchema ...$schemaObjects
     */
    protected function cacheAllLdapSchemaObjects(LdapObjectSchemaFactory $schemaFactory, LdapObjectSchema ...$schemaObjects)
    {
        /** @var LdapObjectSchema $ldapSchemaObject */
        foreach ($schemaObjects as $ldapSchemaObject) {
            $schemaFactory->get($ldapSchemaObject->getSchemaName(), $ldapSchemaObject->getObjectType());
        }
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Add any services tagged as a LDIF URL loader to the LDIF parser service.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdifUrlLoaderPass implements CompilerPassInterface
{
    /**
     * The LDIF URL loader tag name.
     */
    const LDIF_URL_LOADER_TAG = 'ldap_tools.ldif_url_loader';

    /**
     * The LDIF parser service name.
     */
    const LDIF_PARSER = 'ldap_tools.ldif_parser';

    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $urlLoaders = $container->findTaggedServiceIds(self::LDIF_URL_LOADER_TAG);
        if (empty($urlLoaders)) {
            return;
        }
        $parser = $container->findDefinition(self::LDIF_PARSER);

        foreach ($urlLoaders as $id => $loader) {
            if (!isset($loader[0]['type'])) {
                throw new \InvalidArgumentException(sprintf('Service "%s" must define the "type" attribute on "%s" tags.', $id, self::LDIF_URL_LOADER_TAG));
            }
            $parser->addMethodCall('setUrlLoader', [$loader[0]['type'], new Reference($id)]);
        }
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Security\Authentication\Provider;

use LdapTools\Bundle\LdapToolsBundle\Event\LdapLoginEvent;
use LdapTools\Bundle\LdapToolsBundle\Security\LdapAuthenticationTrait;
use LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUserChecker;
use LdapTools\Bundle\LdapToolsBundle\Security\User\LdapUserProvider;
use LdapTools\Exception\LdapConnectionException;
use LdapTools\LdapManager;
use LdapTools\Operation\AuthenticationOperation;
use LdapTools\Operation\AuthenticationResponse;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

/**
 * Authenticate a user against LDAP.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapAuthenticationProvider implements AuthenticationProviderInterface
{
    use LdapAuthenticationTrait;

    /**
     * @var UserProviderInterface
     */
    protected $userProvider;

    /**
     * @var bool
     */
    protected $hideUserNotFoundExceptions;

    /**
     * @var string
     */
    protected $providerKey;

    /**
     * @var LdapUserChecker
     */
    protected $userChecker;

    /**
     * @var EventDispatcherInterface
     */
    protected $dispatcher;

    /**
     * @var array
     */
    protected $options = [
        'login_query_attribute' => null,
    ];

    /**
     * @param string $providerKey
     * @param bool $hideUserNotFoundExceptions
     * @param UserProviderInterface $userProvider
     * @param LdapUserChecker $userChecker
     * @param LdapManager $ldap
     * @param EventDispatcherInterface $dispatcher
     * @param LdapUserProvider $ldapUserProvider
     * @param array $options
     */
    public function __construct(
        $providerKey,
        $hideUserNotFoundExceptions = true,
        UserProviderInterface $userProvider,
        LdapUserChecker $userChecker,
        LdapManager $ldap,
        EventDispatcherInterface $dispatcher,
        LdapUserProvider $ldapUserProvider,
        array $options
    ) {
        $this->userProvider = $userProvider;
        $this->ldap = $ldap;
        $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
        $this->providerKey = $providerKey;
        $this->userChecker = $userChecker;
        $this->dispatcher = $dispatcher;
        $this->ldapUserProvider = $ldapUserProvider;
        $this->options = array_merge($this->options, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function authenticate(TokenInterface $token)
    {
        $domain = $this->ldap->getDomainContext();

        try {
            $this->switchDomainIfNeeded($this->getDomainFromToken($token));
            $this->setLdapCredentialsIfNeeded($token->getUsername(), $token->getCredentials(), $this->userProvider);
            $user = $this->userProvider->loadUserByUsername($token->getUsername());
            $this->userChecker->checkPreAuth($user);
            $token = $this->doAuthentication($user, $token);
            $this->userChecker->checkPostAuth($user);
        } catch (UsernameNotFoundException $e) {
            $this->hideOrThrow($e, $this->hideUserNotFoundExceptions);
        } catch (BadCredentialsException $e) {
            $this->hideOrThrow($e, $this->hideUserNotFoundExceptions);
        } catch (LdapConnectionException $e) {
            $this->hideOrThrow($e, $this->hideUserNotFoundExceptions);
        } finally {
            $this->switchDomainBackIfNeeded($domain);
        }

        return $token;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(TokenInterface $token)
    {
        return ($token instanceof UsernamePasswordToken);
    }

    /**
     * @param UserInterface $user
     * @param TokenInterface $token
     * @return UsernamePasswordToken
     */
    protected function doAuthentication(UserInterface $user, TokenInterface $token)
    {
        $auth = new AuthenticationOperation(
            $this->getBindUsername($user, $this->options['login_query_attribute']),
            $token->getCredentials()
        );
        /** @var AuthenticationResponse $response */
        $response = $this->ldap->getConnection()->execute($auth);

        if (!$response->isAuthenticated()) {
            $this->userChecker->checkLdapErrorCode(
                $user,
                $response->getErrorCode(),
                $this->ldap->getConnection()->getConfig()->getLdapType()
            );

            throw new BadCredentialsException($response->getErrorMessage(), $response->getErrorCode());
        }
        $this->dispatcher->dispatch(LdapLoginEvent::SUCCESS, new LdapLoginEvent($user, $token));

        $newToken = new UsernamePasswordToken($user, null, $this->providerKey, $user->getRoles());
        $newToken->setAttributes($token->getAttributes());

        return $newToken;
    }

    /**
     * @param TokenInterface $token
     * @return string
     */
    protected function getDomainFromToken(TokenInterface $token)
    {
        return $token->hasAttribute('ldap_domain') ? $token->getAttribute('ldap_domain') : '';
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Security\User;

use LdapTools\Object\LdapObject;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;

/**
 * Represents a user from LDAP.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapUser extends LdapObject implements LdapUserInterface, AdvancedUserInterface, \Serializable
{
    /**
     * @var array The Symfony roles for this user.
     */
    protected $roles = [];

    public function __construct()
    {
        parent::__construct([]);
    }

    /**
     * {@inheritdoc}
     */
    public function getSalt()
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getPassword()
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function eraseCredentials()
    {
        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getRoles()
    {
        return $this->roles;
    }

    /**
     * {@inheritdoc}
     */
    public function setRoles(array $roles)
    {
        $this->roles = [];
        foreach ($roles as $role) {
            $this->addRole($role);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function addRole($role)
    {
        $role = strtoupper($role);

        if (!in_array($role, $this->roles)) {
            $this->roles[] = $role;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function removeRole($role)
    {
        $role = strtoupper($role);

        if (in_array($role, $this->roles)) {
            $this->roles = array_diff($this->roles, [$role]);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getUsername()
    {
        return $this->get('username');
    }

    /**
     * {@inheritdoc}
     */
    public function setUsername($username)
    {
        return $this->set('username', $username);
    }

    /**
     * {@inheritdoc}
     */
    public function isAccountNonExpired()
    {
        if (!$this->has('accountExpirationDate') || $this->get('accountExpirationDate') === false) {
            $result = true;
        } elseif ($this->get('accountExpirationDate') instanceof \DateTime) {
            $result = ($this->get('accountExpirationDate') > new \DateTime());
        } else {
            $result = (bool) $this->get('accountExpirationDate');
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function isAccountNonLocked()
    {
        return $this->has('locked') ? !$this->get('locked') : true;
    }

    /**
     * {@inheritdoc}
     */
    public function isCredentialsNonExpired()
    {
        return $this->has('passwordMustChange') ? !$this->get('passwordMustChange') : true;
    }

    /**
     * {@inheritdoc}
     */
    public function isEnabled()
    {
        return $this->has('enabled') ? $this->get('enabled') : true;
    }

    /**
     * {@inheritdoc}
     */
    public function getLdapGuid()
    {
        return $this->get('guid');
    }

    /**
     * {@inheritdoc}
     */
    public function setLdapGuid($guid)
    {
        return $this->set('guid', $guid);
    }

    /**
     * {@inheritdoc}
     */
    public function getGroups()
    {
        return $this->has('groups') ? $this->get('groups') : [];
    }

    /**
     * {@inheritdoc}
     */
    public function serialize()
    {
        return serialize([
            $this->attributes,
            $this->type,
            $this->roles
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function unserialize($serialized)
    {
        list($this->attributes, $this->type, $this->roles) = unserialize($serialized);
    }

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


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Log;

use LdapTools\Log\LdapLoggerInterface;
use LdapTools\Log\LogOperation;

/**
 * Handles LDAP operation logging for use within the profiler.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapProfilerLogger implements LdapLoggerInterface
{
    /**
     * @var LogOperation[]
     */
    protected $opsByDomain = [];

    /**
     * @var LogOperation[]
     */
    protected $allOperations = [];

    /**
     * @var LogOperation[]
     */
    protected $errors = [];

    /**
     * {@inheritdoc}
     */
    public function start(LogOperation $operation)
    {
        if (!isset($this->opsByDomain[$operation->getDomain()])) {
            $this->opsByDomain[$operation->getDomain()] = [];
        }
        $this->opsByDomain[$operation->getDomain()][] = $operation;
        $this->allOperations[] = $operation;
    }

    /**
     * {@inheritdoc}
     */
    public function end(LogOperation $operation)
    {
        if (!is_null($operation->getError())) {
            $this->errors[] = $operation;
        }
    }

    /**
     * Get all of the operations recorded by the profiler. Or get the operations for a specific domain.
     *
     * @param null|string $domain
     * @return LogOperation[]
     */
    public function getOperations($domain = null)
    {
        if (!is_null($domain) && !isset($this->opsByDomain[$domain])) {
            return [];
        } elseif (!is_null($domain)) {
            return $this->opsByDomain[$domain];
        }

        return $this->allOperations;
    }

    /**
     * Get all the operations that had errors.
     *
     * @return LogOperation[]
     */
    public function getErrors()
    {
        return $this->errors;
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Security\User;

use LdapTools\Connection\LdapConnection;
use LdapTools\LdapManager;
use LdapTools\Object\LdapObjectCollection;
use LdapTools\Utilities\LdapUtilities;

/**
 * Maps LDAP groups to Symfony Roles for a LdapUserInterface user.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapRoleMapper
{
    /**
     * @var LdapManager
     */
    protected $ldap;

    /**
     * @var array
     */
    protected $options = [
        'check_groups_recursively' => true,
        'default_role' => 'ROLE_USER',
        'roles' => [],
        'role_ldap_type' => 'group',
        'role_attributes' => [
            'guid' => 'guid',
            'sid' => 'sid',
            'name' => 'name',
            'members' => 'members',
        ],
    ];

    /**
     * @param LdapManager $ldap
     * @param array $options
     */
    public function __construct(LdapManager $ldap, array $options)
    {
        $this->ldap = $ldap;
        $this->options = array_merge($this->options, $options);
    }

    /**
     * Set the roles for the user based on LDAP group membership.
     *
     * @param LdapUserInterface $user
     * @return LdapUserInterface
     */
    public function setRoles(LdapUserInterface $user)
    {
        $roles = [];

        if ($this->options['default_role']) {
            $roles[] = $this->options['default_role'];
        }

        if (!empty($this->options['roles'])) {
            $groups = $this->getGroupsForUser($user);

            foreach ($this->options['roles'] as $role => $roleGroups) {
                if ($this->hasGroupForRoles($roleGroups, $groups)) {
                    $roles[] = $role;
                }
            }
        }

        $user->setRoles($roles);

        return $user;
    }

    /**
     * Check all of the groups that are valid for a specific role against all of the LDAP groups that the user belongs
     * to.
     *
     * @param array $roleGroups
     * @param LdapObjectCollection $ldapGroups
     * @return bool
     */
    protected function hasGroupForRoles(array $roleGroups, LdapObjectCollection $ldapGroups)
    {
        foreach ($roleGroups as $roleGroup) {
            if (LdapUtilities::isValidLdapObjectDn($roleGroup)) {
                $attribute = 'dn';
            } elseif (preg_match(LdapUtilities::MATCH_GUID, $roleGroup)) {
                $attribute = $this->options['role_attributes']['guid'];
            } elseif (preg_match(LdapUtilities::MATCH_SID, $roleGroup)) {
                $attribute = $this->options['role_attributes']['sid'];
            } else {
                $attribute = $this->options['role_attributes']['name'];
            }

            if ($this->hasGroupWithAttributeValue($ldapGroups, $attribute, $roleGroup)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check each LDAP group to see if any of them have an attribute with a specific value.
     *
     * @param LdapObjectCollection $groups
     * @param string $attribute
     * @param string $value
     * @return bool
     */
    protected function hasGroupWithAttributeValue(LdapObjectCollection $groups, $attribute, $value)
    {
        $value = strtolower($value);

        /** @var \LdapTools\Object\LdapObject $group */
        foreach ($groups as $group) {
            if ($group->has($attribute) && strtolower($group->get($attribute)) === $value) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param LdapUserInterface $user
     * @return LdapObjectCollection
     */
    protected function getGroupsForUser(LdapUserInterface $user)
    {
        $select = $this->options['role_attributes'];
        unset($select['members']);

        $query = $this->ldap->buildLdapQuery()
            ->from($this->options['role_ldap_type'])
            ->select(array_values($select));

        /**
         * @todo How to support recursive group checks for all LDAP types? Need a recursive method check of sorts...
         */
        if ($this->ldap->getConnection()->getConfig()->getLdapType() === LdapConnection::TYPE_AD && $this->options['check_groups_recursively']) {
            $query->where($query->filter()->hasMemberRecursively($user->getLdapGuid(), $this->options['role_attributes']['members']));
        } else {
            $query->where([$this->options['role_attributes']['members'] => $user->getLdapGuid()]);
        }

        return $query->getLdapQuery()->getResult();
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;

/**
 * The LDAP form login factory.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class LdapFormLoginFactory extends FormLoginFactory
{
    public function __construct()
    {
        parent::__construct();
        $this->addOption('domain_parameter', '_ldap_domain');
    }

    /**
     * {@inheritdoc}
     */
    public function getKey()
    {
        return 'ldap-tools-form';
    }

    /**
     * {@inheritdoc}
     */
    protected function getListenerId()
    {
        return 'ldap_tools.security.firewall.ldap_form_login_listener';
    }

    /**
     * {@inheritdoc}
     */
    protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
    {
        $provider = 'ldap_tools.security.user.ldap_user_provider.'.$id;
        $decorator = class_exists('Symfony\Component\DependencyInjection\ChildDefinition') ?
            'Symfony\Component\DependencyInjection\ChildDefinition' :
            'Symfony\Component\DependencyInjection\DefinitionDecorator';

        $container->setDefinition($provider, new $decorator('ldap_tools.security.authentication.ldap_authentication_provider'))
            ->replaceArgument(0, $id)
            ->replaceArgument(2, new Reference($userProviderId));

        return $provider;
    }
}


<?php
/**
 * This file is part of the LdapToolsBundle package.
 *
 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LdapTools\Bundle\LdapToolsBundle\Log;

use LdapTools\Log\LdapLoggerInterface;
use LdapTools\Log\LogOperation;
use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;

/**
 * LDAP logger.
 *
 * @author Chad Sikorra <chad.sikorra@gmail.com>
 */
class LdapLogger implements LdapLoggerInterface
{
    /**
     * @var null|LoggerInterface
     */
    protected $logger;

    /**
     * @var null|Stopwatch
     */
    protected $stopwatch;

    /**
     * @param LoggerInterface|null $logger
     * @param Stopwatch|null $stopwatch
     */
    public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null)
    {
        $this->logger = $logger;
        $this->stopwatch = $stopwatch;
    }

    /**
     * {@inheritdoc}
     */
    public function start(LogOperation $log)
    {
        if (!is_null($this->stopwatch)) {
            $this->stopwatch->start('ldaptools', strtolower($log->getOperation()->getName()));
        }
        if (!is_null($this->logger)) {
            $this->log($log);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function end(LogOperation $log)
    {
        if (!is_null($this->stopwatch)) {
            $this->stopwatch->stop('ldaptools');
        }
        if (!is_null($this->logger)) {
            $this->log($log);
        }
    }

    /**
     * Logs a message.
     *
     * @param LogOperation $log
     */
    protected function log(LogOperation $log)
    {
        $message = $this->getLogMessage($log);

        if (!is_null($log->getError())) {
            $this->logger->error($message);
        } else {
            $this->logger->debug($message);
        }
    }

    /**
     * @param LogOperation $log
     * @return string
     */
    protected function getLogMessage(LogOperation $log)
    {
        $startOrStop = is_null($log->getStopTime()) ? 'Start' : 'End';
        $message = "(".$log->getDomain()." on ".$log->getOperation()->getServer().") $startOrStop ".$log->getOperation()->getName()." Operation - ";

        $params = [];
        if (is_null($log->getStopTime())) {
            foreach ($log->getOperation()->getLogArray() as $key => $value) {
                if ($key != "Server") {
                    $params[] = "$key: $value";
                }
            }
        } else {
            if (!is_null($log->getError())) {
                $params[] = "Error: ".$log->getError();
            }
            $params[] = "Completed in ".(round(($log->getStopTime() - $log->getStartTime()) * 1000))." ms.";
        }
        $message .= implode(', ', $params);

        return $message;
    }
}


<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateMediaTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('media', function(Blueprint $table)
      {
        $table->increments('id');
        $table->string('mediable_type');
        $table->integer('mediable_id')->unsigned();
        $table->string('filename');
        $table->string('mime');
        $table->bigInteger('size')->unsigned()->nullable()->default(NULL);
        $table->string('name')->nullable();
        $table->string('alt')->nullable();
        $table->string('title')->nullable();
        $table->string('group');
        $table->boolean('status');
        $table->integer('weight')->unsigned()->default(0);
        $table->timestamps();
      });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    Schema::drop('media');
  }

}


<?php namespace Devfactory\Media;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use File;

trait MediaTrait {

  protected $file;

  protected $media;

  protected $group;
  protected $type;

  protected $options;

  protected $filename_original;
  protected $filename_new;

  protected $public_path;
  protected $files_directory;
  protected $directory;
  protected $directory_uri;

  protected $create_sub_directories;

  /**
   * Setup variables and file systems (basically constructor,
   * but actual trait __contruct()'s are bad apparently)
   */
  private function setup() {
    $this->public_path = rtrim(config('media.config.public_path'), '/\\') . '/';
    $this->files_directory = rtrim(ltrim(config('media.config.files_directory'), '/\\'), '/\\') . '/';

    $this->create_sub_directories = config('media.config.sub_directories');

    $this->directory = $this->public_path . $this->files_directory;
    if ($this->create_sub_directories) {
      $this->directory_uri = Str::lower(class_basename($this)) . '/';
    }

    $this->directory .= $this->directory_uri;

    if (!empty($this->file)) {
      $this->filename_original = $this->file->getClientOriginalName();
      $this->filename_new = $this->getFilename();
    }
  }

  /**
   * Returns a collection of Media related to the model
   *
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function media() {
    return $this->morphMany(config('media.config.model'), 'mediable');
  }

  /**
   * Save the media to disk and DB
   *
   * @param $file object
   *  The Symfony\Component\HttpFoundation\File\UploadedFile Object
   *
   * @param $group string
   *  The group of file being uploaded, to allow for say a profile picture and a
   *  background to be uploaded for a user
   *
   * @param $type string
   *  If the field is for a single file that gets deleted set it to 'single',
   *  or if it is a multiple file upload, set it to 'multiple'
   *
   * @param $options array
   *  An array of extra options for the file
   *
   * @return object
   *  The Devfactory\Media\Models\Media Object
   */
  public function saveMedia($file, $group = 'default', $type = 'single', $options = []) {
    $this->file = $file;
    $this->group = $group;
    $this->type   = $type;
    $this->options = $options;

    $this->setup();

    $this->parseOptions();

    if ($this->type == 'single') {
      $this->removeExistingMedia();
    }

    $result = $this->databasePut();

    $this->storagePut();

    return $result;
  }

  /**
   * Update the meta data of a certain media file.
   *
   * @param $id int
   *  The ID of the media item to update
   *
   * @param $options array
   *  An key => value array of elements in the DB to change, key must be one of:
   *    'alt', 'title', 'name', 'weight'
   *
   * @return void
   */
  public function updateMediaById($id, $options) {
    $this->options = $options;

    $this->parseOptions();

    $model = config('media.config.model');
    $this->media = $model::find($id);

    $this->media->alt = $this->getAlt();
    $this->media->title = $this->getTitle();
    $this->media->name = $this->getName();
    $this->media->weight = $this->getWeight();

    $this->media->save();
  }

  /**
   * Get all the media assigned to the current model instance
   *
   * @param $group string
   *  The file group to retrieve
   *
   * @return object
   *  A Illuminate\Database\Eloquent\Collection Object of
   *  Devfactory\Media\Models\Media Objects
   */
  public function getMedia($group = NULL) {
    if (is_null($group)) {
      return $this->media()->orderBy('weight', 'ASC')->get();
    }

    return $this->getMediaByGroup($group);
  }

  /**
   * Get all the media assigned to the current model instance
   * by the given $group
   *
   * @param $group string
   *  The file group to retrieve
   *
   * @return object
   *  A Illuminate\Database\Eloquent\Collection Object of
   *  Devfactory\Media\Models\Media Objects
   */
  private function getMediaByGroup($group) {
    return $this->media()
      ->where('group', $group)
      ->orderBy('weight', 'ASC')
      ->get();
  }

  /**
   * Get all the media assigned to the current model instance
   * by the given $group
   *
   * @param $id int
   *  The ID of the media media to retrieve
   *
   * @return object
   *  A Illuminate\Database\Eloquent\Collection Object of
   *  Devfactory\Media\Models\Media Objects
   */
  private function getMediaById($id) {
    return $this->media()
      ->find($id);
  }

  /**
   * Delete all the media assigned to the current model instance
   *
   * @param $group string
   *  The file group to delete
   *
   * @return int
   *  The number of elements deleted
   */
  public function deleteMedia($group = NULL) {
    $media = $this->getMedia($group);

    $count = 0;
    foreach ($media as $item) {
      $count += (int) $this->removeMedia($item);
    }

    return $count;
  }

  /**
   * Delete all the media assigned to the current model instance
   *
   * @param $id int
   *  The ID of the media to delete
   *
   * @return int
   *  The number of elements deleted
   */
  public function deleteMediaById($id) {
    $media = $this->getMediaById($id);

    if (!is_null($media)) {
      return $this->removeMedia($media);
    }

    return FALSE;
  }

  /**
   * Delete all the media assigned to the current model instance by
   * the give $group
   *
   * @param $group string
   *  The file group to delete
   *
   * @return int
   *  The number of elements deleted
   */
  private function deleteMediaByGroup($group) {
    return $this->media()
      ->where('group', $group)
      ->delete();
  }

  /**
   * Perform the actual delete of the database row and removal of the
   * file from the filesystem
   *
   * @param $group string
   *  The file group to delete
   *
   * @return bool
   */
  private function removeMedia($media) {
    $this->setup();

    if (File::delete($this->public_path . $this->files_directory . $media->filename)) {
      $media->delete();
      return TRUE;
    }

    return FALSE;
  }

  /**
   * Parse the optional arguments and merge them with the defaults
   */
  private function parseOptions() {
    $default_options = [
      'alt' => NULL,
      'title' => NULL,
      'name' => NULL,
      'weight' => NULL,
    ];

    $this->options += $default_options;

    $this->options = (object) $this->options;
  }

  /**
   * Helper function to process the media filename according to the settings
   *
   * @return string
   *  The parsed filename
   */
  private function getFilename() {
    switch (config('media.config.rename')) {
      case 'transliterate':
        $this->filename_new = \Transliteration::clean_filename($this->filename_original);
        break;
      case 'unique':
        $this->filename_new = md5(microtime() . str_random(5)) .'.'. $this->filename_original;
        break;
      case 'nothing':
        $this->filename_new = $this->file->getClientOriginalName();
        break;
    }

    return $this->fileExistsRename();
  }

  /**
   * Checks if a file exists and creates a new name if it does
   *
   * @return string
   *  The uniquely named file
   */
  private function fileExistsRename() {
    if (!File::exists($this->directory . $this->filename_new)) {
      return $this->filename_new;
    }

    return $this->fileRename();
  }

  /**
   * Appends _X to the file's basename if it already exists
   *
   * @return string
   *  The newly renamed file
   */
  private function fileRename() {
    $filename = $this->filename_new;
    $extension = '.' . File::extension($this->filename_new);
    $basename = rtrim($filename, $extension);

    $increment = 0;

    while (File::exists($this->directory . $filename)) {
      $filename = $basename . '_' . ++$increment . $extension;
    }

    return $this->filename_new = $filename;
  }

  /**
   * Get the alt text for the file, from options, then from db, otherwise ''
   *
   * @return string
   *  The Alt text
   */
  private function getAlt() {
    if (!is_null($this->options->alt)) {
      return $this->options->alt;
    }

    if (!is_null($this->media)) {
      return $this->media->alt;
    }

    return '';
  }

  /**
   * Get the Title text for the file, from options, then from db, otherwise ''
   *
   * @return string
   *  The Title text
   */
  private function getTitle() {
    if (!is_null($this->options->title)) {
      return $this->options->title;
    }

    if (!is_null($this->media)) {
      return $this->media->title;
    }

    return '';
  }

  /**
   * Get the Name text for the file, from options, then from db, otherwise ''
   *
   * @return string
   *  The Name text
   */
  private function getName() {
    if (!is_null($this->options->name)) {
      return $this->options->name;
    }

    if (!is_null($this->media)) {
      return $this->media->name;
    }

    return '';
  }

  /**
   * Calculate the weight of the image from options, then from existing media,
   * then compared to others in the group
   *
   * @return int
   *  The weight of the image
   */
  private function getWeight() {
    if (!is_null($this->options->weight)) {
      return $this->options->weight;
    }

    if (!is_null($this->media)) {
      return $this->media->weight;
    }

    return $this->media()->where('group', $this->group)->count();
  }

  /**
   * Insert the media into the database
   *
   * @return object
   *  Devfactory\Media\Models\Media Object
   */
  private function databasePut() {
    $media = [
      'filename' => $this->directory_uri . $this->filename_new,
      'mime' => $this->file->getMimeType(),
      'size' => $this->file->getSize(),
      'title' => $this->getTitle(),
      'alt' => $this->getAlt(),
      'name' => $this->getName(),
      'group' => $this->group,
      'status' => TRUE,
      'weight' => $this->getWeight(),
    ];

    $model = config('media.config.model');
    return $this->media()->save(new $model($media));
  }

  /**
   * Remove any media that already exists for the current group type
   *
   * @return int
   *  The number of elements removed
   */
  private function removeExistingMedia() {
    $existing_media = $this->getMedia($this->group);

    if (!$existing_media->isEmpty()) {
      return $this->deleteMedia($this->group);
    }

    return 0;
  }

  /**
   * Write the media to the file system
   */
  private function storagePut() {
    if ($this->makeDirectory($this->directory)) {
      $this->file->move($this->directory, $this->filename_new);
    }
  }

  /**
   * Clone a file from passed Media Object to a new Media location on disk
   *
   * @return bool
   *  TRUE if the file copied, FALSE if it could not be copied
   */
  private function storageClone() {
    if ($this->makeDirectory($this->directory)) {
      return File::copy($this->public_path . $this->files_directory . $this->media->filename, $this->directory . $this->filename_new);
    }

    return false;
  }

  /**
   * Creates the passed directory if it doesn't exist
   *
   * @param $directory string
   *  The full path of the directory to create
   *
   * @return bool
   *  TRUE if the directory exists, FALSE if it could not be created
   */
  private function makeDirectory($directory) {
    if (File::isDirectory($directory)) {
      return TRUE;
    }

    return File::makeDirectory($directory, 0755, TRUE);
  }

  /**
   * Clone an existing media item, onto a new model instance.
   *
   * @param object $media
   *  The old Media that should be cloned to the current instance.
   * @param bool $storage_clone
   *  Optionally, also copy the file in storage to a new, unique file
   * @param $clone_attributes
   *  Optionally, override certain attributes on the clone
   *
   * @return \Devfactory\Media\Models\Media
   */
  public function cloneMedia($media, $clone_storage = false, $clone_attributes = []) {
    $this->media = $media->replicate();
    $this->setup();
    $this->filename_new = basename($media->filename);

    if ($clone_storage) {
      $this->fileExistsRename();
      $this->storageClone();
    }

    $this->media->fill($clone_attributes);
    $this->media->filename = $this->directory_uri . $this->filename_new;

    return $this->media()->save($this->media);
  }

}


<?php
/**
 * Simple automated command bus
 *
 * @link      https://github.com/hiqdev/yii2-autobus
 * @package   yii2-autobus
 * @license   BSD-3-Clause
 * @copyright Copyright (c) 2017-2018, HiQDev (http://hiqdev.com/)
 */

namespace hiqdev\yii2\autobus\bus;

use League\Tactician\Middleware;
use yii\base\Model;

/**
 * Class ValidateMiddleware.
 *
 * @author Dmytro Naumenko <d.naumenko.a@gmail.com>
 */
class ValidateMiddleware implements Middleware
{
    /**
     * @param object|Model $command
     * @param callable $next
     *
     * @return mixed
     */
    public function execute($command, callable $next)
    {
        if (!$command->validate()) {
            // TODO: specific exception
            throw new \InvalidArgumentException(implode(', ', $command->getFirstErrors()));
        }

        return $next($command);
    }
}


<?php

namespace UniSharp\Pricing;

use UniSharp\Pricing\Pricing;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\ServiceProvider;

class PricingServiceProvider extends ServiceProvider
{
    /**
     * Boot the services for the application.
     *
     * @return void
     */
    public function boot()
    {
        $this->bootConfig();

    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('pricing', function ($app) {
            return new Pricing(
                $app,
                new Pipeline($app),
                $app['config']['pricing.modules']
            );
        });
    }

    /**
     * Boot configure.
     *
     * @return void
     */
    protected function bootConfig()
    {
        $path = __DIR__ . '/config/pricing.php';

        $this->mergeConfigFrom($path, 'pricing');

        if (function_exists('config_path')) {
            $this->publishes([$path => config_path('pricing.php')]);
        }
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['pricing'];
    }
}


<?php

namespace BladeSvgSage;

use BladeSvg\SvgFactory;

use function App\sage;

/**
 * Blade SVG for Sage
 */
class BladeSvgSage
{
    /**
     * Configuration
     *
     * @var array
     */
    protected $config;

    /**
     * Constructor
     */
    public function __construct()
    {
        if (! function_exists('App\\sage') || ! class_exists('BladeSvg\\SvgFactory') || ! $this->config()) {
            return;
        }

        $this->register();
        $this->directives();
    }

    /**
     * Register BladeSvgSage and SvgFactory with the Sage container.
     *
     * @return void
     */
    public function register()
    {
        sage()->singleton(BladeSvgSage::class, function () {
            return $this;
        });

        sage()->singleton(SvgFactory::class, function () {
            return new SvgFactory($this->config());
        });
    }

    /**
     * Register Directives with Blade.
     *
     * @return void
     */
    public function directives()
    {
        /** Create @icon() Blade directive */
        sage('blade')->compiler()->directive('icon', function ($expression) {
            return "<?php echo e(App\svg_image({$expression})) ?>";
        });

        /** Create @svg() Blade directive */
        sage('blade')->compiler()->directive('svg', function ($expression) {
            return "<?php echo e(App\svg_image({$expression})) ?>";
        });

        /** Create @spritesheet Blade directive */
        sage('blade')->compiler()->directive('spritesheet', function () {
            return "<?php echo e(App\svg_spritesheet()) ?>";
        });
    }

    /**
     * Returns the config.
     *
     * @return \Illuminate\Support\Collection
     */
    protected function config()
    {
        if (! file_exists($config = __DIR__.'/../config/config.php')) {
            return;
        }

        $config = collect(apply_filters('bladesvg', require($config)));

        return $config->merge([
            'svg_path'         => $this->path($config->get('svg_path')),
            'spritesheet_path' => $this->path($config->get('spritesheet_path'))
        ])->all();
    }

    /**
     * Returns the local filesystem path to the theme directory.
     *
     * @param  string $path
     * @return string
     */
    public function path($path = '')
    {
        return \App\config('theme.dir') . (! empty($path) ? str_start($path, '/') : null);
    }

    /**
     * Returns the rendered spritesheet.
     *
     * @return string
     */
    public function spritesheet()
    {
        return sage(SvgFactory::class)->spritesheet();
    }

    /**
     * Returns the rendered SVG for the image specified.
     *
     * @param  string $name
     * @param  string $class
     * @param  array  $attrs
     * @return mixed
     */
    public function svg($name = '', $class = '', $attrs = [])
    {
        if (empty($name)) {
            return;
        }

        return sage(SvgFactory::class)->svg($name, $class, $attrs);
    }
}

/**
 * Initialize BladeSvgSage
 */
if (function_exists('add_action')) {
    add_action('after_setup_theme', function () {
        return new BladeSvgSage;
    }, 20);
}


<?php

namespace AfrazAhmad\ClearAllCachedData;

use Illuminate\Console\Command;

class ClearAllCachedData extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'clear:data';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Clear all cached data';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->call('cache:clear');
        $this->call('view:clear');
        $this->call('route:clear');
        $this->call('clear-compiled');
        $this->call('config:clear');
    }
}


<?php
/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2013-2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 */

use qtism\data\IAssessmentItem;
use qtism\common\datatypes\QtiPoint;
use qtism\common\datatypes\QtiString;
use qtism\common\datatypes\QtiIdentifier;
use qtism\common\datatypes\QtiFloat;
use qtism\common\datatypes\QtiInteger;
use qtism\common\datatypes\QtiBoolean;
use qtism\runtime\common\MultipleContainer;
use qtism\runtime\common\OrderedContainer;
use qtism\common\enums\Cardinality;
use qtism\common\datatypes\QtiPair;
use qtism\common\datatypes\QtiDirectedPair;
use qtism\common\enums\BaseType;
use qtism\runtime\common\ResponseVariable;
use qtism\runtime\common\OutcomeVariable;
use qtism\runtime\common\Variable;

/**
 * The VariableFiller provides a way to fill a QtiSm Runtime Variable with
 * a value coming from the client side. These values are always transmitted as
 * plain strings. However, we need a way to transform these string values as
 * QtiSm compliant variable values. This is the goal of this class.
 *
 * @author Jérôme Bogaerts <jerome@taotesting.com>
 *
 */
class taoQtiCommon_helpers_LegacyVariableFiller extends taoQtiCommon_helpers_AbstractVariableFiller {

    /**
     * Create a new LegacyVariableFiller object.
     *
     * @param IAssessmentItem $itemRef The item the variables you want to fill belong to.
     */
    public function __construct(IAssessmentItem $itemRef) {
        parent::__construct($itemRef);
    }

    /**
     * Fille the variable $variableName with a correctly transformed
     * $clientSideValue.
     *
     * @param string $variableName The variable identifier you want to fill.
     * @param string $clientSideValue The value received from the client-side.
     * @return Variable A Variable object filled with a correctly transformed $clientSideValue.
     * @throws OutOfBoundsException If no variable with $variableName is described in the item.
     * @throws OutOfRangeException If the $clientSideValue does not fit the target variable's baseType.
     */
    public function fill($variableName, $clientSideValue) {
        $variable = null;

        $outcomeDeclarations = $this->getItemRef()->getOutcomeDeclarations();
        $responseDeclarations = $this->getItemRef()->getResponseDeclarations();

        if (isset($responseDeclarations[$variableName]) === true) {
            $variable = ResponseVariable::createFromDataModel($responseDeclarations[$variableName]);
        }
        else if (isset($outcomeDeclarations[$variableName]) === true) {
            $variable = OutcomeVariable::createFromDataModel($outcomeDeclarations[$variableName]);
        }

        if (empty($variable) === true) {
            $itemId = $this->getItemRef()->getIdentifier();
            $msg = "No variable declaration '${variableName}' found in '${itemId}'.";
            throw new OutOfBoundsException($msg);
        }

        common_Logger::d("Filling variable '" . $variable->getIdentifier() . "'.");

        if (is_array($clientSideValue) === false) {
            $clientSideValue = array($clientSideValue);
        }

        try {
            $finalValues = array();
            foreach ($clientSideValue as $val) {
                $finalValues[] = self::transform($variable->getBaseType(), $val);
            }

            if ($variable->getCardinality() === Cardinality::SINGLE) {
                $variable->setValue($finalValues[0]);
            }
            else if ($variable->getCardinality() === Cardinality::MULTIPLE) {
                $variable->setValue(new MultipleContainer($variable->getBaseType(), $finalValues));
            }
            else {
                // Cardinality is ORDERED.
                $variable->setValue(new OrderedContainer($variable->getBaseType(), $finalValues));
            }

            return $variable;
        }
        catch (InvalidArgumentException $e) {
            if (is_array($clientSideValue)) {
                $clientSideValue = implode(',', $clientSideValue);
            }

            $msg = "An error occured while filling variable '${variableName}' with client-side value '${clientSideValue}':" . $e->getMessage();
            throw new InvalidArgumentException($msg, 0, $e);
        }
    }

    /**
     * Transform $value in a $baseType datatype.
     *
     * @param integer $baseType
     * @param mixed $value
     * @return mixed
     * @throws InvalidArgumentException If $baseType is unknown.
     * @throws OutOfRangeException If $value cannot be transformed into $baseType datatype.
     */
    protected static function transform($baseType, $value) {

        switch ($baseType) {
            case BaseType::BOOLEAN:
                if ($value === '') {
                    return null;
                }
                else {
                    return ($value === 'true') ? true : false;
                }
            break;

            case BaseType::DIRECTED_PAIR:
                if (is_array($value) === false) {
                    $msg = "Cannot transform a value into a QTI directedPair, it is not an array, '" . gettype($value) . "' given";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[0])) {
                    $msg = "Cannot transform a value into a QTI directedPair, the first identifier was not given.";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[1])) {
                    $msg = "Cannot transform a value into a QTI directedPair, the second identifier was not given.";
                    throw new OutOfRangeException($msg);
                }
                else {
                    return new QtiDirectedPair($value[0], $value[1]);
                }
            break;

            case BaseType::PAIR:
                if (is_array($value) === false) {
                    $msg = "Cannot transform a value into a QTI pair, it is not an array, '" . gettype($value) . "' given";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[0])) {
                    $msg = "Cannot transform a value into a QTI pair, the first identifier was not given.";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[1])) {
                    $msg = "Cannot transform a value into a QTI pair, the second identifier was not given.";
                    throw new OutOfRangeException($msg);
                }
                else {
                    return new QtiPair($value[0], $value[1]);
                }
            break;

            case BaseType::STRING:
                return new QtiString($value);
            break;
            case BaseType::IDENTIFIER:
                return ($value !== '') ? new QtiIdentifier($value) : null;
            break;

            case BaseType::INTEGER:
                return ($value !== '') ? new QtiInteger(intval($value)) : null;
            break;

            case BaseType::FLOAT:
                return ($value !== '') ? new QtiFloat(floatval($value)) : null;
            break;

            case BaseType::POINT:
                if (is_array($value) === false) {
                    $msg = "Cannot transform a value into a QTI point, it is not an array, '" . gettype($value) . "' given";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[0])) {
                    $msg = "Cannot transform a value into a QTI point, X was not given.";
                    throw new OutOfRangeException($msg);
                }
                else if (empty($value[1])) {
                    $msg = "Cannot transform a value into a QTI point, Y was not given.";
                    throw new OutOfRangeException($msg);
                }
                else {
                    return new QtiPoint(intval($value[0]), intval($value[1]));
                }
            break;

            default:
                $msg = "Not supported baseType constant '${baseType}'.";
                throw new InvalidArgumentException($msg);
            break;
        }
    }
}


<?php
/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 */

use qtism\common\enums\BaseType;
use qtism\common\enums\Cardinality;
use qtism\common\datatypes\files\FileManager;
use qtism\common\datatypes\files\FileSystemFileManager;
use qtism\data\IAssessmentItem;
use qtism\data\state\ResponseDeclaration;
use qtism\data\state\OutcomeDeclaration;
use qtism\runtime\pci\json\Unmarshaller as PciJsonUnmarshaller;
use qtism\runtime\pci\json\UnmarshallingException as PciJsonUnmarshallingException;
use qtism\runtime\common\ResponseVariable;
use qtism\runtime\common\OutcomeVariable;
use qtism\runtime\common\OrderedContainer;

/**
 * The PciVariableFiller aims at providing the necessary tools to client-code that enables
 * to fill variables of a given item with values coming in a PCI JSON representation.
 *
 * @author Jérôme Bogaerts <jerome@taotesting.com>
 *
 */
class taoQtiCommon_helpers_PciVariableFiller extends taoQtiCommon_helpers_AbstractVariableFiller {

    private $fileManager;

    /**
     * Create a new PciVariableFiller object.
     *
     * @param IAssessmentItem $itemRef The item the variables to deal with belong to.
     */
    public function __construct(IAssessmentItem $itemRef, FileManager $fileManager = null) {
        parent::__construct($itemRef);
        $this->fileManager = ($fileManager === null) ? new FileSystemFileManager() : $fileManager;
    }

    /**
     * Fill the variable $variableName with a correctly transformed $clientSideValue.
     *
     * @param string $variableName The variable identifier you want to fill.
     * @param array $clientSideValue The value received from the client-side representing the value of the variable with identifier $variableName.
     * @return Variable A Variable object filled with a correctly transformed $clientSideValue.
     * @throws OutOfBoundsException If no variable with $variableName is described in the item.
     * @throws OutOfRangeException If the $clientSideValue does not fit the target variable's baseType.
     */
    public function fill($variableName, $clientSideValue) {
        $variableDeclaration = $this->findVariableDeclaration($variableName);

        if ($variableDeclaration === false) {
            $itemId = $this->getItemRef()->getIdentifier();
            $msg = "Variable declaration with identifier '${variableName}' not found in item '${itemId}'.";
            throw new \OutOfBoundsException($msg);
        }

        // Create Runtime Variable from Data Model.
        $runtimeVar = ($variableDeclaration instanceof ResponseDeclaration) ? ResponseVariable::createFromDataModel($variableDeclaration) : OutcomeVariable::createFromDataModel($variableDeclaration);

        // Set the data into the runtime variable thanks to the PCI JSON Unmarshaller
        // from QTISM.
        try {
            $unmarshaller = new PciJsonUnmarshaller($this->fileManager);
            $value = $unmarshaller->unmarshall($clientSideValue);

            // Dev's note:
            // The PCI JSON Representation format does make the difference between multiple and ordered containers.
            // We then have to juggle a bit if the target variable has ordered cardinality.
            if ($value !== null && $value->getCardinality() === Cardinality::MULTIPLE && $variableDeclaration->getCardinality() === Cardinality::ORDERED) {
                $value = new OrderedContainer($value->getBaseType(), $value->getArrayCopy());
            }

            $runtimeVar->setValue($value);
        }
        catch (PciJsonUnmarshallingException $e) {
            $strClientSideValue = mb_substr(var_export($clientSideValue, true), 0, 50, TAO_DEFAULT_ENCODING);
            $msg = "Unable to put value '${strClientSideValue}' into variable '${variableName}'.";
            throw new \OutOfRangeException($msg, 0, $e);
        }

        return $runtimeVar;
    }

    /**
     * Get the OutcomeDeclaration/ResponseDeclaration with identifier $variableIdentifier from
     * the item.
     *
     * @param string $variableIdentifier A QTI identifier.
     * @return \qtism\data\state\VariableDeclaration|boolean The variable with identifier $variableIdentifier or false if it could not be found.
     */
    protected function findVariableDeclaration($variableIdentifier) {
        $responseDeclarations = $this->getItemRef()->getResponseDeclarations();

        if (isset($responseDeclarations[$variableIdentifier]) === true) {
            return $responseDeclarations[$variableIdentifier];
        }
        else {
            $outcomeDeclarations = $this->getItemRef()->getOutcomeDeclarations();

            if (isset($outcomeDeclarations[$variableIdentifier]) === true) {
                return $outcomeDeclarations[$variableIdentifier];
            }
            else {
                // Variable $variableIdentifier not found.
                return false;
            }
        }
    }
}


<?php

/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2013-2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 *
 */

use qtism\common\datatypes\QtiPair;
use qtism\common\enums\BaseType;
use qtism\common\enums\Cardinality;
use qtism\runtime\common\Variable;
use qtism\common\datatypes\QtiPoint;

/**
 * This helper class gives a way to build a State (a collection of variables)
 * to be transmitted to the client-side.
 *
 * The result output can be accessed with the tao_helpers_StateOutput::getOutput() method
 * which returns an associative array, that can be transformed as needed for transmission
 * to the client-side.
 *
 * @author Jérôme Bogaerts <jerome@taotesting.com>
 *
 */
class taoQtiCommon_helpers_LegacyStateOutput extends taoQtiCommon_helpers_AbstractStateOutput {

    /**
     * Create a new tao_helpers_StateOutput object.
     *
     */
    public function __construct() {
        parent::__construct();
    }

    /**
     * Get the output array. The returned array is an associative array where keys are the identifier
     * of the variable and the values are the actual values of the variable.
     *
     * Each variable value is represented as an array, even if the cardinality of the variable is SINGLE. The values
     * are always plain string, as the values coming in from the client-side. Find below an example of returned state
     * output. Please be carefull will null values. They will be transformed as empty strings.
     *
     * <code>
     * // The  $stateOutput variable is a StateOutput object where the following variables
     * // where added through the StateOutput::addVariable() method.
     * // - OutcomeVariable => 'SCORE1', Cardinality:SINGLE, BaseType:INTEGER, value: 25
     * // - OutcomeVariable => 'SCORE2', Cardinality:SINGLE, BaseType:URI, value: 'http://bit.ly'
     * // - OutcomeVariable => 'SCORE3', Cardinality:SINGLE, BaseType:BOOLEAN, value: false
     * // - OutcomeVariable => 'SCORE4', Cardinality:SINGLE, BaseType:FLOAT, value: null
     * // - OutcomeVariable => 'SCORE5', Cardinality::ORDERED, BaseType:INTEGER, value: {0, -10, null, 44}
     * // - OutcomeVariable => 'SCORE6', Cardinality::MULTIPLE, BaseType:PAIR, value: [new Pair('A', 'B'), new Pair('C', 'D')]
     * // - OutcomeVariable => 'SCORE7', Cardinality::MULTIPLE, BaseType:POINT, value: [new Point(0, 0), new Point(-3, 4)]
     * // - OutcomeVariable => 'SCORE8', Cardinality::RECORD, value: <'val1': 25.3, 'val2': null, 'val3': new Duration('PT1S')>
     * $stateOutput = new taoQtiCommon_helpers_LegacyStateOutput();
     * // ...
     * // $stateOutput->addVariable() ...
     * // $stateOutput->addVariable() ...
     * // $stateOutput->addVariable() ...
     * // ..
     *
     * $output = $stateOutput->getOutput();
     *
     * // The content of $output is:
     * // array(
     * //     'SCORE1' => array('25'),
     * //     'SCORE2' => array('http://bit.ly'),
     * //     'SCORE3' => array('false'),
     * //     'SCORE4' => array(''),
     * //     'SCORE5' => array('0', '-10', '', '44'),
     * //     'SCORE6' => array(array('A', 'B'), array('C', 'D')),
     * //     'SCORE7' => array(array('0', '0'), array('-3', '4'))
     * //     'SCORE8' => array('val1' => '25.3', 'val2' => '', 'val3' => 'PT1S')
     * // );
     * </code>
     *
     * @return array An array of QTI Variables and their values formatted as explained supra.
     */
    public function &getOutput() {
        return parent::getOutput();
    }

    /**
     * Add a variable to the State that has to be built for the client-side. The final output
     * array is available through the tao_helpers_StateOutput::getOutput() method.
     *
     * @param Variable $variable A QTI Runtime Variable
     */
    public function addVariable(Variable $variable) {
        $output = &$this->getOutput();
        $baseType = $variable->getBaseType();
        $cardinality = $variable->getCardinality();
        $varName = $variable->getIdentifier();

        if ($cardinality === Cardinality::SINGLE) {
            $singleValue = $variable->getValue();
            if (is_null($singleValue) === true) {
                $output[$varName] = array('');
                return;
            }

            $values = array($variable->getValue());
        }
        else {
            $containerValue = $variable->getValue();
            if (is_null($containerValue) === true || $containerValue->isNull() === true) {
                $output[$varName] = array();
                return;
            }

            $values = $containerValue->getArrayCopy(true);
        }

        $output[$varName] = array();

        foreach ($values as $val) {
            if (is_null($val) === true) {
                $output[$varName][] = '';
            }
            else if (gettype($val) === 'boolean') {
                $output[$varName][] = ($val === true) ? 'true' : 'false';
            }
            else if ($val instanceof QtiPoint) {
                $output[$varName][] = array('' . $val->getX(), '' . $val->getY());
            }
            else if ($val instanceof QtiPair) {
                $output[$varName][] = array($val->getFirst(), $val->getSecond());
            }
            else if (gettype($val) === 'object') {
                $output[$varName][] = $val->__toString();
            }
            else {
                $output[$varName][] = '' . $val;
            }
        }
    }
}


<?php
/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2013 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 *
 */

use oat\taoResultServer\models\classes\ResultStorageWrapper;
use qtism\common\enums\BaseType;
use qtism\common\enums\Cardinality;
use qtism\common\datatypes\QtiFile;
use qtism\runtime\common\OutcomeVariable;
use qtism\runtime\common\ResponseVariable;

/**
 * The ResultTransmitter class provides a way to transmit easily
 * a QtiSm Runtime Variable objects to a given Result Server for storage.
 *
 * @author Jérôme Bogaerts <jerome@taotesting.com>
 *
 */
class taoQtiCommon_helpers_ResultTransmitter {

    /**
     * be transmitted to.
     *
     * @var ResultStorageWrapper
     */
    private $resultStorage;

    /**
     * Create a new ResultTransmitter object.
     *
     * @param $storage
     */
    public function __construct(ResultStorageWrapper $storage) {
        $this->setResultStorage($storage);
    }

    /**
     * Set the result server object where the variables will be transmitted to.
     *
     * @param $storage
     */
    protected function setResultStorage(ResultStorageWrapper $storage) {
        $this->resultStorage = $storage;
    }

    /**
     * Get the StateFull result server where the variables will be transmitted to.
     *
     * @return ResultStorageWrapper
     */
    protected function getResultStorage() {
        return $this->resultStorage;
    }

    /**
     * Transmit a QtiSm Runtime Variable to the target Result Server as an Item Result.
     *
     * @param mixed $variables QtiSm Runtime Variable(s).
     * @param string $transmissionId A unique identifier that identifies uniquely the visited item.
     * @param string $itemUri An optional URI that identifies uniquely the item the $variable comes from.
     * @param string $testUri An optional URL that identifies uniquely the test the $variable comes from.
     * @throws taoQtiCommon_helpers_ResultTransmissionException If an error occurs while transmitting the Variable to the target Result Server.
     */
    public function transmitItemVariable($variables, $transmissionId, $itemUri = '', $testUri = '') {

        $itemVariableSet = [];

        if (is_array($variables) === false) {
            $variables = [$variables];
        }

        foreach ($variables as $variable) {
            $identifier = $variable->getIdentifier();

            if ($variable instanceof OutcomeVariable) {
                $value = $variable->getValue();

                $resultVariable = new taoResultServer_models_classes_OutcomeVariable();
                $resultVariable->setIdentifier($identifier);
                $resultVariable->setBaseType(BaseType::getNameByConstant($variable->getBaseType()));
                $resultVariable->setCardinality(Cardinality::getNameByConstant($variable->getCardinality()));
                $resultVariable->setValue(self::transformValue($value));

                $itemVariableSet[] = $resultVariable;
            }
            else if ($variable instanceof ResponseVariable) {
                // ResponseVariable.
                $value = $variable->getValue();

                $resultVariable = new taoResultServer_models_classes_ResponseVariable();
                $resultVariable->setIdentifier($identifier);
                $resultVariable->setBaseType(BaseType::getNameByConstant($variable->getBaseType()));
                $resultVariable->setCardinality(Cardinality::getNameByConstant($variable->getCardinality()));
                $resultVariable->setCandidateResponse(self::transformValue($value));

                // The fact that the response is correct must not be sent for built-in
                // response variables 'duration' and 'numAttempts'.
                if (!in_array($identifier, array('duration', 'numAttempts', 'comment'))) {
                    $resultVariable->setCorrectResponse($variable->isCorrect());
                }

                $itemVariableSet[] = $resultVariable;
            }
        }

        try {
            common_Logger::d("Sending Item Variables to result server.");
            $this->getResultStorage()->storeItemVariables($testUri, $itemUri, $itemVariableSet, $transmissionId);
        }
        catch (Exception $e) {
            $msg = "An error occured while transmitting one or more Outcome/Response Variable(s) to the target result server.";
            $code = taoQtiCommon_helpers_ResultTransmissionException::OUTCOME;
            throw new taoQtiCommon_helpers_ResultTransmissionException($msg, $code);
        }
    }

    /**
     * Transmit a test-level QtiSm Runtime Variable to the target Result Server as a test result.
     *
     * @param mixed $variables An OutcomeVariable object (or an OutcomeVariable array) to be transmitted to the target Result Server.
     * @param string $transmissionId A unique identifier that identifies uniquely the visited test.
     * @param string $testUri An optional URL that identifies uniquely the test the $variable comes from.
     */
    public function transmitTestVariable($variables, $transmissionId, $testUri = '') {

        $testVariableSet = [];

        if (!is_array($variables)) {
            $variables = [$variables];
        }

        foreach ($variables as $variable) {
            $resultVariable = new taoResultServer_models_classes_OutcomeVariable();
            $resultVariable->setIdentifier($variable->getIdentifier());
            $resultVariable->setBaseType(BaseType::getNameByConstant($variable->getBaseType()));
            $resultVariable->setCardinality(Cardinality::getNameByConstant($variable->getCardinality()));

            $value = $variable->getValue();
            $resultVariable->setValue(self::transformValue($value));

            $testVariableSet[] = $resultVariable;
        }

        try {
            common_Logger::d("Sending Test Variables to result server.");
            $this->getResultStorage()->storeTestVariables($testUri, $testVariableSet, $transmissionId);
        }
        catch (Exception $e) {
            $msg = "An error occured while transmitting one or more Outcome Variable(s) to the target result server.";
            $code = taoQtiCommon_helpers_ResultTransmissionException::OUTCOME;
            throw new taoQtiCommon_helpers_ResultTransmissionException($msg, $code);
        }
    }

    /**
     * Transform a QTI Datatype value to a value compliant
     * with result server.
     *
     * @param mixed $value
     * @return string
     */
    private static function transformValue($value) {
        if (gettype($value) === 'object') {

            if ($value instanceof QtiFile) {
                return taoQtiCommon_helpers_Utils::qtiFileToString($value);
            }
            else {
                return $value->__toString();
            }
        }
        else {
            return $value;
        }
    }

}


<?php
/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 */

use qtism\runtime\common\Variable;

/**
 * Implementation of AbstractStateOutput for IMS PCI JSON Representation based output.
 *
 * @author Jérôme Bogaerts <jerome@taotesting.com>
 *
 */
class taoQtiCommon_helpers_PciStateOutput extends taoQtiCommon_helpers_AbstractStateOutput {

    /**
     * Create a new taoQtiCommon_helpers_PciStateOutput.
     *
     */
    public function __construct() {
        parent::__construct();
    }

    /**
     * Add a variable to be transformed into its IMS PCI JSON Representation
     * for a later output.
     *
     * @param Variable $variable
     */
    public function addVariable(Variable $variable) {
        $output = &$this->getOutput();
        $varName = $variable->getIdentifier();
        $marshaller = new taoQtiCommon_helpers_PciJsonMarshaller();
        $output[$varName] = $marshaller->marshall($variable->getValue(), taoQtiCommon_helpers_PciJsonMarshaller::MARSHALL_ARRAY);
    }
}