<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\shell;

use Symfony\Component\VarDumper\Caster\Caster;
use yii\db\ActiveRecord;

/**
 * YiiCaster provides wrapper for casters of psysh
 *
 * @author Daniel Gomez Pan <pana_1990@hotmail.com>
 * @since 2.0
 */
class YiiCaster
{
    /**
     * Get an array representing the properties of a model.
     *
     * @param \yii\db\ActiveRecord $model
     * @return array
     */
    public static function castModel(ActiveRecord $model)
    {
        $attributes = array_merge(
            $model->getAttributes(), $model->getRelatedRecords()
        );
        $results = [];
        foreach ($attributes as $key => $value) {
            $results[Caster::PREFIX_VIRTUAL.$key] = $value;
        }
        return $results;
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\shell;

use yii\console\Controller;
use Psy\Shell;
use Psy\Configuration;

/**
 * Runs interactive shell. That is especially useful when developing an application and you want to try
 * some method of your code.
 *
 * @author Daniel Gomez Pan <pana_1990@hotmail.com>
 * @since 2.0
 */
class ShellController extends Controller
{
    /**
     * @var array include file(s) before starting tinker shell
     */
    public $include = [];

    /**
     * @inheritdoc
     */
    public function options($actionID)
    {
        return array_merge(parent::options($actionID), [
            'include'
        ]);
    }

    /**
     * Runs interactive shell
     */
    public function actionIndex()
    {
        $config = new Configuration;
        $config->getPresenter()->addCasters(
            $this->getCasters()
        );
        $shell = new Shell($config);
        $shell->setIncludes($this->include);
        $shell->run();
    }

    /**
     * @return array casters for psysh
     */
    protected function getCasters()
    {
        return [
            'yii\db\ActiveRecord' => 'yii\shell\YiiCaster::castModel',
        ];
    }
}


<?php

namespace Axllent\Scss;

use Axllent\Scss\Extensions\SourceMapGenerator;
use Leafo\ScssPhp\Compiler;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SilverStripe\Assets\FileNameFilter;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Flushable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\View\Requirements_Backend;
use SilverStripe\Assets\Storage\GeneratedAssetHandler;

/**
 * Scssphp CSS compiler for SilverStripe
 * ======================================
 *
 * Extension to add leafo/scssphp CSS compiler to SilverStripe
 *
 * Usage: See README.md
 *
 * License: MIT-style license http://opensource.org/licenses/MIT
 * Authors: Techno Joy development team (www.technojoy.co.nz)
 */

class ScssCompiler extends Requirements_Backend
{
    /**
     * @config
     */
    private static $variables = [];

    private static $theme_dir = false;

    private static $sourcemap = 'file';

    private static $processed_files = [];

    public function __construct()
    {
        $this->config = Config::inst();

        $this->asset_handler = $this->getAssetHandler();

        $this->file_name_filter = FileNameFilter::create();

        $this->is_dev = Director::isDev();
    }

    /**
     * Gets the default backend storage for generated files
     *
     * @return GeneratedAssetHandler
     */
    public function getAssetHandler()
    {
        return Injector::inst()->get(GeneratedAssetHandler::class);
    }

    /**
     * Register the given stylesheet into the list of requirements.
     * Processes *.scss files if detected and rewrites URLs
     *
     * @param string $file The CSS file to load, relative to site root
     * @param string $media Comma-separated list of media types to use in the link tag (e.g. 'screen,projector')
     */
    public function css($file, $media = null)
    {
        $css_file = $this->processScssFile($file);
        return parent::css($css_file, $media);
    }

    /**
     * Process any scss files and return new filenames
     * @See Requirements_Backend->combineFiles() for options
     */
    public function combineFiles($combinedFileName, $files, $options = array())
    {
        $new_files = [];

        foreach ($files as $file) {
            $new_files[] = $this->processScssFile($file);
        }

        return parent::combineFiles($combinedFileName, $new_files, $options);
    }

    /**
     * Process scss file (if detected) and return new URL
     * @param String (original)
     * @return String (new filename)
     */
    public function processScssFile($file)
    {
        if (!preg_match('/\.scss$/', $file)) { // Not a scss file
            return $file;
        } elseif (!empty(self::$processed_files[$file])) { // already processed
            return self::$processed_files[$file];
        }

        $scss_file = $file;

        // return if not a file
        if (!is_file(Director::getAbsFile($scss_file))) {
            self::$processed_files[$file] = $file;
            return $file;
        }

        // Generate a new CSS filename that includes the original path to avoid naming conflicts.
        // eg: themes/site/css/file.scss becomes themes-site-css-file.css
        $url_friendly_css_name = $this->file_name_filter->filter(
            str_replace('/', '-', preg_replace('/\.scss$/i', '', $scss_file))
        ) . '.css';

        $css_file = $this->getCombinedFilesFolder() . '/' . $url_friendly_css_name;

        $output_file = $this->asset_handler->getContentURL($css_file);

        if (
            is_null($output_file) ||
            $this->is_dev &&
            (
                filemtime(Director::makeRelative($output_file)) < filemtime(Director::getAbsFile($scss_file)) ||
                isset($_GET['flushstyles'])
            )
        ) {
            $base_url = Director::baseURL();

            $scss_base = dirname($base_url . $scss_file) . '/';

            $scss = new Compiler();

            if ($this->is_dev) {
                $scss->setFormatter('Leafo\ScssPhp\Formatter\Expanded');
            } else {
                $scss->setFormatter('Leafo\ScssPhp\Formatter\Crunched');
            }

            $scss->addImportPath(dirname(Director::getAbsFile($scss_file)) . '/');

            $variables = $this->config->get(__CLASS__, 'variables');

            $variables['BaseURL'] = '"' . $base_url . '"';

            $theme_dir = rtrim($this->config->get(__CLASS__, 'theme_dir'), '/') . '/';
            if ($theme_dir) {
                $variables['ThemeDir'] = '"' . $base_url . rtrim(ltrim($theme_dir, '/'), '/') . '"';
            }

            $scss->setVariables($variables);

            $sourcemap = $this->config->get(__CLASS__, 'sourcemap');
            if ($sourcemap && $this->is_dev && in_array(strtolower($sourcemap), ['inline', 'file'])) {
                $map_options = [
                    'sourceMapRootpath' => $scss_base,
                    'sourceMapBasepath' => dirname(Director::getAbsFile($scss_file)),
                ];

                if (strtolower($sourcemap) == 'inline') {
                    $scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
                    $scss->setSourceMapOptions($map_options);
                } elseif (strtolower($sourcemap) == 'file') {
                    $map_options['sourceMapWriteTo'] = $css_file . '.map';
                    $scss->setSourceMap(new SourceMapGenerator($map_options));
                }
            }

            $scss_filename = basename($scss_file);

            $raw_css = $scss->compile(
                file_get_contents(Director::getAbsFile($scss_file)),
                $scss_filename
            );

            $this->asset_handler->setContent($css_file, $raw_css);
            $output_file = $this->asset_handler->getContentURL($css_file);
        }

        $parsed_file = Director::makeRelative($output_file);

        self::$processed_files[$file] = $parsed_file;
        self::$processed_files[$parsed_file] = $parsed_file;

        return $parsed_file;
    }
}


<?php
namespace Axllent\Scss\Extensions;

use FilesystemIterator;
use SilverStripe\Admin\LeftAndMainExtension;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\View\Requirements;
use Leafo\ScssPhp\SourceMap\SourceMapGenerator as Leafo_SourceMapGenerator;

class SourceMapGenerator extends Leafo_SourceMapGenerator
{
    /**
     * @var array
     */
    private $options;

    public function __construct(array $options = [])
    {
    	parent::__construct($options);
    	// can't access options from the child class, so we'll do it the hard way
        $this->options = array_merge($this->defaultOptions, $options);
    }

    /**
     * Saves the source map to a file
     *
     * @param string $content The content to write
     *
     * @return string URL to saved file
     */
    public function saveMap($content)
    {
        $asset_handler = Requirements::backend()->getAssetHandler();

        $css_file = $this->options['sourceMapWriteTo'];
        $asset_handler->setContent($css_file, $content);
        $url = $asset_handler->getContentURL($css_file);

        $this->options['sourceMapURL'] = $url;
        return $this->options['sourceMapURL'];
    }
}


<?php

namespace Simple\SHM;

class Block
{
    /**
     * Holds the system id for the shared memory block
     *
     * @var int
     * @access protected
     */
    protected $id;

    /**
     * Holds the shared memory block id returned by shmop_open
     *
     * @var int
     * @access protected
     */
    protected $shmid;

    /**
     * Holds the default permission (octal) that will be used in created memory blocks
     *
     * @var int
     * @access protected
     */
    protected $perms = 0644;

    /**
     * Shared memory block instantiation
     *
     * In the constructor we'll check if the block we're going to manipulate
     * already exists or needs to be created. If it exists, let's open it.
     *
     * @access public
     * @param string $id (optional) ID of the shared memory block you want to manipulate
     */
    public function __construct($id = null)
    {
        if($id === null) {
            $this->id = $this->generateID();
        } else {
            $this->id = $id;
        }

        if($this->exists($this->id)) {
            $this->shmid = shmop_open($this->id, "w", 0, 0);
        }
    }

    /**
     * Generates a random ID for a shared memory block
     *
     * @access protected
     * @return int System V IPC key generated from pathname and a project identifier
     */
    protected function generateID()
    {
        $id = ftok(__FILE__, "b");
        return $id;
    }

    /**
     * Checks if a shared memory block with the provided id exists or not
     *
     * In order to check for shared memory existance, we have to open it with
     * reading access. If it doesn't exist, warnings will be cast, therefore we
     * suppress those with the @ operator.
     *
     * @access public
     * @param string $id ID of the shared memory block you want to check
     * @return boolean True if the block exists, false if it doesn't
     */
    public function exists($id)
    {
        $status = @shmop_open($id, "a", 0, 0);
        return $status;
    }

    /**
     * Writes on a shared memory block
     *
     * First we check for the block existance, and if it doesn't, we'll create it. Now, if the
     * block already exists, we need to delete it and create it again with a new byte allocation that
     * matches the size of the data that we want to write there. We mark for deletion,  close the semaphore
     * and create it again.
     *
     * @access public
     * @param string $data The data that you wan't to write into the shared memory block
     */
    public function write($data)
    {
        $size = mb_strlen($data, 'UTF-8');

        if($this->exists($this->id)) {
            shmop_delete($this->shmid);
            shmop_close($this->shmid);
            $this->shmid = shmop_open($this->id, "c", $this->perms, $size);
            shmop_write($this->shmid, $data, 0);
        } else {
            $this->shmid = shmop_open($this->id, "c", $this->perms, $size);
            shmop_write($this->shmid, $data, 0);
        }
    }

    /**
     * Reads from a shared memory block
     *
     * @access public
     * @return string The data read from the shared memory block
     */
    public function read()
    {
        $size = shmop_size($this->shmid);
        $data = shmop_read($this->shmid, 0, $size);

        return $data;
    }

    /**
     * Mark a shared memory block for deletion
     *
     * @access public
     */
    public function delete()
    {
        shmop_delete($this->shmid);
    }

    /**
     * Gets the current shared memory block id
     *
     * @access public
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Gets the current shared memory block permissions
     *
     * @access public
     */
    public function getPermissions()
    {
        return $this->perms;
    }

    /**
     * Sets the default permission (octal) that will be used in created memory blocks
     *
     * @access public
     * @param string $perms Permissions, in octal form
     */
    public function setPermissions($perms)
    {
        $this->perms = $perms;
    }

    /**
     * Closes the shared memory block and stops manipulation
     *
     * @access public
     */
    public function __destruct()
    {
        shmop_close($this->shmid);
    }
}


<?php

declare(strict_types=1);

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2018 Spomky-Labs
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */

namespace AESKW;

trait AESKW
{
    /**
     * The initial value used to wrap the key and check the integrity when unwrapped.
     * The RFC3394 set this value to 0xA6A6A6A6A6A6A6A6
     * The RFC5649 set this value to 0xA65959A6XXXXXXXX (The part with XXXXXXXX is the MLI, depends on the padding).
     *
     * @param string $key             The key
     * @param bool   $padding_enabled Enable padding (RFC5649)
     *
     * @see https://tools.ietf.org/html/rfc3394#section-2.2.3.1
     */
    private static function getInitialValue(string &$key, bool $padding_enabled): string
    {
        if (false === $padding_enabled) {
            return \hex2bin('A6A6A6A6A6A6A6A6');
        }

        $MLI = \mb_strlen($key, '8bit');
        $iv = \hex2bin('A65959A6').self::toXBits(32, $MLI);

        $n = \intval(\ceil($MLI / 8));
        $key = \str_pad($key, 8 * $n, "\0", STR_PAD_RIGHT);

        return $iv;
    }

    private static function checkInitialValue(string &$key, bool $padding_enabled, string $iv): bool
    {
        // RFC3394 compliant
        if ($iv === \hex2bin('A6A6A6A6A6A6A6A6')) {
            return true;
        }

        // The RFC3394 is required but the previous check is not satisfied => invalid
        if (false === $padding_enabled) {
            return false;
        }

        // The high-order half of the AIV according to the RFC5649
        if (\hex2bin('A65959A6') !== self::getMSB($iv)) {
            return false;
        }

        $n = \mb_strlen($key, '8bit') / 8;
        $MLI = \hexdec(\bin2hex(\ltrim(self::getLSB($iv), "\0")));

        if (!(8 * ($n - 1) < $MLI && $MLI <= 8 * $n)) {
            return false;
        }

        $b = 8 * $n - $MLI;
        for ($i = 0; $i < $b; ++$i) {
            if ("\0" !== \mb_substr($key, $MLI + $i, 1, '8bit')) {
                return false;
            }
        }
        $key = \mb_substr($key, 0, $MLI, '8bit');

        return true;
    }

    /**
     * @param string $key The Key to wrap
     */
    private static function checkKeySize(string $key, bool $padding_enabled)
    {
        if (empty($key)) {
            throw new \InvalidArgumentException('Bad key size');
        }
        if (false === $padding_enabled && 0 !== \mb_strlen($key, '8bit') % 8) {
            throw new \InvalidArgumentException('Bad key size');
        }
    }

    /**
     * @param string $kek             The Key Encryption Key
     * @param string $key             The key to wrap
     * @param bool   $padding_enabled If false, the key to wrap must be a sequence of one or more 64-bit blocks (RFC3394 compliant), else the key size must be at least one octet (RFC5649 compliant)
     *
     * @return string The wrapped key
     */
    public static function wrap(string $kek, string $key, bool $padding_enabled = false): string
    {
        $A = self::getInitialValue($key, $padding_enabled);
        self::checkKeySize($key, $padding_enabled);
        $P = \str_split($key, 8);
        $N = \count($P);
        $C = [];

        if (1 === $N) {
            $B = self::encrypt($kek, $A.$P[0]);
            $C[0] = self::getMSB($B);
            $C[1] = self::getLSB($B);
        } elseif (1 < $N) {
            $R = $P;
            for ($j = 0; $j <= 5; ++$j) {
                for ($i = 1; $i <= $N; ++$i) {
                    $B = self::encrypt($kek, $A.$R[$i - 1]);
                    $t = $i + $j * $N;
                    $A = self::toXBits(64, $t) ^ self::getMSB($B);
                    $R[$i - 1] = self::getLSB($B);
                }
            }
            $C = \array_merge([$A], $R);
        }

        return \implode('', $C);
    }

    /**
     * @param string $kek             The Key Encryption Key
     * @param string $key             The key to unwrap
     * @param bool   $padding_enabled If false, the AIV check must be RFC3394 compliant, else it must be RFC5649 or RFC3394 compliant
     *
     * @return string The key unwrapped
     */
    public static function unwrap(string $kek, string $key, bool $padding_enabled = false): string
    {
        $P = \str_split($key, 8);
        $A = $P[0];
        $N = \count($P);

        if (2 > $N) {
            throw new \InvalidArgumentException('Bad data');
        }
        if (2 === $N) {
            $B = self::decrypt($kek, $P[0].$P[1]);
            $unwrapped = self::getLSB($B);
            $A = self::getMSB($B);
        } else {
            $R = $P;
            for ($j = 5; $j >= 0; --$j) {
                for ($i = $N - 1; $i >= 1; --$i) {
                    $t = $i + $j * ($N - 1);
                    $B = self::decrypt($kek, (self::toXBits(64, $t) ^ $A).$R[$i]);
                    $A = self::getMSB($B);
                    $R[$i] = self::getLSB($B);
                }
            }
            unset($R[0]);

            $unwrapped = \implode('', $R);
        }
        if (false === self::checkInitialValue($unwrapped, $padding_enabled, $A)) {
            throw new \InvalidArgumentException('Integrity check failed!');
        }

        return $unwrapped;
    }

    private static function toXBits(int $bits, int $value): string
    {
        return \hex2bin(\str_pad(\dechex($value), $bits / 4, '0', STR_PAD_LEFT));
    }

    private static function getMSB(string $value): string
    {
        return \mb_substr($value, 0, \mb_strlen($value, '8bit') / 2, '8bit');
    }

    private static function getLSB(string $value): string
    {
        return \mb_substr($value, \mb_strlen($value, '8bit') / 2, null, '8bit');
    }

    private static function encrypt(string $kek, string $data): string
    {
        return \openssl_encrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
    }

    private static function decrypt(string $kek, string $data): string
    {
        return \openssl_decrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
    }
}


<?php

use WP_CLI\Utils;
use WP_CLI\Formatter;

/**
 * Lists registered sidebars.
 *
 * A [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/) is any widgetized area of your theme.
 *
 * ## EXAMPLES
 *
 *     # List sidebars
 *     $ wp sidebar list --fields=name,id --format=csv
 *     name,id
 *     "Widget Area",sidebar-1
 *     "Inactive Widgets",wp_inactive_widgets
 */
class Sidebar_Command extends WP_CLI_Command {

	private $fields = [
		'name',
		'id',
		'description',
	];

	/**
	 * Lists registered sidebars.
	 *
	 * ## OPTIONS
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific object fields.
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - json
	 *   - ids
	 *   - count
	 *   - yaml
	 * ---
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields will be displayed by default for each sidebar:
	 *
	 * * name
	 * * id
	 * * description
	 *
	 * These fields are optionally available:
	 *
	 * * class
	 * * before_widget
	 * * after_widget
	 * * before_title
	 * * after_title
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp sidebar list --fields=name,id --format=csv
	 *     name,id
	 *     "Widget Area",sidebar-1
	 *     "Inactive Widgets",wp_inactive_widgets
	 *
	 * @subcommand list
	 */
	public function list_( $args, $assoc_args ) {
		global $wp_registered_sidebars;

		Utils\wp_register_unused_sidebar();

		if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
			$sidebars = wp_list_pluck( $wp_registered_sidebars, 'id' );
		} else {
			$sidebars = $wp_registered_sidebars;
		}

		$formatter = new Formatter( $assoc_args, $this->fields );
		$formatter->display_items( $sidebars );
	}

}


<?php

use WP_CLI\Utils;
use WP_CLI\Formatter;

/**
 * Manages widgets, including adding and moving them within sidebars.
 *
 * A [widget](https://developer.wordpress.org/themes/functionality/widgets/) adds content and features to a widget area (also called a [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/)).
 *
 * ## EXAMPLES
 *
 *     # List widgets on a given sidebar
 *     $ wp widget list sidebar-1
 *     +----------+------------+----------+----------------------+
 *     | name     | id         | position | options              |
 *     +----------+------------+----------+----------------------+
 *     | meta     | meta-6     | 1        | {"title":"Meta"}     |
 *     | calendar | calendar-2 | 2        | {"title":"Calendar"} |
 *     +----------+------------+----------+----------------------+
 *
 *     # Add a calendar widget to the second position on the sidebar
 *     $ wp widget add calendar sidebar-1 2
 *     Success: Added widget to sidebar.
 *
 *     # Update option(s) associated with a given widget
 *     $ wp widget update calendar-1 --title="Calendar"
 *     Success: Widget updated.
 *
 *     # Delete one or more widgets entirely
 *     $ wp widget delete calendar-2 archive-1
 *     Success: 2 widgets removed from sidebar.
 */
class Widget_Command extends WP_CLI_Command {

	private $fields = [
		'name',
		'id',
		'position',
		'options',
	];

	/**
	 * Lists widgets associated with a sidebar.
	 *
	 * ## OPTIONS
	 *
	 * <sidebar-id>
	 * : ID for the corresponding sidebar.
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific object fields.
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - ids
	 *   - json
	 *   - count
	 *   - yaml
	 * ---
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields will be displayed by default for each widget:
	 *
	 * * name
	 * * id
	 * * position
	 * * options
	 *
	 * There are no optionally available fields.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp widget list sidebar-1 --fields=name,id --format=csv
	 *     name,id
	 *     meta,meta-5
	 *     search,search-3
	 *
	 * @subcommand list
	 */
	public function list_( $args, $assoc_args ) {

		list( $sidebar_id ) = $args;

		$this->validate_sidebar( $sidebar_id );

		$output_widgets = $this->get_sidebar_widgets( $sidebar_id );

		if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
			$output_widgets = wp_list_pluck( $output_widgets, 'id' );
		}

		$formatter = new Formatter( $assoc_args, $this->fields );
		$formatter->display_items( $output_widgets );

	}

	/**
	 * Adds a widget to a sidebar.
	 *
	 * Creates a new widget entry in the database, and associates it with the
	 * sidebar.
	 *
	 * ## OPTIONS
	 *
	 * <name>
	 * : Widget name.
	 *
	 * <sidebar-id>
	 * : ID for the corresponding sidebar.
	 *
	 * [<position>]
	 * : Widget's current position within the sidebar. Defaults to last
	 *
	 * [--<field>=<value>]
	 * : Widget option to add, with its new value
	 *
	 * ## EXAMPLES
	 *
	 *     # Add a new calendar widget to sidebar-1 with title "Calendar"
	 *     $ wp widget add calendar sidebar-1 2 --title="Calendar"
	 *     Success: Added widget to sidebar.
	 *
	 * @subcommand add
	 */
	public function add( $args, $assoc_args ) {

		list( $name, $sidebar_id ) = $args;
		$position                  = Utils\get_flag_value( $args, 2, 1 ) - 1;
		$this->validate_sidebar( $sidebar_id );

		$widget = $this->get_widget_obj( $name );
		if ( false === $widget ) {
			WP_CLI::error( 'Invalid widget type.' );
		}

		/*
		 * Adding a widget is as easy as:
		 * 1. Creating a new widget option
		 * 2. Adding the widget to the sidebar
		 * 3. Positioning appropriately
		 */
		$widget_options = $this->get_widget_options( $name );
		$option_keys    = $widget_options;
		if ( ! isset( $widget_options['_multiwidget'] ) ) {
			$widget_options['_multiwidget'] = 1;
		}
		unset( $option_keys['_multiwidget'] );
		$option_keys                     = array_keys( $option_keys );
		$last_key                        = array_pop( $option_keys );
		$option_index                    = $last_key + 1;
		$widget_options[ $option_index ] = $this->sanitize_widget_options( $name, $assoc_args, array() );
		$this->update_widget_options( $name, $widget_options );

		$widget_id = $name . '-' . $option_index;
		$this->move_sidebar_widget( $widget_id, null, $sidebar_id, null, $position );

		WP_CLI::success( 'Added widget to sidebar.' );

	}

	/**
	 * Updates options for an existing widget.
	 *
	 * ## OPTIONS
	 *
	 * <widget-id>
	 * : Unique ID for the widget
	 *
	 * [--<field>=<value>]
	 * : Field to update, with its new value
	 *
	 * ## EXAMPLES
	 *
	 *     # Change calendar-1 widget title to "Our Calendar"
	 *     $ wp widget update calendar-1 --title="Our Calendar"
	 *     Success: Widget updated.
	 *
	 * @subcommand update
	 */
	public function update( $args, $assoc_args ) {

		list( $widget_id ) = $args;
		if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
			WP_CLI::error( "Widget doesn't exist." );
		}

		if ( empty( $assoc_args ) ) {
			WP_CLI::error( 'No options specified to update.' );
		}

		list( $name, $option_index ) = $this->get_widget_data( $widget_id );

		$widget_options                  = $this->get_widget_options( $name );
		$clean_options                   = $this->sanitize_widget_options( $name, $assoc_args, $widget_options[ $option_index ] );
		$widget_options[ $option_index ] = array_merge( (array) $widget_options[ $option_index ], $clean_options );
		$this->update_widget_options( $name, $widget_options );

		WP_CLI::success( 'Widget updated.' );

	}

	/**
	 * Moves the position of a widget.
	 *
	 * Changes the order of a widget in its existing sidebar, or moves it to a
	 * new sidebar.
	 *
	 * ## OPTIONS
	 *
	 * <widget-id>
	 * : Unique ID for the widget
	 *
	 * [--position=<position>]
	 * : Assign the widget to a new position.
	 *
	 * [--sidebar-id=<sidebar-id>]
	 * : Assign the widget to a new sidebar
	 *
	 * ## EXAMPLES
	 *
	 *     # Change position of widget
	 *     $ wp widget move recent-comments-2 --position=2
	 *     Success: Widget moved.
	 *
	 *     # Move widget to Inactive Widgets
	 *     $ wp widget move recent-comments-2 --sidebar-id=wp_inactive_widgets
	 *     Success: Widget moved.
	 *
	 * @subcommand move
	 */
	public function move( $args, $assoc_args ) {

		list( $widget_id ) = $args;
		if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
			WP_CLI::error( "Widget doesn't exist." );
		}

		if ( empty( $assoc_args['position'] ) && empty( $assoc_args['sidebar-id'] ) ) {
			WP_CLI::error( 'A new position or new sidebar must be specified.' );
		}

		list( $name, $option_index, $current_sidebar_id, $current_sidebar_index ) = $this->get_widget_data( $widget_id );

		$new_sidebar_id = ! empty( $assoc_args['sidebar-id'] ) ? $assoc_args['sidebar-id'] : $current_sidebar_id;
		$this->validate_sidebar( $new_sidebar_id );

		$new_sidebar_index = ! empty( $assoc_args['position'] ) ? $assoc_args['position'] - 1 : $current_sidebar_index;
		// Moving between sidebars adds to the top
		if ( $new_sidebar_id !== $current_sidebar_id && $new_sidebar_index === $current_sidebar_index ) {
			// Human-readable positions are different than numerically indexed array
			$new_sidebar_index = 0;
		}

		$this->move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_sidebar_index, $new_sidebar_index );

		WP_CLI::success( 'Widget moved.' );

	}

	/**
	 * Deactivates one or more widgets from an active sidebar.
	 *
	 * Moves widgets to Inactive Widgets.
	 *
	 * ## OPTIONS
	 *
	 * <widget-id>...
	 * : Unique ID for the widget(s)
	 *
	 * ## EXAMPLES
	 *
	 *     # Deactivate the recent-comments-2 widget.
	 *     $ wp widget deactivate recent-comments-2
	 *     Success: 1 widget deactivated.
	 *
	 * @subcommand deactivate
	 */
	public function deactivate( $args, $assoc_args ) {

		$count  = 0;
		$errors = 0;

		foreach ( $args as $widget_id ) {
			if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
				WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." );
				$errors++;
				continue;
			}

			list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
			if ( 'wp_inactive_widgets' === $sidebar_id ) {
				WP_CLI::warning( sprintf( "'%s' is already deactivated.", $widget_id ) );
				continue;
			}

			$this->move_sidebar_widget( $widget_id, $sidebar_id, 'wp_inactive_widgets', $sidebar_index, 0 );

			$count++;

		}

		Utils\report_batch_operation_results( 'widget', 'deactivate', count( $args ), $count, $errors );
	}

	/**
	 * Deletes one or more widgets from a sidebar.
	 *
	 * ## OPTIONS
	 *
	 * <widget-id>...
	 * : Unique ID for the widget(s)
	 *
	 * ## EXAMPLES
	 *
	 *     # Delete the recent-comments-2 widget from its sidebar.
	 *     $ wp widget delete recent-comments-2
	 *     Success: Deleted 1 of 1 widgets.
	 *
	 * @subcommand delete
	 */
	public function delete( $args, $assoc_args ) {

		$count  = 0;
		$errors = 0;

		foreach ( $args as $widget_id ) {
			if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
				WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." );
				$errors++;
				continue;
			}

			// Remove the widget's settings.
			list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
			$widget_options = $this->get_widget_options( $name );
			unset( $widget_options[ $option_index ] );
			$this->update_widget_options( $name, $widget_options );

			// Remove the widget from the sidebar.
			$all_widgets = $this->wp_get_sidebars_widgets();
			unset( $all_widgets[ $sidebar_id ][ $sidebar_index ] );
			$all_widgets[ $sidebar_id ] = array_values( $all_widgets[ $sidebar_id ] );
			update_option( 'sidebars_widgets', $all_widgets );

			$count++;
		}

		Utils\report_batch_operation_results( 'widget', 'delete', count( $args ), $count, $errors );
	}

	/**
	 * Resets sidebar.
	 *
	 * Removes all widgets from the sidebar and places them in Inactive Widgets.
	 *
	 * ## OPTIONS
	 *
	 * [<sidebar-id>...]
	 * : One or more sidebars to reset.
	 *
	 * [--all]
	 * : If set, all sidebars will be reset.
	 *
	 * ## EXAMPLES
	 *
	 *     # Reset a sidebar
	 *     $ wp widget reset sidebar-1
	 *     Success: Sidebar 'sidebar-1' reset.
	 *
	 *     # Reset multiple sidebars
	 *     $ wp widget reset sidebar-1 sidebar-2
	 *     Success: Sidebar 'sidebar-1' reset.
	 *     Success: Sidebar 'sidebar-2' reset.
	 *
	 *     # Reset all sidebars
	 *     $ wp widget reset --all
	 *     Success: Sidebar 'sidebar-1' reset.
	 *     Success: Sidebar 'sidebar-2' reset.
	 *     Success: Sidebar 'sidebar-3' reset.
	 */
	public function reset( $args, $assoc_args ) {

		global $wp_registered_sidebars;

		$all = Utils\get_flag_value( $assoc_args, 'all', false );

		// Bail if no arguments and no all flag.
		if ( ! $all && empty( $args ) ) {
			WP_CLI::error( 'Please specify one or more sidebars, or use --all.' );
		}

		// Fetch all sidebars if all flag is set.
		if ( $all ) {
			$args = array_keys( $wp_registered_sidebars );
		}

		// Sidebar ID wp_inactive_widgets is reserved by WP core for inactive widgets.
		if ( isset( $args['wp_inactive_widgets'] ) ) {
			unset( $args['wp_inactive_widgets'] );
		}

		// Check if no registered sidebar.
		if ( empty( $args ) ) {
			WP_CLI::error( 'No sidebar registered.' );
		}

		$count  = 0;
		$errors = 0;
		foreach ( $args as $sidebar_id ) {
			if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) {
				WP_CLI::warning( sprintf( 'Invalid sidebar: %s', $sidebar_id ) );
				$errors++;
				continue;
			}

			$widgets = $this->get_sidebar_widgets( $sidebar_id );
			if ( empty( $widgets ) ) {
				WP_CLI::warning( sprintf( "Sidebar '%s' is already empty.", $sidebar_id ) );
			} else {
				foreach ( $widgets as $widget ) {
					$widget_id = $widget->id;
					list( $name, $option_index, $new_sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
					$this->move_sidebar_widget( $widget_id, $new_sidebar_id, 'wp_inactive_widgets', $sidebar_index, 0 );
				}
				WP_CLI::log( sprintf( "Sidebar '%s' reset.", $sidebar_id ) );
				$count++;
			}
		}

		Utils\report_batch_operation_results( 'sidebar', 'reset', count( $args ), $count, $errors );
	}

	/**
	 * Checks whether a sidebar is a valid sidebar
	 *
	 * @param string $sidebar_id
	 */
	private function validate_sidebar( $sidebar_id ) {
		global $wp_registered_sidebars;

		Utils\wp_register_unused_sidebar();

		if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) {
			WP_CLI::error( 'Invalid sidebar.' );
		}
	}

	/**
	 * Checks whether the specified widget is on the sidebar
	 *
	 * @param string $widget_id
	 */
	private function validate_sidebar_widget( $widget_id ) {

		$sidebars_widgets = $this->wp_get_sidebars_widgets();

		$widget_exists = false;
		foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {

			if ( in_array( $widget_id, $widgets, true ) ) {
				$widget_exists = true;
				break;
			}
		}
		return $widget_exists;
	}

	/**
	 * Gets the widgets (and their associated data) for a given sidebar
	 *
	 * @param string $sidebar_id
	 * @return array
	 */
	private function get_sidebar_widgets( $sidebar_id ) {

		$all_widgets = $this->wp_get_sidebars_widgets();

		if ( empty( $all_widgets[ $sidebar_id ] ) ) {
			return array();
		}

		$prepared_widgets = array();
		foreach ( $all_widgets[ $sidebar_id ] as $key => $widget_id ) {

			$prepared_widget = new stdClass();

			$parts        = explode( '-', $widget_id );
			$option_index = array_pop( $parts );
			$widget_name  = implode( '-', $parts );

			$prepared_widget->name     = $widget_name;
			$prepared_widget->id       = $widget_id;
			$prepared_widget->position = $key + 1;
			$widget_options            = get_option( 'widget_' . $widget_name );
			$prepared_widget->options  = $widget_options[ $option_index ];

			$prepared_widgets[] = $prepared_widget;
		}

		return $prepared_widgets;
	}

	/**
	 * Re-implementation of wp_get_sidebars_widgets()
	 * because the original has a nasty global component
	 */
	private function wp_get_sidebars_widgets() {
		$sidebars_widgets = get_option( 'sidebars_widgets', array() );

		if ( is_array( $sidebars_widgets ) && isset( $sidebars_widgets['array_version'] ) ) {
			unset( $sidebars_widgets['array_version'] );
		}

		return $sidebars_widgets;
	}

	/**
	 * Gets the widget's name, option index, sidebar, and sidebar index from its ID
	 *
	 * @param string $widget_id
	 * @return array
	 */
	private function get_widget_data( $widget_id ) {

		$parts        = explode( '-', $widget_id );
		$option_index = array_pop( $parts );
		$name         = implode( '-', $parts );

		$sidebar_id    = false;
		$sidebar_index = false;
		$all_widgets   = $this->wp_get_sidebars_widgets();
		foreach ( $all_widgets as $s_id => &$widgets ) {

			$key = array_search( $widget_id, $widgets, true );
			if ( false !== $key ) {
				$sidebar_id    = $s_id;
				$sidebar_index = $key;
				break;
			}
		}

		return array( $name, $option_index, $sidebar_id, $sidebar_index );
	}

	/**
	 * Gets the options for a given widget
	 *
	 * @param string $name
	 * @return array
	 */
	private function get_widget_options( $name ) {
		return get_option( 'widget_' . $name, array() );
	}

	/**
	 * Updates the options for a given widget
	 *
	 * @param string $name
	 * @param mixed
	 */
	private function update_widget_options( $name, $value ) {
		update_option( 'widget_' . $name, $value );
	}

	/**
	 * Repositions a widget within a sidebar or move to another sidebar.
	 *
	 * @param string $widget_id
	 * @param string|null $current_sidebar_id
	 * @param string $new_sidebar_id
	 * @param int|null $current_index
	 * @param int $new_index
	 */
	private function move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_index, $new_index ) {

		$all_widgets     = $this->wp_get_sidebars_widgets();
		$needs_placement = true;
		// Existing widget
		if ( $current_sidebar_id && ! is_null( $current_index ) ) {

			$widgets = $all_widgets[ $current_sidebar_id ];
			if ( $current_sidebar_id !== $new_sidebar_id ) {

				unset( $widgets[ $current_index ] );

			} else {

				$part = array_splice( $widgets, $current_index, 1 );
				array_splice( $widgets, $new_index, 0, $part );

				$needs_placement = false;

			}

			$all_widgets[ $current_sidebar_id ] = array_values( $widgets );

		}

		if ( $needs_placement ) {
			$widgets                        = ! empty( $all_widgets[ $new_sidebar_id ] ) ? $all_widgets[ $new_sidebar_id ] : array();
			$before                         = array_slice( $widgets, 0, $new_index, true );
			$after                          = array_slice( $widgets, $new_index, count( $widgets ), true );
			$widgets                        = array_merge( $before, array( $widget_id ), $after );
			$all_widgets[ $new_sidebar_id ] = array_values( $widgets );
		}

		update_option( 'sidebars_widgets', $all_widgets );

	}

	/**
	 * Gets a widget's instantiated object based on its name
	 *
	 * @param string $id_base Name of the widget
	 * @return WP_Widget|false
	 */
	private function get_widget_obj( $id_base ) {
		global $wp_widget_factory;

		$widget = wp_filter_object_list( $wp_widget_factory->widgets, array( 'id_base' => $id_base ) );
		if ( empty( $widget ) ) {
			return false;
		}

		return array_pop( $widget );
	}

	/**
	 * Cleans up a widget's options based on its update callback
	 *
	 * @param string $id_base Name of the widget
	 * @param mixed $dirty_options
	 * @param mixed $old_options
	 * @return mixed
	 */
	private function sanitize_widget_options( $id_base, $dirty_options, $old_options ) {

		$widget = $this->get_widget_obj( $id_base );
		if ( empty( $widget ) ) {
			return array();
		}

		// No easy way to determine expected array keys for $dirty_options
		// because Widget API dependent on the form fields
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Whitelisting due to above reason.
		return @$widget->update( $dirty_options, $old_options );

	}

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random;

use Rych\Random\Encoder\EncoderInterface;
use Rych\Random\Generator\GeneratorInterface;

/**
 * Random data main class
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class Random
{

    /**
     * @var \Rych\Random\Encoder\EncoderInterface
     */
    protected $encoder;

    /**
     * @var \Rych\Random\GeneratorInterface
     */
    protected $generator;

    /**
     * Class constructor.
     *
     * @param  \Rych\Random\Generator\GeneratorInterface $generator
     * @param  \Rych\Random\Encoder\EncoderInterface     $encoder
     * @return void
     */
    public function __construct(GeneratorInterface $generator = null, EncoderInterface $encoder = null)
    {
        if (!$encoder) {
            // Really just a pass-thru "encoder"
            $encoder = new Encoder\RawEncoder;
        }

        if (!$generator) {
            $factory = new Generator\GeneratorFactory;
            $generator = $factory->getGenerator();
        }

        $this->setEncoder($encoder);
        $this->setGenerator($generator);
    }

    /**
     * Get the currently registered encoder instance.
     *
     * @return \Rych\Random\Encoder\EncoderInterface
     */
    public function getEncoder()
    {
        return $this->encoder;
    }

    /**
     * Set an encoder instance.
     *
     * @param  \Rych\Random\Encoder\EncoderInterface $encoder
     * @return \Rych\Random\Random
     */
    public function setEncoder(Encoder\EncoderInterface $encoder)
    {
        $this->encoder = $encoder;

        return $this;
    }

    /**
     * Get the currently registered generator instance.
     *
     * @return \Rych\Random\Generator\GeneratorInstance
     */
    public function getGenerator()
    {
        return $this->generator;
    }

    /**
     * Set a generator instance.
     *
     * @param  \Rych\Random\Generator\GeneratorInterface $generator
     * @return \Rych\Random\Random
     */
    public function setGenerator(Generator\GeneratorInterface $generator)
    {
        $this->generator = $generator;

        return $this;
    }

    /**
     * Get a random raw byte string of the specified length.
     *
     * @param  integer $length The length of the requested string.
     * @return string  A random raw byte string of the specified length.
     */
    public function getRandomBytes($length)
    {
        return $this->encoder->encode($this->generator->generate($length));
    }

    /**
     * Get a random integer within the specified range.
     *
     * @param  integer $min The minimum expected value. Defaults to 0.
     * @param  integer $max The maximum expected value. Defaults to PHP_INT_MAX.
     * @return integer A random integer between the specified values, inclusive.
     */
    public function getRandomInteger($min = 0, $max = PHP_INT_MAX)
    {
        $min = (int) $min;
        $max = (int) $max;
        $range = $max - $min;

        $bits  = $this->getBitsInInteger($range);
        $bytes = $this->getBytesInBits($bits);
        $mask  = (int) ((1 << $bits) - 1);

        do {
            $byteString = $this->generator->generate($bytes);
            $result = hexdec(bin2hex($byteString)) & $mask;
        } while ($result > $range);

        return (int) $result + $min;
    }

    /**
     * Get a random string of the specified length.
     *
     * @param  integer $length The length of the requested string.
     * @return string  A random string of the specified length, consisting of
     *     characters from the base64 character set.
     */
    public function getRandomString($length, $charset = null)
    {
        $length = (int) $length;
        if (!$charset) {
            $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./';
        }

        $charsetLength = strlen($charset);
        $neededBytes = $this->getBytesInBits($length * ($this->getBitsInInteger($charsetLength) + 1));

        $string = '';
        do {
            $byteString = $this->generator->generate($neededBytes);
            for ($i = 0; $i < $neededBytes; ++$i) {
                if (ord($byteString[$i]) > (255 - (255 % $charsetLength))) {
                    continue;
                }
                $string .= $charset[ord($byteString[$i]) % $charsetLength];
            }
        } while (strlen($string) < $length);

        return substr($string, 0, $length);
    }

    /**
     * Determine the number of bits required to represent a given number.
     *
     * For example, the number 64 can be represented in 7 bits (0b1000000),
     * so this method would return 7.
     *
     * @param  integer $number
     * @return integer
     */
    protected function getBitsInInteger($number)
    {
        if ($number == 0) {
            return 0;
        }

        $bits = 1;
        while ($number >>= 1) {
            ++$bits;
        }

        return $bits;
    }

    /**
     * Determine the number of bytes in the specified number of bits.
     *
     * @param  integer $bits
     * @return integer
     */
    protected function getBytesInBits($bits)
    {
        return (int) ceil($bits / 8);
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Encoder;

/**
 * Base32 encoder
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class Base32Encoder implements EncoderInterface
{

    private $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';

    /**
     * Encode a string of raw bytes.
     *
     * @param  string $string
     * @return string
     */
    public function encode($string)
    {
        $encoded = '';

        if ($string) {
            $binString = '';

            // Build a binary string representation of the input string
            foreach (str_split($string) as $char) {
                $binString .= str_pad(decbin(ord($char)), 8, 0, STR_PAD_LEFT);
            }

            // Encode the data in 5-bit chunks
            foreach (str_split($binString, 5) as $chunk) {
                $chunk = str_pad($chunk, 5, 0, STR_PAD_RIGHT);
                $encoded .= $this->charset[bindec($chunk)];
            }

            // Add padding to the encoded string as required
            if (strlen($encoded) % 8) {
                $encoded .= str_repeat($this->charset[32], 8 - (strlen($encoded) % 8));
            }
        }

        return $encoded;
    }

    /**
     * Decode an encoded string.
     *
     * @param  string $string
     * @return string
     */
    public function decode($string)
    {
        $decoded = '';
        $string = preg_replace("/[^{$this->charset}]/", '', rtrim(strtoupper($string), $this->charset[32]));

        if ($string) {
            $binString = '';
            foreach (str_split($string) as $char) {
                $binString .= str_pad(decbin(strpos($this->charset, $char)), 5, 0, STR_PAD_LEFT);
            }
            $binString = substr($binString, 0, (floor(strlen($binString) / 8) * 8));

            foreach (str_split($binString, 8) as $chunk) {
                $chunk = str_pad($chunk, 8, 0, STR_PAD_RIGHT);
                $decoded .= chr(bindec($chunk));
            }
        }

        return $decoded;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * Mock random data generator
 *
 * This generator is used in testing. It always reports that it is not
 * supported, so it cannot be picked up by the factory automatically.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class MockGenerator implements GeneratorInterface
{

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

    /**
     * Set the expected fake random data to return.
     *
     * @param  string $string
     * @return void
     */
    public function setMockString($string)
    {
        $this->randomString = $string;
    }

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bytes = '';

        if (strlen(!$this->randomString)) {
            $this->randomString = chr(0);
        }

        do {
            $bytes .= $this->randomString;
        } while (strlen($bytes) < $byteCount);

        return substr($bytes, 0, $byteCount);
    }

    /**
     * Test system support for this generator.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        return false;
    }

    /**
     * Get the generator priority.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_LOW;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * /dev/urandom random data generator
 *
 * This generator simply reads from /dev/urandom. it is only supported on
 * non-Windows platforms which provide the interface.
 *
 * The /dev/urandom special file provides a stream of cryptographically secure
 * pseudo-random bytes. It is the non-blocking version of /dev/random. More
 * information may be found at {@link http://en.wikipedia.org/wiki//dev/random
 * Wikipedia}.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class URandomGenerator implements GeneratorInterface
{

    /**
     * @var string
     */
    protected $file = '/dev/urandom';

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bytes = '';

        if (self::isSupported()) {
            if ($fp = @fopen($this->file, 'rb')) {
                if (function_exists('stream_set_read_buffer')) {
                    stream_set_read_buffer($fp, 0);
                }

                $fileBytes = fread($fp, $byteCount);
                if ($fileBytes) {
                    $bytes = $fileBytes;
                }
            }
        }

        return str_pad($bytes, $byteCount, chr(0));
    }

    /**
     * Test system support for this generator.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        $supported = false;

        $class = __CLASS__;
        $self = new $class;

        if (file_exists($self->file) && is_readable($self->file)) {
            $supported = true;
        }

        return $supported;
    }

    /**
     * Get the generator priority.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_MEDIUM;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * Clock drift algorithm random data generator
 *
 * This generator provides a pure PHP CSPRNG. It gathers entropy by measuring
 * the time it takes to calculate a number of SHA-1 hashes. This is a very slow
 * generator, and it should only be used in cases where better generators are
 * unavailable.
 *
 * Taken from https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP with
 * permission.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @author George Argyros
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class ClockDriftGenerator implements GeneratorInterface
{

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bitsPerRound = 2;
        $msecPerRound = 400;
        $hashLength = 20;
        $total = $byteCount;
        $generated = '';

        do {
            $bytes = ($total > $hashLength) ? $hashLength : $total;
            $total -= $bytes;

            $entropy = rand() . uniqid(mt_rand(), true);
            $entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
            $entropy .= memory_get_usage();

            for ($i = 0; $i < 3; $i++) {
                $counter1 = microtime(true);
                $var = sha1(mt_rand());
                for ($j = 0; $j < 50; $j++) {
                    $var = sha1($var);
                }
                $counter2 = microtime(true);
                $entropy .= $counter1 . $counter2;
            }

            $rounds = (int) ($msecPerRound * 50 / (int) (($counter2 - $counter1) * 1000000));
            $iterations = $bytes * (int) (ceil(8 / $bitsPerRound));

            for ($i = 0; $i < $iterations; $i++) {
                $counter1 = microtime();
                $var = sha1(mt_rand());
                for ($j = 0; $j < $rounds; $j++) {
                    $var = sha1($var);
                }
                $counter2 = microtime();
                $entropy .= $counter1 . $counter2;
            }

            $generated .= sha1($entropy, true);
        } while ($byteCount > strlen($generated));

        $bytes = substr($generated, 0, $byteCount);

        return str_pad($bytes, $byteCount, chr(0));
    }

    /**
     * Test system support for this generator.
     *
     * There are no dependencies for this generator, so it's universally
     * supported.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        return true;
    }

    /**
     * Get the generator priority.
     *
     * The algorithm used in this generator is very slow. As such, it has a low
     * priority. Ideally, this generator should only be used as a fallback when
     * no others are available.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_LOW;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * OpenSSL random data generator
 *
 * This generator provides an interface to the openssl extension. On most
 * platforms, the extension will use the CSPRNG provided by the OpenSSL library.
 * Due to a bug in the extension, this generator is unavailable on PHP
 * versions < 5.3.7 on Windows platforms. These buggy versions attempted to
 * gather additional entropy from an attached display device. While this worked
 * fine on workstations, this would cause headless servers to run very slowly
 * or hang.
 *
 * The behavior of the openssl extension on Windows was modified further
 * starting with PHP 5.4.0 to bypass the OpenSSL CSPRNG completely and use
 * Windows' built-in CSPRNG instead via Microsoft's CryptoAPI.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class OpenSSLGenerator implements GeneratorInterface
{

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bytes = '';

        if (self::isSupported()) {
            $sslStrong = false;
            $sslBytes = openssl_random_pseudo_bytes($byteCount, $sslStrong);
            if ($sslStrong) {
                $bytes = $sslBytes;
            }
            unset ($sslStrong);
        }

        return str_pad($bytes, $byteCount, chr(0));
    }

    /**
     * Test system support for this generator.
     *
     * PHP versions prior to 5.3.7 have a bug in the Windows implementation of
     * this generator. The implementation used OpenSSL functions which could
     * cause blocking for an indefinite period of time on headless
     * non-interactive Windows servers. Because of this, the generator is not
     * supported for PHP versions < 5.3.7 on Windows.
     *
     * The OpenSSL function this generator uses simply wraps Microsoft's
     * CryptoAPI on PHP versions >= 5.4.0 on Windows. It should be noted that
     * this is also exactly how the MCrypt generator operates on Windows.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        $supported = false;
        if (function_exists('openssl_random_pseudo_bytes')) {
            if (version_compare(PHP_VERSION, '5.3.7') >= 0 || (PHP_OS & "\xDF\xDF\xDF") !== 'WIN') {
                $supported = true;
            }
        }

        return $supported;
    }

    /**
     * Get the generator priority.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_HIGH;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * CAPICOM random data generator
 *
 * Microsoft has deprecated the CAPICOM interface. Please see
 * {@link http://blogs.msdn.com/b/karinm/archive/2009/01/19/capicom-dll-removed-from-windows-sdk-for-windows-7.aspx
 *  the MSDN announcement} for more information.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class CapicomGenerator implements GeneratorInterface
{

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bytes = '';

        if (self::isSupported()) {
            try {
                $util = new \COM('CAPICOM.Utilities.1');
                $capicomBytes = base64_decode($util->GetRandom($byteCount, 0));
                if ($capicomBytes) {
                    $bytes = $capicomBytes;
                }
            } catch (\Exception $e) {
            }
        }

        return str_pad($bytes, $byteCount, chr(0));
    }

    /**
     * Test system support for this generator.
     *
     * This generator is only supported on Windows platforms when the COM
     * extension is loaded.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        $supported = false;
        if ((PHP_OS & "\xDF\xDF\xDF") === 'WIN' && class_exists('\\COM', false)) {
            $supported = true;
        }

        return $supported;
    }

    /**
     * Get the generator priority.
     *
     * CAPICOM has been deprecated by Microsoft, and this generator priority
     * reflects this fact.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_MEDIUM;
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * Generator factory
 *
 * This class provides a method to select the best available random generator.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class GeneratorFactory
{

    /**
     * @var array
     */
    private $generators = array ();

    /**
     * @var array
     */
    private $generatorPaths = array ();

    /**
     * Class constructor.
     *
     * @return void
     */
    public function __construct()
    {
        $this->addGeneratorPath(__NAMESPACE__, __DIR__);
    }

    /**
     * Add a search path for custom generators.
     *
     * @param string $prefix Namespace prefix used by generator classes
     *     in this path.
     * @param  string  $path Directory path containing generator classes.
     * @return boolean Returns true on success, false otherwise.
     */
    public function addGeneratorPath($prefix, $path)
    {
        $path = realpath($path);
        $success = false;
        if ($path && !array_key_exists($prefix, $this->generatorPaths)) {
            $this->generatorPaths[$prefix] = $path;
            $success = true;
        }

        return $success;
    }

    /**
     * Clear all registered generator paths.
     *
     * @return self Returns an instance of self for method chaining.
     */
    public function clearGeneratorPaths()
    {
        $this->generatorPaths = array ();

        return $this;
    }

    /**
     * Get all registered generator paths.
     *
     * @return array An associative array of registered paths, using the prefix
     *     as a key.
     */
    public function getGeneratorPaths()
    {
        return $this->generatorPaths;
    }

    /**
     * Remove a registered generator path.
     *
     * @param  type $prefixOrPath The prefix or directory path to remove.
     * @return self Returns an instance of self for method chaining.
     */
    public function removeGeneratorPath($prefixOrPath)
    {
        if (array_key_exists($prefixOrPath, $this->generatorPaths)) {
            $prefix = $prefixOrPath;
            unset($this->generatorPaths[$prefix]);
        } elseif (($prefixOrPath = realpath($prefixOrPath)) && in_array($prefixOrPath, $this->generatorPaths)) {
            $path = $prefixOrPath;
            $generatorPaths = $this->generatorPaths;
            $this->generatorPaths = array_filter($generatorPaths, function ($value) use ($path) {
                return ($value != $path);
            });
        }

        return $this;
    }

    /**
     * Get a supported random data generator.
     *
     * Checks the available supported generators and attempts to return the best
     * possible generator.
     *
     * @return \Rych\Random\Generator\GeneratorInterface
     * @uses \Rych\Random\Generator\GeneratorInterface::getPriority() Used to
     *     sort the available generators from lowest to highest priority.
     */
    public function getGenerator()
    {
        if (!$this->generators) {
            $this->loadGenerators();
        }

        $generators = $this->generators;
        usort($generators, function ($a, $b) {
            return ($b::getPriority() - $a::getPriority());
        });

        $generator = null;
        if (isset ($generators[0])) {
            $class = $generators[0];
            $generator = new $class;
        }

        return $generator;
    }

    /**
     * Populate the available generators array.
     *
     * @return void
     * @uses \Rych\Random\Generator\GeneratorInterface Used to determine if a
     *     generator is supported by the system.
     */
    public function loadGenerators()
    {
        // Reset the list of loaded classes
        $this->generators = array ();

        // Loop through each registered path
        foreach ($this->generatorPaths as $generatorPrefix => $generatorPath) {
            // Build the iterators to search the path
            $dirIterator = new \RecursiveDirectoryIterator($generatorPath);
            $iteratorIterator = new \RecursiveIteratorIterator($dirIterator);
            $generatorIterator = new \RegexIterator($iteratorIterator, '/^.+\.php$/i');

            // Loop through the iterator looking for GeneratorInterface
            // instances
            foreach ($generatorIterator as $generatorFile) {
                // Determine additional namespace from file path
                $pathName = $generatorFile->getPath();
                if (substr($pathName, 0, strlen($generatorPath)) == $generatorPath) {
                    $pathName = substr($pathName, strlen($generatorPath), strlen($pathName));
                }

                // Best guess for class name is Prefix\Path\File
                $class = $generatorPrefix . '\\' . $pathName . $generatorFile->getBasename('.php');
                $class = str_replace('/', '\\', $class);

                // If the class doesn't exists and isn't autoloaded, try to
                // load it from the discovered file.
                if (!class_exists($class, true) && !in_array($generatorFile->getPathName(), get_included_files())) {
                    include $generatorFile->getPathname();
                }

                // If the class exists and implements GeneratorInterface,
                // append it to our list.
                if (class_exists($class, false) && in_array(__NAMESPACE__ . '\\GeneratorInterface', class_implements($class))) {
                    if ($class::isSupported()) {
                        $this->generators[] = $class;
                    }
                }
            }
        }
    }

}


<?php
/**
 * Ryan's Random Data Library
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */

namespace Rych\Random\Generator;

/**
 * MCrypt random data generator
 *
 * This generator provides an interface to the mcrypt extension. The extension
 * simply wraps the operating system's built-in CSPRNG. On Windows platforms,
 * that means using Microsoft's CryptoAPI. Non-Windows platforms will read from
 * /dev/urandom.
 *
 * @package Rych\Random
 * @author Ryan Chouinard <rchouinard@gmail.com>
 * @copyright Copyright (c) 2013, Ryan Chouinard
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 */
class MCryptGenerator implements GeneratorInterface
{

    /**
     * Generate a string of random data.
     *
     * @param  integer $byteCount The desired number of bytes.
     * @return string  Returns the generated string.
     */
    public function generate($byteCount)
    {
        $bytes = '';

        if (self::isSupported()) {
            $mcryptStr = mcrypt_create_iv($byteCount, MCRYPT_DEV_URANDOM);
            if ($mcryptStr !== false) {
                $bytes = $mcryptStr;
            }
        }

        return str_pad($bytes, $byteCount, chr(0));
    }

    /**
     * Test system support for this generator.
     *
     * @return boolean Returns true if the generator is supported on the current
     *     platform, otherwise false.
     */
    public static function isSupported()
    {
        $supported = false;
        if (function_exists('mcrypt_create_iv')) {
            $supported = true;
        }

        return $supported;
    }

    /**
     * Get the generator priority.
     *
     * @return integer Returns an integer indicating the priority of the
     *     generator. Lower numbers represent lower priorities.
     */
    public static function getPriority()
    {
        return GeneratorInterface::PRIORITY_HIGH;
    }

}


<?php

namespace MarvinLabs\Html\Bootstrap\Elements;

use MarvinLabs\Html\Bootstrap\Contracts\ShowsErrors;
use MarvinLabs\Html\Bootstrap\Elements\Traits\Assemblable;
use Spatie\Html\Elements\Div;

/**
 * A group of radio components
 */
class RadioGroup extends Div implements ShowsErrors
{
    use Assemblable;

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

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

    /** @var string|null */
    private $selectedOption = null;

    /** @var string */
    private $name = 'radio_group';

    /** @var boolean */
    private $inline = false;

    /** @var \Spatie\Html\BaseElement|null */
    private $error = null;

    /** @var \MarvinLabs\Html\Bootstrap\Contracts\FormState|null */
    private $formState = null;

    public function __construct($formState)
    {
        parent::__construct();
        $this->formState = $formState;
    }

    /** @return static */
    public function options(array $options)
    {
        $element = clone $this;
        $element->options = $options;
        $element->radios = $element->createRadios($options);

        return $element;
    }

    /** @return static */
    public function selectedOption($selectedOption)
    {
        $element = clone $this;
        $element->selectedOption = $selectedOption;

        return $element;
    }

    /** @return static */
    public function name(string $name)
    {
        $element = clone $this;
        $element->name = $name;

        return $element;
    }

    /** @return static */
    public function inline($inline = true)
    {
        $element = clone $this;
        $element->inline = $inline;

        return $element;
    }

    /**
     * For some attributes, return the value of the backing control instead of our own
     *
     * @param string $attribute
     * @param null   $fallback
     *
     * @return mixed
     */
    public function getAttribute($attribute, $fallback = null)
    {
        if ('name' === $attribute)
        {
            return $this->name;
        }

        return parent::getAttribute($attribute, $fallback);
    }

    public function __call($name, $arguments)
    {
        // Radio setters for all radios of the group
        foreach (['radio' => '', 'forgetRadio' => 'forget', 'addRadio' => 'add'] as $needle => $replacement)
        {
            if ($name !== $needle && starts_with($name, $needle))
            {
                $name = str_replace($needle, $replacement, $name);
                if (empty($name))
                {
                    return parent::__call($name, $arguments);
                }

                if (empty($replacement))
                {
                    $name = \lcfirst($name);
                }

                $element = clone $this;
                $element->radios = collect($element->radios)
                    ->map(function ($radio) use ($name, $arguments) {
                        return $radio->{$name}(...$arguments);
                    });

                return $element;
            }
        }

        return parent::__call($name, $arguments); // TODO: Change the autogenerated stub
    }

    public function showError($error)
    {
        $element = clone $this;
        $element->error = $error;

        return $element;
    }

    /** @Override */
    protected function assemble()
    {
        $element = clone $this;

        $radios = collect($element->radios);
        if ($element->error!==null)
        {
            $lastRadio = $radios->pop();
            $radios->push($lastRadio->addChild($element->error));
        }

        return $element
            ->addChildren($radios,
                function ($radio) {
                    return $radio
                        ->checked($this->getFieldValue($this->name, $this->selectedOption) == $radio->getValue())
                        ->name($this->name)
                        ->inline($this->inline);
                })
            ->addClass('radio-group');
    }

    /**
     * @param string $name
     * @param mixed  $default
     *
     * @return mixed
     */
    private function getFieldValue($name, $default)
    {
        return $this->formState !== null
            ? $this->formState->getFieldValue($name, $default)
            : $default;
    }

    private function createRadios($options): array
    {
        return collect($options)
            ->map(function ($description, $value) {
                $radio = new Radio($this->formState);
                return $radio
                    ->description($description)
                    ->value($value);
            })
            ->toArray();
    }

}


<?php

namespace MarvinLabs\Html\Bootstrap\Elements;

use MarvinLabs\Html\Bootstrap\Contracts\ShowsErrors;
use Spatie\Html\Elements\Div;
use Spatie\Html\Elements\Label;

/**
 * Class FormGroup
 *
 * @package MarvinLabs\Html\Bootstrap\Elements
 *
 *          Wraps a control, label, errors on that field, etc.
 */
class FormGroup extends ControlWrapper
{
    /** @var  \MarvinLabs\Html\Bootstrap\Contracts\FormState */
    private $formState;

    /** @var  \Spatie\Html\Elements\Label|null */
    private $label;

    /** @var  \Spatie\Html\BaseElement|null */
    private $helpText;

    /**
     * FormGroup constructor.
     *
     * @param \MarvinLabs\Html\Bootstrap\Contracts\FormState $formState
     * @param \Spatie\Html\BaseElement                       $control
     */
    public function __construct($formState, $control = null)
    {
        parent::__construct($control, ['form-group']);
        $this->formState = $formState;
    }

    /**
     * @param string $text
     * @param bool   $screenReaderOnly
     * @param array  $extraClasses
     *
     * @return static
     */
    public function label($text, $screenReaderOnly = false, $extraClasses = [])
    {
        if ($text === null)
        {
            return $this;
        }

        $element = clone $this;
        $element->label = Label::create()
                               ->text($text)
                               ->addClassIf($screenReaderOnly, 'sr-only')
                               ->addClass('col-form-label')
                               ->addClass($extraClasses);

        return $element;
    }

    /**
     * @param string $text
     * @param bool   $small
     * @param bool   $muted
     *
     * @param null   $extraClass
     *
     * @return static
     */
    public function helpText($text, $small = true, $muted = true, $extraClass = null)
    {
        if ($text === null)
        {
            return $this;
        }

        $element = clone $this;

        $element->helpText = $small
            ? Small::create()->text($text)
            : P::create()->text($text);

        $element->helpText = $element->helpText
            ->addClassIf($extraClass !== null, $extraClass)
            ->addClassIf($muted, 'text-muted');

        return $element;
    }

    /**
     * Show the group as an horizontal row, using the specified configuration. This method should be called last
     * when the label and controls are all initialized.
     *
     * The row configuration will be taken from config("bs4.form_rows.$rowConfig")
     *
     * @param string $rowConfig The reference to the configuration (without prefix).
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\FormGroup|static
     *
     * @throws \InvalidArgumentException When the configuration does not exist
     * @throws \Psr\Container\NotFoundExceptionInterface Problem in dependency retrieval for 'config'
     * @throws \Psr\Container\ContainerExceptionInterface Problem in dependency retrieval for 'config'
     */
    public function showAsRow($rowConfig = 'default')
    {
        $rowConfig = app('config')->get("bs4.form_rows.$rowConfig", null);
        if ($rowConfig === null)
        {
            throw new \InvalidArgumentException("Unknown configuration entry: bs4.form_rows.$rowConfig");
        }

        $element = clone $this;

        // Add a class to ourselves, to the control wrapper and to the label
        $element = $element->addClass('row');

        $element->controlWrapper = $element->controlWrapper ?? Div::create();
        $element->controlWrapper = $element->controlWrapper->addClass($rowConfig['control_wrapper'] ?? []);

        $element->label = $element->label ?? $element->label('', true)->label;
        $element->label = $element->label->addClass($rowConfig['label'] ?? []);

        return $element;
    }

    protected function wrapControl()
    {
        $element = clone $this;

        $fieldName = $element->getControlAttribute('name');
        $fieldId = field_name_to_id($fieldName);
        $helpTextId = field_name_to_id($fieldName) . '_helptext';

        // Label
        if ($element->label !== null)
        {
            $element = $element->addChild($element->label->for($fieldId));
        }

        // Control
        $controlElement = null;
        if ($element->control !== null)
        {
            $controlElement = $element->control
                ->attributeIf($element->helpText !== null, 'aria-describedby', $helpTextId);
        }

        // Help text
        $helpTextElement = null;
        if ($element->helpText !== null)
        {
            $helpTextElement = $element->helpText->id($helpTextId);
        }

        // Error messages
        $errorElement = null;
        if ($element->formState !== null && !$element->formState->shouldHideErrors())
        {
            $error = $element->formState->getFieldError($fieldName);
            if ($fieldName !== null && !empty($error))
            {
                $errorElement = Div::create()
                                   ->addClass(['invalid-feedback'])
                                   ->text($error);
            }
        }

        $errorHasBeenAppended = false;
        if (is_a($controlElement, ShowsErrors::class))
        {
            $controlElement = $controlElement->showError($errorElement);
            $errorHasBeenAppended = true;
        }

        // Wrap it all up
        if ($element->controlWrapper !== null)
        {
            $element->controlWrapper = $element->controlWrapper
                ->addChildIf($controlElement !== null, $controlElement)
                ->addChildIf($helpTextElement !== null, $helpTextElement)
                ->addChildIf($errorElement !== null && !$errorHasBeenAppended, $errorElement);

            $element = $element->addChild($element->controlWrapper);
        }
        else
        {
            $element = $element
                ->addChildIf($controlElement !== null, $controlElement)
                ->addChildIf($helpTextElement !== null, $helpTextElement)
                ->addChildIf($errorElement !== null && !$errorHasBeenAppended, $errorElement);
        }

        return $element;
    }

}


<?php

namespace MarvinLabs\Html\Bootstrap\Elements;

use MarvinLabs\Html\Bootstrap\Contracts\FormState;
use MarvinLabs\Html\Bootstrap\Elements\Traits\Assemblable;
use MarvinLabs\Html\Bootstrap\Elements\Traits\Disablable;
use MarvinLabs\Html\Bootstrap\Elements\Traits\SizableComponent;
use Spatie\Html\Elements\Input as BaseInput;

/**
 * Class Input
 * @package MarvinLabs\Html\Bootstrap\Elements
 *
 *          Input element with some BS4 helpers
 */
class Input extends BaseInput
{
    use SizableComponent, Disablable, Assemblable;

    // Used by SizableComponent
    protected $sizableClass = 'form-control';

    /** @var bool Show the input as plain text (used in conjunction with readonly) */
    private $plainText = false;

    /** @var  \MarvinLabs\Html\Bootstrap\Contracts\FormState */
    private $formState;

    /**
     * Input constructor.
     *
     * @param FormState $formState
     */
    public function __construct($formState)
    {
        parent::__construct();
        $this->formState = $formState;
    }

    /**
     * Make the input read only
     *
     * @param bool $showAsPlainText Style the input just like plain text (no border, padding, etc.)
     *
     * @return static
     */
    public function readOnly($showAsPlainText = false)
    {
        $element = clone $this;
        $element->plainText = $showAsPlainText;

        return $element->attribute('readonly', 'readonly');
    }

    /** @Override */
    protected function assemble()
    {
        $element = clone $this;

        $type = $this->getAttribute('type', 'text');

        if (\in_array($type, ['radio', 'checkbox'], true))
        {
            $element = $element->addClass('custom-control-input');
        }
        else if ($type === 'file')
        {
            $element = $element->addClass('custom-file-input');
        }
        else
        {
            $element = $element->addClass($this->plainText ? 'form-control-plaintext' : 'form-control');
        }

        // Add class for fields with error
        if ($element->formState !== null
            && $element->formState->hasFieldErrors($element->getAttribute('name')))
        {
            $element = $element->addClass('is-invalid');
        }

        return $element;
    }
}


<?php

namespace MarvinLabs\Html\Bootstrap\Traits;

use Illuminate\Contracts\Support\Htmlable;
use MarvinLabs\Html\Bootstrap\Contracts\FormState;
use MarvinLabs\Html\Bootstrap\Elements\Button;
use MarvinLabs\Html\Bootstrap\Elements\CheckBox;
use MarvinLabs\Html\Bootstrap\Elements\CustomFile;
use MarvinLabs\Html\Bootstrap\Elements\File;
use MarvinLabs\Html\Bootstrap\Elements\FormGroup;
use MarvinLabs\Html\Bootstrap\Elements\Input;
use MarvinLabs\Html\Bootstrap\Elements\InputGroup;
use MarvinLabs\Html\Bootstrap\Elements\Radio;
use MarvinLabs\Html\Bootstrap\Elements\RadioGroup;
use MarvinLabs\Html\Bootstrap\Elements\Select;
use MarvinLabs\Html\Bootstrap\Elements\TextArea;
use RuntimeException;
use Spatie\Html\Elements\Form;

/**
 * @target \MarvinLabs\Html\Bootstrap\Bootstrap
 */
trait BuildsForms
{
    /** @var Form */
    private $currentForm;

    /** @var \MarvinLabs\Html\Bootstrap\Contracts\FormState */
    private $formState;

    /**
     * Open a form
     *
     * @param string $method  The method to target for form action
     * @param string $action  The form action
     * @param array  $options A set of options for the form
     *
     * Valid options are:
     *
     *   - files      => bool    Does the form accept files
     *   - inline     => bool    Shall we render an inline form (Bootstrap specific)
     *   - model      => mixed   The model to bind to the form
     *   - hideErrors => bool    Hide field errors
     *   - attributes => array   Attributes to set on the form element
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     * @throws \Exception When trying to open a form before closing the previous one
     */
    public function openForm($method, $action, array $options = []): Htmlable
    {
        // Initialize the form state
        if ($this->formState !== null || $this->currentForm !== null)
        {
            throw new RuntimeException('You cannot open another form before closing the previous one');
        }
        $this->formState = app()->make(FormState::class);
        $this->formState->setModel($options['model'] ?? null);
        $this->formState->setHideErrors($options['hideErrors'] ?? false);

        // Create a form element with sane defaults
        $this->currentForm = Form::create();

        // Handle form method consequences (token / hidden method field)
        //
        // If Laravel needs to spoof the form's method, we'll append a hidden
        // field containing the actual method
        //
        // On any other method that get, the form needs a CSRF token
        $method = strtoupper($method);

        if (\in_array($method, ['DELETE', 'PATCH', 'PUT'], true))
        {
            $this->currentForm = $this->currentForm->addChild($this->hidden('_method', $method));
            $method = 'POST';
        }

        if ($method !== 'GET')
        {
            $this->currentForm = $this->currentForm->addChild($this->token());
        }

        if ($options['files'] ?? false)
        {
            $this->currentForm = $this->currentForm->acceptsFiles();
        }

        return $this->currentForm
            ->method($method)
            ->action($action)
            ->addClassIf($options['inline'] ?? false, 'form-inline')
            ->attributesIf($options['attributes'] ?? false, $options['attributes'] ?? [])
            ->open();
    }

    /**
     * Close a form previously open with openForm()
     *
     * @return \Illuminate\Contracts\Support\Htmlable
     */
    public function closeForm(): Htmlable
    {
        $out = $this->currentForm->close();

        $this->currentForm = null;
        $this->formState = null;

        return $out;
    }

    /**
     * @param \Spatie\Html\BaseElement|null $control
     * @param string|null                   $label
     * @param string|null                   $helpText
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\FormGroup
     */
    public function formGroup($control = null, $label = null, $helpText = null): FormGroup
    {
        $element = new FormGroup($this->formState, $control);

        return $element->helpText($helpText)->label($label);
    }

    /**
     * @param \Spatie\Html\BaseElement|null $control
     * @param string|null                   $prefix
     * @param string|null                   $suffix
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\InputGroup
     */
    public function inputGroup($control = null, $prefix = null, $suffix = null): InputGroup
    {
        $element = new InputGroup($control);

        return $element->prefix($prefix)->suffix($suffix);
    }

    /**
     * @param string|null $type
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function input($type = null, $name = null, $value = null): Input
    {
        $value = $this->getFieldValue($name, $value);
        $element = new Input($this->formState);

        return $element
            ->typeIf($type, $type)
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name))
            ->valueIf($value !== null, $value);
    }

    /**
     * @param string|null $name
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\File
     */
    public function simpleFile($name = null): File
    {
        $element = new File($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name));
    }

    /**
     * @param string|null $name
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\CustomFile
     */
    public function file($name = null, $description = null): CustomFile
    {
        $element = new CustomFile($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name, 'wrapper'))
            ->description($description);
    }

    /**
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Textarea
     */
    public function textArea($name = null, $value = null): TextArea
    {
        $value = $this->getFieldValue($name, $value);
        $element = new TextArea($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name))
            ->valueIf($value !== null, $value);
    }

    /**
     * @param string|null $name
     * @param string|null $description
     * @param bool        $isChecked
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\CheckBox
     */
    public function checkBox($name = null, $description = null, $isChecked = false): CheckBox
    {
        $isChecked = $this->getFieldValue($name, $isChecked);
        $element = new CheckBox($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name, 'wrapper'))
            ->description($description)
            ->checked($isChecked);
    }

    /**
     * @param string|null $name
     * @param string|null $description
     * @param bool        $isChecked
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Radio
     */
    public function radio($name = null, $description = null, $isChecked = false): Radio
    {
        $isChecked = $this->getFieldValue($name, $isChecked);
        $element = new Radio($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name, 'wrapper'))
            ->description($description)
            ->checked($isChecked);
    }

    /**
     * @param string|null $name
     * @param array       $options
     * @param string      $selectedOption
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\RadioGroup
     */
    public function radioGroup($name, $options, $selectedOption = null): RadioGroup
    {
        $element = new RadioGroup($this->formState);

        return $element
            ->name($name)
            ->id(field_name_to_id($name) . '_radio_group')
            ->options($options)
            ->selectedOption($selectedOption);
    }

    /**
     * @param string|null          $name
     * @param array|iterable       $options
     * @param string|iterable|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Select
     */
    public function select($name = null, $options = [], $value = null): Select
    {
        $value = $this->getFieldValue($name, $value);
        $element = new Select($this->formState);

        return $element
            ->nameIf($name, $name)
            ->idIf($name, field_name_to_id($name))
            ->options($options)
            ->valueIf($value !== null, $value);
    }

    /**
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function text($name = null, $value = null): Input
    {
        return $this->input('text', $name, $value);
    }

    /**
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function password($name = null, $value = null): Input
    {
        return $this->input('password', $name, $value);
    }

    /**
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function email($name = null, $value = null): Input
    {
        return $this->input('email', $name, $value);
    }

    /**
     * @param string|null $name
     * @param string|null $value
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function hidden($name = null, $value = null): Input
    {
        $value = $this->getFieldValue($name, $value);
        $element = new Input($this->formState);

        return $element
            ->type('hidden')
            ->nameIf($name, $name)
            ->valueIf($value !== null, $value);
    }

    /**
     * CSRF token hidden field
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Input
     */
    public function token(): Input
    {
        return $this->hidden('_token', $this->request->session()->token());
    }

    /**
     * @param string|\Spatie\Html\BaseElement $text
     * @param string                          $variant
     * @param bool                            $outlined
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Button
     */
    public function submit($text, $variant = 'primary', $outlined = false): Button
    {
        return $this->button($text, $variant, $outlined)->type('submit');
    }

    /**
     * @param        $text
     * @param string $variant
     * @param bool   $outlined
     *
     * @return \MarvinLabs\Html\Bootstrap\Elements\Button
     * @throws \Spatie\Html\Exceptions\InvalidHtml
     */
    public function button($text, $variant = 'secondary', $outlined = false): Button
    {
        return Button::create()->variant($variant, $outlined)->html($text);
    }

    protected function getFormState(): FormState
    {
        return $this->formState;
    }

    /**
     * @param string $name
     * @param mixed  $default
     *
     * @return mixed
     */
    private function getFieldValue($name, $default)
    {
        return $this->formState !== null
            ? $this->formState->getFieldValue($name, $default)
            : $default;
    }
}


<?php

namespace MarvinLabs\Html\Bootstrap\Elements;

use MarvinLabs\Html\Bootstrap\Elements\Traits\SizableComponent;
use Spatie\Html\Elements\Div;
use Spatie\Html\Elements\Span;

/**
 * Allows adding stuff on either side of a textual input field.
 *
 * See https://getbootstrap.com/docs/4.1/components/input-group/
 */
class InputGroup extends ControlWrapper
{
    use SizableComponent;

    // Used by SizableComponent
    protected $sizableClass = 'input-group';

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

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

    public function __construct($control = null)
    {
        parent::__construct($control, ['input-group']);
    }

    /**
     * Add an addon before the field
     *
     * @param string|\Spatie\Html\BaseElement $prefix      One or more prefixes to append before the field
     * @param bool                            $isPlainText Is the prefix to be handled like a button, dropdown, etc.
     *
     * @return static
     */
    public function prefix($prefix, $isPlainText = true)
    {
        if ($prefix === null)
        {
            return $this;
        }

        $element = clone $this;
        $element->prefixes[] = [
            'content'   => $prefix,
            'plaintext' => $isPlainText,
        ];

        return $element;
    }

    /**
     * Add an addon after the field
     *
     * @param string|\Spatie\Html\BaseElement $suffix      One or more suffixes to append before the field
     * @param bool                            $isPlainText Is the prefix to be handled like a button, dropdown, etc.
     *
     * @return static
     */
    public function suffix($suffix, $isPlainText = true)
    {
        if ($suffix === null)
        {
            return $this;
        }

        $element = clone $this;
        $element->suffixes[] = [
            'content'   => $suffix,
            'plaintext' => $isPlainText,
        ];

        return $element;
    }

    protected function wrapControl()
    {
        $element = clone $this;
        $element = $element->assembleAddons($this->prefixes, 'input-group-prepend');
        $element = $element->addChild($this->control);
        $element = $element->assembleAddons($this->suffixes, 'input-group-append');

        return $element;
    }

    /**
     * Add the child elements corresponding to the given addons
     *
     * @param array  $addons
     * @param string $addonContainerClass
     *
     * @return static
     */
    private function assembleAddons($addons, $addonContainerClass)
    {
        if (0 === \count($addons))
        {
            return $this;
        }

        $div = Div::create()
                  ->addClass($addonContainerClass)
                  ->addChildren($addons,
                      function ($token) {
                          $content = $token['content'] ?? '';
                          $plainText = $token['plaintext'] ?? true;

                          // When not instructed to treat as plain text, use it directly
                          if (!$plainText)
                          {
                              return $content;
                          }

                          // When instructed to use as plain text, we wrap inside a span
                          $span = Span::create()->addClass('input-group-text');

                          return \is_string($content)
                              ? $span->text($content)
                              : $span->addChild($content);
                      });

        return $this->addChild($div);
    }
}