summaryrefslogtreecommitdiff
path: root/vendor/oojs/oojs-ui/php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/oojs/oojs-ui/php')
-rw-r--r--vendor/oojs/oojs-ui/php/Element.php297
-rw-r--r--vendor/oojs/oojs-ui/php/ElementMixin.php55
-rw-r--r--vendor/oojs/oojs-ui/php/Exception.php6
-rw-r--r--vendor/oojs/oojs-ui/php/HtmlSnippet.php36
-rw-r--r--vendor/oojs/oojs-ui/php/Layout.php21
-rw-r--r--vendor/oojs/oojs-ui/php/Tag.php365
-rw-r--r--vendor/oojs/oojs-ui/php/Theme.php58
-rw-r--r--vendor/oojs/oojs-ui/php/Widget.php71
-rw-r--r--vendor/oojs/oojs-ui/php/elements/ButtonElement.php102
-rw-r--r--vendor/oojs/oojs-ui/php/elements/FlaggedElement.php133
-rw-r--r--vendor/oojs/oojs-ui/php/elements/GroupElement.php129
-rw-r--r--vendor/oojs/oojs-ui/php/elements/IconElement.php76
-rw-r--r--vendor/oojs/oojs-ui/php/elements/IndicatorElement.php78
-rw-r--r--vendor/oojs/oojs-ui/php/elements/LabelElement.php77
-rw-r--r--vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php88
-rw-r--r--vendor/oojs/oojs-ui/php/elements/TitledElement.php74
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FieldLayout.php140
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php32
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FormLayout.php47
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/PanelLayout.php61
-rw-r--r--vendor/oojs/oojs-ui/php/themes/ApexTheme.php6
-rw-r--r--vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php39
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php28
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php110
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php166
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php70
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php99
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/IconWidget.php34
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php32
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/InputWidget.php144
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/LabelWidget.php48
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php60
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php151
33 files changed, 2933 insertions, 0 deletions
diff --git a/vendor/oojs/oojs-ui/php/Element.php b/vendor/oojs/oojs-ui/php/Element.php
new file mode 100644
index 00000000..eaa8c825
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Element.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * DOM element abstraction.
+ *
+ * @abstract
+ */
+class Element extends Tag {
+
+ /* Static properties */
+
+ /**
+ * HTML tag name.
+ *
+ * This may be ignored if getTagName() is overridden.
+ *
+ * @var string
+ */
+ public static $tagName = 'div';
+
+ /**
+ * Default text direction, used for some layout calculations. Use setDefaultDir() to change.
+ *
+ * Currently only per-document directionality is supported.
+ *
+ * @var string
+ */
+ public static $defaultDir = 'ltr';
+
+ /* Members */
+
+ /**
+ * Element data.
+ *
+ * @var mixed
+ */
+ protected $data = null;
+
+ /**
+ * Mixins.
+ *
+ * @var array List mixed in objects.
+ */
+ protected $mixins = array();
+
+ /* Methods */
+
+ /**
+ * @param array $config Configuration options
+ * @param string[] $config['classes'] CSS class names to add
+ * @param string $config['id'] HTML id attribute
+ * @param string $config['text'] Text to insert
+ * @param array $config['content'] Content to append (after text), strings
+ * or Element objects. Strings will be HTML-escaped for output, use an
+ * HtmlSnippet instance to prevent that.
+ * @param mixed $config['data'] Element data
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $this->getTagName() );
+
+ // Initialization
+ if ( isset( $config['infusable'] ) && is_bool( $config['infusable'] ) ) {
+ $this->setInfusable( $config['infusable'] );
+ }
+ if ( isset( $config['data'] ) ) {
+ $this->setData( $config['data'] );
+ }
+ if ( isset( $config['classes'] ) && is_array( $config['classes'] ) ) {
+ $this->addClasses( $config['classes'] );
+ }
+ if ( isset( $config['id'] ) ) {
+ $this->setAttributes( array( 'id' => $config['id'] ) );
+ }
+ if ( isset( $config['text'] ) ) {
+ // JS compatibility
+ $this->appendContent( $config['text'] );
+ }
+ if ( isset( $config['content'] ) ) {
+ $this->appendContent( $config['content'] );
+ }
+ }
+
+ /**
+ * Call a mixed-in method.
+ *
+ * This makes the methods of a mixin accessible through the element being mixed into.
+ *
+ * Triggers an error if the method is not found, as normal.
+ *
+ * @param string $method Method name
+ * @param array $arguments Method arguments
+ * @return mixed Result of method call
+ */
+ public function __call( $method, $arguments ) {
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( method_exists( $mixin, $method ) ) {
+ return call_user_func_array( array( $mixin, $method ), $arguments );
+ }
+ }
+ // Fail normally
+ trigger_error(
+ 'Call to undefined method ' . __CLASS__ . '::' . $method . '()',
+ E_USER_ERROR
+ );
+ }
+
+ /**
+ * Get a mixed-in target property.
+ *
+ * This makes the target of a mixin accessible through the element being mixed into.
+ *
+ * The target's property name is statically configured by the mixin class.
+ *
+ * Triggers a notice if the property is not found, as normal.
+ *
+ * @param string $name Property name
+ * @return Tag|null Target property or null if not found
+ */
+ public function __get( $name ) {
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( isset( $mixin::$targetPropertyName ) && $mixin::$targetPropertyName === $name ) {
+ return $mixin->target;
+ }
+ }
+ // Fail normally
+ trigger_error( 'Undefined property: ' . $name, E_USER_NOTICE );
+ return null;
+ }
+
+ /**
+ * Get the HTML tag name.
+ *
+ * Override this method to base the result on instance information.
+ *
+ * @return string HTML tag name
+ */
+ public function getTagName() {
+ return $this::$tagName;
+ }
+
+ /**
+ * Get element data.
+ *
+ * @return mixed Element data
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+ /**
+ * Set element data.
+ *
+ * @param mixed $data Element data
+ * @chainable
+ */
+ public function setData( $data ) {
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Check if element supports one or more methods.
+ *
+ * @param string|string[] $methods Method or list of methods to check
+ * @return boolean All methods are supported
+ */
+ public function supports( $methods ) {
+ $support = 0;
+ $methods = (array)$methods;
+
+ foreach ( $methods as $method ) {
+ if ( method_exists( $this, $method ) ) {
+ $support++;
+ continue;
+ }
+
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( method_exists( $mixin, $method ) ) {
+ $support++;
+ break;
+ }
+ }
+ }
+
+ return count( $methods ) === $support;
+ }
+
+ /**
+ * Mixin a class.
+ *
+ * @param ElementMixin $mixin Mixin object
+ */
+ public function mixin( ElementMixin $mixin ) {
+ $this->mixins[] = $mixin;
+ }
+
+ /**
+ * Add the necessary properties to the given `$config` array to allow
+ * reconstruction of this widget via its constructor.
+ * @param array &$config
+ * An array which will be mutated to add the necessary configuration
+ * properties. Unless you are implementing a subclass, you should
+ * always pass a new empty `array()`.
+ * @return array
+ * A configuration array which can be passed to this object's
+ * constructor to recreate it. This is a return value to allow
+ * the safe use of copy-by-value functions like `array_merge` in
+ * the implementation.
+ */
+ public function getConfig( &$config ) {
+ foreach ( $this->mixins as $mixin ) {
+ $config = $mixin->getConfig( $config );
+ }
+ if ( $this->data !== null ) {
+ $config['data'] = $this->data;
+ }
+ return $config;
+ }
+
+ /**
+ * Create a modified version of the configuration array suitable for
+ * JSON serialization by replacing `Tag` references and
+ * `HtmlSnippet`s.
+ *
+ * @return array
+ * A serialized configuration array.
+ */
+ private function getSerializedConfig() {
+ // Ensure that '_' comes first in the output.
+ $config = array( '_' => true );
+ $config = $this->getConfig( $config );
+ // Post-process config array to turn Tag references into ID references
+ // and HtmlSnippet references into a { html: 'string' } JSON form.
+ $replaceElements = function( &$item ) {
+ if ( $item instanceof Tag ) {
+ $item->ensureInfusableId();
+ $item = array( 'tag' => $item->getAttribute( 'id' ) );
+ } elseif ( $item instanceof HtmlSnippet ) {
+ $item = array( 'html' => (string) $item );
+ }
+ };
+ array_walk_recursive( $config, $replaceElements );
+ // Set '_' last to ensure that subclasses can't accidentally step on it.
+ $config['_'] = preg_replace( '/^OOUI\\\\/', '', get_class( $this ) );
+ return $config;
+ }
+
+ protected function getGeneratedAttributes() {
+ $attributesArray = parent::getGeneratedAttributes();
+ // Add `data-ooui` attribute from serialized config array.
+ if ( $this->infusable ) {
+ $serialized = $this->getSerializedConfig();
+ $attributesArray['data-ooui'] = json_encode( $serialized );
+ }
+ return $attributesArray;
+ }
+
+ /**
+ * Render element into HTML.
+ *
+ * @return string HTML serialization
+ */
+ public function toString() {
+ Theme::singleton()->updateElementClasses( $this );
+ if ( $this->isInfusable() ) {
+ $this->ensureInfusableId();
+ }
+ return parent::toString();
+ }
+
+ /**
+ * Get the direction of the user interface for a given element.
+ *
+ * Currently only per-document directionality is supported.
+ *
+ * @param Tag $element Element to check
+ * @return string Text direction, either 'ltr' or 'rtl'
+ */
+ public static function getDir( Tag $element ) {
+ return self::$defaultDir;
+ }
+
+ /**
+ * Set the default direction of the user interface.
+ *
+ * @return string Text direction, either 'ltr' or 'rtl'
+ */
+ public static function setDefaultDir( $dir ) {
+ self::$defaultDir = $dir === 'rtl' ? 'rtl' : 'ltr';
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/ElementMixin.php b/vendor/oojs/oojs-ui/php/ElementMixin.php
new file mode 100644
index 00000000..7f54076e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/ElementMixin.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace OOUI;
+
+class ElementMixin {
+
+ /* Properties */
+
+ /**
+ * Tag being targeted.
+ *
+ * @var Tag
+ */
+ public $target = null;
+
+ /**
+ * Element being mixed into.
+ *
+ * @var Element
+ */
+ protected $element = null;
+
+ /**
+ * Property name for accessing the target on the element.
+ *
+ * @var string
+ */
+ public static $targetPropertyName = '';
+
+ /* Methods */
+
+ /**
+ * Create element.
+ *
+ * @param Element $element Element being mixed into
+ * @param Tag $tag Tag being targeted
+ * @param array $config Configuration options
+ */
+ public function __construct( Element $element, Tag $target, array $config = array() ) {
+ $this->element = $element;
+ $this->target = $target;
+ }
+
+ /**
+ * Add properties to the given `$config` array to allow reconstruction
+ * of this widget via its constructor. This method is meant to be
+ * overridden by subclasses of `ElementMixin`.
+ *
+ * @return array A configuration array.
+ * @see Element::getConfig()
+ */
+ public function getConfig( &$config ) {
+ return $config;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Exception.php b/vendor/oojs/oojs-ui/php/Exception.php
new file mode 100644
index 00000000..2f5ba1b4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Exception.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace OOUI;
+
+class Exception extends \Exception {
+}
diff --git a/vendor/oojs/oojs-ui/php/HtmlSnippet.php b/vendor/oojs/oojs-ui/php/HtmlSnippet.php
new file mode 100644
index 00000000..e0889fca
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/HtmlSnippet.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Wraps a HTML snippet for use with Tag::appendContent() and Tag::prependContent().
+ */
+class HtmlSnippet {
+
+ /* Members */
+
+ /**
+ * HTML snippet this instance represents.
+ *
+ * @var string
+ */
+ protected $content;
+
+ /* Methods */
+
+ /**
+ * @param string $content
+ */
+ public function __construct( $content ) {
+ $this->content = $content;
+ }
+
+ /**
+ * Render into HTML.
+ *
+ * @return string Unchanged HTML snippet
+ */
+ public function __toString() {
+ return $this->content;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Layout.php b/vendor/oojs/oojs-ui/php/Layout.php
new file mode 100644
index 00000000..7a4e58d0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Layout.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Container for elements.
+ *
+ * @abstract
+ */
+class Layout extends Element {
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-layout' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Tag.php b/vendor/oojs/oojs-ui/php/Tag.php
new file mode 100644
index 00000000..da8c2bfa
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Tag.php
@@ -0,0 +1,365 @@
+<?php
+
+namespace OOUI;
+
+class Tag {
+
+ /* Members */
+
+ /**
+ * Tag name for this instance.
+ *
+ * @var string HTML tag name
+ */
+ protected $tag = '';
+
+ /**
+ * Attributes.
+ *
+ * @var array HTML attributes
+ */
+ protected $attributes = array();
+
+ /**
+ * Classes.
+ *
+ * @var array CSS classes
+ */
+ protected $classes = array();
+
+ /**
+ * Content.
+ *
+ * @var array Content text and elements
+ */
+ protected $content = array();
+
+ /**
+ * Group.
+ *
+ * @var GroupElement|null Group element is in
+ */
+ protected $elementGroup = null;
+
+ /**
+ * Infusion support.
+ *
+ * @var boolean Whether to serialize tag/element/widget state for client-side use.
+ */
+ protected $infusable = false;
+
+ /* Methods */
+
+ /**
+ * Create element.
+ *
+ * @param string $tag HTML tag name
+ */
+ public function __construct( $tag = 'div' ) {
+ $this->tag = $tag;
+ }
+
+ /**
+ * Check for CSS class.
+ *
+ * @param string $name CSS class name
+ * @return boolean
+ */
+ public function hasClass( $class ) {
+ return in_array( $class, $this->classes );
+ }
+
+ /**
+ * Add CSS classes.
+ *
+ * @param array $classes List of classes to add
+ * @chainable
+ */
+ public function addClasses( array $classes ) {
+ $this->classes = array_merge( $this->classes, $classes );
+ return $this;
+ }
+
+ /**
+ * Remove CSS classes.
+ *
+ * @param array $classes List of classes to remove
+ * @chainable
+ */
+ public function removeClasses( array $classes ) {
+ $this->classes = array_diff( $this->classes, $classes );
+ return $this;
+ }
+
+ /**
+ * Toggle CSS classes.
+ *
+ * @param array $classes List of classes to add
+ * @param boolean $toggle Add classes
+ * @chainable
+ */
+ public function toggleClasses( array $classes, $toggle = null ) {
+ if ( $toggle === null ) {
+ $this->classes = array_diff(
+ array_merge( $this->classes, $classes ),
+ array_intersect( $this->classes, $classes )
+ );
+ } elseif ( $toggle ) {
+ $this->classes = array_merge( $this->classes, $classes );
+ } else {
+ $this->classes = array_diff( $this->classes, $classes );
+ }
+ return $this;
+ }
+
+ /**
+ * Get HTML attribute value.
+ *
+ * @param string $key HTML attribute name
+ * @return string|null
+ */
+ public function getAttribute( $key ) {
+ return isset( $this->attributes[$key] ) ? $this->attributes[$key] : null;
+ }
+
+ /**
+ * Add HTML attributes.
+ *
+ * @param array $attributes List of attribute key/value pairs to add
+ * @chainable
+ */
+ public function setAttributes( array $attributes ) {
+ foreach ( $attributes as $key => $value ) {
+ $this->attributes[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Set value of input element ('value' attribute for most, element content for textarea).
+ *
+ * @param string $value Value to set
+ * @chainable
+ */
+ public function setValue( $value ) {
+ if ( strtolower( $this->tag ) === 'textarea' ) {
+ $this->clearContent();
+ $this->appendContent( $value );
+ } else {
+ $this->setAttributes( array( 'value' => $value ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Remove HTML attributes.
+ *
+ * @param array $keys List of attribute keys to remove
+ * @chainable
+ */
+ public function removeAttributes( array $keys ) {
+ foreach ( $keys as $key ) {
+ unset( $this->attributes[$key] );
+ }
+ return $this;
+ }
+
+ /**
+ * Add content to the end.
+ *
+ * Accepts variadic arguments (the $content argument can be repeated any number of times).
+ *
+ * @param string|Tag|HtmlSnippet $content Content to append. Strings will be HTML-escaped
+ * for output, use a HtmlSnippet instance to prevent that.
+ * @chainable
+ */
+ public function appendContent( /* $content... */ ) {
+ $contents = func_get_args();
+ $this->content = array_merge( $this->content, $contents );
+ return $this;
+ }
+
+ /**
+ * Add content to the beginning.
+ *
+ * Accepts variadic arguments (the $content argument can be repeated any number of times).
+ *
+ * @param string|Tag|HtmlSnippet $content Content to prepend. Strings will be HTML-escaped
+ * for output, use a HtmlSnippet instance to prevent that.
+ * @chainable
+ */
+ public function prependContent( /* $content... */ ) {
+ $contents = func_get_args();
+ array_splice( $this->content, 0, 0, $contents );
+ return $this;
+ }
+
+ /**
+ * Remove all content.
+ *
+ * @chainable
+ */
+ public function clearContent() {
+ $this->content = array();
+ return $this;
+ }
+
+ /**
+ * Get group element is in.
+ *
+ * @return GroupElement|null Group element, null if none
+ */
+ public function getElementGroup() {
+ return $this->elementGroup;
+ }
+
+ /**
+ * Set group element is in.
+ *
+ * @param GroupElement|null $group Group element, null if none
+ * @chainable
+ */
+ public function setElementGroup( $group ) {
+ $this->elementGroup = $group;
+ return $this;
+ }
+
+ /**
+ * Enable widget for client-side infusion.
+ *
+ * @param boolean $infusable True to allow tag/element/widget to be referenced client-side.
+ * @chainable
+ */
+ public function setInfusable( $infusable ) {
+ $this->infusable = $infusable;
+ return $this;
+ }
+
+ /**
+ * Get client-side infusability.
+ *
+ * @return boolean If this tag/element/widget can be referenced client-side.
+ */
+ public function isInfusable() {
+ return $this->infusable;
+ }
+
+ private static $id_cnt = 0;
+ /**
+ * Ensure that this given Tag is infusable and has a unique `id`
+ * attribute.
+ * @chainable
+ */
+ public function ensureInfusableId() {
+ $this->setInfusable( true );
+ if ( $this->getAttribute( 'id' ) === null ) {
+ $this->setAttributes( array( 'id' => "ooui-" . ( self::$id_cnt++ ) ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Return an augmented `attributes` array, including synthetic attributes
+ * which are created from other properties (like the `classes` array)
+ * but which shouldn't be retained in the user-visible `attributes`.
+ * @return array An attributes array.
+ */
+ protected function getGeneratedAttributes() {
+ // Copy attributes, add `class` attribute from `$this->classes` array.
+ $attributesArray = $this->attributes;
+ if ( $this->classes ) {
+ $attributesArray['class'] = implode( ' ', array_unique( $this->classes ) );
+ }
+ if ( $this->infusable ) {
+ // Indicate that this is "just" a tag (not a widget)
+ $attributesArray['data-ooui'] = json_encode( array( '_' => 'Tag' ) );
+ }
+ return $attributesArray;
+ }
+
+ /**
+ * Render element into HTML.
+ *
+ * @return string HTML serialization
+ */
+ public function toString() {
+ $attributes = '';
+ foreach ( $this->getGeneratedAttributes() as $key => $value ) {
+ if ( !preg_match( '/^[0-9a-zA-Z-]+$/', $key ) ) {
+ throw new Exception( 'Attribute name must consist of only ASCII letters, numbers and dash' );
+ }
+
+ if ( $key === 'href' || $key === 'action' ) {
+ // Make it impossible to point a link or a form to a 'javascript:' URL. There's no good way
+ // to blacklist them because of very lax parsing, so instead we whitelist known-good
+ // protocols (and also accept protocol-less and protocol-relative links). There are no good
+ // reasons to ever use 'javascript:' URLs anyway.
+ $protocolWhitelist = array(
+ // Sourced from MediaWiki's $wgUrlProtocols
+ 'bitcoin', 'ftp', 'ftps', 'geo', 'git', 'gopher', 'http', 'https', 'irc', 'ircs',
+ 'magnet', 'mailto', 'mms', 'news', 'nntp', 'redis', 'sftp', 'sip', 'sips', 'sms', 'ssh',
+ 'svn', 'tel', 'telnet', 'urn', 'worldwind', 'xmpp',
+ );
+
+ // Protocol-relative URLs are handled really badly by parse_url()
+ if ( substr( $value, 0, 2 ) === '//' ) {
+ $url = "http:$value";
+ } else {
+ $url = $value;
+ }
+ // Must suppress warnings when the value is not a valid URL. parse_url() returns false then.
+ // @codingStandardsIgnoreStart
+ $scheme = @parse_url( $url, PHP_URL_SCHEME );
+ // @codingStandardsIgnoreEnd
+
+ if ( !( $scheme === null || in_array( strtolower( $scheme ), $protocolWhitelist ) ) ) {
+ throw new Exception( "Potentially unsafe '$key' attribute value. " .
+ "Scheme: '$scheme'; value: '$value'." );
+ }
+ }
+
+ // Use single-quotes around the attribute value in HTML, because
+ // some of the values might be JSON strings
+ // 1. Encode both single and double quotes (and other special chars)
+ $value = htmlspecialchars( $value, ENT_QUOTES );
+ // 2. Decode double quotes, for readability.
+ $value = str_replace( '&quot;', '"', $value );
+ // 3. Wrap attribute value in single quotes in the HTML.
+ $attributes .= ' ' . $key . "='" . $value . "'";
+ }
+
+ // Content
+ $content = '';
+ foreach ( $this->content as $part ) {
+ if ( is_string( $part ) ) {
+ $content .= htmlspecialchars( $part );
+ } elseif ( $part instanceof Tag || $part instanceof HtmlSnippet ) {
+ $content .= (string)$part;
+ }
+ }
+
+ if ( !preg_match( '/^[0-9a-zA-Z]+$/', $this->tag ) ) {
+ throw new Exception( 'Tag name must consist of only ASCII letters and numbers' );
+ }
+
+ // Tag
+ return '<' . $this->tag . $attributes . '>' . $content . '</' . $this->tag . '>';
+ }
+
+ /**
+ * Magic method implementation.
+ *
+ * PHP doesn't allow __toString to throw exceptions and will trigger a fatal error if it does.
+ * This is a wrapper around the real toString() to convert them to errors instead.
+ *
+ * @return string
+ */
+ public function __toString() {
+ try {
+ return $this->toString();
+ } catch ( Exception $ex ) {
+ trigger_error( (string)$ex, E_USER_ERROR );
+ return '';
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Theme.php b/vendor/oojs/oojs-ui/php/Theme.php
new file mode 100644
index 00000000..d36b6d82
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Theme.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Theme logic.
+ *
+ * @abstract
+ */
+class Theme {
+
+ /* Members */
+
+ private static $singleton;
+
+ /* Static Methods */
+
+ public static function setSingleton( Theme $theme ) {
+ self::$singleton = $theme;
+ }
+
+ public static function singleton() {
+ if ( !self::$singleton ) {
+ throw new Exception( __METHOD__ . ' was called with no singleton theme set.' );
+ }
+
+ return self::$singleton;
+ }
+
+ /**
+ * Get a list of classes to be applied to a widget.
+ *
+ * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
+ * otherwise state transitions will not work properly.
+ *
+ * @param Element $element Element for which to get classes
+ * @return array Categorized class names with `on` and `off` lists
+ */
+ public function getElementClasses( Element $element ) {
+ return array( 'on' => array(), 'off' => array() );
+ }
+
+ /**
+ * Update CSS classes provided by the theme.
+ *
+ * For elements with theme logic hooks, this should be called any time there's a state change.
+ *
+ * @param Element $element Element for which to update classes
+ * @return array Categorized class names with `on` and `off` lists
+ */
+ public function updateElementClasses( Element $element ) {
+ $classes = $this->getElementClasses( $element );
+
+ $element
+ ->removeClasses( $classes['off'] )
+ ->addClasses( $classes['on'] );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Widget.php b/vendor/oojs/oojs-ui/php/Widget.php
new file mode 100644
index 00000000..04152aa5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Widget.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * User interface control.
+ *
+ * @abstract
+ */
+class Widget extends Element {
+
+ /* Properties */
+
+ /**
+ * Disabled.
+ *
+ * @var boolean Widget is disabled
+ */
+ protected $disabled = false;
+
+ /* Methods */
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['disabled'] Disable (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Initialize config
+ $config = array_merge( array( 'disabled' => false ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-widget' ) );
+ $this->setDisabled( $config['disabled'] );
+ }
+
+ /**
+ * Check if the widget is disabled.
+ *
+ * @return boolean Button is disabled
+ */
+ public function isDisabled() {
+ return $this->disabled;
+ }
+
+ /**
+ * Set the disabled state of the widget.
+ *
+ * This should probably change the widgets' appearance and prevent it from being used.
+ *
+ * @param boolean $disabled Disable widget
+ * @chainable
+ */
+ public function setDisabled( $disabled ) {
+ $this->disabled = !!$disabled;
+ $this->toggleClasses( array( 'oo-ui-widget-disabled' ), $this->disabled );
+ $this->toggleClasses( array( 'oo-ui-widget-enabled' ), !$this->disabled );
+ $this->setAttributes( array( 'aria-disabled' => $this->disabled ? 'true' : 'false' ) );
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->disabled ) {
+ $config['disabled'] = $this->disabled;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/ButtonElement.php b/vendor/oojs/oojs-ui/php/elements/ButtonElement.php
new file mode 100644
index 00000000..f9acf2d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/ButtonElement.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with a button.
+ *
+ * Buttons are used for controls which can be clicked. They can be configured to use tab indexing
+ * and access keys for accessibility purposes.
+ *
+ * @abstract
+ */
+class ButtonElement extends ElementMixin {
+ /**
+ * Button is framed.
+ *
+ * @var boolean
+ */
+ protected $framed = false;
+
+ /**
+ * Button's access key.
+ *
+ * @var string
+ */
+ protected $accessKey = null;
+
+ public static $targetPropertyName = 'button';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param boolean $config['framed'] Render button with a frame (default: true)
+ * @param string $config['accessKey'] Button's access key
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['button'] ) ? $config['button'] : new Tag( 'a' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->element->addClasses( array( 'oo-ui-buttonElement' ) );
+ $this->target->addClasses( array( 'oo-ui-buttonElement-button' ) );
+ $this->toggleFramed( isset( $config['framed'] ) ? $config['framed'] : true );
+ $this->setAccessKey( isset( $config['accessKey'] ) ? $config['accessKey'] : null );
+ $this->target->setAttributes( array(
+ 'role' => 'button',
+ ) );
+ }
+
+ /**
+ * Toggle frame.
+ *
+ * @param boolean $framed Make button framed, omit to toggle
+ * @chainable
+ */
+ public function toggleFramed( $framed = null ) {
+ $this->framed = $framed !== null ? !!$framed : !$this->framed;
+ $this->element->toggleClasses( array( 'oo-ui-buttonElement-framed' ), $this->framed );
+ $this->element->toggleClasses( array( 'oo-ui-buttonElement-frameless' ), !$this->framed );
+ }
+
+ /**
+ * Check if button has a frame.
+ *
+ * @return boolean Button is framed
+ */
+ public function isFramed() {
+ return $this->framed;
+ }
+
+ /**
+ * Set access key.
+ *
+ * @param string $accessKey Button's access key, use empty string to remove
+ * @chainable
+ */
+ public function setAccessKey( $accessKey ) {
+ $accessKey = is_string( $accessKey ) && strlen( $accessKey ) ? $accessKey : null;
+
+ if ( $this->accessKey !== $accessKey ) {
+ if ( $accessKey !== null ) {
+ $this->target->setAttributes( array( 'accesskey' => $accessKey ) );
+ } else {
+ $this->target->removeAttributes( array( 'accesskey' ) );
+ }
+ $this->accessKey = $accessKey;
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->framed !== true ) {
+ $config['framed'] = $this->framed;
+ }
+ if ( $this->accessKey !== null ) {
+ $config['accessKey'] = $this->accessKey;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php b/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php
new file mode 100644
index 00000000..bd5dc80d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with named flags that can be added, removed, listed and checked.
+ *
+ * A flag, when set, adds a CSS class on the `$element` by combining `oo-ui-flaggedElement-` with
+ * the flag name. Flags are primarily useful for styling.
+ *
+ * @abstract
+ */
+class FlaggedElement extends ElementMixin {
+ /**
+ * Flags.
+ *
+ * @var string
+ */
+ protected $flags = array();
+
+ public static $targetPropertyName = 'flagged';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string|string[] $config['flags'] Flags describing importance and functionality, e.g.
+ * 'primary', 'safe', 'progressive', 'destructive' or 'constructive'
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['flagged'] ) ? $config['flagged'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setFlags( isset( $config['flags'] ) ? $config['flags'] : null );
+ }
+
+ /**
+ * Check if a flag is set.
+ *
+ * @param string $flag Name of flag
+ * @return boolean Has flag
+ */
+ public function hasFlag( $flag ) {
+ return isset( $this->flags[$flag] );
+ }
+
+ /**
+ * Get the names of all flags set.
+ *
+ * @return string[] Flag names
+ */
+ public function getFlags() {
+ return array_keys( $this->flags );
+ }
+
+ /**
+ * Clear all flags.
+ *
+ * @chainable
+ */
+ public function clearFlags() {
+ $remove = array();
+ $classPrefix = 'oo-ui-flaggedElement-';
+
+ foreach ( $this->flags as $flag ) {
+ $remove[] = $classPrefix . $flag;
+ }
+
+ $this->target->removeClasses( $remove );
+ $this->flags = array();
+
+ return $this;
+ }
+
+ /**
+ * Add one or more flags.
+ *
+ * @param string|array $flags One or more flags to add, or an array keyed by flag name
+ * containing boolean set/remove instructions.
+ * @chainable
+ */
+ public function setFlags( $flags ) {
+ $add = array();
+ $remove = array();
+ $classPrefix = 'oo-ui-flaggedElement-';
+
+ if ( is_string( $flags ) ) {
+ // Set
+ if ( !isset( $this->flags[$flags] ) ) {
+ $this->flags[$flags] = true;
+ $add[] = $classPrefix . $flags;
+ }
+ } elseif ( is_array( $flags ) ) {
+ foreach ( $flags as $key => $value ) {
+ if ( is_numeric( $key ) ) {
+ // Set
+ if ( !isset( $this->flags[$value] ) ) {
+ $this->flags[$value] = true;
+ $add[] = $classPrefix . $value;
+ }
+ } else {
+ if ( $value ) {
+ // Set
+ if ( !isset( $this->flags[$key] ) ) {
+ $this->flags[$key] = true;
+ $add[] = $classPrefix . $key;
+ }
+ } else {
+ // Remove
+ if ( isset( $this->flags[$key] ) ) {
+ unset( $this->flags[$key] );
+ $remove[] = $classPrefix . $key;
+ }
+ }
+ }
+ }
+ }
+
+ $this->target
+ ->addClasses( $add )
+ ->removeClasses( $remove );
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( !empty( $this->flags ) ) {
+ $config['flags'] = $this->getFlags();
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/GroupElement.php b/vendor/oojs/oojs-ui/php/elements/GroupElement.php
new file mode 100644
index 00000000..93d3c7a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/GroupElement.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing a sequence of child elements.
+ *
+ * @abstract
+ */
+class GroupElement extends ElementMixin {
+ /**
+ * List of items in the group.
+ *
+ * @var Element[]
+ */
+ protected $items = array();
+
+ public static $targetPropertyName = 'group';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['group'] ) ? $config['group'] : new Tag( 'div' );
+ parent::__construct( $element, $target, $config );
+ }
+
+ /**
+ * Check if there are no items.
+ *
+ * @return boolean Group is empty
+ */
+ public function isEmpty() {
+ return !count( $this->items );
+ }
+
+ /**
+ * Get items.
+ *
+ * @return Element[] Items
+ */
+ public function getItems() {
+ return $this->items;
+ }
+
+ /**
+ * Add items.
+ *
+ * Adding an existing item will move it.
+ *
+ * @param Element[] $items Items
+ * @param number $index Index to insert items at
+ * @chainable
+ */
+ public function addItems( array $items, $index = null ) {
+ foreach ( $items as $item ) {
+ // Check if item exists then remove it first, effectively "moving" it
+ $currentIndex = array_search( $item, $this->items );
+ if ( $currentIndex !== false ) {
+ $this->removeItems( array( $item ) );
+ // Adjust index to compensate for removal
+ if ( $currentIndex < $index ) {
+ $index--;
+ }
+ }
+ // Add the item
+ $item->setElementGroup( $this );
+ }
+
+ if ( $index === null || $index < 0 || $index >= count( $this->items ) ) {
+ $this->items = array_merge( $this->items, $items );
+ } else {
+ array_splice( $this->items, $index, 0, $items );
+ }
+
+ // Update actual target element contents to reflect our list
+ $this->target->clearContent();
+ call_user_func_array( array( $this->target, 'appendContent' ), $this->items );
+
+ return $this;
+ }
+
+ /**
+ * Remove items.
+ *
+ * @param Element[] $items Items to remove
+ * @chainable
+ */
+ public function removeItems( $items ) {
+ foreach ( $items as $item ) {
+ $index = array_search( $item, $this->items );
+ if ( $index !== false ) {
+ $item->setElementGroup( null );
+ array_splice( $this->items, $index, 1 );
+ }
+ }
+
+ // Update actual target element contents to reflect our list
+ $this->target->clearContent();
+ call_user_func_array( array( $this->target, 'appendContent' ), $this->items );
+
+ return $this;
+ }
+
+ /**
+ * Clear all items.
+ *
+ * Items will be detached, not removed, so they can be used later.
+ *
+ * @chainable
+ */
+ public function clearItems() {
+ foreach ( $this->items as $item ) {
+ $item->setElementGroup( null );
+ }
+
+ $this->items = array();
+ $this->target->clearContent();
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $config['items'] = $this->items;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/IconElement.php b/vendor/oojs/oojs-ui/php/elements/IconElement.php
new file mode 100644
index 00000000..b6d27376
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/IconElement.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing an icon.
+ *
+ * Icons are graphics, about the size of normal text. They can be used to aid the user in locating
+ * a control or convey information in a more space efficient way. Icons should rarely be used
+ * without labels; such as in a toolbar where space is at a premium or within a context where the
+ * meaning is very clear to the user.
+ *
+ * @abstract
+ */
+class IconElement extends ElementMixin {
+ /**
+ * Symbolic icon name.
+ *
+ * @var string
+ */
+ protected $icon = null;
+
+ public static $targetPropertyName = 'icon';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['icon'] Symbolic icon name
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'iconElement' is a very stupid way to call '$icon'
+ $target = isset( $config['iconElement'] ) ? $config['iconElement'] : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-iconElement-icon' ) );
+ $this->setIcon( isset( $config['icon'] ) ? $config['icon'] : null );
+ }
+
+ /**
+ * Set icon name.
+ *
+ * @param string|null $icon Symbolic icon name
+ * @chainable
+ */
+ public function setIcon( $icon = null ) {
+ if ( $this->icon !== null ) {
+ $this->target->removeClasses( array( 'oo-ui-icon-' . $this->icon ) );
+ }
+ if ( $icon !== null ) {
+ $this->target->addClasses( array( 'oo-ui-icon-' . $icon ) );
+ }
+
+ $this->icon = $icon;
+ $this->element->toggleClasses( array( 'oo-ui-iconElement' ), (bool)$this->icon );
+
+ return $this;
+ }
+
+ /**
+ * Get icon name.
+ *
+ * @return string Icon name
+ */
+ public function getIcon() {
+ return $this->icon;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->icon !== null ) {
+ $config['icon'] = $this->icon;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php b/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php
new file mode 100644
index 00000000..56238b6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing an indicator.
+ *
+ * Indicators are graphics, smaller than normal text. They can be used to describe unique status or
+ * behavior. Indicators should only be used in exceptional cases; such as a button that opens a menu
+ * instead of performing an action directly, or an item in a list which has errors that need to be
+ * resolved.
+ *
+ * @abstract
+ */
+class IndicatorElement extends ElementMixin {
+ /**
+ * Symbolic indicator name
+ *
+ * @var string|null
+ */
+ protected $indicator = null;
+
+ public static $targetPropertyName = 'indicator';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['indicator'] Symbolic indicator name
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'indicatorElement' is a very stupid way to call '$indicator'
+ $target = isset( $config['indicatorElement'] )
+ ? $config['indicatorElement']
+ : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-indicatorElement-indicator' ) );
+ $this->setIndicator( isset( $config['indicator'] ) ? $config['indicator'] : null );
+ }
+
+ /**
+ * Set indicator name.
+ *
+ * @param string|null $indicator Symbolic name of indicator to use or null for no indicator
+ * @chainable
+ */
+ public function setIndicator( $indicator = null ) {
+ if ( $this->indicator !== null ) {
+ $this->target->removeClasses( array( 'oo-ui-indicator-' . $this->indicator ) );
+ }
+ if ( $indicator !== null ) {
+ $this->target->addClasses( array( 'oo-ui-indicator-' . $indicator ) );
+ }
+
+ $this->indicator = $indicator;
+ $this->element->toggleClasses( array( 'oo-ui-indicatorElement' ), (bool)$this->indicator );
+
+ return $this;
+ }
+
+ /**
+ * Get indicator name.
+ *
+ * @return string Symbolic name of indicator
+ */
+ public function getIndicator() {
+ return $this->indicator;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->indicator !== null ) {
+ $config['indicator'] = $this->indicator;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/LabelElement.php b/vendor/oojs/oojs-ui/php/elements/LabelElement.php
new file mode 100644
index 00000000..d5cf7bee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/LabelElement.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing a label.
+ *
+ * @abstract
+ */
+class LabelElement extends ElementMixin {
+ /**
+ * Label value.
+ *
+ * @var string|HtmlSnippet|null
+ */
+ protected $label = null;
+
+ public static $targetPropertyName = 'label';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string|HtmlSnippet $config['label'] Label text
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'labelElement' is a very stupid way to call '$label'
+ $target = isset( $config['labelElement'] ) ? $config['labelElement'] : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-labelElement-label' ) );
+ $this->setLabel( isset( $config['label'] ) ? $config['label'] : null );
+ }
+
+ /**
+ * Set the label.
+ *
+ * An empty string will result in the label being hidden. A string containing only whitespace will
+ * be converted to a single `&nbsp;`.
+ *
+ * @param string|HtmlSnippet|null $label Label text
+ * @chainable
+ */
+ public function setLabel( $label ) {
+ $this->label = $label;
+
+ $this->target->clearContent();
+ if ( $this->label !== null ) {
+ if ( is_string( $this->label ) && $this->label !== '' && trim( $this->label ) === '' ) {
+ $this->target->appendContent( new HtmlSnippet( '&nbsp;' ) );
+ } else {
+ $this->target->appendContent( $label );
+ }
+ }
+
+ $this->element->toggleClasses( array( 'oo-ui-labelElement' ), !!$this->label );
+
+ return $this;
+ }
+
+ /**
+ * Get the label.
+ *
+ * @return string|HtmlSnippet|null Label text
+ */
+ public function getLabel() {
+ return $this->label;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->label !== null ) {
+ $config['label'] = $this->label;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php b/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php
new file mode 100644
index 00000000..223b5371
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element supporting "sequential focus navigation" using the 'tabindex' attribute.
+ *
+ * @abstract
+ */
+class TabIndexedElement extends ElementMixin {
+ /**
+ * Tab index value.
+ *
+ * @var number|null
+ */
+ protected $tabIndex = null;
+
+ public static $targetPropertyName = 'tabIndexed';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param number|null $config['tabIndex'] Tab index value. Use 0 to use default ordering, use -1 to
+ * prevent tab focusing, use null to suppress the `tabindex` attribute. (default: 0)
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['tabIndexed'] ) ? $config['tabIndexed'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setTabIndex( isset( $config['tabIndex'] ) ? $config['tabIndex'] : 0 );
+ }
+
+ /**
+ * Set tab index value.
+ *
+ * @param number|null $tabIndex Tab index value or null for no tab index
+ * @chainable
+ */
+ public function setTabIndex( $tabIndex ) {
+ $tabIndex = is_numeric( $tabIndex ) ? $tabIndex : null;
+
+ if ( $this->tabIndex !== $tabIndex ) {
+ $this->tabIndex = $tabIndex;
+ $this->updateTabIndex();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Update the tabIndex attribute, in case of changes to tabIndex or disabled
+ * state.
+ *
+ * @chainable
+ */
+ public function updateTabIndex() {
+ $disabled = $this->element->isDisabled();
+ if ( $this->tabIndex !== null ) {
+ $this->target->setAttributes( array(
+ // Do not index over disabled elements
+ 'tabindex' => $disabled ? -1 : $this->tabIndex,
+ // ChromeVox and NVDA do not seem to inherit this from parent elements
+ 'aria-disabled' => ( $disabled ? 'true' : 'false' )
+ ) );
+ } else {
+ $this->target->removeAttributes( array( 'tabindex', 'aria-disabled' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Get tab index value.
+ *
+ * @return number|null Tab index value
+ */
+ public function getTabIndex() {
+ return $this->tabIndex;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->tabIndex !== 0 ) {
+ $config['tabIndex'] = $this->tabIndex;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/TitledElement.php b/vendor/oojs/oojs-ui/php/elements/TitledElement.php
new file mode 100644
index 00000000..5f1317c4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/TitledElement.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with a title.
+ *
+ * Titles are rendered by the browser and are made visible when hovering the element. Titles are
+ * not visible on touch devices.
+ *
+ * @abstract
+ */
+class TitledElement extends ElementMixin {
+ /**
+ * Title text.
+ *
+ * @var string
+ */
+ protected $title = null;
+
+ public static $targetPropertyName = 'titled';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['title'] Title. If not provided, the static property 'title' is used.
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['titled'] ) ? $config['titled'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setTitle(
+ isset( $config['title'] ) ? $config['title'] :
+ ( isset( $element::$title ) ? $element::$title : null )
+ );
+ }
+
+ /**
+ * Set title.
+ *
+ * @param string|null $title Title text or null for no title
+ * @chainable
+ */
+ public function setTitle( $title ) {
+ if ( $this->title !== $title ) {
+ $this->title = $title;
+ if ( $title !== null ) {
+ $this->target->setAttributes( array( 'title' => $title ) );
+ } else {
+ $this->target->removeAttributes( array( 'title' ) );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get title.
+ *
+ * @return string Title string
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->title !== null ) {
+ $config['title'] = $this->title;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php b/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php
new file mode 100644
index 00000000..ef0d4c6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout made of a field and optional label.
+ *
+ * Available label alignment modes include:
+ * - left: Label is before the field and aligned away from it, best for when the user will be
+ * scanning for a specific label in a form with many fields
+ * - right: Label is before the field and aligned toward it, best for forms the user is very
+ * familiar with and will tab through field checking quickly to verify which field they are in
+ * - top: Label is before the field and above it, best for when the user will need to fill out all
+ * fields from top to bottom in a form with few fields
+ * - inline: Label is after the field and aligned toward it, best for small boolean fields like
+ * checkboxes or radio buttons
+ */
+class FieldLayout extends Layout {
+
+ /**
+ * Alignment.
+ *
+ * @var string
+ */
+ protected $align;
+
+ /**
+ * Field widget to be laid out.
+ *
+ * @var Widget
+ */
+ protected $fieldWidget;
+
+ private $field, $body, $help;
+
+ /**
+ * @param Widget $fieldWidget Field widget
+ * @param array $config Configuration options
+ * @param string $config['align'] Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * (default: 'left')
+ * @param string $config['help'] Explanatory text shown as a '?' icon.
+ */
+ public function __construct( $fieldWidget, array $config = array() ) {
+ // Allow passing positional parameters inside the config array
+ if ( is_array( $fieldWidget ) && isset( $fieldWidget['fieldWidget'] ) ) {
+ $config = $fieldWidget;
+ $fieldWidget = $config['fieldWidget'];
+ }
+
+ $hasInputWidget = $fieldWidget instanceof InputWidget;
+
+ // Config initialization
+ $config = array_merge( array( 'align' => 'left' ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->fieldWidget = $fieldWidget;
+ $this->field = new Tag( 'div' );
+ $this->body = new Tag( $hasInputWidget ? 'label' : 'div' );
+ if ( isset( $config['help'] ) ) {
+ $this->help = new ButtonWidget( array(
+ 'classes' => array( 'oo-ui-fieldLayout-help' ),
+ 'framed' => false,
+ 'icon' => 'info',
+ 'title' => $config['help'],
+ ) );
+ } else {
+ $this->help = '';
+ }
+
+ // Mixins
+ $this->mixin( new LabelElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-fieldLayout' ) )
+ ->appendContent( $this->help, $this->body );
+ $this->body->addClasses( array( 'oo-ui-fieldLayout-body' ) );
+ $this->field
+ ->addClasses( array( 'oo-ui-fieldLayout-field' ) )
+ ->toggleClasses( array( 'oo-ui-fieldLayout-disable' ), $this->fieldWidget->isDisabled() )
+ ->appendContent( $this->fieldWidget );
+
+ $this->setAlignment( $config['align'] );
+ }
+
+ /**
+ * Get the field.
+ *
+ * @return Widget Field widget
+ */
+ public function getField() {
+ return $this->fieldWidget;
+ }
+
+ /**
+ * Set the field alignment mode.
+ *
+ * @param string $value Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * @chainable
+ */
+ protected function setAlignment( $value ) {
+ if ( $value !== $this->align ) {
+ // Default to 'left'
+ if ( !in_array( $value, array( 'left', 'right', 'top', 'inline' ) ) ) {
+ $value = 'left';
+ }
+ // Reorder elements
+ $this->body->clearContent();
+ if ( $value === 'inline' ) {
+ $this->body->appendContent( $this->field, $this->label );
+ } else {
+ $this->body->appendContent( $this->label, $this->field );
+ }
+ // Set classes. The following classes can be used here:
+ // * oo-ui-fieldLayout-align-left
+ // * oo-ui-fieldLayout-align-right
+ // * oo-ui-fieldLayout-align-top
+ // * oo-ui-fieldLayout-align-inline
+ if ( $this->align ) {
+ $this->removeClasses( array( 'oo-ui-fieldLayout-align-' . $this->align ) );
+ }
+ $this->addClasses( array( 'oo-ui-fieldLayout-align-' . $value ) );
+ $this->align = $value;
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $config['fieldWidget'] = $this->fieldWidget;
+ $config['align'] = $this->align;
+ if ( $this->help !== '' ) {
+ $config['help'] = $this->help->getTitle();
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php b/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php
new file mode 100644
index 00000000..f9faa353
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout made of a fieldset and optional legend.
+ *
+ * Just add FieldLayout items.
+ */
+class FieldsetLayout extends Layout {
+ /**
+ * @param array $config Configuration options
+ * @param FieldLayout[] $config['items'] Items to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new LabelElement( $this, $config ) );
+ $this->mixin( new GroupElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-fieldsetLayout' ) )
+ ->prependContent( $this->icon, $this->label, $this->group );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FormLayout.php b/vendor/oojs/oojs-ui/php/layouts/FormLayout.php
new file mode 100644
index 00000000..ebeb89de
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FormLayout.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout with an HTML form.
+ */
+class FormLayout extends Layout {
+
+ /* Static properties */
+
+ public static $tagName = 'form';
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['method'] HTML form `method` attribute
+ * @param string $config['action'] HTML form `action` attribute
+ * @param string $config['enctype'] HTML form `enctype` attribute
+ * @param FieldsetLayout[] $config['items'] Items to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new GroupElement( $this, array_merge( $config, array( 'group' => $this ) ) ) );
+
+ // Initialization
+ $attributeWhitelist = array( 'method', 'action', 'enctype' );
+ $this
+ ->addClasses( array( 'oo-ui-formLayout' ) )
+ ->setAttributes( array_intersect_key( $config, array_flip( $attributeWhitelist ) ) );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+
+ public function getConfig( &$config ) {
+ foreach ( array( 'method', 'action', 'enctype' ) as $attr ) {
+ $value = $this->getAttribute( $attr );
+ if ( $value !== null ) {
+ $config[$attr] = $value;
+ }
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php b/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php
new file mode 100644
index 00000000..64931374
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout that expands to cover the entire area of its parent, with optional scrolling and padding.
+ */
+class PanelLayout extends Layout {
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['scrollable'] Allow vertical scrolling (default: false)
+ * @param boolean $config['padded'] Pad the content from the edges (default: false)
+ * @param boolean $config['expanded'] Expand size to fill the entire parent element
+ * (default: true)
+ * @param boolean $config['framed'] Wrap in a frame to visually separate from outside content
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Config initialization
+ $config = array_merge( array(
+ 'scrollable' => false,
+ 'padded' => false,
+ 'expanded' => true,
+ 'framed' => false,
+ ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-panelLayout' ) );
+ if ( $config['scrollable'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-scrollable' ) );
+ }
+ if ( $config['padded'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-padded' ) );
+ }
+ if ( $config['expanded'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-expanded' ) );
+ }
+ if ( $config['framed'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-framed' ) );
+ }
+ }
+ public function getConfig( &$config ) {
+ if ( $this->hasClass( 'oo-ui-panelLayout-scrollable' ) ) {
+ $config['scrollable'] = true;
+ }
+ if ( $this->hasClass( 'oo-ui-panelLayout-padded' ) ) {
+ $config['padded'] = true;
+ }
+ if ( !$this->hasClass( 'oo-ui-panelLayout-expanded' ) ) {
+ $config['expanded'] = false;
+ }
+ if ( $this->hasClass( 'oo-ui-panelLayout-framed' ) ) {
+ $config['framed'] = true;
+ }
+ $config['content'] = $this->content;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/themes/ApexTheme.php b/vendor/oojs/oojs-ui/php/themes/ApexTheme.php
new file mode 100644
index 00000000..e812736e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/themes/ApexTheme.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace OOUI;
+
+class ApexTheme extends Theme {
+}
diff --git a/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php b/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php
new file mode 100644
index 00000000..86e4c353
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace OOUI;
+
+class MediaWikiTheme extends Theme {
+
+ /* Methods */
+
+ public function getElementClasses( Element $element ) {
+ $variants = array(
+ 'warning' => false,
+ 'invert' => false,
+ 'progressive' => false,
+ 'constructive' => false,
+ 'destructive' => false
+ );
+
+ // Parent method
+ $classes = parent::getElementClasses( $element );
+
+ if ( $element->supports( array( 'hasFlag' ) ) ) {
+ $isFramed = $element->supports( array( 'isFramed' ) ) && $element->isFramed();
+ if ( $isFramed && ( $element->isDisabled() || $element->hasFlag( 'primary' ) ) ) {
+ $variants['invert'] = true;
+ } else {
+ $variants['progressive'] = $element->hasFlag( 'progressive' );
+ $variants['constructive'] = $element->hasFlag( 'constructive' );
+ $variants['destructive'] = $element->hasFlag( 'destructive' );
+ $variants['warning'] = $element->hasFlag( 'warning' );
+ }
+ }
+
+ foreach ( $variants as $variant => $toggle ) {
+ $classes[$toggle ? 'on' : 'off'][] = 'oo-ui-image-' . $variant;
+ }
+
+ return $classes;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php
new file mode 100644
index 00000000..79d3aaa1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Group widget for multiple related buttons.
+ *
+ * Use together with ButtonWidget.
+ */
+class ButtonGroupWidget extends Widget {
+ /**
+ * @param array $config Configuration options
+ * @param ButtonWidget[] $config['items'] Buttons to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new GroupElement( $this, array_merge( $config, array( 'group' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-buttonGroupWidget' ) );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php
new file mode 100644
index 00000000..b3bcb63b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * A button that is an input widget. Intended to be used within a FormLayout.
+ */
+class ButtonInputWidget extends InputWidget {
+ /* Properties */
+
+ /**
+ * Whether to use `<input/>` rather than `<button/>`.
+ *
+ * @var boolean
+ */
+ protected $useInputTag;
+
+ private $labelElementMixin;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['type'] HTML tag `type` attribute, may be 'button', 'submit' or 'reset'
+ * (default: 'button')
+ * @param boolean $config['useInputTag'] Whether to use `<input/>` rather than `<button/>`. Only
+ * useful if you need IE 6 support in a form with multiple buttons. If you use this option,
+ * icons and indicators will not be displayed, it won't be possible to have a non-plaintext
+ * label, and it won't be possible to set a value (which will internally become identical to the
+ * label). (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Configuration initialization
+ $config = array_merge( array( 'type' => 'button', 'useInputTag' => false ), $config );
+
+ // Properties (must be set before parent constructor, which calls setValue())
+ $this->useInputTag = $config['useInputTag'];
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new ButtonElement( $this,
+ array_merge( $config, array( 'button' => $this->input ) ) ) );
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+ // HACK: We need to have access to the mixin to override the setLabel() method
+ $this->mixin( $this->labelElementMixin = new LabelElement( $this, $config ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this->input ) ) ) );
+
+ // Initialization
+ if ( !$config['useInputTag'] ) {
+ $this->input->appendContent( $this->icon, $this->label, $this->indicator );
+ }
+
+ // HACK: This is done in LabelElement mixin, but doesn't call our overridden method because of
+ // how we implement mixins. Switching to traits will fix that.
+ $this->setLabel( isset( $config['label'] ) ? $config['label'] : null );
+
+ $this->addClasses( array( 'oo-ui-buttonInputWidget' ) );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( $config['useInputTag'] ? 'input' : 'button' );
+ $input->setAttributes( array( 'type' => $config['type'] ) );
+ return $input;
+ }
+
+ /**
+ * Set label value.
+ *
+ * Overridden to support setting the 'value' of `<input/>` elements.
+ *
+ * @param string|null $label Label text
+ * @chainable
+ */
+ public function setLabel( $label ) {
+ $this->labelElementMixin->setLabel( $label );
+
+ if ( $this->useInputTag ) {
+ // Discard non-plaintext labels
+ $label = is_string( $label ) ? $label : '';
+ $this->input->setValue( $label );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the value of the input.
+ *
+ * Overridden to disable for `<input/>` elements, which have value identical to the label.
+ *
+ * @param string $value New value
+ * @chainable
+ */
+ public function setValue( $value ) {
+ if ( !$this->useInputTag ) {
+ parent::setValue( $value );
+ }
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->useInputTag ) {
+ $config['useInputTag'] = true;
+ }
+ $config['type'] = $this->input->getAttribute( 'type' );
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php
new file mode 100644
index 00000000..f26608b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Generic widget for buttons.
+ */
+class ButtonWidget extends Widget {
+
+ /**
+ * Hyperlink to visit when clicked.
+ *
+ * @var string
+ */
+ protected $href = null;
+
+ /**
+ * Target to open hyperlink in.
+ *
+ * @var string
+ */
+ protected $target = null;
+
+ /**
+ * Search engine traversal hint.
+ *
+ * True if search engines should avoid following this hyperlink.
+ *
+ * @var boolean
+ */
+ protected $noFollow = true;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['href'] Hyperlink to visit when clicked
+ * @param string $config['target'] Target to open hyperlink in
+ * @param boolean $config['noFollow'] Search engine traversal hint (default: true)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new ButtonElement( $this, $config ) );
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+ $this->mixin( new LabelElement( $this, $config ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this->button ) ) ) );
+ $this->mixin( new FlaggedElement( $this, $config ) );
+ $this->mixin( new TabIndexedElement( $this,
+ array_merge( $config, array( 'tabIndexed' => $this->button ) ) ) );
+
+ // Initialization
+ $this->button->appendContent( $this->icon, $this->label, $this->indicator );
+ $this
+ ->addClasses( array( 'oo-ui-buttonWidget' ) )
+ ->appendContent( $this->button );
+
+ $this->setHref( isset( $config['href'] ) ? $config['href'] : null );
+ $this->setTarget( isset( $config['target'] ) ? $config['target'] : null );
+ $this->setNoFollow( isset( $config['noFollow'] ) ? $config['noFollow'] : true );
+ }
+
+ /**
+ * Get hyperlink location.
+ *
+ * @return string Hyperlink location
+ */
+ public function getHref() {
+ return $this->href;
+ }
+
+ /**
+ * Get hyperlink target.
+ *
+ * @return string Hyperlink target
+ */
+ public function getTarget() {
+ return $this->target;
+ }
+
+ /**
+ * Get search engine traversal hint.
+ *
+ * @return boolean Whether search engines should avoid traversing this hyperlink
+ */
+ public function getNoFollow() {
+ return $this->noFollow;
+ }
+
+ /**
+ * Set hyperlink location.
+ *
+ * @param string|null $href Hyperlink location, null to remove
+ */
+ public function setHref( $href ) {
+ $this->href = is_string( $href ) ? $href : null;
+
+ $this->updateHref();
+
+ return $this;
+ }
+
+ /**
+ * Update the href attribute, in case of changes to href or disabled
+ * state.
+ *
+ * @chainable
+ */
+ public function updateHref() {
+ if ( $this->href !== null && !$this->isDisabled() ) {
+ $this->button->setAttributes( array( 'href' => $this->href ) );
+ } else {
+ $this->button->removeAttributes( array( 'href' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Set hyperlink target.
+ *
+ * @param string|null $target Hyperlink target, null to remove
+ */
+ public function setTarget( $target ) {
+ $this->target = is_string( $target ) ? $target : null;
+
+ if ( $this->target !== null ) {
+ $this->button->setAttributes( array( 'target' => $target ) );
+ } else {
+ $this->button->removeAttributes( array( 'target' ) );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set search engine traversal hint.
+ *
+ * @param boolean $noFollow True if search engines should avoid traversing this hyperlink
+ */
+ public function setNoFollow( $noFollow ) {
+ $this->noFollow = is_bool( $noFollow ) ? $noFollow : true;
+
+ if ( $this->noFollow ) {
+ $this->button->setAttributes( array( 'rel' => 'nofollow' ) );
+ } else {
+ $this->button->removeAttributes( array( 'rel' ) );
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->href !== null ) {
+ $config['href'] = $this->href;
+ }
+ if ( $this->target !== null ) {
+ $config['target'] = $this->target;
+ }
+ if ( $this->noFollow !== true ) {
+ $config['noFollow'] = $this->noFollow;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php
new file mode 100644
index 00000000..bda09c66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Checkbox input widget.
+ */
+class CheckboxInputWidget extends InputWidget {
+
+ /* Properties */
+
+ /**
+ * Whether the checkbox is selected.
+ *
+ * @var boolean
+ */
+ protected $selected;
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['selected'] Whether the checkbox is initially selected
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-checkboxInputWidget' ) );
+ $this->setSelected( isset( $config['selected'] ) ? $config['selected'] : false );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => 'checkbox' ) );
+ return $input;
+ }
+
+ /**
+ * Set selection state of this checkbox.
+ *
+ * @param boolean $state Whether the checkbox is selected
+ * @chainable
+ */
+ public function setSelected( $state ) {
+ $this->selected = (bool)$state;
+ if ( $this->selected ) {
+ $this->input->setAttributes( array( 'checked' => 'checked' ) );
+ } else {
+ $this->input->removeAttributes( array( 'checked' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Check if this checkbox is selected.
+ *
+ * @return boolean Checkbox is selected
+ */
+ public function isSelected() {
+ return $this->selected;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->selected ) {
+ $config['selected'] = $this->selected;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php
new file mode 100644
index 00000000..ae541a66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Dropdown input widget, wrapping a `<select>` element. Intended to be used within a
+ * OO.ui.FormLayout.
+ */
+class DropdownInputWidget extends InputWidget {
+
+ /**
+ * HTML `<option>` tags for this widget.
+ * @var Tag[]
+ */
+ protected $options = array();
+
+ /**
+ * @param array $config Configuration options
+ * @param array[] $config['options'] Array of menu options in the format
+ * `array( 'data' => …, 'label' => … )`
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->setOptions( isset( $config['options'] ) ? $config['options'] : array() );
+ $this->addClasses( array( 'oo-ui-dropdownInputWidget' ) );
+ }
+
+ protected function getInputElement( $config ) {
+ return new Tag( 'select' );
+ }
+
+ public function setValue( $value ) {
+ $this->value = $this->cleanUpValue( $value );
+ foreach ( $this->options as &$opt ) {
+ if ( $opt->getAttribute( 'value' ) === $this->value ) {
+ $opt->setAttributes( array( 'selected' => 'selected' ) );
+ } else {
+ $opt->removeAttributes( array( 'selected' ) );
+ }
+ }
+ return $this;
+ }
+
+
+ /**
+ * Set the options available for this input.
+ *
+ * @param array[] $options Array of menu options in the format
+ * `array( 'data' => …, 'label' => … )`
+ * @chainable
+ */
+ public function setOptions( $options ) {
+ $value = $this->getValue();
+ $isValueAvailable = false;
+ $this->options = array();
+
+ // Rebuild the dropdown menu
+ $this->input->clearContent();
+ foreach ( $options as $opt ) {
+ $option = new Tag( 'option' );
+ $option->setAttributes( array( 'value' => $opt['data'] ) );
+ $option->appendContent( isset( $opt['label'] ) ? $opt['label'] : $opt['data'] );
+
+ if ( $value === $opt['data'] ) {
+ $isValueAvailable = true;
+ }
+
+ $this->options[] = $option;
+ $this->input->appendContent( $option );
+ }
+
+ // Restore the previous value, or reset to something sensible
+ if ( $isValueAvailable ) {
+ // Previous value is still available
+ $this->setValue( $value );
+ } else {
+ // No longer valid, reset
+ if ( count( $options ) ) {
+ $this->setValue( $options[0]['data'] );
+ }
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $o = array();
+ foreach ( $this->options as $option ) {
+ $label = $option->content[0];
+ $data = $option->getAttribute( 'value' );
+ $o[] = array( 'data' => $data, 'label' => $label );
+ }
+ $config['options'] = $o;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/IconWidget.php b/vendor/oojs/oojs-ui/php/widgets/IconWidget.php
new file mode 100644
index 00000000..f8273f37
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/IconWidget.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Icon widget.
+ *
+ * See IconElement for more information.
+ */
+class IconWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IconElement( $this,
+ array_merge( $config, array( 'iconElement' => $this ) ) ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this ) ) ) );
+ $this->mixin( new FlaggedElement( $this,
+ array_merge( $config, array( 'flagged' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-iconWidget' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php b/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php
new file mode 100644
index 00000000..01f2055d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Indicator widget.
+ *
+ * See IndicatorElement for more information.
+ */
+class IndicatorWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IndicatorElement( $this,
+ array_merge( $config, array( 'indicatorElement' => $this ) ) ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-indicatorWidget' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/InputWidget.php b/vendor/oojs/oojs-ui/php/widgets/InputWidget.php
new file mode 100644
index 00000000..234d3145
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/InputWidget.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Base class for input widgets.
+ *
+ * @abstract
+ */
+class InputWidget extends Widget {
+
+ /* Properties */
+
+ /**
+ * Input element.
+ *
+ * @var Tag
+ */
+ protected $input;
+
+ /**
+ * Input value.
+ *
+ * @var string
+ */
+ protected $value = '';
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['name'] HTML input name (default: '')
+ * @param string $config['value'] Input value (default: '')
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->input = $this->getInputElement( $config );
+
+ // Mixins
+ $this->mixin( new FlaggedElement( $this,
+ array_merge( $config, array( 'flagged' => $this ) ) ) );
+ $this->mixin( new TabIndexedElement( $this,
+ array_merge( $config, array( 'tabIndexed' => $this->input ) ) ) );
+
+ // Initialization
+ if ( isset( $config['name'] ) ) {
+ $this->input->setAttributes( array( 'name' => $config['name'] ) );
+ }
+ if ( $this->isDisabled() ) {
+ $this->input->setAttributes( array( 'disabled' => 'disabled' ) );
+ }
+ $this
+ ->addClasses( array( 'oo-ui-inputWidget' ) )
+ ->appendContent( $this->input );
+ $this->appendContent( new Tag( 'span' ) );
+ $this->setValue( isset( $config['value'] ) ? $config['value'] : null );
+ }
+
+ /**
+ * Get input element.
+ *
+ * @param array $config Configuration options
+ * @return Tag Input element
+ */
+ protected function getInputElement( $config ) {
+ return new Tag( 'input' );
+ }
+
+ /**
+ * Get the value of the input.
+ *
+ * @return string Input value
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Sets the direction of the current input, either RTL or LTR
+ *
+ * @param boolean $isRTL
+ */
+ public function setRTL( $isRTL ) {
+ if ( $isRTL ) {
+ $this->input->removeClasses( array( 'oo-ui-ltr' ) );
+ $this->input->addClasses( array( 'oo-ui-rtl' ) );
+ } else {
+ $this->input->removeClasses( array( 'oo-ui-rtl' ) );
+ $this->input->addClasses( array( 'oo-ui-ltr' ) );
+ }
+ }
+
+ /**
+ * Set the value of the input.
+ *
+ * @param string $value New value
+ * @chainable
+ */
+ public function setValue( $value ) {
+ $this->value = $this->cleanUpValue( $value );
+ $this->input->setValue( $this->value );
+ return $this;
+ }
+
+ /**
+ * Clean up incoming value.
+ *
+ * Ensures value is a string, and converts null to empty string.
+ *
+ * @param string $value Original value
+ * @return string Cleaned up value
+ */
+ protected function cleanUpValue( $value ) {
+ if ( $value === null ) {
+ return '';
+ } else {
+ return (string)$value;
+ }
+ }
+
+ public function setDisabled( $state ) {
+ parent::setDisabled( $state );
+ if ( isset( $this->input ) ) {
+ if ( $this->isDisabled() ) {
+ $this->input->setAttributes( array( 'disabled' => 'disabled' ) );
+ } else {
+ $this->input->removeAttributes( array( 'disabled' ) );
+ }
+ }
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $name = $this->input->getAttribute( 'name' );
+ if ( $name !== null ) {
+ $config['name'] = $name;
+ }
+ if ( $this->value !== '' ) {
+ $config['value'] = $this->value;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php b/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php
new file mode 100644
index 00000000..b59a5f25
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Label widget.
+ */
+class LabelWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /* Properties */
+
+ /**
+ * Associated input element.
+ *
+ * @var InputWidget|null
+ */
+ protected $input;
+
+ /**
+ * @param array $config Configuration options
+ * @param InputWidget $config['input'] Input widget this label is for
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new LabelElement( $this,
+ array_merge( $config, array( 'labelElement' => $this ) ) ) );
+
+ // Properties
+ $this->input = isset( $config['input'] ) ? $config['input'] : null;
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-labelWidget' ) );
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->input !== null ) {
+ $config['input'] = $this->input;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php
new file mode 100644
index 00000000..26da29d0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Radio input widget.
+ */
+class RadioInputWidget extends InputWidget {
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['selected'] Whether the radio button is initially selected
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-radioInputWidget' ) );
+ $this->setSelected( isset( $config['selected'] ) ? $config['selected'] : false );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => 'radio' ) );
+ return $input;
+ }
+
+ /**
+ * Set selection state of this radio button.
+ *
+ * @param boolean $state Whether the button is selected
+ */
+ public function setSelected( $state ) {
+ // RadioInputWidget doesn't track its state.
+ if ( $state ) {
+ $this->input->setAttributes( array( 'checked' => 'checked' ) );
+ } else {
+ $this->input->removeAttributes( array( 'checked' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Check if this radio button is selected.
+ *
+ * @return boolean Radio is selected
+ */
+ public function isSelected() {
+ return $this->input->getAttribute( 'checked' ) === 'checked';
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->isSelected() ) {
+ $config['selected'] = true;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php
new file mode 100644
index 00000000..a5f31f74
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Input widget with a text field.
+ */
+class TextInputWidget extends InputWidget {
+
+ /* Properties */
+
+ /**
+ * Prevent changes.
+ *
+ * @var boolean
+ */
+ protected $readOnly = false;
+
+ /**
+ * Allow multiple lines of text.
+ *
+ * @var boolean
+ */
+ protected $multiline = false;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['type'] HTML tag `type` attribute (default: 'text')
+ * @param string $config['placeholder'] Placeholder text
+ * @param boolean $config['autofocus'] Ask the browser to focus this widget, using the 'autofocus'
+ * HTML attribute (default: false)
+ * @param boolean $config['readOnly'] Prevent changes (default: false)
+ * @param number $config['maxLength'] Maximum allowed number of characters to input
+ * @param boolean $config['multiline'] Allow multiple lines of text (default: false)
+ * @param boolean $config['required'] Mark the field as required (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Config initialization
+ $config = array_merge( array(
+ 'readOnly' => false,
+ 'autofocus' => false,
+ 'required' => false,
+ ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->multiline = isset( $config['multiline'] ) ? (bool)$config['multiline'] : false;
+
+ // Mixins
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-textInputWidget' ) )
+ ->appendContent( $this->icon, $this->indicator );
+ $this->setReadOnly( $config['readOnly'] );
+ if ( isset( $config['placeholder'] ) ) {
+ $this->input->setAttributes( array( 'placeholder' => $config['placeholder'] ) );
+ }
+ if ( isset( $config['maxLength'] ) ) {
+ $this->input->setAttributes( array( 'maxlength' => $config['maxLength'] ) );
+ }
+ if ( $config['autofocus'] ) {
+ $this->input->setAttributes( array( 'autofocus' => 'autofocus' ) );
+ }
+ if ( $config['required'] ) {
+ $this->input->setAttributes( array( 'required' => 'required', 'aria-required' => 'true' ) );
+ }
+ }
+
+ /**
+ * Check if the widget is read-only.
+ *
+ * @return boolean
+ */
+ public function isReadOnly() {
+ return $this->readOnly;
+ }
+
+ /**
+ * Set the read-only state of the widget. This should probably change the widget's appearance and
+ * prevent it from being used.
+ *
+ * @param boolean $state Make input read-only
+ * @chainable
+ */
+ public function setReadOnly( $state ) {
+ $this->readOnly = (bool)$state;
+ if ( $this->readOnly ) {
+ $this->input->setAttributes( array( 'readonly' => 'readonly' ) );
+ } else {
+ $this->input->removeAttributes( array( 'readonly' ) );
+ }
+ return $this;
+ }
+
+ protected function getInputElement( $config ) {
+ if ( isset( $config['multiline'] ) && $config['multiline'] ) {
+ return new Tag( 'textarea' );
+ } else {
+ $type = isset( $config['type'] ) ? $config['type'] : 'text';
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => $type ) );
+ return $input;
+ }
+ }
+
+ /**
+ * Check if input supports multiple lines.
+ *
+ * @return boolean
+ */
+ public function isMultiline() {
+ return (bool)$this->multiline;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->isMultiline() ) {
+ $config['multiline'] = true;
+ } else {
+ $type = $this->input->getAttribute( 'type' );
+ if ( $type !== 'text' ) {
+ $config['type'] = $type;
+ }
+ }
+ if ( $this->isReadOnly() ) {
+ $config['readOnly'] = true;
+ }
+ $placeholder = $this->input->getAttribute( 'placeholder' );
+ if ( $placeholder !== null ) {
+ $config['placeholder'] = $placeholder;
+ }
+ $maxlength = $this->input->getAttribute( 'maxlength' );
+ if ( $maxlength !== null ) {
+ $config['maxLength'] = $maxlength;
+ }
+ $autofocus = $this->input->getAttribute( 'autofocus' );
+ if ( $autofocus !== null ) {
+ $config['autofocus'] = true;
+ }
+ $required = $this->input->getAttribute( 'required' );
+ $ariarequired = $this->input->getAttribute( 'aria-required' );
+ if ( ( $required !== null ) || ( $ariarequired !== null ) ) {
+ $config['required'] = true;
+ }
+ return parent::getConfig( $config );
+ }
+}