<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\Routing;

use Valkyrja\Application;
use Valkyrja\Http\Exceptions\NotFoundHttpException;
use Valkyrja\Http\Request;
use Valkyrja\Http\RequestMethod;
use Valkyrja\Http\Response;
use Valkyrja\Routing\Annotations\RouteAnnotations;
use Valkyrja\Routing\Events\RouteMatched;
use Valkyrja\Routing\Exceptions\InvalidRouteName;
use Valkyrja\Support\Providers\Provides;
use Valkyrja\View\View;

/**
 * Class Router.
 *
 * @author Melech Mizrachi
 */
class NativeRouter implements Router
{
    use Provides;

    /**
     * Application.
     *
     * @var \Valkyrja\Application
     */
    protected $app;

    /**
     * The route collection.
     *
     * @var \Valkyrja\Routing\RouteCollection
     */
    protected static $collection;

    /**
     * Whether route's have been setup yet.
     *
     * @var bool
     */
    protected static $setup = false;

    /**
     * Router constructor.
     *
     * @param Application $application The application
     */
    public function __construct(Application $application)
    {
        $this->app = $application;
    }

    /**
     * Set a single route.
     *
     * @param Route $route The route
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function addRoute(Route $route): void
    {
        // Verify the dispatch
        $this->app->dispatcher()->verifyDispatch($route);

        // Set the path to the validated cleaned path (/some/path)
        $route->setPath($this->validatePath($route->getPath()));
        // Ensure the request methods are set
        $route->getRequestMethods();

        // If this is a dynamic route
        if ($route->isDynamic()) {
            // Set the dynamic route's properties through the path parser
            $this->setDynamicRoute($route);
        }

        // Set the route in the collection
        self::$collection->addRoute($route);
    }

    /**
     * Helper function to set a GET addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function get(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::GET, RequestMethod::HEAD]);

        $this->addRoute($route);
    }

    /**
     * Helper function to set a POST addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function post(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::POST]);

        $this->addRoute($route);
    }

    /**
     * Helper function to set a PUT addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function put(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::PUT]);

        $this->addRoute($route);
    }

    /**
     * Helper function to set a PATCH addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function patch(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::PATCH]);

        $this->addRoute($route);
    }

    /**
     * Helper function to set a DELETE addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function delete(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::DELETE]);

        $this->addRoute($route);
    }

    /**
     * Helper function to set a HEAD addRoute.
     *
     * @param Route $route The route
     *
     * @throws \Exception
     *
     * @return void
     */
    public function head(Route $route): void
    {
        $route->setRequestMethods([RequestMethod::HEAD]);

        $this->addRoute($route);
    }

    /**
     * Get all routes set by the application.
     *
     * @return array
     */
    public function getRoutes(): array
    {
        return self::$collection->getRoutes();
    }

    /**
     * Get a route by name.
     *
     * @param string $name The name of the route to get
     *
     * @throws \Valkyrja\Routing\Exceptions\InvalidRouteName
     *
     * @return \Valkyrja\Routing\Route
     */
    public function route(string $name): Route
    {
        // If no route was found
        if (! $this->routeIsset($name)) {
            throw new InvalidRouteName($name);
        }

        return self::$collection->namedRoute($name);
    }

    /**
     * Determine whether a route name exists.
     *
     * @param string $name The name of the route
     *
     * @return bool
     */
    public function routeIsset(string $name): bool
    {
        return self::$collection->issetNamedRoute($name);
    }

    /**
     * Get a route url by name.
     *
     * @param string $name     The name of the route to get
     * @param array  $data     [optional] The route data if dynamic
     * @param bool   $absolute [optional] Whether this url should be absolute
     *
     * @throws \Valkyrja\Routing\Exceptions\InvalidRouteName
     *
     * @return string
     */
    public function routeUrl(string $name, array $data = null, bool $absolute = null): string
    {
        // Get the matching route
        $route = $this->route($name);
        // Set the host to use if this is an absolute url
        // or the config is set to always use absolute urls
        // or the route is secure (needs https:// appended)
        $host = $absolute
        || $this->app->config()['routing']['useAbsoluteUrls']
        || $route->isSecure()
            ? $this->routeHost($route)
            : '';
        // Get the path from the generator
        $path = $route->getSegments()
            ? $this->app->pathGenerator()->parse(
                $route->getSegments(),
                $data,
                $route->getParams()
            )
            : $route->getPath();

        return $host . $this->validateRouteUrl($path);
    }

    /**
     * Get a route from a request.
     *
     * @param Request $request The request
     *
     * @throws \InvalidArgumentException
     *
     * @return null|Route
     *      The route if found or null when no static route is
     *      found for the path and method combination specified
     */
    public function requestRoute(Request $request): ? Route
    {
        $requestMethod = $request->getMethod();
        // Decode the request uri
        $requestUri = rawurldecode($request->getPathOnly());

        return $this->matchRoute($requestUri, $requestMethod);
    }

    /**
     * Get a route by path.
     *
     * @param string $path   The path
     * @param string $method [optional] The method type of get
     *
     * @throws \InvalidArgumentException
     *
     * @return null|Route
     *      The route if found or null when no static route is
     *      found for the path and method combination specified
     */
    public function matchRoute(string $path, string $method = null): ? Route
    {
        // Validate the path
        $path   = $this->validatePath($path);
        $method = $method ?? RequestMethod::GET;

        if (null !== $route = $this->matchStaticRoute($path, $method)) {
            return $route;
        }

        return $this->matchDynamicRoute($path, $method);
    }

    /**
     * Determine if a uri is valid.
     *
     * @param string $uri The uri to check
     *
     * @throws \InvalidArgumentException
     *
     * @return bool
     */
    public function isInternalUri(string $uri): bool
    {
        // Replace the scheme if it exists
        $uri = str_replace(['http://', 'https://'], '', $uri);

        // Get the host of the uri
        $host = (string) substr($uri, 0, strpos($uri, '/'));

        // If the host does not match the current request uri's host
        if ($host && $host !== $this->app->request()->getHttpHost()) {
            // Return false immediately
            return false;
        }

        // Get only the path (full string from the first slash to the end
        // of the path)
        $uri = (string) substr($uri, strpos($uri, '/'), \count($uri));

        // Try to match the route
        $route = $this->matchRoute($uri);

        return $route instanceof Route;
    }

    /**
     * Dispatch the route and find a match.
     *
     * @param Request $request The request
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Http\Exceptions\NotFoundHttpException
     *
     * @return \Valkyrja\Http\Response
     */
    public function dispatch(Request $request): Response
    {
        // Check the returned route
        if (null === $route = $this->requestRoute($request)) {
            // If it was null throw a not found exception
            throw new NotFoundHttpException();
        }

        // If the route is secure and the current request is not secure
        if ($route->isSecure() && ! $request->isSecure()) {
            // Throw the redirect to the secure path
            return $this->app->redirect()->secure($request->getPath());
        }

        // Dispatch the route's before request handled middleware
        $middlewareReturn = $this->routeRequestMiddleware($request, $route);

        // If the middleware returned a response
        if ($middlewareReturn instanceof Response) {
            // Return the response
            return $middlewareReturn;
        }

        // Trigger an event for route matched
        $this->app->events()->trigger(RouteMatched::class, [$route, $request]);
        // Set the found route in the service container
        $this->app->container()->singleton(Route::class, $route);

        // Attempt to dispatch the route using any one of the callable options
        $dispatch = $this->app->dispatcher()->dispatchCallable(
            $route,
            $route->getMatches()
        );

        // Get the response from the dispatch
        $response = $this->getResponseFromDispatch($dispatch);

        // Dispatch the route's before request handled middleware and return
        // the response
        $this->routeResponseMiddleware($request, $response, $route);

        return $response;
    }

    /**
     * Setup routes.
     *
     * @param bool $force    [optional] Whether to force setup
     * @param bool $useCache [optional] Whether to use cache
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function setup(bool $force = false, bool $useCache = true): void
    {
        // If route's have already been setup, no need to do it again
        if (self::$setup && ! $force) {
            return;
        }

        self::$setup = true;

        // If the application should use the routes cache file
        if ($useCache && $this->app->config()['routing']['useCache']) {
            $this->setupFromCache();

            // Then return out of setup
            return;
        }

        self::$collection = new RouteCollection();

        // If annotations are enabled and routing should use annotations
        if (
            $this->app->config()['routing']['useAnnotations']
            && $this->app->config()['annotations']['enabled']
        ) {
            // Setup annotated routes
            $this->setupAnnotatedRoutes();

            // If only annotations should be used for routing
            if ($this->app->config()['routing']['useAnnotationsExclusively']) {
                // Return to avoid loading routes file
                return;
            }
        }

        // Include the routes file
        // NOTE: Included if annotations are set or not due to possibility of
        // routes being defined within the controllers as well as within the
        // routes file
        require $this->app->config()['routing']['filePath'];
    }

    /**
     * Get a cacheable representation of the data.
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return array
     */
    public function getCacheable(): array
    {
        $this->setup(true, false);

        return [
            'collection' => base64_encode(serialize(self::$collection)),
        ];
    }

    /**
     * The items provided by this provider.
     *
     * @return array
     */
    public static function provides(): array
    {
        return [
            Router::class,
        ];
    }

    /**
     * Publish the provider.
     *
     * @param Application $app The application
     *
     * @return void
     */
    public static function publish(Application $app): void
    {
        $app->container()->singleton(
            Router::class,
            new static($app)
        );

        $app->router()->setup();
    }

    /**
     * Validate a path.
     *
     * @param string $path The path
     *
     * @return string
     */
    protected function validatePath(string $path): string
    {
        return '/' . trim($path, '/');
    }

    /**
     * Set a dynamic route's properties.
     *
     * @param Route $route The route
     *
     * @return void
     */
    protected function setDynamicRoute(Route $route): void
    {
        // Parse the path
        $parsedRoute = $this->app->pathParser()->parse($route->getPath());

        // Set the properties
        $route->setRegex($parsedRoute['regex']);
        $route->setParams($parsedRoute['params']);
        $route->setSegments($parsedRoute['segments']);
    }

    /**
     * Get a route's host.
     *
     * @param Route $route The route
     *
     * @return string
     */
    protected function routeHost(Route $route): string
    {
        return 'http'
            . ($route->isSecure() ? 's' : '')
            . '://'
            . request()->getHttpHost();
    }

    /**
     * Validate the route url.
     *
     * @param string $path The path
     *
     * @return string
     */
    protected function validateRouteUrl(string $path): string
    {
        // If the last character is not a slash and the config is set to
        // ensure trailing slash
        if (
            $path[-1] !== '/'
            && $this->app->config()['routing']['trailingSlash']
        ) {
            // add a trailing slash
            $path .= '/';
        }

        return $path;
    }

    /**
     * Try to match a static route by path and method.
     *
     * @param string $path   The path
     * @param string $method The method
     *
     * @return null|Route
     *      The route if found or null when no static route is
     *      found for the path and method combination specified
     */
    protected function matchStaticRoute(string $path, string $method): ? Route
    {
        $route = null;

        // Let's check if the route is set in the static routes
        if (self::$collection->issetStaticRoute($method, $path)) {
            $route = $this->getMatchedStaticRoute($path, $method);
        }

        if (null !== $route && $this->isValidMethod($route, $method)) {
            return $route;
        }

        return null;
    }

    /**
     * Try to match a dynamic route by path and method.
     *
     * @param string $path   The path
     * @param string $method The method
     *
     * @return null|Route
     *      The route if found or null when no static route is
     *      found for the path and method combination specified
     */
    protected function matchDynamicRoute(string $path, string $method): ? Route
    {
        // The route to return (null by default)
        $route = null;
        // The dynamic routes
        $dynamicRoutes = self::$collection->getDynamicRoutes($method);

        // Attempt to find a match using dynamic routes that are set
        foreach ($dynamicRoutes as $regex => $dynamicRoute) {
            // If the preg match is successful, we've found our route!
            /* @var array $matches */
            if (preg_match($regex, $path, $matches)) {
                $route = $this->getMatchedDynamicRoute($dynamicRoute, $matches, $method);

                break;
            }
        }

        // If the route was found and the method is valid
        if (null !== $route && $this->isValidMethod($route, $method)) {
            // Return the route
            return $route;
        }

        return null;
    }

    /**
     * @param Route  $route  The route
     * @param string $method The method
     *
     * @return bool
     */
    protected function isValidMethod(Route $route, string $method): bool
    {
        return \in_array($method, $route->getRequestMethods(), true);
    }

    /**
     * Get a matched static route.
     *
     * @param string $path   The path
     * @param string $method The method
     *
     * @return \Valkyrja\Routing\Route
     */
    protected function getMatchedStaticRoute(string $path, string $method): Route
    {
        return clone self::$collection->staticRoute($method, $path);
    }

    /**
     * Get a matched dynamic route.
     *
     * @param string $path    The path
     * @param array  $matches The regex matches
     * @param string $method  The request method
     *
     * @return \Valkyrja\Routing\Route
     */
    protected function getMatchedDynamicRoute(string $path, array $matches, string $method): Route
    {
        // Clone the route to avoid changing the one set in the master array
        $dynamicRoute = clone self::$collection->dynamicRoute($method, $path);
        // The first match is the path itself
        unset($matches[0]);

        // Iterate through the matches
        foreach ($matches as $key => $match) {
            // If there is no match (middle of regex optional group)
            if (! $match) {
                // Set the value to null so the controller's action
                // can use the default it sets
                $matches[$key] = null;
            }
        }

        // Set the matches
        $dynamicRoute->setMatches($matches);

        return $dynamicRoute;
    }

    /**
     * Dispatch a route's before request handled middleware.
     *
     * @param Request $request The request
     * @param Route   $route   The route
     *
     * @return mixed
     */
    protected function routeRequestMiddleware(Request $request, Route $route)
    {
        // If the route has no middleware
        if (null === $route->getMiddleware()) {
            // Return the request passed through
            return $request;
        }

        return $this->app->kernel()->requestMiddleware(
            $request,
            $route->getMiddleware()
        );
    }

    /**
     * Dispatch a route's after request handled middleware.
     *
     * @param Request  $request  The request
     * @param Response $response The response
     * @param Route    $route    The route
     *
     * @return void
     */
    protected function routeResponseMiddleware(Request $request, Response $response, Route $route): void
    {
        // If the route has no middleware
        if (null === $route->getMiddleware()) {
            // Return the response passed through
            return;
        }

        $this->app->kernel()->responseMiddleware(
            $request,
            $response,
            $route->getMiddleware()
        );
    }

    /**
     * Get a response from a dispatch.
     *
     * @param mixed $dispatch The dispatch
     *
     * @return \Valkyrja\Http\Response
     */
    protected function getResponseFromDispatch($dispatch): Response
    {
        // If the dispatch failed, 404
        if (! $dispatch) {
            $this->app->abort();
        }

        // If the dispatch is a Response then simply return it
        if ($dispatch instanceof Response) {
            return $dispatch;
        }

        // If the dispatch is a View, render it then wrap it in a new response
        // and return it
        if ($dispatch instanceof View) {
            return $this->app->response($dispatch->render());
        }

        // Otherwise its a string so wrap it in a new response and return it
        return $this->app->response((string) $dispatch);
    }

    /**
     * Setup the router from cache.
     *
     * @return void
     */
    protected function setupFromCache(): void
    {
        // Set the application routes with said file
        $cache = $this->app->config()['cache']['routing']
            ?? require $this->app->config()['routing']['cacheFilePath'];

        self::$collection = unserialize(
            base64_decode($cache['collection'], true),
            [
                'allowed_classes' => [
                    RouteCollection::class,
                    Route::class,
                ],
            ]
        );
    }

    /**
     * Setup annotated routes.
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    protected function setupAnnotatedRoutes(): void
    {
        /** @var RouteAnnotations $routeAnnotations */
        $routeAnnotations = $this->app->container()->getSingleton(
            RouteAnnotations::class
        );

        // Get all the annotated routes from the list of controllers
        $routes = $routeAnnotations->getRoutes(
            ...$this->app->config()['routing']['controllers']
        );

        // Iterate through the routes
        foreach ($routes as $route) {
            // Set the route
            $this->addRoute($route);
        }
    }
}


<?php
namespace cymapgt\core\utility\notifier;

use cymapgt\Exception\NotifierEmailException;
use cymapgt\core\utility\notifier\abstractclass\NotifierInterface;

/**
 * class NotifierEmailSwiftMailerService
 *
 * This interface implements the Notifier interface to provide a standard
 * method of configuring and using SwiftMailer library from SensioLabs
 *
 * PHP versions 7.0+
 *
 * @category    utility
 * @package     notifier
 * @author      <cogana@gmail.com>
 * @copyright   2017 CYMAPGT
 */

class NotifierEmailSwiftMailerService implements NotifierInterface
{
    //the notifier type e.g. Email, SMS
    protected $notifierType  = null;

    //array of configurations required
    protected $configuration = array(
                                    'EMAIL_CLIENT'          => 'SwiftMailer',
                                    'USERNAME'              => '',
                                    'PASSWORD'              => '',
                                    'HOST'                  => '',
                                    'IS_SMTP'               => true,
                                    'SMTP_DEBUG'            => 2,
                                    'SMTP_AUTH'             => true,
                                    'PORT'                  => 587,
                                    'SMTP_SECURE'           => 'tls',
                                    'LINE_ENDING'           => '\n',
                                    'CHARSET'               => 'utf-8',
                                    'RETURN_PATH'           => '',
                                    'EMAIL_FROM'            => '',
                                    'EMAIL_FROM_NAME'       => '',
                                    'WORD_WRAP'             => 80,
                                    'IS_HTML'               => true,
                                    "USE_ANTIFLOOD"         => false,
                                    "ANTIFLOOD_FREQUENCY_MAILS"   => null,
                                    "ANTIFLOOD_FREQUENCY_SECONDS"   => null,
                                    "USE_THROTTLER"         => false,
                                    "THROTTLER_MAILS_PER_MINUTE" => null,
                                    "THROTTLER_BYTES_PER_MINUTE" => null
                               );

    //template settings e.g. if using php, smarty or twig
    protected $templatePath = '';
    protected $templateName = '';

    //init array, for additional data needed to do the job, after configuration
    protected $initArr = array();

    //messages array, where message payloads are stored before sending
    protected $messages = array();

    protected $mailHandler = null;

    /**
     * function __construct() - Constructor method
     */
    public function __construct(\Swift_SmtpTransport $swiftMailerTransport){
        $this->mailHandler = new \Swift_Mailer($swiftMailerTransport);
    }

    /**
     * function setNotifierType() - Set the notifier type as per NotifierType data store
     * @param   string $type  - The notification type
     *
     * @return  void
     */
    public function setNotifierType($type){
        $this->notifierType = $type;
    }

    /**
     * function getNotifierType() - Get the notifier type as per NotifierType data store
     *
     * @return  string
     */
    public function getNotifierType(){
        return $this->notifierType;
    }

    /**
     * function setConfiguration() - Set all required configurations, passed in as an array
     * @param   array $configuration  - Array of various required configuration options
     *
     * @return  void
     */
    public function setConfiguration($configuration) {
        $emailRequiredConfigurations = $this->configuration;
        $configDiff = array_diff($emailRequiredConfigurations, $configuration);

        //validate that all required email settings are present
        if (
            is_array($configDiff
            && count($configDiff))
        ) {
            throw new NotifierEmailException('The configuration array does not contain all required Email configurations');
        }

        //set the configuration provided
        $this->configuration = $configuration;
    }

    /**
     * function getConfiguration() - Return the set configuration option array
     *
     * @return  array
     */
    public function getConfiguration() {
        return $this->configuration;
    }

    /**
     * function setTemplatePath() - Set the path to template files to use for the notifier service
     *
     * @param   string $path     - Path to the notification template file
     * @param   string $name     - Name of the notification template file
     *
     * @return  void
     */
    public function setTemplatePath($path, $name) {
        $this->templateName = $name;
        $this->templatePath = $path;
    }

    /**
     * function getTemplatePath() - Return the set template path
     *
     * @return  string
     */
    public function getTemplatePath(){
        return $this->templatePath;
    }

    /**
     * function getTemplateName() - Return the set template name
     *
     * @return  string
     */
    public function getTemplateName() {
        return $this->templateName;
    }

     /**
     * function initialize() - Initialize the object to workable state, should be called after
     *                         setting the configurations
     * @param   array $initArr     - Any additional initialization parameters
     *
     * @return  void
     */
    public function initialize($initArr) {
        $this->initArr = $initArr;
    }

    /**
     * function setMessage() - Input the actual message payload
     * @param   array $message - Array containing message payload e.g head,body,subject
     *
     * @return  void
     */
    public function setMessage($message) {
        $this->messages[] = $message;
    }

     /**
     * function getMessage() - Return all message payloads
     *
     * @return  array
     */
    public function getMessage() {
        return $this->messages;
    }

    /**
     * function getMessageById() - Return a message by its id
     *
     * @param mixed  $messageId
     *
     * @return array
     */
    public function getMessageById($messageId) {
        if (isset($this->messages[$messageId])) {
            return $this->messages[$messageId];
        } else {
            return false;
        }
    }

    /**
     * function resetMessageById() - Unset the message from $messages array
     *                               based on its ID
     * @param   mixed $messageId
     *
     * @return  boolean
     */
    public function resetMessageById($messageId) {
        if (isset($this->messages[$messageId])) {
            unset($this->messages[$messageId]);
            return true;
        } else {
            return false;
        }
    }

    /**
     * function resetMessagesAll() - Unset all messages in $messages array
     *
     * @return  boolean
     */
    public function resetMessagesAll() {
        foreach ($this->messages as $key => $message) {
            unset($this->messages[$key]);
        }
    }

    /**
     * function resetMessageById() - Send message where the payload has only
     *                               one message in its listing
     * @param   mixed $messageId
     *
     * @return  boolean
     */
    public function sendMessageOne($sendSettings) {
        $this->_bootstrapMailHandler();

        //get the key of the first message
        reset($this->messages);
        $key = key($this->messages);
        $this->_bootstrapMailMessage($key);

        //TODO: Chain exceptions here
        //try {
        $messageObj = $this->messages[$key]['messageObj'];
        $this->mailHandler->send($messageObj);
        //} catch (phpmailerException $ex) {
            //echo $ex->errorMessage();
        //}

        //pop the message stack
        $msgStack = $this->messages;
        array_shift($msgStack);
        $this->messages = $msgStack;
    }

    /**
     * function sendMessageById() - Set message using its id
     *
     * @param   array $sendSettings - Additional send settings
     * @param   int   $messageId    - The message Id
     *
     * @return  boolean
     */
    public function sendMessageById($sendSettings, $messageId){
        $messageId = (int) $messageId;

        if (!(isset($this->messages[$messageId]))) {
            throw new NotifierEmailException('Trying to send Message by Id, for an Id that is not set');
        }

        $this->_bootstrapMailHandler();
        $this->_bootstrapMailMessage($messageId);

        //phpmailer exception used
        try {
            $this->mailHandler->Send();
        } catch (phpmailerException $ex) {
            echo $ex->errorMessage();
        }
    }

    /**
     * function sendMessagesAll() - Send all messages in the messages
     *                              array
     *
     * @param   array $sendSettings - Additional send settings
     *
     * @return  boolean
     */
    public function sendMessagesAll($sendSettings){
        while (count($this->messages) > 0) {
            $this->sendMessageOne($sendSettings);
        }
    }

    /**
     * function _bootstrapMailHandler() - Prepare the mailHandler by setting the configurations
     *
     * @return void
     *
     * @access private
     */
    private function _bootstrapMailHandler(){
        //confirm that mailHandler is an instance of PHPMailer
        if(!isset($this->mailHandler)
          && !($this->mailHandler instanceof \Swift_SmtpTransport)
        ) {
            throw new NotifierEmailException("The mail handler is not an instance of Swift_SmtpTransport");
        }
    }

    /**
     * function _bootstrapMailMessage() - Bootstrap a message for sending, clearing previous settings
     *
     * @param   $messageId  - The message Id
     *
     * @return void
     */
    private function _bootstrapMailMessage($messageId){
       //validate that the message exists
        if(!isset($this->messages[($messageId)])){
            throw new NotifierEmailException('Illegal attempt to bootstrap a message that doesnt exist');
        }

        $swiftMessage = new \Swift_Message();

        $swiftMessage->setFrom($this->configuration['EMAIL_FROM']);

        $arrMsgTo = array();

        //add receipients on main address
        foreach($this->messages[($messageId)]['TO'] as $emailUser) {
            if (isset($emailUser['name'])) {
                $arrMsgTo[($emailUser['email'])] = $emailUser['name'];
            } else {
                $arrMsgTo[] = $emailUser['email'];
            }
        }

        //embed the recipients to the Swift Message
        $swiftMessage->setTo($arrMsgTo);
        $arrMsgCC = array();

        if (array_key_exists('CC', $this->messages[($messageId)])) {
            //add recepients on CC
            foreach($this->messages[($messageId)]["CC"] as $emailUser){
                if (isset($emailUser['name'])) {
                    $arrMsgCC[($emailUser['email'])] = $emailUser['name'];
                } else {
                    $arrMsgCC[] = $emailUser['email'];
                }
            }

            if (count($arrMsgCC)) {
                $swiftMessage->setCc($arrMsgCC);
            }
        }

        //add recepients on bCC
        if (array_key_exists('BCC', $this->messages[($messageId)])) {
            //add recepients on BCC
            foreach($this->messages[($messageId)]["BCC"] as $emailUser){
                if (isset($emailUser['name'])) {
                    $arrMsgBCC[($emailUser['email'])] = $emailUser['name'];
                } else {
                    $arrMsgBCC[] = $emailUser['email'];
                }
            }

            if (count($arrMsgBCC)) {
                $swiftMessage->setBcc($arrMsgBCC);
            }
        }

        //set ReturnPath
        $returnPath = $this->configuration['RETURN_PATH'];
        $swiftMessage->setReturnPath($returnPath);

        //Handle Attachments
        foreach($this->messages[($messageId)]['ATTACHMENTS'] as $attachmentFilename => $attachmentFilepath) {
            if (!(is_int($attachmentFilename))) {
                $swiftMessage->attach(\Swift_Attachment::fromPath($attachmentFilepath)->setFilename($attachmentFilename));
            } else {
                $swiftMessage->attach(\Swift_Attachment::fromPath($attachmentFilepath));
            }

        }

        //add the subject
        $swiftMessage->setSubject($this->messages[($messageId)]["SUBJECT"]);

        //add the email altbody
        $swiftMessage->addPart($this->messages[($messageId)]["ALT_BODY"]);

        //set the message body (as HTML)
        $swiftMessage->setBody($this->messages[($messageId)]["MSG_HTML"], 'text/html');

        //Check for Anti-Flood Setting
        if (array_key_exists('USE_ANTIFLOOD', $this->configuration)) {
            $antiFloodConfig = $this->configuration['USE_ANTIFLOOD'];
        } else {
            $antiFloodConfig = false;
        }

        //Set AntiFlood Settings
        if ($antiFloodConfig) {
            $antiFloodFrequencyMails   = $this->configuration['ANTIFLOOD_FREQUENCY_MAILS'];
            $antiFloodFrequencySeconds = $this->configuration['ANTIFLOOD_FREQUENCY_SECONDS'];
            $this->mailHandler->registerPlugin(
                new \Swift_Plugins_AntiFloodPlugin($antiFloodFrequencyMails, $antiFloodFrequencySeconds)
            );
        }

        //Check for Throttler Setting
        if (array_key_exists('USE_THROTTLER', $this->configuration)) {
            $throttlerConfig = $this->configuration['USE_THROTTLER'];
        } else {
            $throttlerConfig = false;
        }

        //Set AntiFlood Settings
        if ($throttlerConfig) {
            $throttlerMailsPerMinute = $this->configuration['THROTTLER_MAILS_PER_MINUTE'];
            $throttlerBytesPerMinute = $this->configuration['THROTTLER_BYTES_PER_MINUTE'];
            $this->mailHandler->registerPlugin(
                new \Swift_Plugins_ThrottlerPlugin(
                    $throttlerMailsPerMinute,
                    \Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE
                )
            );
            $this->mailHandler->registerPlugin(
                new \Swift_Plugins_ThrottlerPlugin(
                    $throttlerBytesPerMinute,
                    \Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE
                )
            );
        }

        $this->messages[($messageId)]['messageObj'] = $swiftMessage;

        //set the bootstrapped property to true
        $this->messages[($messageId)]["IS_BOOSTRAPPED"] = true;
    }
}


<?php

namespace Reaction\Widgets\Bootstrap;

use Reaction;
use Reaction\Helpers\Html;
use Reaction\Helpers\ArrayHelper;

/**
 * Modal renders a modal window that can be toggled by clicking on a button.
 *
 * The following example will show the content enclosed between the [[begin()]]
 * and [[end()]] calls within the modal window:
 *
 * ~~~php
 * Modal::begin([
 *     'header' => '
Hello world
',
 *     'toggleButton' => ['label' => 'click me'],
 * ]);
 *
 * echo 'Say hello...';
 *
 * Modal::end();
 * ~~~
 */
class Modal extends Widget
{
    const SIZE_LARGE = "modal-lg";
    const SIZE_SMALL = "modal-sm";
    const SIZE_DEFAULT = "";

    /**
     * @var string the header content in the modal window.
     */
    public $header;
    /**
     * @var array additional header options
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $headerOptions = [];
    /**
     * @var array body options
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $bodyOptions = ['class' => 'modal-body'];
    /**
     * @var string the footer content in the modal window.
     */
    public $footer;
    /**
     * @var string additional footer options
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $footerOptions;
    /**
     * @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default.
     */
    public $size;
    /**
     * @var bool vertically centered modal
     */
    public $centered = false;
    /**
     * @var array|false the options for rendering the close button tag.
     * The close button is displayed in the header of the modal window. Clicking
     * on the button will hide the modal window. If this is false, no close button will be rendered.
     *
     * The following special options are supported:
     *
     * - tag: string, the tag name of the button. Defaults to 'button'.
     * - label: string, the label of the button. Defaults to '×'.
     *
     * The rest of the options will be rendered as the HTML attributes of the button tag.
     * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
     * for the supported HTML attributes.
     */
    public $closeButton = [];
    /**
     * @var array the options for rendering the toggle button tag.
     * The toggle button is used to toggle the visibility of the modal window.
     * If this property is false, no toggle button will be rendered.
     *
     * The following special options are supported:
     *
     * - tag: string, the tag name of the button. Defaults to 'button'.
     * - label: string, the label of the button. Defaults to 'Show'.
     *
     * The rest of the options will be rendered as the HTML attributes of the button tag.
     * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals)
     * for the supported HTML attributes.
     */
    public $toggleButton = false;

    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();

        $this->initOptions();
        $modalDialogOptions = ['class' => 'modal-dialog ' . $this->size];
        $modalDialogOptions['class'] .= ($this->centered ? ' modal-dialog-centered' : '');

        echo $this->renderToggleButton() . "\n";
        echo $this->htmlHlp->beginTag('div', $this->options) . "\n";
        echo $this->htmlHlp->beginTag('div', $modalDialogOptions) . "\n";
        echo $this->htmlHlp->beginTag('div', ['class' => 'modal-content']) . "\n";
        echo $this->renderHeader() . "\n";
        echo $this->renderBodyBegin() . "\n";
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        echo "\n" . $this->renderBodyEnd();
        echo "\n" . $this->renderFooter();
        echo "\n" . Html::endTag('div'); // modal-content
        echo "\n" . Html::endTag('div'); // modal-dialog
        echo "\n" . Html::endTag('div');

        $this->registerPlugin('modal');
    }

    /**
     * Renders the header HTML markup of the modal
     * @return string the rendering result
     */
    protected function renderHeader()
    {
        $button = $this->renderCloseButton();
        if ($button !== null) {
            $this->header = $this->header . "\n" . $button;
        }
        if ($this->header !== null) {
            Html::addCssClass($this->headerOptions, ['widget' => 'modal-header']);
            return $this->htmlHlp->tag('div', "\n" . $this->header . "\n", $this->headerOptions);
        } else {
            return null;
        }
    }

    /**
     * Renders the opening tag of the modal body.
     * @return string the rendering result
     */
    protected function renderBodyBegin()
    {
        return $this->htmlHlp->beginTag('div', $this->bodyOptions);
    }

    /**
     * Renders the closing tag of the modal body.
     * @return string the rendering result
     */
    protected function renderBodyEnd()
    {
        return Html::endTag('div');
    }

    /**
     * Renders the HTML markup for the footer of the modal
     * @return string the rendering result
     */
    protected function renderFooter()
    {
        if ($this->footer !== null) {
            Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']);
            return $this->htmlHlp->tag('div', "\n" . $this->footer . "\n", $this->footerOptions);
        } else {
            return null;
        }
    }

    /**
     * Renders the toggle button.
     * @return string the rendering result
     */
    protected function renderToggleButton()
    {
        if (($toggleButton = $this->toggleButton) !== false) {
            $tag = ArrayHelper::remove($toggleButton, 'tag', 'button');
            $label = ArrayHelper::remove($toggleButton, 'label', 'Show');
            if ($tag === 'button' && !isset($toggleButton['type'])) {
                $toggleButton['type'] = 'button';
            }

            return $this->htmlHlp->tag($tag, $label, $toggleButton);
        } else {
            return null;
        }
    }

    /**
     * Renders the close button.
     * @return string the rendering result
     */
    protected function renderCloseButton()
    {
        if (($closeButton = $this->closeButton) !== false) {
            $tag = ArrayHelper::remove($closeButton, 'tag', 'button');
            $label = ArrayHelper::remove($closeButton, 'label', '×');
            $label = $this->htmlHlp->tag('span', $label, ['aria-hidden' => 'true']);
            if ($tag === 'button' && !isset($closeButton['type'])) {
                $closeButton['type'] = 'button';
            }

            return $this->htmlHlp->tag($tag, $label, $closeButton);
        } else {
            return null;
        }
    }

    /**
     * Initializes the widget options.
     * This method sets the default values for various options.
     */
    protected function initOptions()
    {
        $this->options = array_merge([
            'class' => 'fade',
            'role' => 'dialog',
            'tabindex' => -1,
        ], $this->options);
        Html::addCssClass($this->options, ['widget' => 'modal']);

        if ($this->clientOptions !== false) {
            $this->clientOptions = array_merge(['show' => false], $this->clientOptions);
        }

        if ($this->closeButton !== false) {
            $this->closeButton = array_merge([
                'data-dismiss' => 'modal',
                'aria-hidden' => 'true',
                'class' => 'close',
            ], $this->closeButton);
        }

        if ($this->toggleButton !== false) {
            $this->toggleButton = array_merge([
                'data-toggle' => 'modal',
            ], $this->toggleButton);
            if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
                $this->toggleButton['data-target'] = '#' . $this->options['id'];
            }
        }
    }
}


<?php
namespace Reaction\I18n;

use Reaction;
use Reaction\Promise\ExtendedPromiseInterface;
use function Reaction\Promise\reject;
use function Reaction\Promise\resolve;

/**
 * GettextMessageSource represents a message source that is based on GNU Gettext.
 *
 * Each GettextMessageSource instance represents the message translations
 * for a single domain. And each message category represents a message context
 * in Gettext. Translated messages are stored as either a MO or PO file,
 * depending on the [[useMoFile]] property value.
 *
 * All translations are saved under the [[basePath]] directory.
 *
 * Translations in one language are kept as MO or PO files under an individual
 * subdirectory whose name is the language ID. The file name is specified via
 * [[catalog]] property, which defaults to 'messages'.
 */
class GettextMessageSource extends MessageSource
{
    const MO_FILE_EXT = '.mo';
    const PO_FILE_EXT = '.po';

    /**
     * @var string
     */
    public $basePath = '@app/Messages';
    /**
     * @var string
     */
    public $catalog = 'messages';
    /**
     * @var bool
     */
    public $useMoFile = true;
    /**
     * @var bool
     */
    public $useBigEndian = false;
    /**
     * @var array|null All used categories
     */
    protected $_categories;
    /**
     * @var GettextFile[]
     */
    protected $_gettextFiles = [];

    /**
     * Loads the message translation for the specified $language and $category.
     * If translation for specific locale code such as `en-US` isn't found it
     * tries more generic `en`. When both are present, the `en-US` messages will be merged
     * over `en`. See [[loadFallbackMessages]] for details.
     * If the $language is less specific than [[sourceLanguage]], the method will try to
     * load the messages for [[sourceLanguage]]. For example: [[sourceLanguage]] is `en-GB`,
     * $language is `en`. The method will load the messages for `en` and merge them over `en-GB`.
     *
     * @param string $category the message category
     * @param string $language the target language
     * @return ExtendedPromiseInterface with array the loaded messages. The keys are original messages, and the values are translated messages.
     * @see loadFallbackMessages
     * @see sourceLanguage
     */
    protected function loadMessages($category, $language)
    {
        $messageFile = $this->getMessageFilePath($language);
        return $this->loadMessagesFromFile($messageFile, $category)
            ->otherwise(function() { return null; })
            ->then(function($messages) use ($category, $language, $messageFile) {
                $fallbackLanguage = substr($language, 0, 2);
                $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2);

                if ($fallbackLanguage !== $language) {
                    return $this->loadFallbackMessages($category, $fallbackLanguage, $messages, $messageFile);
                } elseif ($language === $fallbackSourceLanguage) {
                    return $this->loadFallbackMessages($category, $this->sourceLanguage, $messages, $messageFile);
                } else {
                    if ($messages === null) {
                        Reaction::warning("The message file for category '$category' does not exist: $messageFile");
                    }
                    return (array)$messages;
                }
            });
    }

    /**
     * The method is normally called by [[loadMessages]] to load the fallback messages for the language.
     * Method tries to load the $category messages for the $fallbackLanguage and adds them to the $messages array.
     *
     * @param string $category the message category
     * @param string $fallbackLanguage the target fallback language
     * @param array $messages the array of previously loaded translation messages.
     * The keys are original messages, and the values are the translated messages.
     * @param string $originalMessageFile the path to the file with messages. Used to log an error message
     * in case when no translations were found.
     * @return ExtendedPromiseInterface with array the loaded messages. The keys are original messages, and the values are the translated messages.
     */
    protected function loadFallbackMessages($category, $fallbackLanguage, $messages, $originalMessageFile)
    {
        $fallbackMessageFile = $this->getMessageFilePath($fallbackLanguage);
        return $this->loadMessagesFromFile($fallbackMessageFile, $category)
            ->otherwise(function() { return null; })
            ->then(function($fallbackMessages) use ($category, $fallbackLanguage, $messages, $originalMessageFile, $fallbackMessageFile) {
                if (
                    $messages === null && $fallbackMessages === null
                    && $fallbackLanguage !== $this->sourceLanguage
                    && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2)
                ) {
                    Reaction::warning("The message file for category '$category' does not exist: $originalMessageFile "
                        . "Fallback file does not exist as well: $fallbackMessageFile");
                } elseif (empty($messages)) {
                    return $fallbackMessages;
                } elseif (!empty($fallbackMessages)) {
                    foreach ($fallbackMessages as $key => $value) {
                        if (!empty($value) && empty($messages[$key])) {
                            $messages[$key] = $fallbackMessages[$key];
                        }
                    }
                }

                return (array)$messages;
            });
    }

    /**
     * Returns message file path for the specified language and category.
     *
     * @param string $language the target language
     * @return string path to message file
     */
    protected function getMessageFilePath($language)
    {
        $messageFile = Reaction::$app->getAlias($this->basePath) . '/' . $language . '/' . $this->catalog;
        if ($this->useMoFile) {
            $messageFile .= self::MO_FILE_EXT;
        } else {
            $messageFile .= self::PO_FILE_EXT;
        }

        return $messageFile;
    }

    /**
     * Loads the message translation for the specified language and category or returns null if file doesn't exist.
     *
     * @param string $messageFile path to message file
     * @param string $category the message category
     * @return ExtendedPromiseInterface with array array of messages or null if file not found
     */
    protected function loadMessagesFromFile($messageFile, $category)
    {
        $gettextFile = $this->getGettextFile($messageFile);
        if ($gettextFile === null) {
            return reject(new Reaction\Exceptions\Error("Message file '{$messageFile}' not found"));
        }
        $messages = $gettextFile->load($category);
        if (!is_array($messages)) {
            $messages = [];
        }

        return resolve($messages);
    }

    /**
     * Get gettext file class
     * @param string $messageFile
     * @return GettextFile|null
     */
    protected function getGettextFile($messageFile)
    {
        if (isset($this->_gettextFiles[$messageFile])) {
            return $this->_gettextFiles[$messageFile];
        }
        if (!is_file($messageFile)) {
            return null;
        }
        if ($this->useMoFile) {
            $gettextFile = new GettextMoFile(['filePath' => $messageFile, 'useBigEndian' => $this->useBigEndian]);
        } else {
            $gettextFile = new GettextPoFile(['filePath' => $messageFile]);
        }
        return $this->_gettextFiles[$messageFile] = $gettextFile;
    }

    /**
     * Find all categories used in message source
     * @return ExtendedPromiseInterface
     */
    protected function findAllCategories()
    {
        if (isset($this->_categories)) {
            return resolve($this->_categories);
        }
        $languages = Reaction::$app->getI18n()->languages;
        $messageFiles = [];
        foreach ($languages as $language) {
            $messageFiles[] = $this->getMessageFilePath($language);
        }

        //Use resolve()->then() to convert PromiseInterface to ExtendedPromiseInterface
        return resolve(true)
            ->then(function() use ($messageFiles) {
                $categories = [];
                foreach ($messageFiles as $messageFile) {
                    $gettext = $this->getGettextFile($messageFile);
                    $categories = Reaction\Helpers\ArrayHelper::merge($categories, $gettext->getCategories());
                }
                $this->_categories = array_unique($categories);
                sort($this->_categories);
                return $this->_categories;
            });
    }
}


<?php
namespace FzyCommon\Service\Search\Base;

use FzyCommon\Service\Search\Base;
use FzyCommon\Util\Params;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use FzyCommon\Exception\Search\NotFound;

/**
 * Class DQL
 * @package FzyCommon\Service\Search\Base
 * Base class for DQL based searches
 */
abstract class DQL extends Base
{
    /**
     * This function is used by the class to get
     * the entity's repository to be returned
     * @return mixed
     */
    abstract protected function getRepository();

    /**
     * Alias for the primary repository in the DQL statement
     * @return string
     */
    abstract public function getRepositoryAlias();

    /**
     * Map of what tables have been joined in this query already
     * @var array
     */
    protected $joinMap = array();

    /**
     * This is invoked on every result of the singular/query
     * search for uniformly transform the result set
     * @param $entity
     * @param  Param              $params
     * @param  array|\Traversable $results
     * @param  bool               $asEntity - Keep entity as entity
     * @return array
     */
    protected function process($entity, Params $params, $results, $asEntity = false)
    {
        return $asEntity ? $entity : $entity->flatten();
    }

    /**
     * Performs a query base don the params for a collection
     * of objects to be returned. This function ought to
     * set the $limit, $offset, and $total values
     * @param  Params $params
     * @return array
     */
    protected function querySearch(Params $params)
    {
        $qb = $this->getCustomizedQueryBuilder($params);

        return $this->getQBResult($params, $this->queryHook($params, $qb));
    }

    /**
     * @param Params $params
     *
     * @return QueryBuilder
     */
    protected function getCustomizedQueryBuilder(Params $params)
    {
        $qb = $this->em()->createQueryBuilder();

        $this->setupQueryBuilder($params, $qb);

        $this->addFilters($params, $qb); // add filters to the query
        $this->addOrdering($params, $qb); // add ordering constraints
        $this->addOffset($params, $qb); // add offset constraints
        $this->addLimit($params, $qb); // add limit constraint

        return $qb;
    }

    /**
     * Returns an array or other iterable object containing results
     * @param  Param           $params
     * @param  Query           $query
     * @return Paginator|array
     */
    protected function getQBResult(Params $params, Query $query)
    {
        $paginated = new Paginator($query);
        $paginated->setUseOutputWalkers(false);
        $this->setTotal($paginated->count());

        return $paginated;
    }

    /**
     * Hook to allow subclasses to modify the final query
     * being passed into the pagination object
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return Query
     */
    protected function queryHook(Params $params, QueryBuilder $qb)
    {
        return $qb->getQuery();
    }

    /**
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function setupQueryBuilder(Params $params, QueryBuilder $qb)
    {
        $this->addSelect($params, $qb);
        $this->addFrom($params, $qb);

        return $this;
    }

    /**
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addSelect(Params $params, QueryBuilder $qb)
    {
        $qb->select($this->getRepositoryAlias());

        return $this;
    }

    /**
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addFrom(Params $params, QueryBuilder $qb)
    {
        $qb->from($this->getRepository(), $this->getRepositoryAlias());

        return $this;
    }

    /**
     * Add where clauses to the query
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addFilters(Params $params, QueryBuilder $qb)
    {
        $this->filterByIdentifier($params, $qb)
            ->filterBySearch($params, $qb);

        return $this;
    }

    /**
     * Filters by specified id
     * @param Params       $params
     * @param QueryBuilder $qb
     *
     * @return DQL
     */
    protected function filterByIdentifier(Params $params, QueryBuilder $qb)
    {
        return $this->quickParamFilter($params, $qb, $this->getIdParam(), 'id');
    }

    /**
     * Default search hook for datatables
     *
     * @param Params       $params
     * @param QueryBuilder $qb
     *
     * @return $this
     */
    protected function filterBySearch(Params $params, QueryBuilder $qb)
    {
        if ($query = $params->getWrapped('search')->get('value')) {
            $this->searchFilter($params, $qb, $query);
        }

        return $this;
    }

    /**
     * This function is passed the datatables search query value
     * and should appropriately filter the query builder object
     * based on what makes sense for this entity.
     *
     * @param Params       $params
     * @param QueryBuilder $qb
     * @param $search
     *
     * @return $this
     */
    protected function searchFilter(Params $params, QueryBuilder $qb, $search)
    {
        $qb->andWhere($this->alias('id').' LIKE :search')->setParameter('search', $search);

        return $this;
    }

    /**
     * If there is some ordering that needs to be applied, do it here
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addOrdering(Params $params, QueryBuilder $qb)
    {
        $orderDir = "ASC";
        if ($params->has('orderDir') && (strtoupper($params->get('orderDir')) == "DESC")) {
            $orderDir = "DESC";
        }
        if ($params->has('orderBy') && $params->get('orderBy') != "") {
            $qb->orderBy($this->alias($params->get('orderBy')), $orderDir);
        }

        return $this;
    }

    /**
     * Set the offset for the query results
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addOffset(Params $params, QueryBuilder $qb)
    {
        if ($this->getOffset() && $this->getOffset() > 0) {
            $qb->setFirstResult($this->getOffset());
        }

        return $this;
    }

    /**
     * Set the limit for the query results
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function addLimit(Params $params, QueryBuilder $qb)
    {
        if ($this->getLimit() && $this->getLimit() > 0) {
            $qb->setMaxResults($this->getLimit());
        }

        return $this;
    }

    /**
     * Finds an element in this domain which has the specified ID
     *
     * @param $id
     * @throws NotFound
     * @return \FzyCommon\Entity\BaseInterface
     */
    public function find($id)
    {
        $params = Params::create(array($this->getIdParam() => $id, 'limit' => 1));
        $qb = $this->getCustomizedQueryBuilder($params);
        $results = $this->getQBResult($params, $this->queryHook($params, $qb));
        if ($results->count() != 1) {
            throw new NotFound("Unable to locate entity ".$this->getRepository()." with id ".$id);
        }
        /* @var $results \Doctrine\ORM\Tools\Pagination\Paginator */
        /**
         * Note: this is a paginator object so the only way to access result elements is by iterating
         */
        foreach ($results as $result) {
            // return the first (and only) result
            return $result;
        }
    }

    /**
     * Convenience method to add an AND WHERE clause in a common format.
     * If $queryParameterName is unspecified, $requestParameterName is used for both
     *
     * @param  Param        $params
     * @param  QueryBuilder $qb
     * @param $requestParameterName
     * @param  null         $queryParameterName
     * @return $this
     */
    protected function quickParamFilter(Params $params, QueryBuilder $qb, $requestParameterName, $queryParameterName = null)
    {
        if ($queryParameterName === null) {
            $queryParameterName = $requestParameterName;
        }
        if ($params->has($requestParameterName)) {
            $qb->andWhere($this->alias($queryParameterName).' = :'.$queryParameterName)->setParameter($queryParameterName, $params->get($requestParameterName));
        }

        return $this;
    }

    /**
     * Convenience function so we don't have dots running around everywhere
     * @param $propertyName
     * @return string
     */
    public function alias($propertyName)
    {
        return $this->getRepositoryAlias().'.'.$propertyName;
    }

    /**
     * Resets the state of this update service
     * @return $this
     */
    public function reset()
    {
        $this->joinMap = array();

        return parent::reset();
    }

    /**
     * Allows you to track which tables you have already joined on this query
     *
     * @param QueryBuilder $qb
     * @param $property
     * @param $joinedAlias
     * @param bool         $autoAlias
     *
     * @return $this
     */
    public function safeJoin(QueryBuilder $qb, $property, $joinedAlias, $autoAlias = true)
    {
        if ($autoAlias) {
            $property = $this->alias($property);
        }
        if (!isset($this->joinMap[$property])) {
            $qb->join($property, $joinedAlias);
            $this->joinMap[$property] = $joinedAlias;
        }

        return $this;
    }

    /**
     * Returns the sort order specified by Datatables params (ASC / DESC)
     * @param  Params $params
     * @return string
     */
    protected function getDataTablesSortOrder(Params $params)
    {
        $default = 'ASC';
        $order = strtoupper($params->getWrapped('order')->getWrapped(0)->get('dir', $default));
        if (!in_array($order, array($default, 'DESC'))) {
            $order = $default;
        }

        return $order;
    }

    /**
     * Returns the column name selected for the sort
     * @param  Params     $params
     * @return array|null
     */
    protected function getDataTablesSortColumn(Params $params)
    {
        $column = $params->getWrapped('order')->getWrapped(0)->get('column', 0);

        return $params->getWrapped('columns')->getWrapped($column)->get('data');
    }

    /**
     * Uses datatables request paramters to determine the column name to order by
     * @param  Params       $params
     * @param  QueryBuilder $qb
     * @return $this
     */
    protected function orderByDataTablesParams(Params $params, QueryBuilder $qb)
    {
        $order = $this->getDataTablesSortOrder($params);
        $sort = $this->getDataTablesSortColumn($params);
        if ($sort) {
            $qb->addOrderBy($this->alias($sort), $order);
        }

        return $this;
    }

    /**
     * Convenience method to add an AND WHERE MEMBER(s) OF clause in a common format.
     * If $queryParameterName is unspecified, $requestParameterName is used for both.
     * Any additional expressions will be added to the disjunction.
     *
     * @param  Params       $params
     * @param  QueryBuilder $qb
     * @param $requestParameterName
     * @param  null         $queryParameterName
     * @return $this
     */
    protected function addWhereMemberOfFilter(Params $params, QueryBuilder $qb, $requestParameterName, $queryParameterName = null)
    {
        if ($queryParameterName === null) {
            $queryParameterName = $requestParameterName;
        }
        if ($params->has($requestParameterName)) {
            $qb->andWhere(implode(' OR ', $this->getExpressionsForWhereMembersOf($params, $qb, $requestParameterName, $queryParameterName)));
        }

        return $this;
    }

    /**
     * Conveniance method to return a collection of "WHERE MEMBER OF" expressions in a common format.
     * If $queryParameterName is unspecified, $requestParameterName is used for both.
     * Any additional expressions will be added to the disjunction.
     *
     * @param  Params       $params
     * @param  QueryBuilder $qb
     * @param $requestParameterName
     * @param  null         $queryParameterName
     * @return array
     */
    protected function getExpressionsForWhereMembersOf(Params $params, QueryBuilder $qb, $requestParameterName, $queryParameterName = null)
    {
        $expressions = array();
        foreach ((array) $params->get($requestParameterName) as $key => $value) {
            $param = $queryParameterName.$key;
            $expressions[] = $qb->expr()->orX(':'.$param.' MEMBER OF '.$this->alias($queryParameterName));
            $qb->setParameter($param, $value);
        }

        return $expressions;
    }

    /**
     * Convenience method to add an AND WHERE IN clause in a common format.
     * If $queryParameterName is unspecified, $requestParameterName is used for both
     *
     * @param  Params       $params
     * @param  QueryBuilder $qb
     * @param $requestParameterName
     * @param  null         $queryParameterName
     * @return $this
     */
    protected function addWhereInFilter(Params $params, QueryBuilder $qb, $requestParameterName, $queryParameterName = null)
    {
        if ($queryParameterName === null) {
            $queryParameterName = $requestParameterName;
        }
        if ($params->has($requestParameterName)) {
            $qb->andWhere($qb->expr()->in($this->alias($queryParameterName), ':'.$requestParameterName))->setParameter($requestParameterName, $params->get($requestParameterName));
        }

        return $this;
    }
}


<?php

namespace Sasedev\Commons\SharedBundle\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\ConstraintValidatorInterface;

/**
 * PhoneValidator ConstraintValidator
 *
 * @author sasedev <seif.salah@gmail.com>
 */
class PhoneValidator extends ConstraintValidator
{

	/**
	 * Check if a Phonenumber is valid
	 *
	 * @param string $value
	 * @param Constraint $constraint
	 *
	 * @return bool
	 *
	 */
	public function isValid($value, Constraint $constraint)
	{
		if (empty($value)) {
			return true;
		}

		$ret = $this->validateNumber($value, $constraint->format);

		if (!$ret) {
			$this->setMessage($constraint->message);
		}

		return $ret;
	}

	/**
	 * Validate Phonenumber
	 *
	 * @param string $value
	 * @param string $format
	 *
	 * @return bool
	 *
	 */
	protected function validateNumber($value, $format)
	{
		$ret = preg_match($format, trim($value));

		return ($ret !== false && $ret >= 1);
	}

	/**
	 * {@inheritDoc} @see ConstraintValidatorInterface::validate()
	 *
	 * @param string $value
	 * @param Constraint $constraint
	 *
	 *
	 */
	public function validate($value, Constraint $constraint)
	{
		if (!empty($value)) {
			if (!preg_match($constraint->format, $value)) {
				$this->context->addViolation($constraint->message, array('%string%' => $value));
			}
		}
	}
}


<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * XML_Util
 *
 * XML Utilities package
 *
 * PHP versions 4 and 5
 *
 * LICENSE:
 *
 * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  XML
 * @package   XML_Util
 * @author    Stephan Schmidt <schst@php.net>
 * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
 * @license   http://opensource.org/licenses/bsd-license New BSD License
 * @version   CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $
 * @link      http://pear.php.net/package/XML_Util
 */

/**
 * error code for invalid chars in XML name
 */
define('XML_UTIL_ERROR_INVALID_CHARS', 51);

/**
 * error code for invalid chars in XML name
 */
define('XML_UTIL_ERROR_INVALID_START', 52);

/**
 * error code for non-scalar tag content
 */
define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60);

/**
 * error code for missing tag name
 */
define('XML_UTIL_ERROR_NO_TAG_NAME', 61);

/**
 * replace XML entities
 */
define('XML_UTIL_REPLACE_ENTITIES', 1);

/**
 * embedd content in a CData Section
 */
define('XML_UTIL_CDATA_SECTION', 5);

/**
 * do not replace entitites
 */
define('XML_UTIL_ENTITIES_NONE', 0);

/**
 * replace all XML entitites
 * This setting will replace <, >, ", ' and &
 */
define('XML_UTIL_ENTITIES_XML', 1);

/**
 * replace only required XML entitites
 * This setting will replace <, " and &
 */
define('XML_UTIL_ENTITIES_XML_REQUIRED', 2);

/**
 * replace HTML entitites
 * @link http://www.php.net/htmlentities
 */
define('XML_UTIL_ENTITIES_HTML', 3);

/**
 * Collapse all empty tags.
 */
define('XML_UTIL_COLLAPSE_ALL', 1);

/**
 * Collapse only empty XHTML tags that have no end tag.
 */
define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);

/**
 * utility class for working with XML documents
 *

 * @category  XML
 * @package   XML_Util
 * @author    Stephan Schmidt <schst@php.net>
 * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
 * @license   http://opensource.org/licenses/bsd-license New BSD License
 * @version   Release: 1.2.1
 * @link      http://pear.php.net/package/XML_Util
 */
class XML_Util
{
    /**
     * return API version
     *
     * @return string $version API version
     * @access public
     * @static
     */
    function apiVersion()
    {
        return '1.1';
    }

    /**
     * replace XML entities
     *
     * With the optional second parameter, you may select, which
     * entities should be replaced.
     *
     *
     * require_once 'XML/Util.php';
     *
     * // replace XML entites:
     * $string = XML_Util::replaceEntities('This string contains < & >.');
     *
     *
     * With the optional third parameter, you may pass the character encoding
     *
     * require_once 'XML/Util.php';
     *
     * // replace XML entites in UTF-8:
     * $string = XML_Util::replaceEntities(
     *     'This string contains < & > as well as ä, ö, ß, à and ê',
     *     XML_UTIL_ENTITIES_HTML,
     *     'UTF-8'
     * );
     *
     *
     * @param string $string          string where XML special chars
     *                                should be replaced
     * @param int    $replaceEntities setting for entities in attribute values
     *                                (one of XML_UTIL_ENTITIES_XML,
     *                                XML_UTIL_ENTITIES_XML_REQUIRED,
     *                                XML_UTIL_ENTITIES_HTML)
     * @param string $encoding        encoding value (if any)...
     *                                must be a valid encoding as determined
     *                                by the htmlentities() function
     *
     * @return string string with replaced chars
     * @access public
     * @static
     * @see reverseEntities()
     */
    function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
        $encoding = 'ISO-8859-1')
    {
        switch ($replaceEntities) {
        case XML_UTIL_ENTITIES_XML:
            return strtr($string, array(
                '&'  => '&',
                '>'  => '>',
                '<'  => '<',
                '"'  => '"',
                '\'' => ''' ));
            break;
        case XML_UTIL_ENTITIES_XML_REQUIRED:
            return strtr($string, array(
                '&' => '&',
                '<' => '<',
                '"' => '"' ));
            break;
        case XML_UTIL_ENTITIES_HTML:
            return htmlentities($string, ENT_COMPAT, $encoding);
            break;
        }
        return $string;
    }

    /**
     * reverse XML entities
     *
     * With the optional second parameter, you may select, which
     * entities should be reversed.
     *
     *
     * require_once 'XML/Util.php';
     *
     * // reverse XML entites:
     * $string = XML_Util::reverseEntities('This string contains < & >.');
     *
     *
     * With the optional third parameter, you may pass the character encoding
     *
     * require_once 'XML/Util.php';
     *
     * // reverse XML entites in UTF-8:
     * $string = XML_Util::reverseEntities(
     *     'This string contains < & > as well as'
     *     . ' ä, ö, ß, à and ê',
     *     XML_UTIL_ENTITIES_HTML,
     *     'UTF-8'
     * );
     *
     *
     * @param string $string          string where XML special chars
     *                                should be replaced
     * @param int    $replaceEntities setting for entities in attribute values
     *                                (one of XML_UTIL_ENTITIES_XML,
     *                                XML_UTIL_ENTITIES_XML_REQUIRED,
     *                                XML_UTIL_ENTITIES_HTML)
     * @param string $encoding        encoding value (if any)...
     *                                must be a valid encoding as determined
     *                                by the html_entity_decode() function
     *
     * @return string string with replaced chars
     * @access public
     * @static
     * @see replaceEntities()
     */
    function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
        $encoding = 'ISO-8859-1')
    {
        switch ($replaceEntities) {
        case XML_UTIL_ENTITIES_XML:
            return strtr($string, array(
                '&'  => '&',
                '>'   => '>',
                '<'   => '<',
                '"' => '"',
                ''' => '\'' ));
            break;
        case XML_UTIL_ENTITIES_XML_REQUIRED:
            return strtr($string, array(
                '&'  => '&',
                '<'   => '<',
                '"' => '"' ));
            break;
        case XML_UTIL_ENTITIES_HTML:
            return html_entity_decode($string, ENT_COMPAT, $encoding);
            break;
        }
        return $string;
    }

    /**
     * build an xml declaration
     *
     *
     * require_once 'XML/Util.php';
     *
     * // get an XML declaration:
     * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
     *
     *
     * @param string $version    xml version
     * @param string $encoding   character encoding
     * @param bool   $standalone document is standalone (or not)
     *
     * @return string xml declaration
     * @access public
     * @static
     * @uses attributesToString() to serialize the attributes of the XML declaration
     */
    function getXMLDeclaration($version = '1.0', $encoding = null,
        $standalone = null)
    {
        $attributes = array(
            'version' => $version,
        );
        // add encoding
        if ($encoding !== null) {
            $attributes['encoding'] = $encoding;
        }
        // add standalone, if specified
        if ($standalone !== null) {
            $attributes['standalone'] = $standalone ? 'yes' : 'no';
        }

        return sprintf('<?xml%s?>',
            XML_Util::attributesToString($attributes, false));
    }

    /**
     * build a document type declaration
     *
     *
     * require_once 'XML/Util.php';
     *
     * // get a doctype declaration:
     * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
     *
     *
     * @param string $root        name of the root tag
     * @param string $uri         uri of the doctype definition
     *                            (or array with uri and public id)
     * @param string $internalDtd internal dtd entries
     *
     * @return string doctype declaration
     * @access public
     * @static
     * @since 0.2
     */
    function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
    {
        if (is_array($uri)) {
            $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
        } elseif (!empty($uri)) {
            $ref = sprintf(' SYSTEM "%s"', $uri);
        } else {
            $ref = '';
        }

        if (empty($internalDtd)) {
            return sprintf('<!DOCTYPE %s%s>', $root, $ref);
        } else {
            return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
        }
    }

    /**
     * create string representation of an attribute list
     *
     *
     * require_once 'XML/Util.php';
     *
     * // build an attribute string
     * $att = array(
     *              'foo'   =>  'bar',
     *              'argh'  =>  'tomato'
     *            );
     *
     * $attList = XML_Util::attributesToString($att);
     *
     *
     * @param array      $attributes attribute array
     * @param bool|array $sort       sort attribute list alphabetically,
     *                               may also be an assoc array containing
     *                               the keys 'sort', 'multiline', 'indent',
     *                               'linebreak' and 'entities'
     * @param bool       $multiline  use linebreaks, if more than
     *                               one attribute is given
     * @param string     $indent     string used for indentation of
     *                               multiline attributes
     * @param string     $linebreak  string used for linebreaks of
     *                               multiline attributes
     * @param int        $entities   setting for entities in attribute values
     *                               (one of XML_UTIL_ENTITIES_NONE,
     *                               XML_UTIL_ENTITIES_XML,
     *                               XML_UTIL_ENTITIES_XML_REQUIRED,
     *                               XML_UTIL_ENTITIES_HTML)
     *
     * @return string string representation of the attributes
     * @access public
     * @static
     * @uses replaceEntities() to replace XML entities in attribute values
     * @todo allow sort also to be an options array
     */
    function attributesToString($attributes, $sort = true, $multiline = false,
        $indent = '    ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML)
    {
        /*
         * second parameter may be an array
         */
        if (is_array($sort)) {
            if (isset($sort['multiline'])) {
                $multiline = $sort['multiline'];
            }
            if (isset($sort['indent'])) {
                $indent = $sort['indent'];
            }
            if (isset($sort['linebreak'])) {
                $multiline = $sort['linebreak'];
            }
            if (isset($sort['entities'])) {
                $entities = $sort['entities'];
            }
            if (isset($sort['sort'])) {
                $sort = $sort['sort'];
            } else {
                $sort = true;
            }
        }
        $string = '';
        if (is_array($attributes) && !empty($attributes)) {
            if ($sort) {
                ksort($attributes);
            }
            if ( !$multiline || count($attributes) == 1) {
                foreach ($attributes as $key => $value) {
                    if ($entities != XML_UTIL_ENTITIES_NONE) {
                        if ($entities === XML_UTIL_CDATA_SECTION) {
                            $entities = XML_UTIL_ENTITIES_XML;
                        }
                        $value = XML_Util::replaceEntities($value, $entities);
                    }
                    $string .= ' ' . $key . '="' . $value . '"';
                }
            } else {
                $first = true;
                foreach ($attributes as $key => $value) {
                    if ($entities != XML_UTIL_ENTITIES_NONE) {
                        $value = XML_Util::replaceEntities($value, $entities);
                    }
                    if ($first) {
                        $string .= ' ' . $key . '="' . $value . '"';
                        $first   = false;
                    } else {
                        $string .= $linebreak . $indent . $key . '="' . $value . '"';
                    }
                }
            }
        }
        return $string;
    }

    /**
     * Collapses empty tags.
     *
     * @param string $xml  XML
     * @param int    $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL)
     *                      or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
     *
     * @return string XML
     * @access public
     * @static
     * @todo PEAR CS - unable to avoid "space after open parens" error
     *       in the IF branch
     */
    function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL)
    {
        if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
            return preg_replace(
                '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
                . 'param)([^>]*)><\/\\1>/s',
                '<\\1\\2 />',
                $xml);
        } else {
            return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml);
        }
    }

    /**
     * create a tag
     *
     * This method will call XML_Util::createTagFromArray(), which
     * is more flexible.
     *
     *
     * require_once 'XML/Util.php';
     *
     * // create an XML tag:
     * $tag = XML_Util::createTag('myNs:myTag',
     *     array('foo' => 'bar'),
     *     'This is inside the tag',
     *     'http://www.w3c.org/myNs#');
     *
     *
     * @param string $qname           qualified tagname (including namespace)
     * @param array  $attributes      array containg attributes
     * @param mixed  $content         the content
     * @param string $namespaceUri    URI of the namespace
     * @param int    $replaceEntities whether to replace XML special chars in
     *                                content, embedd it in a CData section
     *                                or none of both
     * @param bool   $multiline       whether to create a multiline tag where
     *                                each attribute gets written to a single line
     * @param string $indent          string used to indent attributes
     *                                (_auto indents attributes so they start
     *                                at the same column)
     * @param string $linebreak       string used for linebreaks
     * @param bool   $sortAttributes  Whether to sort the attributes or not
     *
     * @return string XML tag
     * @access public
     * @static
     * @see createTagFromArray()
     * @uses createTagFromArray() to create the tag
     */
    function createTag($qname, $attributes = array(), $content = null,
        $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
        $multiline = false, $indent = '_auto', $linebreak = "\n",
        $sortAttributes = true)
    {
        $tag = array(
            'qname'      => $qname,
            'attributes' => $attributes
        );

        // add tag content
        if ($content !== null) {
            $tag['content'] = $content;
        }

        // add namespace Uri
        if ($namespaceUri !== null) {
            $tag['namespaceUri'] = $namespaceUri;
        }

        return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline,
            $indent, $linebreak, $sortAttributes);
    }

    /**
     * create a tag from an array
     * this method awaits an array in the following format
     *

     * array(
     *     // qualified name of the tag
     *     'qname' => $qname
     *
     *     // namespace prefix (optional, if qname is specified or no namespace)
     *     'namespace' => $namespace
     *
     *     // local part of the tagname (optional, if qname is specified)
     *     'localpart' => $localpart,
     *
     *     // array containing all attributes (optional)
     *     'attributes' => array(),
     *
     *     // tag content (optional)
     *     'content' => $content,
     *
     *     // namespaceUri for the given namespace (optional)
     *     'namespaceUri' => $namespaceUri
     * )
     *

     *
     *
     * require_once 'XML/Util.php';
     *
     * $tag = array(
     *     'qname'        => 'foo:bar',
     *     'namespaceUri' => 'http://foo.com',
     *     'attributes'   => array('key' => 'value', 'argh' => 'fruit&vegetable'),
     *     'content'      => 'I\'m inside the tag',
     * );
     * // creating a tag with qualified name and namespaceUri
     * $string = XML_Util::createTagFromArray($tag);
     *
     *
     * @param array  $tag             tag definition
     * @param int    $replaceEntities whether to replace XML special chars in
     *                                content, embedd it in a CData section
     *                                or none of both
     * @param bool   $multiline       whether to create a multiline tag where each
     *                                attribute gets written to a single line
     * @param string $indent          string used to indent attributes
     *                                (_auto indents attributes so they start
     *                                at the same column)
     * @param string $linebreak       string used for linebreaks
     * @param bool   $sortAttributes  Whether to sort the attributes or not
     *
     * @return string XML tag
     * @access public
     * @static
     * @see createTag()
     * @uses attributesToString() to serialize the attributes of the tag
     * @uses splitQualifiedName() to get local part and namespace of a qualified name
     * @uses createCDataSection()
     * @uses raiseError()
     */
    function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
        $multiline = false, $indent = '_auto', $linebreak = "\n",
        $sortAttributes = true)
    {
        if (isset($tag['content']) && !is_scalar($tag['content'])) {
            return XML_Util::raiseError('Supplied non-scalar value as tag content',
            XML_UTIL_ERROR_NON_SCALAR_CONTENT);
        }

        if (!isset($tag['qname']) && !isset($tag['localPart'])) {
            return XML_Util::raiseError('You must either supply a qualified name '
                . '(qname) or local tag name (localPart).',
                XML_UTIL_ERROR_NO_TAG_NAME);
        }

        // if no attributes hav been set, use empty attributes
        if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
            $tag['attributes'] = array();
        }

        if (isset($tag['namespaces'])) {
            foreach ($tag['namespaces'] as $ns => $uri) {
                $tag['attributes']['xmlns:' . $ns] = $uri;
            }
        }

        if (!isset($tag['qname'])) {
            // qualified name is not given

            // check for namespace
            if (isset($tag['namespace']) && !empty($tag['namespace'])) {
                $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
            } else {
                $tag['qname'] = $tag['localPart'];
            }
        } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
            // namespace URI is set, but no namespace

            $parts = XML_Util::splitQualifiedName($tag['qname']);

            $tag['localPart'] = $parts['localPart'];
            if (isset($parts['namespace'])) {
                $tag['namespace'] = $parts['namespace'];
            }
        }

        if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
            // is a namespace given
            if (isset($tag['namespace']) && !empty($tag['namespace'])) {
                $tag['attributes']['xmlns:' . $tag['namespace']] =
                    $tag['namespaceUri'];
            } else {
                // define this Uri as the default namespace
                $tag['attributes']['xmlns'] = $tag['namespaceUri'];
            }
        }

        // check for multiline attributes
        if ($multiline === true) {
            if ($indent === '_auto') {
                $indent = str_repeat(' ', (strlen($tag['qname'])+2));
            }
        }

        // create attribute list
        $attList = XML_Util::attributesToString($tag['attributes'],
            $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities);
        if (!isset($tag['content']) || (string)$tag['content'] == '') {
            $tag = sprintf('<%s%s />', $tag['qname'], $attList);
        } else {
            switch ($replaceEntities) {
            case XML_UTIL_ENTITIES_NONE:
                break;
            case XML_UTIL_CDATA_SECTION:
                $tag['content'] = XML_Util::createCDataSection($tag['content']);
                break;
            default:
                $tag['content'] = XML_Util::replaceEntities($tag['content'],
                    $replaceEntities);
                break;
            }
            $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
                $tag['qname']);
        }
        return $tag;
    }

    /**
     * create a start element
     *
     *
     * require_once 'XML/Util.php';
     *
     * // create an XML start element:
     * $tag = XML_Util::createStartElement('myNs:myTag',
     *     array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
     *
     *
     * @param string $qname          qualified tagname (including namespace)
     * @param array  $attributes     array containg attributes
     * @param string $namespaceUri   URI of the namespace
     * @param bool   $multiline      whether to create a multiline tag where each
     *                               attribute gets written to a single line
     * @param string $indent         string used to indent attributes (_auto indents
     *                               attributes so they start at the same column)
     * @param string $linebreak      string used for linebreaks
     * @param bool   $sortAttributes Whether to sort the attributes or not
     *
     * @return string XML start element
     * @access public
     * @static
     * @see createEndElement(), createTag()
     */
    function createStartElement($qname, $attributes = array(), $namespaceUri = null,
        $multiline = false, $indent = '_auto', $linebreak = "\n",
        $sortAttributes = true)
    {
        // if no attributes hav been set, use empty attributes
        if (!isset($attributes) || !is_array($attributes)) {
            $attributes = array();
        }

        if ($namespaceUri != null) {
            $parts = XML_Util::splitQualifiedName($qname);
        }

        // check for multiline attributes
        if ($multiline === true) {
            if ($indent === '_auto') {
                $indent = str_repeat(' ', (strlen($qname)+2));
            }
        }

        if ($namespaceUri != null) {
            // is a namespace given
            if (isset($parts['namespace']) && !empty($parts['namespace'])) {
                $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
            } else {
                // define this Uri as the default namespace
                $attributes['xmlns'] = $namespaceUri;
            }
        }

        // create attribute list
        $attList = XML_Util::attributesToString($attributes, $sortAttributes,
            $multiline, $indent, $linebreak);
        $element = sprintf('<%s%s>', $qname, $attList);
        return  $element;
    }

    /**
     * create an end element
     *
     *
     * require_once 'XML/Util.php';
     *
     * // create an XML start element:
     * $tag = XML_Util::createEndElement('myNs:myTag');
     *
     *
     * @param string $qname qualified tagname (including namespace)
     *
     * @return string XML end element
     * @access public
     * @static
     * @see createStartElement(), createTag()
     */
    function createEndElement($qname)
    {
        $element = sprintf('</%s>', $qname);
        return $element;
    }

    /**
     * create an XML comment
     *
     *
     * require_once 'XML/Util.php';
     *
     * // create an XML start element:
     * $tag = XML_Util::createComment('I am a comment');
     *
     *
     * @param string $content content of the comment
     *
     * @return string XML comment
     * @access public
     * @static
     */
    function createComment($content)
    {
        $comment = sprintf('', $content);
        return $comment;
    }

    /**
     * create a CData section
     *
     *
     * require_once 'XML/Util.php';
     *
     * // create a CData section
     * $tag = XML_Util::createCDataSection('I am content.');
     *
     *
     * @param string $data data of the CData section
     *
     * @return string CData section with content
     * @access public
     * @static
     */
    function createCDataSection($data)
    {
        return sprintf('<![CDATA[%s]]>',
            preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data)));

    }

    /**
     * split qualified name and return namespace and local part
     *
     *
     * require_once 'XML/Util.php';
     *
     * // split qualified tag
     * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
     *
     * the returned array will contain two elements:
     *

     * array(
     *     'namespace' => 'xslt',
     *     'localPart' => 'stylesheet'
     * );
     *

     *
     * @param string $qname     qualified tag name
     * @param string $defaultNs default namespace (optional)
     *
     * @return array array containing namespace and local part
     * @access public
     * @static
     */
    function splitQualifiedName($qname, $defaultNs = null)
    {
        if (strstr($qname, ':')) {
            $tmp = explode(':', $qname);
            return array(
                'namespace' => $tmp[0],
                'localPart' => $tmp[1]
            );
        }
        return array(
            'namespace' => $defaultNs,
            'localPart' => $qname
        );
    }

    /**
     * check, whether string is valid XML name
     *
     *
XML names are used for tagname, attribute names and various
     * other, lesser known entities.

     *
An XML name may only consist of alphanumeric characters,
     * dashes, undescores and periods, and has to start with a letter
     * or an underscore.

     *
     *
     * require_once 'XML/Util.php';
     *
     * // verify tag name
     * $result = XML_Util::isValidName('invalidTag?');
     * if (is_a($result, 'PEAR_Error')) {
     *    print 'Invalid XML name: ' . $result->getMessage();
     * }
     *
     *
     * @param string $string string that should be checked
     *
     * @return mixed true, if string is a valid XML name, PEAR error otherwise
     * @access public
     * @static
     * @todo support for other charsets
     * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
     */
    function isValidName($string)
    {
        // check for invalid chars
        if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
            return XML_Util::raiseError('XML names may only start with letter '
                . 'or underscore', XML_UTIL_ERROR_INVALID_START);
        }

        // check for invalid chars
        if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/',
            $string)
        ) {
            return XML_Util::raiseError('XML names may only contain alphanumeric '
                . 'chars, period, hyphen, colon and underscores',
                XML_UTIL_ERROR_INVALID_CHARS);
        }
        // XML name is valid
        return true;
    }

    /**
     * replacement for XML_Util::raiseError
     *
     * Avoids the necessity to always require
     * PEAR.php
     *
     * @param string $msg  error message
     * @param int    $code error code
     *
     * @return PEAR_Error
     * @access public
     * @static
     * @todo PEAR CS - should this use include_once instead?
     */
    function raiseError($msg, $code)
    {
        require_once 'PEAR.php';
        return PEAR::raiseError($msg, $code);
    }
}
?>