<?php

namespace Flintstone;

use Flintstone\Cache\ArrayCache;
use Flintstone\Cache\CacheInterface;
use Flintstone\Formatter\FormatterInterface;
use Flintstone\Formatter\SerializeFormatter;

class Config
{
    /**
     * Config.
     *
     * @var array
     */
    protected $config = [];

    /**
     * Constructor.
     *
     * @param array $config
     */
    public function __construct(array $config = [])
    {
        $config = $this->normalizeConfig($config);
        $this->setDir($config['dir']);
        $this->setExt($config['ext']);
        $this->setGzip($config['gzip']);
        $this->setCache($config['cache']);
        $this->setFormatter($config['formatter']);
        $this->setSwapMemoryLimit($config['swap_memory_limit']);
    }

    /**
     * Normalize the user supplied config.
     *
     * @param array $config
     *
     * @return array
     */
    protected function normalizeConfig(array $config): array
    {
        $defaultConfig = [
            'dir' => getcwd(),
            'ext' => '.dat',
            'gzip' => false,
            'cache' => true,
            'formatter' => null,
            'swap_memory_limit' => 2097152, // 2MB
        ];

        return array_replace($defaultConfig, $config);
    }

    /**
     * Get the dir.
     *
     * @return string
     */
    public function getDir(): string
    {
        return $this->config['dir'];
    }

    /**
     * Set the dir.
     *
     * @param string $dir
     *
     * @throws Exception
     */
    public function setDir(string $dir)
    {
        if (!is_dir($dir)) {
            throw new Exception('Directory does not exist: ' . $dir);
        }

        $this->config['dir'] = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
    }

    /**
     * Get the ext.
     *
     * @return string
     */
    public function getExt(): string
    {
        if ($this->useGzip()) {
            return $this->config['ext'] . '.gz';
        }

        return $this->config['ext'];
    }

    /**
     * Set the ext.
     *
     * @param string $ext
     */
    public function setExt(string $ext)
    {
        if (substr($ext, 0, 1) !== '.') {
            $ext = '.' . $ext;
        }

        $this->config['ext'] = $ext;
    }

    /**
     * Use gzip?
     *
     * @return bool
     */
    public function useGzip(): bool
    {
        return $this->config['gzip'];
    }

    /**
     * Set gzip.
     *
     * @param bool $gzip
     */
    public function setGzip(bool $gzip)
    {
        $this->config['gzip'] = $gzip;
    }

    /**
     * Get the cache.
     *
     * @return CacheInterface|false
     */
    public function getCache()
    {
        return $this->config['cache'];
    }

    /**
     * Set the cache.
     *
     * @param mixed $cache
     *
     * @throws Exception
     */
    public function setCache($cache)
    {
        if (!is_bool($cache) && !$cache instanceof CacheInterface) {
            throw new Exception('Cache must be a boolean or an instance of Flintstone\Cache\CacheInterface');
        }

        if ($cache === true) {
            $cache = new ArrayCache();
        }

        $this->config['cache'] = $cache;
    }

    /**
     * Get the formatter.
     *
     * @return FormatterInterface
     */
    public function getFormatter(): FormatterInterface
    {
        return $this->config['formatter'];
    }

    /**
     * Set the formatter.
     *
     * @param FormatterInterface|null $formatter
     *
     * @throws Exception
     */
    public function setFormatter($formatter)
    {
        if ($formatter === null) {
            $formatter = new SerializeFormatter();
        }

        if (!$formatter instanceof FormatterInterface) {
            throw new Exception('Formatter must be an instance of Flintstone\Formatter\FormatterInterface');
        }

        $this->config['formatter'] = $formatter;
    }

    /**
     * Get the swap memory limit.
     *
     * @return int
     */
    public function getSwapMemoryLimit(): int
    {
        return $this->config['swap_memory_limit'];
    }

    /**
     * Set the swap memory limit.
     *
     * @param int $limit
     */
    public function setSwapMemoryLimit(int $limit)
    {
        $this->config['swap_memory_limit'] = $limit;
    }
}


<?php

namespace ZendTwig\View;

use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\ListenerAggregateTrait;
use Zend\View\Renderer\RendererInterface;
use Zend\View\ViewEvent;

class TwigStrategy implements ListenerAggregateInterface
{
    use ListenerAggregateTrait;

    /**
     * @var \Zend\View\Renderer\RendererInterface
     */
    protected $renderer;

    /**
     * @var bool
     */
    protected $forceRender = false;

    /**
     * TwigStrategy constructor.
     *
     * @param \Zend\View\Renderer\RendererInterface $renderer
     */
    public function __construct(RendererInterface $renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * Attach one or more listeners
     *
     * Implementors may add an optional $priority argument; the EventManager
     * implementation will pass this to the aggregate.
     *
     * @param EventManagerInterface $events
     * @param int                   $priority
     *
     * @return void
     */
    public function attach(EventManagerInterface $events, $priority = 1)
    {
        $this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRender'], $priority);
        $this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority);
    }

    /**
     * @param \Zend\View\ViewEvent $e
     *
     * @return \Zend\View\Renderer\RendererInterface|null
     */
    public function selectRender(ViewEvent $e)
    {
        if ($this->isForceRender()) {
            return $this->renderer;
        }

        $model = $e->getModel();
        if ($model instanceof TwigModel) {
            return $this->renderer;
        }

        return null;
    }

    /**
     * @param \Zend\View\ViewEvent $e
     */
    public function injectResponse(ViewEvent $e)
    {
        if ($this->renderer !== $e->getRenderer()) {
            return;
        }

        $result   = $e->getResult();
        $response = $e->getResponse();

        $response->setContent($result);
    }

    /**
     * @param bool $forceRender
     *
     * @return TwigStrategy
     */
    public function setForceRender(bool $forceRender) : TwigStrategy
    {
        $this->forceRender = $forceRender;

        return $this;
    }

    /**
     * @return bool
     */
    public function isForceRender() : bool
    {
        return $this->forceRender;
    }
}


<?php

namespace ZendTwig\Loader;

use Twig\Error\LoaderError;
use Twig\Loader\LoaderInterface;
use Twig\Source;

class MapLoader implements LoaderInterface
{
    /**
     * Array of templates to filenames.
     * @var array
     */
    protected $map = [];

    /**
     * Add to the map.
     *
     * @param string $name
     * @param string $path
     *
     * @throws \Twig_Error_Loader
     * @return MapLoader
     */
    public function add($name, $path) : MapLoader
    {
        if ($this->exists($name)) {
            throw new LoaderError(sprintf(
                'Name "%s" already exists in map',
                $name
            ));
        }

        $this->map[$name] = $path;

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function exists($name) : bool
    {
        return array_key_exists($name, $this->map);
    }

    /**
     * {@inheritDoc}
     */
    public function getCacheKey($name) : string
    {
        return $name;
    }

    /**
     * {@inheritDoc}
     */
    public function isFresh($name, $time) : bool
    {
        if (!$this->exists($name)) {
            throw new LoaderError(sprintf(
                'Unable to find template "%s" from template map',
                $name
            ));
        }

        if (!file_exists($this->map[$name])) {
            throw new LoaderError(sprintf(
                'Unable to open file "%s" from template map',
                $this->map[$name]
            ));
        }

        return filemtime($this->map[$name]) <= $time;
    }

    /**
     * Returns the source context for a given template logical name.
     *
     * @param string $name The template logical name
     *
     * @return Source
     *
     * @throws LoaderError When $name is not found
     */
    public function getSourceContext($name) : Source
    {
        if (!$this->exists($name)) {
            throw new LoaderError(sprintf(
                'Unable to find template "%s" from template map',
                $name
            ));
        }

        if (!file_exists($this->map[$name])) {
            throw new LoaderError(sprintf(
                'Unable to open file "%s" from template map',
                $this->map[$name]
            ));
        }

        $content = file_get_contents($this->map[$name]);
        $source = new Source($content, $name, $this->map[$name]);

        return $source;
    }
}


<?php
namespace ZendTwig;

use Twig\Environment;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\View\Exception\InvalidArgumentException;
use ZendTwig\Renderer\TwigRenderer;

class Module implements ConfigProviderInterface, BootstrapListenerInterface
{
    const MODULE_NAME = 'zend_twig';

    /**
     * Listen to the bootstrap event
     *
     * @param \Zend\Mvc\MvcEvent|EventInterface $e
     *
     * @return array|void
     */
    public function onBootstrap(EventInterface $e)
    {
        $app       = $e->getApplication();
        $container = $app->getServiceManager();

        /**
         * @var Environment $env
         */
        $config      = $container->get('Configuration');
        $env         = $container->get(Environment::class);
        $name        = static::MODULE_NAME;
        $options     = $envOptions = empty($config[$name]) ? [] : $config[$name];
        $extensions  = empty($options['extensions']) ? [] : $options['extensions'];
        $renderer    = $container->get(TwigRenderer::class);

        // Setup extensions
        foreach ($extensions as $extension) {
            // Allows modules to override/remove extensions.
            if (empty($extension)) {
                continue;
            } elseif (is_string($extension)) {
                if ($container->has($extension)) {
                    $extension = $container->get($extension);
                } else {
                    $extension = new $extension($container, $renderer);
                }
            } elseif (!is_object($extension)) {
                throw new InvalidArgumentException('Extensions should be a string or object.');
            }

            if (!$env->hasExtension(get_class($extension))) {
                $env->addExtension($extension);
            }
        }

        return;
    }

    /**
     * Returns configuration to merge with application configuration
     *
     * @return array|\Traversable
     */
    public function getConfig()
    {
        return include __DIR__ . '/../config/module.config.php';
    }
}


<?php

namespace ZendTwig\View;

use Twig\TwigFunction;
use ZendTwig\Extension\Extension;

class FallbackFunction
{
    /**
     * @param       $name
     *
     * @return TwigFunction
     */
    public static function build($name)
    {
        /**
         * Create callback function for injection of Zend View Helpers
         *
         * @param \Twig_Environment $env
         * @param array             ...$args
         *
         * @return mixed
         */
        $callable = function ($env, ... $args) use ($name) {
            /**
             * @var \ZendTwig\Extension\Extension $extension
             */
            $extension = $env->getExtension(Extension::class);
            $plugin = $extension->getRenderer()
                                ->plugin($name);

            if (is_callable($plugin)) {
                // helper should implement __invoke() function
                $args = empty($args) ? [] : $args;
                return call_user_func_array($plugin, $args);
            } else {
                return $plugin;
            }
        };

        $options = [
            'needs_environment' => true,
            'is_safe'           => ['all'],
        ];

        return new TwigFunction($name, $callable, $options);
    }
}


<?php

namespace ZendTwig\Renderer;

use Twig\Environment;

use Twig\Loader\ChainLoader;
use Zend\View\Exception\RuntimeException;
use Zend\View\Helper\ViewModel;
use ZendTwig\Resolver\TwigResolver;
use ZendTwig\View\HelperPluginManager as TwigHelperPluginManager;

use Zend\View\Exception\DomainException;
use Zend\View\Exception\InvalidArgumentException;
use Zend\View\Helper\AbstractHelper;
use Zend\View\HelperPluginManager as ZendHelperPluginManager;
use Zend\View\Model\ModelInterface;
use Zend\View\Renderer\RendererInterface;
use Zend\View\Renderer\TreeRendererInterface;
use Zend\View\Resolver\ResolverInterface;
use Zend\View\View;

class TwigRenderer implements RendererInterface, TreeRendererInterface
{
    /**
     * @var bool
     */
    protected $canRenderTrees = true;

    /**
     * @var Environment
     */
    protected $environment;

    /**
     * @var ChainLoader
     */
    protected $loader;

    /**
     * @var \Zend\View\View
     */
    protected $view;

    /**
     * @var TwigResolver
     */
    protected $resolver;

    /**
     * Helper plugin manager
     *
     * @var TwigHelperPluginManager
     */
    private $twigHelpers;

    /**
     * Helper plugin manager
     *
     * @var ZendHelperPluginManager
     */
    private $zendHelpers;

    /**
     * Force \Zend\View\Model\ViewModel::$terminate to true
     * @var bool
     */
    protected $forceStandalone = false;

    /**
     * @param \Zend\View\View   $view
     * @param Environment  $env
     * @param ResolverInterface $resolver
     */
    public function __construct(View $view, Environment $env = null, ResolverInterface $resolver = null)
    {
        $this->setView($view)
             ->setEnvironment($env)
             ->setLoader($env->getLoader())
             ->setResolver($resolver);
    }

    /**
     * Overloading: proxy to helpers
     *
     * Proxies to the attached plugin manager to retrieve, return, and potentially
     * execute helpers.
     *
     * * If the helper does not define __invoke, it will be returned
     * * If the helper does define __invoke, it will be called as a function
     *
     * @param  string $method
     * @param  array  $argv
     *
     * @return mixed
     */
    public function __call($method, $argv)
    {
        $plugin = $this->plugin($method);

        if (is_callable($plugin)) {
            return call_user_func_array($plugin, $argv);
        }

        // @codeCoverageIgnoreStart
        return $plugin;
        // @codeCoverageIgnoreEnd
    }

    /**
     * Get plugin instance
     *
     * @param  string     $name    Name of plugin to return
     * @param  null|array $options Options to pass to plugin constructor (if not already instantiated)
     *
     * @return AbstractHelper|callable
     */
    public function plugin($name, array $options = null)
    {
        $helper = $this->getTwigHelpers()->setRenderer($this);

        if ($helper->has($name)) {
            return $helper->get($name, $options);
        }

        return $this->getHelperPluginManager()->get($name, $options);
    }

    /**
     * Return the template engine object, if any
     *
     * If using a third-party template engine, such as Smarty, patTemplate,
     * phplib, etc, return the template engine object. Useful for calling
     * methods on these objects, such as for setting filters, modifiers, etc.
     *
     * @return TwigRenderer
     */
    public function getEngine() : TwigRenderer
    {
        return $this;
    }

    /**
     * Processes a view script and returns the output.
     *
     * @param  string|ModelInterface   $nameOrModel The script/resource process, or a view model
     * @param  null|array|\ArrayAccess $values      Values to use during rendering
     *
     * @return string The script output.
     */
    public function render($nameOrModel, $values = null) : string
    {
        $model = $nameOrModel;
        if ($model instanceof ModelInterface) {
            $nameOrModel = $model->getTemplate();

            if (empty($nameOrModel)) {
                throw new DomainException(sprintf(
                    '%s: received View Model argument, but template is empty',
                    __METHOD__
                ));
            }

            $options = $model->getOptions();
            $options = empty($options) ? [] : $options;
            foreach ($options as $setting => $value) {
                $method = 'set' . $setting;
                if (method_exists($this, $method)) {
                    $this->$method($value);
                }
                unset($method, $setting, $value);
            }

            /**
             * Give view model awareness via ViewModel helper
             * @var \Zend\View\Helper\ViewModel $helper
             */
            $helper = $this->plugin(ViewModel::class);
            $helper->setCurrent($model);

            $values = $model->getVariables();
        }

        if (!$this->canRender($nameOrModel)) {
            throw new RuntimeException(sprintf(
                '%s: Unable to render template "%s"; resolver could not resolve to a file',
                __METHOD__,
                $nameOrModel
            ));
        }

        if ($model instanceof ModelInterface && $model->hasChildren() && $this->canRenderTrees()) {
            if (!isset($values[$model->captureTo()])) {
                $values[$model->captureTo()] = '';
            }

            foreach ($model->getChildren() as $child) {
                /**
                 * @var \Zend\View\Model\ViewModel $child
                 * @var \Twig_Template             $template
                 */
                $result = $this->render($child, $values);

                if ($this->isForceStandalone() || $child->terminate()) {
                    return $result;
                }

                $child->setOption('has_parent', true);

                if ($child->isAppend()) {
                    $values[$child->captureTo()] .= $result;
                } else {
                    $values[$child->captureTo()] = $result;
                }
            }
        }

        /** @var \Twig_Template $template */
        $template = $this->getResolver()->resolve($nameOrModel, $this);

        return $template->render((array)$values);
    }

    /**
     * Can the template be rendered?
     *
     * @param string $name
     *
     * @return bool
     */
    public function canRender($name)
    {
        return $this->getLoader()
                    ->exists($name);
    }

    /**
     * Indicate whether the renderer is capable of rendering trees of view models
     *
     * @return bool
     */
    public function canRenderTrees()
    {
        return $this->canRenderTrees;
    }

    /**
     * @param boolean $canRenderTrees
     *
     * @return TwigRenderer
     */
    public function setCanRenderTrees($canRenderTrees)
    {
        $this->canRenderTrees = !!$canRenderTrees;

        return $this;
    }

    /**
     * @return \Zend\View\View
     */
    public function getView()
    {
        return $this->view;
    }

    /**
     * @param \Zend\View\View $view
     *
     * @return TwigRenderer
     */
    public function setView(View $view) : TwigRenderer
    {
        $this->view = $view;

        $view->getEventManager();

        return $this;
    }

    /**
     * @return Environment
     */
    public function getEnvironment() : Environment
    {
        if (!$this->environment instanceof Environment) {
            throw new InvalidArgumentException(sprintf(
                'Twig environment must be Twig_Environment; got type "%s" instead',
                (is_object($this->loader) ? get_class($this->loader) : gettype($this->loader))
            ));
        }

        return $this->environment;
    }

    /**
     * @param Environment $environment
     *
     * @return TwigRenderer
     */
    public function setEnvironment($environment) : TwigRenderer
    {
        $this->environment = $environment;

        return $this;
    }

    /**
     * @return ChainLoader
     */
    public function getLoader() : ChainLoader
    {
        if (!$this->loader instanceof ChainLoader) {
            throw new InvalidArgumentException(sprintf(
                'Twig loader must implement Twig_Loader_Chain; got type "%s" instead',
                (is_object($this->loader) ? get_class($this->loader) : gettype($this->loader))
            ));
        }

        return $this->loader;
    }

    /**
     * @param \Twig_LoaderInterface $loader
     *
     * @return TwigRenderer
     */
    public function setLoader($loader) : TwigRenderer
    {
        $this->loader = $loader;

        return $this;
    }

    /**
     * @return TwigResolver
     */
    public function getResolver() : TwigResolver
    {
        return $this->resolver;
    }

    /**
     * @param ResolverInterface|TwigResolver $resolver
     *
     * @return TwigRenderer
     */
    public function setResolver(ResolverInterface $resolver = null) : TwigRenderer
    {
        $this->resolver = $resolver;

        return $this;
    }

    /**
     * @return TwigHelperPluginManager
     */
    public function getTwigHelpers() : TwigHelperPluginManager
    {
        return $this->twigHelpers;
    }

    /**
     * @param TwigHelperPluginManager $twigHelpers
     *
     * @return TwigRenderer
     */
    public function setTwigHelpers($twigHelpers) : TwigRenderer
    {
        $this->twigHelpers = $twigHelpers;

        return $this;
    }

    /**
     * @return \Zend\View\HelperPluginManager
     */
    public function getHelperPluginManager() : ZendHelperPluginManager
    {
        return $this->zendHelpers;
    }

    /**
     * Set helper plugin manager instance
     *
     * @param ZendHelperPluginManager $helpers
     *
     * @return TwigRenderer
     */
    public function setZendHelpers(ZendHelperPluginManager $helpers) : TwigRenderer
    {
        $this->zendHelpers = $helpers;

        return $this;
    }

    /**
     * @param boolean $forceStandalone
     * @return TwigRenderer
     */
    public function setForceStandalone($forceStandalone) : TwigRenderer
    {
        $this->forceStandalone = !!$forceStandalone;
        return $this;
    }

    /**
     * @return bool
     */
    public function isForceStandalone() : bool
    {
        return $this->forceStandalone;
    }
}


<?php
require('../fpdf.php');

class PDF extends FPDF
{
// Page header
function Header()
{
	// Logo
	$this->Image('logo.png',10,6,30);
	// Arial bold 15
	$this->SetFont('Arial','B',15);
	// Move to the right
	$this->Cell(80);
	// Title
	$this->Cell(30,10,'Title',1,0,'C');
	// Line break
	$this->Ln(20);
}

// Page footer
function Footer()
{
	// Position at 1.5 cm from bottom
	$this->SetY(-15);
	// Arial italic 8
	$this->SetFont('Arial','I',8);
	// Page number
	$this->Cell(0,10,'Page '.$this->PageNo().'/{nb}',0,0,'C');
}
}

// Instanciation of inherited class
$pdf = new PDF();
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->SetFont('Times','',12);
for($i=1;$i<=40;$i++)
	$pdf->Cell(0,10,'Printing line number '.$i,0,1);
$pdf->Output();
?>


<?php namespace Codedge\Fpdf;

use Illuminate\Support\ServiceProvider;

class FpdfServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        $this->publishes([
            __DIR__.'/config/fpdf.php' => config_path('fpdf.php'),
        ], 'config');
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $configPath = __DIR__ . '/config/fpdf.php';
        $this->mergeConfigFrom($configPath, 'fpdf');

        $this->app->call( [ $this, 'registerFpdf' ] );
    }

    /**
     * Register the Fpdf instance
     *
     * @return void
     */
    public function registerFpdf()
    {
        $this->app->singleton('fpdf', function()
        {
            return new Fpdf\Fpdf(
                config('fpdf.orientation'), config('fpdf.unit'), config('fpdf.size')
            );
        });
    }

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


<?php
require('../fpdf.php');

class PDF extends FPDF
{
// Load data
function LoadData($file)
{
	// Read file lines
	$lines = file($file);
	$data = array();
	foreach($lines as $line)
		$data[] = explode(';',trim($line));
	return $data;
}

// Simple table
function BasicTable($header, $data)
{
	// Header
	foreach($header as $col)
		$this->Cell(40,7,$col,1);
	$this->Ln();
	// Data
	foreach($data as $row)
	{
		foreach($row as $col)
			$this->Cell(40,6,$col,1);
		$this->Ln();
	}
}

// Better table
function ImprovedTable($header, $data)
{
	// Column widths
	$w = array(40, 35, 40, 45);
	// Header
	for($i=0;$i<count($header);$i++)
		$this->Cell($w[$i],7,$header[$i],1,0,'C');
	$this->Ln();
	// Data
	foreach($data as $row)
	{
		$this->Cell($w[0],6,$row[0],'LR');
		$this->Cell($w[1],6,$row[1],'LR');
		$this->Cell($w[2],6,number_format($row[2]),'LR',0,'R');
		$this->Cell($w[3],6,number_format($row[3]),'LR',0,'R');
		$this->Ln();
	}
	// Closing line
	$this->Cell(array_sum($w),0,'','T');
}

// Colored table
function FancyTable($header, $data)
{
	// Colors, line width and bold font
	$this->SetFillColor(255,0,0);
	$this->SetTextColor(255);
	$this->SetDrawColor(128,0,0);
	$this->SetLineWidth(.3);
	$this->SetFont('','B');
	// Header
	$w = array(40, 35, 40, 45);
	for($i=0;$i<count($header);$i++)
		$this->Cell($w[$i],7,$header[$i],1,0,'C',true);
	$this->Ln();
	// Color and font restoration
	$this->SetFillColor(224,235,255);
	$this->SetTextColor(0);
	$this->SetFont('');
	// Data
	$fill = false;
	foreach($data as $row)
	{
		$this->Cell($w[0],6,$row[0],'LR',0,'L',$fill);
		$this->Cell($w[1],6,$row[1],'LR',0,'L',$fill);
		$this->Cell($w[2],6,number_format($row[2]),'LR',0,'R',$fill);
		$this->Cell($w[3],6,number_format($row[3]),'LR',0,'R',$fill);
		$this->Ln();
		$fill = !$fill;
	}
	// Closing line
	$this->Cell(array_sum($w),0,'','T');
}
}

$pdf = new PDF();
// Column headings
$header = array('Country', 'Capital', 'Area (sq km)', 'Pop. (thousands)');
// Data loading
$data = $pdf->LoadData('countries.txt');
$pdf->SetFont('Arial','',14);
$pdf->AddPage();
$pdf->BasicTable($header,$data);
$pdf->AddPage();
$pdf->ImprovedTable($header,$data);
$pdf->AddPage();
$pdf->FancyTable($header,$data);
$pdf->Output();
?>


<?php

namespace Amp\Process\Internal\Windows;

use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\Loop;
use Amp\Process\Internal\ProcessStatus;
use Amp\Process\ProcessException;

/**
 * @internal
 * @codeCoverageIgnore Windows only.
 */
final class SocketConnector
{
    const SERVER_SOCKET_URI = 'tcp://127.0.0.1:0';
    const SECURITY_TOKEN_SIZE = 16;
    const CONNECT_TIMEOUT = 1000;

    /** @var resource */
    private $server;

    /** @var PendingSocketClient[] */
    private $pendingClients = [];

    /** @var Handle[] */
    private $pendingProcesses = [];

    /** @var string */
    public $address;

    /** @var int */
    public $port;

    public function __construct()
    {
        $flags = \STREAM_SERVER_LISTEN | \STREAM_SERVER_BIND;
        $this->server = \stream_socket_server(self::SERVER_SOCKET_URI, $errNo, $errStr, $flags);

        if (!$this->server) {
            throw new \Error("Failed to create TCP server socket for process wrapper: {$errNo}: {$errStr}");
        }

        if (!\stream_set_blocking($this->server, false)) {
            throw new \Error("Failed to set server socket to non-blocking mode");
        }

        list($this->address, $this->port) = \explode(':', \stream_socket_get_name($this->server, false));
        $this->port = (int) $this->port;

        Loop::unreference(Loop::onReadable($this->server, [$this, 'onServerSocketReadable']));
    }

    private function failClientHandshake($socket, int $code)
    {
        \fwrite($socket, \chr(SignalCode::HANDSHAKE_ACK) . \chr($code));
        \fclose($socket);

        unset($this->pendingClients[(int) $socket]);
    }

    public function failHandleStart(Handle $handle, string $message, ...$args)
    {
        Loop::cancel($handle->connectTimeoutWatcher);

        unset($this->pendingProcesses[$handle->wrapperPid]);

        foreach ($handle->sockets as $socket) {
            \fclose($socket);
        }

        $error = new ProcessException(\vsprintf($message, $args));

        $deferreds = $handle->stdioDeferreds;
        $deferreds[] = $handle->joinDeferred;
        $handle->stdioDeferreds = [];

        foreach ($deferreds as $deferred) {
            $deferred->fail($error);
        }
    }

    /**
     * Read data from a client socket.
     *
     * This method cleans up internal state as appropriate. Returns null if the read fails or needs to be repeated.
     *
     * @param resource            $socket
     * @param int                 $length
     * @param PendingSocketClient $state
     *
     * @return string|null
     */
    private function readDataFromPendingClient($socket, int $length, PendingSocketClient $state)
    {
        $data = \fread($socket, $length);

        if ($data === false || $data === '') {
            return null;
        }

        $data = $state->receivedDataBuffer . $data;

        if (\strlen($data) < $length) {
            $state->receivedDataBuffer = $data;
            return null;
        }

        $state->receivedDataBuffer = '';

        Loop::cancel($state->readWatcher);

        return $data;
    }

    public function onReadableHandshake($watcher, $socket)
    {
        $socketId = (int) $socket;
        $pendingClient = $this->pendingClients[$socketId];

        if (null === $data = $this->readDataFromPendingClient($socket, self::SECURITY_TOKEN_SIZE + 6, $pendingClient)) {
            return;
        }

        $packet = \unpack('Csignal/Npid/Cstream_id/a*client_token', $data);

        // validate the client's handshake
        if ($packet['signal'] !== SignalCode::HANDSHAKE) {
            $this->failClientHandshake($socket, HandshakeStatus::SIGNAL_UNEXPECTED);
            return;
        }

        if ($packet['stream_id'] > 2) {
            $this->failClientHandshake($socket, HandshakeStatus::INVALID_STREAM_ID);
            return;
        }

        if (!isset($this->pendingProcesses[$packet['pid']])) {
            $this->failClientHandshake($socket, HandshakeStatus::INVALID_PROCESS_ID);
            return;
        }

        $handle = $this->pendingProcesses[$packet['pid']];

        if (isset($handle->sockets[$packet['stream_id']])) {
            $this->failClientHandshake($socket, HandshakeStatus::DUPLICATE_STREAM_ID);
            \trigger_error(\sprintf(
                "%s: Received duplicate socket for process #%s stream #%d",
                self::class,
                $handle->pid,
                $packet['stream_id']
            ), E_USER_WARNING);
            return;
        }

        if (!\hash_equals($packet['client_token'], $handle->securityTokens[$packet['stream_id']])) {
            $this->failClientHandshake($socket, HandshakeStatus::INVALID_CLIENT_TOKEN);
            $this->failHandleStart($handle, "Invalid client security token for stream #%d", $packet['stream_id']);
            return;
        }

        $ackData = \chr(SignalCode::HANDSHAKE_ACK) . \chr(HandshakeStatus::SUCCESS)
            . $handle->securityTokens[$packet['stream_id'] + 3];

        // Unless we set the security token size so high that it won't fit in the
        // buffer, this probably shouldn't ever happen unless something has gone wrong
        if (\fwrite($socket, $ackData) !== self::SECURITY_TOKEN_SIZE + 2) {
            unset($this->pendingClients[$socketId]);
            return;
        }

        $pendingClient->pid = $packet['pid'];
        $pendingClient->streamId = $packet['stream_id'];
        $pendingClient->readWatcher = Loop::onReadable($socket, [$this, 'onReadableHandshakeAck']);
    }

    public function onReadableHandshakeAck($watcher, $socket)
    {
        $socketId = (int) $socket;
        $pendingClient = $this->pendingClients[$socketId];

        // can happen if the start promise was failed
        if (!isset($this->pendingProcesses[$pendingClient->pid]) || $this->pendingProcesses[$pendingClient->pid]->status === ProcessStatus::ENDED) {
            \fclose($socket);
            Loop::cancel($watcher);
            Loop::cancel($pendingClient->timeoutWatcher);
            unset($this->pendingClients[$socketId]);
            return;
        }

        if (null === $data = $this->readDataFromPendingClient($socket, 2, $pendingClient)) {
            return;
        }

        Loop::cancel($pendingClient->timeoutWatcher);

        unset($this->pendingClients[$socketId]);
        $handle = $this->pendingProcesses[$pendingClient->pid];

        $packet = \unpack('Csignal/Cstatus', $data);

        if ($packet['signal'] !== SignalCode::HANDSHAKE_ACK || $packet['status'] !== HandshakeStatus::SUCCESS) {
            $this->failHandleStart(
                $handle,
                "Client rejected handshake with code %d for stream #%d",
                $packet['status'],
                $pendingClient->streamId
            );
            return;
        }

        $handle->sockets[$pendingClient->streamId] = $socket;

        if (\count($handle->sockets) === 3) {
            $handle->childPidWatcher = Loop::onReadable($handle->sockets[0], [$this, 'onReadableChildPid'], $handle);

            $deferreds = $handle->stdioDeferreds;
            $handle->stdioDeferreds = []; // clear, so there's no double resolution if process spawn fails

            $deferreds[0]->resolve(new ResourceOutputStream($handle->sockets[0]));
            $deferreds[1]->resolve(new ResourceInputStream($handle->sockets[1]));
            $deferreds[2]->resolve(new ResourceInputStream($handle->sockets[2]));
        }
    }

    public function onReadableChildPid($watcher, $socket, Handle $handle)
    {
        $data = \fread($socket, 5);

        if ($data === false || $data === '') {
            return;
        }

        Loop::cancel($handle->childPidWatcher);
        Loop::cancel($handle->connectTimeoutWatcher);

        $handle->childPidWatcher = null;

        if (\strlen($data) !== 5) {
            $this->failHandleStart(
                $handle,
                'Failed to read PID from wrapper: Received %d of 5 expected bytes',
                \strlen($data)
            );
            return;
        }

        $packet = \unpack('Csignal/Npid', $data);

        if ($packet['signal'] !== SignalCode::CHILD_PID) {
            $this->failHandleStart(
                $handle,
                "Failed to read PID from wrapper: Unexpected signal code %d",
                $packet['signal']
            );
            return;
        }

        // Required, because a process might be destroyed while starting
        if ($handle->status === ProcessStatus::STARTING) {
            $handle->status = ProcessStatus::RUNNING;
            $handle->exitCodeWatcher = Loop::onReadable($handle->sockets[0], [$this, 'onReadableExitCode'], $handle);

            if (!$handle->exitCodeRequested) {
                Loop::unreference($handle->exitCodeWatcher);
            }
        }

        $handle->pidDeferred->resolve($packet['pid']);

        unset($this->pendingProcesses[$handle->wrapperPid]);
    }

    public function onReadableExitCode($watcher, $socket, Handle $handle)
    {
        $data = \fread($socket, 5);

        if ($data === false || $data === '') {
            return;
        }

        Loop::cancel($handle->exitCodeWatcher);
        $handle->exitCodeWatcher = null;

        if (\strlen($data) !== 5) {
            $handle->status = ProcessStatus::ENDED;
            $handle->joinDeferred->fail(new ProcessException(
                \sprintf('Failed to read exit code from wrapper: Received %d of 5 expected bytes', \strlen($data))
            ));
            return;
        }

        $packet = \unpack('Csignal/Ncode', $data);

        if ($packet['signal'] !== SignalCode::EXIT_CODE) {
            $this->failHandleStart(
                $handle,
                "Failed to read exit code from wrapper: Unexpected signal code %d",
                $packet['signal']
            );
            return;
        }

        $handle->status = ProcessStatus::ENDED;
        $handle->joinDeferred->resolve($packet['code']);
        $handle->stdin->close();
        $handle->stdout->close();
        $handle->stderr->close();

        // Explicitly \fclose() sockets, as resource streams shut only one side down.
        foreach ($handle->sockets as $sock) {
            @\fclose($sock);
        }
    }

    public function onClientSocketConnectTimeout($watcher, $socket)
    {
        $id = (int) $socket;

        Loop::cancel($this->pendingClients[$id]->readWatcher);
        unset($this->pendingClients[$id]);

        \fclose($socket);
    }

    public function onServerSocketReadable()
    {
        $socket = \stream_socket_accept($this->server);

        if (!\stream_set_blocking($socket, false)) {
            throw new \Error("Failed to set client socket to non-blocking mode");
        }

        $pendingClient = new PendingSocketClient;
        $pendingClient->readWatcher = Loop::onReadable($socket, [$this, 'onReadableHandshake']);
        $pendingClient->timeoutWatcher = Loop::delay(self::CONNECT_TIMEOUT, [$this, 'onClientSocketConnectTimeout'], $socket);

        $this->pendingClients[(int) $socket] = $pendingClient;
    }

    public function onProcessConnectTimeout($watcher, Handle $handle)
    {
        $running = \is_resource($handle->proc) && \proc_get_status($handle->proc)['running'];

        $error = null;
        if (!$running) {
            $error = \stream_get_contents($handle->wrapperStderrPipe);
        }
        $error = $error ?: 'Process did not connect to server before timeout elapsed';

        foreach ($handle->sockets as $socket) {
            \fclose($socket);
        }

        $error = new ProcessException(\trim($error));
        foreach ($handle->stdioDeferreds as $deferred) {
            $deferred->fail($error);
        }

        \fclose($handle->wrapperStderrPipe);
        \proc_close($handle->proc);

        $handle->joinDeferred->fail($error);
    }

    public function registerPendingProcess(Handle $handle)
    {
        // Use Loop::defer() to start the timeout only after the loop has ticked once. This prevents issues with many
        // things started at once, see https://github.com/amphp/process/issues/21.
        $handle->connectTimeoutWatcher = Loop::defer(function () use ($handle) {
            $handle->connectTimeoutWatcher = Loop::delay(
                self::CONNECT_TIMEOUT,
                [$this, 'onProcessConnectTimeout'],
                $handle
            );
        });

        $this->pendingProcesses[$handle->wrapperPid] = $handle;
    }
}


<?php

namespace Amp\Process;

use Amp\ByteStream\InputStream;
use Amp\ByteStream\PendingReadError;
use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\StreamException;
use Amp\Deferred;
use Amp\Failure;
use Amp\Promise;
use Amp\Success;

final class ProcessInputStream implements InputStream
{
    /** @var Deferred */
    private $initialRead;

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

    /** @var bool */
    private $referenced = true;

    /** @var ResourceInputStream */
    private $resourceStream;

    /** @var StreamException|null */
    private $error;

    public function __construct(Promise $resourceStreamPromise)
    {
        $resourceStreamPromise->onResolve(function ($error, $resourceStream) {
            if ($error) {
                $this->error = new StreamException("Failed to launch process", 0, $error);
                if ($this->initialRead) {
                    $initialRead = $this->initialRead;
                    $this->initialRead = null;
                    $initialRead->fail($this->error);
                }
                return;
            }

            $this->resourceStream = $resourceStream;

            if (!$this->referenced) {
                $this->resourceStream->unreference();
            }

            if ($this->shouldClose) {
                $this->resourceStream->close();
            }

            if ($this->initialRead) {
                $initialRead = $this->initialRead;
                $this->initialRead = null;
                $initialRead->resolve($this->shouldClose ? null : $this->resourceStream->read());
            }
        });
    }

    /**
     * Reads data from the stream.
     *
     * @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
     *
     * @throws PendingReadError Thrown if another read operation is still pending.
     */
    public function read(): Promise
    {
        if ($this->initialRead) {
            throw new PendingReadError;
        }

        if ($this->error) {
            return new Failure($this->error);
        }

        if ($this->resourceStream) {
            return $this->resourceStream->read();
        }

        if ($this->shouldClose) {
            return new Success; // Resolve reads on closed streams with null.
        }

        $this->initialRead = new Deferred;

        return $this->initialRead->promise();
    }

    public function reference()
    {
        $this->referenced = true;

        if ($this->resourceStream) {
            $this->resourceStream->reference();
        }
    }

    public function unreference()
    {
        $this->referenced = false;

        if ($this->resourceStream) {
            $this->resourceStream->unreference();
        }
    }

    public function close()
    {
        $this->shouldClose = true;

        if ($this->initialRead) {
            $initialRead = $this->initialRead;
            $this->initialRead = null;
            $initialRead->resolve();
        }

        if ($this->resourceStream) {
            $this->resourceStream->close();
        }
    }
}


<?php

namespace Amp\Process;

use Amp\Loop;
use Amp\Process\Internal\Posix\Runner as PosixProcessRunner;
use Amp\Process\Internal\ProcessHandle;
use Amp\Process\Internal\ProcessRunner;
use Amp\Process\Internal\ProcessStatus;
use Amp\Process\Internal\Windows\Runner as WindowsProcessRunner;
use Amp\Promise;
use function Amp\call;

final class Process
{
    /** @var ProcessRunner */
    private $processRunner;

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

    /** @var string */
    private $cwd = "";

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

    /** @var array */
    private $options;

    /** @var ProcessHandle */
    private $handle;

    /** @var int|null */
    private $pid;

    /**
     * @param   string|string[] $command Command to run.
     * @param   string|null     $cwd Working directory or use an empty string to use the working directory of the
     *     parent.
     * @param   mixed[]         $env Environment variables or use an empty array to inherit from the parent.
     * @param   mixed[]         $options Options for `proc_open()`.
     *
     * @throws \Error If the arguments are invalid.
     */
    public function __construct($command, string $cwd = null, array $env = [], array $options = [])
    {
        $command = \is_array($command)
            ? \implode(" ", \array_map(__NAMESPACE__ . "\\escapeArguments", $command))
            : (string) $command;

        $cwd = $cwd ?? "";

        $envVars = [];
        foreach ($env as $key => $value) {
            if (\is_array($value)) {
                throw new \Error("\$env cannot accept array values");
            }

            $envVars[(string) $key] = (string) $value;
        }

        $this->command = $command;
        $this->cwd = $cwd;
        $this->env = $envVars;
        $this->options = $options;

        $this->processRunner = Loop::getState(self::class);

        if ($this->processRunner === null) {
            $this->processRunner = IS_WINDOWS
                ? new WindowsProcessRunner
                : new PosixProcessRunner;

            Loop::setState(self::class, $this->processRunner);
        }
    }

    /**
     * Stops the process if it is still running.
     */
    public function __destruct()
    {
        if ($this->handle !== null) {
            $this->processRunner->destroy($this->handle);
        }
    }

    public function __clone()
    {
        throw new \Error("Cloning is not allowed!");
    }

    /**
     * Start the process.
     *
     * @return Promise<int> Resolves with the PID.
     *
     * @throws StatusError If the process has already been started.
     */
    public function start(): Promise
    {
        if ($this->handle) {
            throw new StatusError("Process has already been started.");
        }

        return call(function () {
            $this->handle = $this->processRunner->start($this->command, $this->cwd, $this->env, $this->options);
            return $this->pid = yield $this->handle->pidDeferred->promise();
        });
    }

    /**
     * Wait for the process to end.
     *
     * @return Promise <int> Succeeds with process exit code or fails with a ProcessException if the process is killed.
     *
     * @throws StatusError If the process has already been started.
     */
    public function join(): Promise
    {
        if (!$this->handle) {
            throw new StatusError("Process has not been started.");
        }

        return $this->processRunner->join($this->handle);
    }

    /**
     * Forcibly end the process.
     *
     * @throws StatusError If the process is not running.
     * @throws ProcessException If terminating the process fails.
     */
    public function kill()
    {
        if (!$this->isRunning()) {
            throw new StatusError("Process is not running.");
        }

        $this->processRunner->kill($this->handle);
    }

    /**
     * Send a signal signal to the process.
     *
     * @param int $signo Signal number to send to process.
     *
     * @throws StatusError If the process is not running.
     * @throws ProcessException If sending the signal fails.
     */
    public function signal(int $signo)
    {
        if (!$this->isRunning()) {
            throw new StatusError("Process is not running.");
        }

        $this->processRunner->signal($this->handle, $signo);
    }

    /**
     * Returns the PID of the child process.
     *
     * @return int
     *
     * @throws StatusError If the process has not started or has not completed starting.
     */
    public function getPid(): int
    {
        if (!$this->pid) {
            throw new StatusError("Process has not been started or has not completed starting.");
        }

        return $this->pid;
    }

    /**
     * Returns the command to execute.
     *
     * @return string The command to execute.
     */
    public function getCommand(): string
    {
        return $this->command;
    }

    /**
     * Gets the current working directory.
     *
     * @return string The current working directory an empty string if inherited from the current PHP process.
     */
    public function getWorkingDirectory(): string
    {
        if ($this->cwd === "") {
            return \getcwd() ?: "";
        }

        return $this->cwd;
    }

    /**
     * Gets the environment variables array.
     *
     * @return string[] Array of environment variables.
     */
    public function getEnv(): array
    {
        return $this->env;
    }

    /**
     * Gets the options to pass to proc_open().
     *
     * @return mixed[] Array of options.
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Determines if the process is still running.
     *
     * @return bool
     */
    public function isRunning(): bool
    {
        return $this->handle && $this->handle->status !== ProcessStatus::ENDED;
    }

    /**
     * Gets the process input stream (STDIN).
     *
     * @return ProcessOutputStream
     */
    public function getStdin(): ProcessOutputStream
    {
        if (!$this->handle || $this->handle->status === ProcessStatus::STARTING) {
            throw new StatusError("Process has not been started or has not completed starting.");
        }

        return $this->handle->stdin;
    }

    /**
     * Gets the process output stream (STDOUT).
     *
     * @return ProcessInputStream
     */
    public function getStdout(): ProcessInputStream
    {
        if (!$this->handle || $this->handle->status === ProcessStatus::STARTING) {
            throw new StatusError("Process has not been started or has not completed starting.");
        }

        return $this->handle->stdout;
    }

    /**
     * Gets the process error stream (STDERR).
     *
     * @return ProcessInputStream
     */
    public function getStderr(): ProcessInputStream
    {
        if (!$this->handle || $this->handle->status === ProcessStatus::STARTING) {
            throw new StatusError("Process has not been started or has not completed starting.");
        }

        return $this->handle->stderr;
    }

    public function __debugInfo(): array
    {
        return [
            'command' => $this->getCommand(),
            'cwd' => $this->getWorkingDirectory(),
            'env' => $this->getEnv(),
            'options' => $this->getOptions(),
            'pid' => $this->pid,
            'status' => $this->handle ? $this->handle->status : -1,
        ];
    }
}


<?php

namespace Jorenvh\Share;

class Share
{
    /**
     * The url of the page to share
     *
     * @var string
     */
    protected $url;

    /**
     * Optional text for Twitter
     * and Linkedin title
     *
     * @var string
     */
    protected $title;

    /**
     * Extra options for the share links
     *
     * @var array
     */
    protected $options = [];

    /**
     * Html to prefix before the share links
     *
     * @var string
     */
    protected $prefix = '

';

    /**
     * Html to append after the share links
     *
     * @var string
     */
    protected $suffix = '

';

    /**
     * The generated html
     *
     * @var string
     */
    protected $html = '';

    /**
     * Return a string with html at the end
     * of the chain.
     *
     * @return string
     */
    public function __toString()
    {
        $this->html = $this->prefix . $this->html;
        $this->html .= $this->suffix;

        return $this->html;
    }

    /**
     * @param $url
     * @param null $title
     * @param array $options
     * @param null $prefix
     * @param null $suffix
     * @return $this
     */
    public function page($url, $title = null, $options = [], $prefix = null, $suffix = null)
    {
        $this->url = $url;
        $this->title = $title;
        $this->options = $options;

        $this->setPrefixAndSuffix($prefix, $suffix);

        return $this;
    }

    /**
     * @param null $title
     * @param array $options
     * @param null $prefix
     * @param null $suffix
     * @return $this
     */
    public function currentPage($title = null, $options = [], $prefix = null, $suffix = null)
    {
        $url = request()->getUri();

        return $this->page($url, $title, $options, $prefix, $suffix);
    }

    /**
     * Facebook share link
     *
     * @return $this
     */
    public function facebook()
    {
        $url = config('laravel-share.services.facebook.uri') . $this->url;

        $this->buildLink('facebook', $url);

        return $this;
    }

    /**
     * Twitter share link
     *
     * @return $this
     */
    public function twitter()
    {
        if (is_null($this->title)) {
            $this->title = config('laravel-share.services.twitter.text');
        }

        $base = config('laravel-share.services.twitter.uri');
        $url = $base . '?text=' . urlencode($this->title) . '&url=' . $this->url;

        $this->buildLink('twitter', $url);

        return $this;
    }

    /**
     * Google Plus share link
     *
     * @return $this
     */
    public function googlePlus()
    {
        $url = config('laravel-share.services.gplus.uri') . $this->url;

        $this->buildLink('gplus', $url);

        return $this;
    }

    /**
     * Whatsapp share link
     *
     * @return $this
     */
    public function whatsapp()
    {
        $url = config('laravel-share.services.whatsapp.uri') . $this->url;

        $this->buildLink('whatsapp', $url);

        return $this;
    }

    /**
     * Linked in share link
     *
     * @param string $summary
     * @return $this
     */
    public function linkedin($summary = '')
    {
        $base = config('laravel-share.services.linkedin.uri');
        $mini = config('laravel-share.services.linkedin.extra.mini');
        $url = $base . '?mini=' . $mini . '&url=' . $this->url . '&title=' . urlencode($this->title) . '&summary=' . urlencode($summary);

        $this->buildLink('linkedin', $url);

        return $this;
    }

    /**
     * Build a single link
     *
     * @param $provider
     * @param $url
     */
    protected function buildLink($provider, $url)
    {
        $fontAwesomeVersion = config('laravel-share.fontAwesomeVersion', 4);

        $this->html .= trans("laravel-share::laravel-share-fa$fontAwesomeVersion.$provider", [
            'url' => $url,
            'class' => key_exists('class', $this->options) ? $this->options['class'] : '',
            'id' => key_exists('id', $this->options) ? $this->options['id'] : '',
        ]);

    }

    /**
     * Optionally Set custom prefix and/or suffix
     *
     * @param $prefix
     * @param $suffix
     */
    protected function setPrefixAndSuffix($prefix, $suffix)
    {
        if (!is_null($prefix)) {
            $this->prefix = $prefix;
        }

        if (!is_null($suffix)) {
            $this->suffix = $suffix;
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Command;

use Mautic\CoreBundle\Command\ModeratedCommand;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use MauticPlugin\MauticEnhancerBundle\Helper\EnhancerHelper;
use MauticPlugin\MauticEnhancerBundle\Integration\CorrectAddressIntegration as CAI;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZipArchive;

/**
 * Class UpdateCorrectAddressDataCommand.
 */
class UpdateCorrectAddressDataCommand extends ModeratedCommand
{
    protected function configure()
    {
        $this->setName('mautic:integration:enhancer:updatecorrectaddress');
        $this->setDescription('Installs the latest data files available from Expirian');
        $this->setHelp(
            'This command will download and replace the data files used by CorrectAddress. These are proprietary files available from Expirian'
        );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $output->writeln('<info>Starting Expirian data update.</info>');

            /** @var IntegrationHelper $integrationHelper */
            $integrationHelper = $this->getContainer()->get('mautic.helper.integration');
            $enhancerHelper    = new EnhancerHelper($integrationHelper);
            /** @var \MauticPlugin\MauticEnhancerBundle\Helper\EnhancerHelper $correctAddress */
            $correctAddress = $enhancerHelper->getIntegration('CorrectAddress');
            $settings       = $correctAddress->getIntegrationSettings()->getFeatureSettings();
            $keys           = $correctAddress->getKeys();

            if (function_exists('ssh2_connect')) {
                $sconn = call_user_func('ssh2_connect', $settings[CAI::CA_REMOTE_HOST]);
                call_user_func('ssh2_auth_password', $sconn, $keys[CAI::CA_REMOTE_USER], $keys[CAI::CA_REMOTE_PSWD]);
                $sftp = call_user_func('ssh2_sftp', $sconn);
            } else {
                throw new \Exception(
                    'Required ssh2 extension is not installed',
                    -1
                );
            }
            $output->writeln('<info>SFTP connection established, downloading data file</info>');

            $source = 'ssh2.sftp://'.intval($sftp).$settings[CAI::CA_REMOTE_PATH].'/'.$settings[CAI::CA_REMOTE_FILE];

            $buffer = tempnam(sys_get_temp_dir(), 'ca_'.\date('Y-m-d'));
            if (file_exists($buffer)) {
                unlink($buffer);
            }
            $dest = $buffer.'.zip';
            $rfp  = fopen($source, 'r');
            $wfp  = fopen($dest, 'w');

            $reads = 0;
            do {
                if (!fwrite($wfp, fread($rfp, 8388608))) {
                    break;
                }
                ++$reads;
                if (0 === ($reads % 100)) {
                    $output->write('.');
                }
            } while (true);
            $output->writeln('<info>Copied data archive to '.$dest.' on local filesystem.</info>');

            //extract the new files
            $extractor = new ZipArchive();
            $extractor->open($dest, ZipArchive::CHECKCONS);
            $extractor->extractTo($buffer);
            if ($extractor->close() && is_dir($buffer)) {
                unlink($dest);
                $output->writeln('<info>Archive extracted to '.$buffer.'.</info>');

                if (is_dir($settings[CAI::CA_CORRECTA_DATA].'_bak')) {
                    $this->cleanDir($settings[CAI::CA_CORRECTA_DATA].'_bak');
                    $output->writeln('<info>'.$settings[CAI::CA_CORRECTA_DATA].'_bak cleaned.</info>');
                }
                rename($settings[CAI::CA_CORRECTA_DATA], $settings[CAI::CA_CORRECTA_DATA].'_bak');

                if (rename($buffer, $settings[CAI::CA_CORRECTA_DATA])) {
                    $output->writeln('<info>Expirian data update complete.</info>');

                    return 0;
                } else {
                    $output->writeln(
                        '<error>Failed to move '.$buffer.' to '.$settings[CAI::CA_CORRECTA_DATA].'</error>'
                    );

                    return 1;
                }
            } else {
                $output->writeln('<error>Failed to unzip '.$dest.' to '.$buffer.'</error>');

                return 1;
            }
        } catch (\Exception $e) {
            $output->writeln('<error>Failed to update data: '.$e->getMessage().'</error>');
            $output->write($e->getTraceAsString());
            $output->writeln('');

            return $e->getCode();
        }
    }

    /**
     * @param string $dirName
     *
     * @return bool
     */
    protected function cleanDir($dirName)
    {
        if (file_exists($dirName)) {
            if (is_dir($dirName)) {
                $rm_path = new \RecursiveDirectoryIterator($dirName, \RecursiveDirectoryIterator::SKIP_DOTS);
                $rm_ls   = new \RecursiveIteratorIterator($rm_path, \RecursiveIteratorIterator::CHILD_FIRST);

                foreach ($rm_ls as $rm_file) {
                    $rm_file->isDir() ? rmdir($rm_file->getRealPath()) : unlink($rm_file->getRealPath());
                }
                rmdir($dirName);
            } else {
                unlink($dirName);
            }
        } else {
            mkdir($dirName, 0755, true);
            rmdir($dirName);
        }

        return true;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Entity;

use Mautic\CoreBundle\Entity\CommonRepository;
use MauticPlugin\MauticEnhancerBundle\Model\GenderNameModel;

class PluginEnhancerGenderNameRepository extends CommonRepository
{
    /**
     * @return int
     */
    public function verifyReferenceTable()
    {
        try {
            $sql     = 'SELECT COUNT(*) FROM '.$this->getTableName();
            $results = $this->getEntityManager()->getConnection()->fetchArray($sql);

            return $results[0][0];
        } catch (\Exception $e) {
            $this->createReferenceTable();

            return 0;
        }
    }

    public function getTableName()
    {
        return MAUTIC_TABLE_PREFIX.PluginEnhancerGenderName::TABLE_NAME;
    }

    public function createReferenceTable()
    {
        $table_name = $this->getTableName();

        $sql = <<<EOSQL
CREATE TABLE `$table_name` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `gender` varchar(1) NOT NULL,
  `probability` float(7,4) NOT NULL,
  `count` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `key_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
EOSQL;
        try {
            $this->getEntityManager()->getConnection()->exec($sql);
        } catch (\Exception $e) {
            exit('Failed to create '.$table_name.': '.$e->getMessage());
        }
    }

    /**
     * @param GenderNameModel $model
     *
     * @throws \Doctrine\ORM\OptimisticLockException
     */
    public function updateReferenceTable(GenderNameModel $model)
    {
        $this->emptyReferenceTable();
        $em = $this->getEntityManager();

        $preppedData = $model->prepareGenderNameData();
        $batchSize   = 200;
        $count       = 0;

        echo 'Inserting data'.PHP_EOL;
        foreach ($preppedData as $datum) {
            $record = new PluginEnhancerGenderName();
            $record
                ->setName($datum['name'])
                ->setGender($datum['gender'])
                ->setProbability($datum['probability'])
                ->setCount($datum['count']);
            $em->persist($record);
            ++$count;
            if (0 === ($count % $batchSize)) {
                $em->flush();
                $em->clear();
            }
        }
        $em->flush();
        $em->clear();
    }

    public function emptyReferenceTable()
    {
        try {
            $sql = 'TRUNCATE '.$this->getTableName();
            $this->getEntityManager()->getConnection()->exec($sql);
        } catch (\Exception $e) {
            $this->createReferenceTable();
        }
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Model;

use Doctrine\ORM\EntityRepository;
use GuzzleHttp\Client;
use Mautic\CoreBundle\Model\AbstractCommonModel;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerAnura;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerAnuraRepository;
use MauticPlugin\MauticEnhancerBundle\Integration\AnuraIntegration;

class AnuraModel extends AbstractCommonModel
{
    /**
     * @var string
     */
    protected $endpoint;

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

    /**
     * @param AnuraIntegration $integration
     */
    public function setup(AnuraIntegration $integration)
    {
        $keys           = $integration->getKeys();
        $this->endpoint = $keys['endpoint'];
        $this->instance = $keys['instance'];
    }

    /**
     * @return PluginEnhancerAnuraRepository|EntityRepository
     */
    public function getRepository()
    {
        return $this->em->getRepository($this->getEntityName());
    }

    /**
     * @return string
     */
    public function getEntityName()
    {
        return PluginEnhancerAnura::class;
    }

    /**
     * @param $ipAddress
     * @param $userAgent
     *
     * @return string
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function getResult($ipAddress, $userAgent)
    {
        /** @var PluginEnhancerAnura $record */
        $record = $this->getRepository()->findByIpAndUserAgent($ipAddress, $userAgent);

        if (null === $record || 'failed' === $record->getResult()) {
            //perform lookup, save result
            $httpClient = new Client();

            $payload = [
                'instance' => $this->instance,
                'ip'       => $ipAddress,
                'ua'       => $userAgent,
            ];

            try {
                $response = $httpClient->request('GET', $this->endpoint, ['query' => $payload]);
                $result   = json_decode($response->getBody()->getContents(), true);
            } catch (\Exception $e) {
                $this->logger->error($e->getMessage());
                $result = ['result' => 'failed'];
            }

            if (null === $record) {
                $record = new PluginEnhancerAnura();
                $record
                    ->setIpAddress($ipAddress)
                    ->setUserAgent($userAgent);
            }
            $record
                ->setDateAdded(new \DateTime())
                ->setResult($result['result']);

            $this->getRepository()->saveEntity($record);
        }

        return $record->getResult();
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;

/**
 * Class XverifyIntegration.
 *
 * Allow verification of a lead's email address using X-verify on a configurable
 * list of campaigns.
 *
 * @todo Hook up to the plugin stats
 */
class XverifyIntegration extends AbstractEnhancerIntegration
{
    /* @var \MauticPlugin\MauticEnhancerBundle\Integration\NonFreeEnhancerTrait */
    use NonFreeEnhancerTrait;

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'keys';
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'Xverify';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Email Validation with '.$this->getName();
    }

    /**
     * @return array
     */
    public function getRequiredKeyFields()
    {
        return [
            'server' => 'mautic.enhancer.integration.xverify.server.label',
            'apikey' => 'mautic.enhancer.integration.xverify.apikey.label',
        ];
    }

    /**
     * Get the array key for clientId.
     *
     * @return string
     */
    public function getClientIdKey()
    {
        return 'mautic.enhancer.integration.xverify.server.label';
    }

    /**
     * Get the array key for client secret.
     *
     * @return string
     */
    public function getClientSecretKey()
    {
        return 'mautic.enhancer.integration.xverify.apikey.label';
    }

    /**
     * @param array $settings
     *
     * @return array
     */
    public function getAvailableLeadFields($settings = [])
    {
        return [
            'email'     => ['type' => 'text'],
            'homephone' => ['type' => 'text'],
            'cellphone' => ['type' => 'text'],
            'workphone' => ['type' => 'text'],
        ];
    }

    /**
     * @return array|mixed
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'email_valid'     => [
                'label' => 'Xverify Email Valid',
                'type'  => 'boolean',
            ],
            'workphone_valid' => [
                'label' => 'Xverify Work Phone Valid',
                'type'  => 'boolean',
            ],
            'cellphone_valid' => [
                'label' => 'Xverify Mobile Phone Valid',
                'type'  => 'boolean',
            ],
            'homephone_valid' => [
                'label' => 'Xverify Home Phone Valid',
                'type'  => 'boolean',
            ],
        ];
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $persist = false;
        if (!empty($lead)) {
            $settings            = $this->getIntegrationSettings()->getFeatureSettings();
            $contactFieldMapping = $settings['leadFields'];
            $keys                = $this->getKeys();

            $params = [
                'apikey' => $keys['apikey'],
                'domain' => $keys['server'],
                'type'   => 'json',
            ];

            foreach ($contactFieldMapping as $integrationFieldName => $mauticFieldName) {
                $fieldToUpdate = $integrationFieldName.'_valid'; //which validation field will we update?
                try {
                    $fieldValue = $lead->getFieldValue($mauticFieldName);
                    if (!empty($fieldValue)) {
                        switch ($integrationFieldName) {
                            case 'cellphone':
                            case 'homephone':
                            case 'workphone':
                                // phone API call
                                $service  = 'phone';
                                $fieldKey = 'phone';
                                if (is_null(
                                    $lead->getFieldValue($fieldToUpdate)
                                )) { // only if we havent checked already
                                    $response = $this->makeCall($service, $params, $fieldKey, $fieldValue);
                                    $this->applyCost($lead);
                                    $persist = true;
                                    $status  = $this->getResponseStatus($response, $fieldKey);
                                    if (!is_null($status)) {
                                        $lead->addUpdatedField($fieldToUpdate, $status);
                                        $this->logger->addDebug(
                                            'XVERIFY: verification values to update: '.$fieldToUpdate.' => '.$status
                                        );
                                    }
                                }
                                break;

                            case 'email':
                                // email API call
                                $service  = 'emails';
                                $fieldKey = 'email';
                                if (is_null(
                                    $lead->getFieldValue($fieldToUpdate)
                                )) { // only if we havent checked already
                                    $response = $this->makeCall($service, $params, $fieldKey, $fieldValue);
                                    $this->applyCost($lead);
                                    $persist = true;
                                    $status  = $this->getResponseStatus($response, $fieldKey);
                                    if (!is_null($status)) {
                                        $lead->addUpdatedField($fieldToUpdate, $status, null);
                                        $this->logger->addDebug(
                                            'XVERIFY: verification values to update: '.$fieldToUpdate.' => '.$status
                                        );
                                    }
                                }
                                break;

                            default:      // no matching case
                                continue; // dont do anything - go to next loop iteration
                        }
                    }
                } catch (\Exception $e) {
                    $this->logIntegrationError($e);

                    return false;
                }
            }
        }

        return $persist;
    }

    /**
     * @param $service
     * @param $params
     * @param $fieldKey
     * @param $fieldValue
     *
     * @return mixed|string
     */
    protected function makeCall($service, $params, $fieldKey, $fieldValue)
    {
        // the response object has a lot of value-add data, that may help to enhance lead data, for a future feature request

        // @todo - Update to use Guzzle.

        // set a timeout default to 20 seconds
        $settings = ['curl_options' => [CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_TIMEOUT => 3]];

        $url      = "http://www.xverify.com/services/$service/verify/?$fieldKey=$fieldValue"; // valid entries for service: "emails", "phone", "address"
        $response = $this->makeRequest(
            $url,
            ['append_to_query' => $params],
            'GET',
            $settings
        );

        return $response;
    }

    /**
     * @param $response
     * @param $fieldKey
     *
     * @return int|null
     */
    protected function getResponseStatus($response, $fieldKey)
    {
        $status = null; // default because if we cant get it, its because its invalid
        if (!empty($response) && !empty($fieldKey)) {
            if (isset($response[$fieldKey]['status']) && !empty($response[$fieldKey]['status'])) {
                $status = $response[$fieldKey]['status'] == 'valid' ? 1 : 0;
            }
        }

        return $status;
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: nbush
 * Date: 2/11/19
 * Time: 11:56 AM.
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Doctrine\Common\Collections\ArrayCollection;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\UtmTag;

class TrustedFormIntegration extends AbstractEnhancerIntegration
{
    /* @var \MauticPlugin\MauticEnhancerBundle\Integration\NonFreeEnhancerTrait */
    use NonFreeEnhancerTrait {
        appendToForm as appendNonFreeFields;
    }

    /** @var string */
    const CERT_REAL_HOST = 'cert.trustedform.com';

    /** @var string */
    const CERT_URL_FIELD = 'xx_trusted_form_cert_url';

    /**
     * @return string
     */
    public function getName()
    {
        return 'TrustedForm';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Trusted Form';
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $persist = false;
        if ($lead->getFieldValue(self::CERT_URL_FIELD) && !$lead->getFieldValue('trusted_form_created_at')) {
            $trustedFormClaim = $lead->getFieldValue(self::CERT_URL_FIELD);
            $parts            = parse_url($trustedFormClaim);
            if ('https' !== $parts['scheme'] || self::CERT_REAL_HOST !== $parts['host']) {
                $this->logger->warning('Not Processing Suspicious TrustedForm URL: '.$trustedFormClaim);

                return false;
            }

            $parameters = $this->getFingers($lead);
            if ($lead->getId()) {
                $parameters['reference'] = ''.$lead->getId();
                $identifier              = $lead->getId();
            } else {
                $identifier = $lead->getEmail();
            }

            /** @var ArrayCollection|array $utmData */
            $utmData = $lead->getUtmTags();
            // Get the UTM Tags as an array of entities.
            if ($utmData instanceof ArrayCollection) {
                $utmData = $utmData->toArray();
            }
            if (is_array($utmData) && !empty($utmData)) {
                // Get the last UTM Source.
                $utmSources = [];
                /** @var UtmTag $utmTag */
                foreach ($utmData as $utmTag) {
                    if (!empty(trim($utmTag->getUtmSource()))) {
                        $utmSources[$utmTag->getDateAdded()->getTimestamp()] = $utmTag->getUtmSource();
                    }
                }
                ksort($utmSources);
                $parameters['vendor'] = array_pop($utmSources);
            }

            $authKeys = $this->getKeys();
            $settings = [
                'authorize_session' => true,
                'content_type'      => 'application/json',
                'encode_parameters' => 'json',
                'headers'           => ['Accept: application/json'],
                'return_raw'        => true,
                'curl_options'      => [
                    CURLOPT_USERPWD        => "$authKeys[username]:$authKeys[password]",
                    CURLOPT_CONNECTTIMEOUT => 1,
                    CURLOPT_TIMEOUT        => 10,
                ],
            ];

            for ($try = 0; $try < 3; ++$try) {
                $response = $this->makeRequest($trustedFormClaim, $parameters, 'post', $settings);
                if (!$response || !isset($response->body)) {
                    $this->logger->error(
                        'TrustedForm: Failed to respond with lead '.$identifier.'. Body: '.(!empty($response->body) ? $response->body : 'null')
                    );
                } else {
                    $data = json_decode($response->body);
                    switch ($response->code) {
                        case 200:
                        case 201:

                            // Set new value for xx_trusted_form_cert_url from $data->xx_trusted_form_cert_url
                            if (
                                !empty($data->{self::CERT_URL_FIELD})
                                && $data->{self::CERT_URL_FIELD} !== $lead->getFieldValue(self::CERT_URL_FIELD)
                            ) {
                                $lead->addUpdatedField(self::CERT_URL_FIELD, $data->{self::CERT_URL_FIELD});
                                $persist = true;
                            }

                            // Set new value for trusted_form_created_at from created_at
                            if (
                                !empty($data->created_at)
                                && $data->created_at !== $lead->getFieldValue('trusted_form_created_at')
                            ) {
                                $lead->addUpdatedField('trusted_form_created_at', $data->created_at);
                                $persist = true;
                            }

                            // Set new value for trusted_form_expires_at from expires_at
                            if (
                                !empty($data->expires_at)
                                && $data->expires_at !== $lead->getFieldValue('trusted_form_expires_at')
                            ) {
                                $lead->addUpdatedField('trusted_form_expires_at', $data->expires_at);
                                $persist = true;
                            }

                            // Set new value for trusted_form_share_url from share_url
                            if (
                                !empty($data->share_url)
                                && $data->share_url !== $lead->getFieldValue('trusted_form_share_url')
                            ) {
                                $lead->addUpdatedField('trusted_form_share_url', $data->share_url);
                                $persist = true;
                            }
                            $this->logger->info(
                                'TrustedForm: Contact '.$identifier.' '.(!$persist ? 'NOT ' : '').'updated. '.(!empty($data->message) ? $data->message : '')
                            );

                            if (!empty($data->warnings)) {
                                foreach ($data->warnings as $warning) {
                                    $this->logger->error('TrustedForm warning with contact '.$identifier.' '.$warning);
                                }
                            }
                            break 2;

                        case 404:
                            $this->logger->error(
                                'TrustedForm: Invalid Certificate: '.(!empty($data->message) ? $data->message : '')
                            );
                            break 2;

                        case 401:
                        case 403:
                            $this->logger->error(
                                'TrustedForm: Authentication Failure: '.(!empty($data->message) ? $data->message : '')
                            );
                            break 2;

                        case 502:
                        case 503:
                            $this->logger->error('TrustedForm: Exceeded rate limit (try '.($try + 1).'/3).');
                            // 100ms delay before retrying.
                            usleep(100000);
                            break;

                        default:
                            $this->logger->error(
                                'TrustedForm: Unrecognized response code: '.(!empty($data->code) ? $data->code : '').' '.(!empty($data->message) ? $data->message : '')
                            );
                            break 2;
                    }
                }
            }
        }

        return $persist;
    }

    /**
     * Creates an array of fingerprintable fields.
     *
     * @param Lead $lead
     *
     * @return array
     */
    protected function getFingers(Lead $lead)
    {
        $fingers = [];
        //Trusted form "should" convert these...
        if ($lead->getEmail()) {
            $fingers['email'] = strtolower($lead->getEmail());
        }

        if ($lead->getPhone()) {
            $fingers['phone'] = preg_replace('/\D/', '', $lead->getPhone());
        }

        if ($lead->getMobile()) {
            $fingers['mobile'] = preg_replace('/\D/', '', $lead->getMobile());
        }

        return $fingers;
    }

    /**
     * Get the type of authentication required for this API.  Values can be none, key, oauth2 or callback
     * (will call $this->authenticationTypeCallback).
     *
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'basic';
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        $this->appendNonFreeFields($builder, $data, $formArea, true);
    }

    /**
     * @returns array[]
     */
    protected function getEnhancerFieldArray()
    {
        return [
            self::CERT_URL_FIELD      => [
                'type'  => 'url',
                'label' => 'Trusted Form Cert',
            ],
            'trusted_form_created_at' => [
                'type'  => 'datetime',
                'label' => 'Trusted Form Cert Claimed',
            ],
            'trusted_form_expires_at' => [
                'type'  => 'datetime',
                'label' => 'Trusted Form Cert Expires',
            ],
            'trusted_form_share_url'  => [
                'type'  => 'url',
                'label' => 'Trusted Form Shareable Cert',
            ],
        ];
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

/**
 * Class RandomIntegration.
 */
class RandomIntegration extends AbstractEnhancerIntegration
{
    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'none';
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'Random';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Generate Random Number Token';
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea && !isset($data['random_field_name'])) {
            $builder->add(
                'random_field_name',
                TextType::class,
                [
                    'label' => $this->translator->trans('mautic.enhancer.integration.random.field_name.label'),
                    'attr'  => [
                        'tooltip' => $this->translator->trans('mautic.enhancer.integration.random.field_name.tooltip'),
                    ],
                    'data'  => '',
                ]
            )
                ->add(
                    'autorun_enabled',
                    HiddenType::class,
                    [
                        'data' => true,
                    ]
                );
        }
    }

    /**
     * @return array[]
     */
    protected function getEnhancerFieldArray()
    {
        $settings = $this->getIntegrationSettings()->getFeatureSettings();

        return [
            $settings['random_field_name'] => [
                'label' => 'Random Value',
                'type'  => 'number',
            ],
        ];
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        if (!empty($lead)) {
            $settings = $this->getIntegrationSettings()->getFeatureSettings();

            if (!$lead->getFieldValue($settings['random_field_name'])) {
                $lead->addUpdatedField($settings['random_field_name'], rand(1, 100));

                return true;
            }
        }

        return false;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Exception;
use Mautic\CoreBundle\Helper\PhoneNumberHelper;
use Mautic\CoreBundle\Model\AbstractCommonModel;
use Mautic\LeadBundle\Entity\Lead;
use MauticPlugin\MauticEnhancerBundle\Entity\PluginEnhancerBlacklist;
use MauticPlugin\MauticEnhancerBundle\Model\BlacklistModel;

/**
 * Class BlacklistIntegration.
 */
class BlacklistIntegration extends AbstractEnhancerIntegration
{
    /* @var \MauticPlugin\MauticEnhancerBundle\Integration\NonFreeEnhancerTrait */
    use NonFreeEnhancerTrait {
        appendToForm as appendNonFreeFields;
    }

    /** @var int Default number of minutes to consider a blacklist lookup valid, before making the call again. */
    const AGE_DEFAULT = 1440;

    /** @var string Default URL to check a single Phone number against The Blacklist service */
    const ENDPOINT_DEFAULT = 'https://api.theblacklist.click/standard/api/v1';

    /**
     * @var BlacklistModel
     */
    protected $integrationModel;

    /** @var PhoneNumberHelper */
    private $phoneHelper;

    /**
     * @return string
     */
    public function getName()
    {
        return 'Blacklist';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Blacklist Check';
    }

    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'keys';
    }

    /**
     * @return array
     */
    public function getRequiredKeyFields()
    {
        return [
            'key' => $this->translator->trans('mautic.enhancer.integration.blacklist.key.label'),
        ];
    }

    /**
     * @return array
     */
    public function getSupportedFeatures()
    {
        return ['push_lead'];
    }

    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array                                        $data
     * @param string                                       $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'endpoint',
                'text',
                [
                    'label'      => $this->translator->trans('mautic.enhancer.integration.blacklist.endpoint.label'),
                    'data'       => !empty($data['endpoint']) ? $data['endpoint'] : self::ENDPOINT_DEFAULT,
                    'required'   => true,
                    'label_attr' => ['class' => 'control-label'],
                    'attr'       => [
                        'class'   => 'form-control',
                        'tooltip' => $this->translator->trans('mautic.enhancer.integration.blacklist.endpoint.tooltip'),
                    ],
                ]
            );

            $builder->add(
                'age',
                'text',
                [
                    'label'      => $this->translator->trans('mautic.enhancer.integration.blacklist.age.label'),
                    'data'       => !empty($data['age']) ? $data['age'] : self::AGE_DEFAULT,
                    'required'   => true,
                    'label_attr' => ['class' => 'control-label'],
                    'attr'       => [
                        'class'   => 'form-control',
                        'tooltip' => $this->translator->trans('mautic.enhancer.integration.blacklist.age.tooltip'),
                    ],
                ]
            );

            $this->appendNonFreeFields($builder, $data, $formArea, true);
        }
    }

    /**
     * @return array|mixed
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'blacklist_result'   => [
                'label' => $this->translator->trans('mautic.enhancer.integration.blacklist.blacklist_result.label'),
                'type'  => 'boolean',
                'group' => 'enhancement',
            ],
            'blacklist_code'     => [
                'label' => $this->translator->trans('mautic.enhancer.integration.blacklist.blacklist_code.label'),
                'group' => 'enhancement',
            ],
            'blacklist_wireless' => [
                'label' => $this->translator->trans('mautic.enhancer.integration.blacklist.blacklist_wireless.label'),
                'type'  => 'boolean',
                'group' => 'enhancement',
            ],
        ];
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function doEnhancement(Lead $lead)
    {
        $save = false;

        if (!empty($lead)) {
            // Only perform lookup on the first occurring phone number, home and then (if not found) mobile.
            // Presumably phone numbers should be validated by this point, but let's make sure, just in case.
            $phone = $this->phoneValidate($lead->getPhone());
            if (!$phone) {
                $phone = $this->phoneValidate($lead->getMobile());
            }
            if ($phone) {
                $settings   = $this->getIntegrationSettings()->getFeatureSettings();
                $ageMinutes = isset($settings['age']) ? intval($settings['age']) : self::AGE_DEFAULT;

                try {
                    /** @var PluginEnhancerBlacklist $record */
                    $record = $this->getModel()->getRecord($phone, $ageMinutes);
                } catch (Exception $exception) {
                    $this->handleEnchancerException('Blacklist', $exception);
                    $this->logger->error('Blacklist Enhancer: '.$exception->getMessage());
                }
                if ($record) {
                    $lead->addUpdatedField('blacklist_result', $record->getResult());
                    $lead->addUpdatedField('blacklist_code', $record->getCode());
                    $lead->addUpdatedField('blacklist_wireless', $record->getWireless());
                    $save = true;
                }
            }
        }

        return $save;
    }

    /**
     * Get the old E164 format, regardless of how the number came in.
     *
     * @param $phone
     *
     * @return string
     */
    private function phoneValidate($phone)
    {
        $result = null;
        $phone  = trim($phone);
        if (!empty($phone)) {
            if (!$this->phoneHelper) {
                $this->phoneHelper = new PhoneNumberHelper();
            }
            try {
                $phone = $this->phoneHelper->format($phone);
                if (!empty($phone)) {
                    $result = $phone;
                }
            } catch (\Exception $e) {
            }
        }

        return $result;
    }

    /**
     * @return BlacklistModel|AbstractCommonModel
     */
    public function getModel()
    {
        if (!isset($this->integrationModel)) {
            $this->integrationModel = $this->factory->getModel('enhancer.blacklist');
            $this->integrationModel->setup($this);
        }

        return $this->integrationModel;
    }
}


<?php

/*
 * @copyright   2018 Mautic Contributors. All rights reserved
 * @author      Digital Media Solutions, LLC
 *
 * @link        http://mautic.org
 *
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace MauticPlugin\MauticEnhancerBundle\Integration;

use Mautic\LeadBundle\Entity\Lead;

/**
 * Class AgeFromBirthdateIntegration.
 */
class AgeFromBirthdateIntegration extends AbstractEnhancerIntegration
{
    /**
     * @return string
     */
    public function getAuthenticationType()
    {
        return 'none';
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'AgeFromBirthdate';
    }

    /**
     * @return string
     */
    public function getDisplayName()
    {
        return 'Age From Date of Birth Data Enhancer';
    }

    /**
     * @return array[]
     */
    protected function getEnhancerFieldArray()
    {
        return [
            'afb_age' => [
                'label' => 'Age',
                'type'  => 'number',
            ],
        ];
    }

    /**
     * @param \Symfony\Component\Form\FormBuilder $builder
     * @param array                               $data
     * @param string                              $formArea
     */
    public function appendToForm(&$builder, $data, $formArea)
    {
        if ('features' === $formArea) {
            $builder->add(
                'autorun_enabled',
                \Mautic\CoreBundle\Form\Type\YesNoButtonGroupType::class,
                [
                    'label'       => $this->translator->trans('mautic.enhancer.autorun.label'),
                    'data'        => !isset($data['autorun_enabled']) ? false : $data['autorun_enabled'],
                    'required'    => false,
                    'empty_value' => false,
                    'label_attr'  => ['class' => 'control-label'],
                    'attr'        => [
                        'class'   => 'form-control',
                        'tooltip' => $this->translator->trans('mautic.enhancer.autorun.tooltip'),
                    ],
                ]
            );
        }
    }

    /**
     * @param Lead $lead
     *
     * @return bool
     */
    public function doEnhancement(Lead $lead)
    {
        $save = false;
        $this->logger->info('AgeFromBirthdate:doEnhancemet');
        // Get original field values.
        $dobStr = $dobOrig = $lead->getFieldValue('dob');
        $day    = $dayOrig = $lead->getFieldValue('dob_day');
        $month  = $monthOrig = $lead->getFieldValue('dob_month');
        $year   = $yearOrig = $lead->getFieldValue('dob_year');
        $age    = $ageOrig = $lead->getFieldValue('afb_age');
        $today  = new \DateTime();
        try {
            if ($dobOrig instanceof \DateTime) {
                // For BC.
                $dobStr = $dobOrig = $dobOrig->format('Y-m-d');
            }
            if (
                $dobStr
                && '0000-00-00' !== $dobStr
                && $today->format('Y-m-d') != $dobStr
            ) {
                // DOB field to date/month/day fields.
                $dob   = new \DateTime(is_numeric($dobStr) ? '@'.$dobStr : $dobStr);
                $day   = (int) $dob->format('d');
                $month = (int) $dob->format('m');
                $year  = (int) $dob->format('Y');
            } elseif ($yearOrig) {
                // Date/month/day fields to DOB field with normalization.
                $year = (int) $yearOrig;
                if ($year) {
                    $day   = max(1, min(31, (int) $dayOrig));
                    $month = max(1, min(12, (int) $monthOrig));
                    $dob   = new \DateTime(sprintf('%04d-%02d-%02d', $year, $month, $day));
                }
            } elseif ($ageOrig) {
                // @todo - Support age back to DOB estimation.
            }
        } catch (\Exception $e) {
            // Allow DateTime to fail gracefully.
        }
        // Generate age if DOB was found valid.
        try {
            if (isset($dob) && $dob) {
                $yearDiff = (int) $today->diff($dob)->y;
                if ($yearDiff > -1 && $yearDiff < 120) {
                    $age    = $yearDiff;
                    $dobStr = $dob->format('Y-m-d');
                }
            }
        } catch (\Exception $e) {
            // Dont write dob fields because weirdness in what was sent
            return false;
        }
        // See if any field values changed (intentionally not type checking).
        if ($dobStr && $dobOrig != $dobStr) {
            $lead->addUpdatedField('dob', $dobStr, $dobOrig);
            $save = true;
        }
        if ($day && $dayOrig != $day) {
            $lead->addUpdatedField('dob_day', $day, $dayOrig);
            $save = true;
        }
        if ($month && $monthOrig != $month) {
            $lead->addUpdatedField('dob_month', $month, $monthOrig);
            $save = true;
        }
        if ($year && $yearOrig != $year) {
            $lead->addUpdatedField('dob_year', $year, $yearOrig);
            $save = true;
        }
        if ($age && $ageOrig != $age) {
            $lead->addUpdatedField('afb_age', $age, $ageOrig);
            $save = true;
        }

        return $save;
    }
}