1<?php 2 3namespace MediaWiki\Widget; 4 5/** 6 * Check matrix widget. Displays a matrix of checkboxes for given options 7 * 8 * @copyright 2018 MediaWiki Widgets Team and others; see AUTHORS.txt 9 * @license MIT 10 */ 11class CheckMatrixWidget extends \OOUI\Widget { 12 /** @var string|null */ 13 protected $name; 14 /** @var string|null */ 15 protected $id; 16 /** @var array */ 17 protected $columns; 18 /** @var array */ 19 protected $rows; 20 /** @var array */ 21 protected $tooltips; 22 /** @var array */ 23 protected $values; 24 /** @var array */ 25 protected $forcedOn; 26 /** @var array */ 27 protected $forcedOff; 28 29 /** 30 * Operates similarly to MultiSelectWidget, but instead of using an array of 31 * options, uses an array of rows and an array of columns to dynamically 32 * construct a matrix of options. The tags used to identify a particular cell 33 * are of the form "columnName-rowName" 34 * 35 * @param array $config Configuration array with the following options: 36 * - columns 37 * - Required associative array mapping column labels (as HTML) to their tags. 38 * - rows 39 * - Required associative array mapping row labels (as HTML) to their tags. 40 * - force-options-on 41 * - Array of column-row tags to be displayed as enabled but unavailable to change. 42 * - force-options-off 43 * - Array of column-row tags to be displayed as disabled but unavailable to change. 44 * - tooltips 45 * - Optional associative array mapping row labels to tooltips (as text, will be escaped). 46 */ 47 public function __construct( array $config = [] ) { 48 // Configuration initialization 49 50 parent::__construct( $config ); 51 52 $this->name = $config['name'] ?? null; 53 $this->id = $config['id'] ?? null; 54 55 // Properties 56 $this->rows = $config['rows'] ?? []; 57 $this->columns = $config['columns'] ?? []; 58 $this->tooltips = $config['tooltips'] ?? []; 59 60 $this->values = $config['values'] ?? []; 61 62 $this->forcedOn = $config['forcedOn'] ?? []; 63 $this->forcedOff = $config['forcedOff'] ?? []; 64 65 // Build the table 66 $table = new \OOUI\Tag( 'table' ); 67 $table->addClasses( [ 'mw-htmlform-matrix mw-widget-checkMatrixWidget-matrix' ] ); 68 $thead = new \OOUI\Tag( 'thead' ); 69 $table->appendContent( $thead ); 70 $tr = new \OOUI\Tag( 'tr' ); 71 72 // Build the header 73 $tr->appendContent( $this->getCellTag( "\u{00A0}" ) ); 74 foreach ( $this->columns as $columnLabel => $columnTag ) { 75 $tr->appendContent( 76 $this->getCellTag( new \OOUI\HtmlSnippet( $columnLabel ), 'th' ) 77 ); 78 } 79 $thead->appendContent( $tr ); 80 81 // Build the options matrix 82 $tbody = new \OOUI\Tag( 'tbody' ); 83 $table->appendContent( $tbody ); 84 foreach ( $this->rows as $rowLabel => $rowTag ) { 85 $tbody->appendContent( 86 $this->getTableRow( $rowLabel, $rowTag ) 87 ); 88 } 89 90 // Initialization 91 $this->addClasses( [ 'mw-widget-checkMatrixWidget' ] ); 92 $this->appendContent( $table ); 93 } 94 95 /** 96 * Get a formatted table row for the option, with 97 * a checkbox widget. 98 * 99 * @param string $label Row label (as HTML) 100 * @param string $tag Row tag name 101 * @return \OOUI\Tag The resulting table row 102 */ 103 private function getTableRow( $label, $tag ) { 104 $row = new \OOUI\Tag( 'tr' ); 105 $tooltip = $this->getTooltip( $label ); 106 $labelFieldConfig = $tooltip ? [ 'help' => $tooltip ] : []; 107 // Build label cell 108 $labelField = new \OOUI\FieldLayout( 109 new \OOUI\Widget(), // Empty widget, since we don't have the checkboxes here 110 [ 111 'label' => new \OOUI\HtmlSnippet( $label ), 112 'align' => 'inline', 113 ] + $labelFieldConfig 114 ); 115 $row->appendContent( $this->getCellTag( $labelField ) ); 116 117 // Build checkbox column cells 118 foreach ( $this->columns as $columnTag ) { 119 $thisTag = "$columnTag-$tag"; 120 121 // Construct a checkbox 122 $checkbox = new \OOUI\CheckboxInputWidget( [ 123 'value' => $thisTag, 124 'name' => $this->name ? "{$this->name}[]" : null, 125 'id' => $this->id ? "{$this->id}-$thisTag" : null, 126 'selected' => $this->isTagChecked( $thisTag ), 127 'disabled' => $this->isTagDisabled( $thisTag ), 128 ] ); 129 130 $row->appendContent( $this->getCellTag( $checkbox ) ); 131 } 132 return $row; 133 } 134 135 /** 136 * Get an individual cell tag with requested content 137 * 138 * @param mixed $content Content for the <td> cell 139 * @param string $tagElement 140 * @return \OOUI\Tag Resulting cell 141 */ 142 private function getCellTag( $content, $tagElement = 'td' ) { 143 $cell = new \OOUI\Tag( $tagElement ); 144 $cell->appendContent( $content ); 145 return $cell; 146 } 147 148 /** 149 * Check whether the given tag's checkbox should 150 * be checked 151 * 152 * @param string $tagName Tag name 153 * @return bool Tag should be checked 154 */ 155 private function isTagChecked( $tagName ) { 156 // If the tag is in the value list 157 return in_array( $tagName, (array)$this->values, true ) || 158 // Or if the tag is forced on 159 in_array( $tagName, (array)$this->forcedOn, true ); 160 } 161 162 /** 163 * Check whether the given tag's checkbox should 164 * be disabled 165 * 166 * @param string $tagName Tag name 167 * @return bool Tag should be disabled 168 */ 169 private function isTagDisabled( $tagName ) { 170 return ( 171 // If the entire widget is disabled 172 $this->isDisabled() || 173 // If the tag is 'forced on' or 'forced off' 174 in_array( $tagName, (array)$this->forcedOn, true ) || 175 in_array( $tagName, (array)$this->forcedOff, true ) 176 ); 177 } 178 179 /** 180 * Get the tooltip help associated with this row 181 * 182 * @param string $label Label name 183 * @return string Tooltip. Null if none is available. 184 */ 185 private function getTooltip( $label ) { 186 return $this->tooltips[ $label ] ?? null; 187 } 188 189 protected function getJavaScriptClassName() { 190 return 'mw.widgets.CheckMatrixWidget'; 191 } 192 193 public function getConfig( &$config ) { 194 $config += [ 195 'name' => $this->name, 196 'id' => $this->id, 197 'rows' => $this->rows, 198 'columns' => $this->columns, 199 'tooltips' => $this->tooltips, 200 'forcedOff' => $this->forcedOff, 201 'forcedOn' => $this->forcedOn, 202 'values' => $this->values, 203 ]; 204 return parent::getConfig( $config ); 205 } 206} 207