Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace tool_brickfield\local\htmlchecker\common;
  18  
  19  /**
  20   * Helper test base for tests dealing with color difference and luminosity.
  21   *
  22   * @package    tool_brickfield
  23   * @copyright  2020 onward: Brickfield Education Labs, www.brickfield.ie
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  class brickfield_accessibility_color_test extends brickfield_accessibility_test {
  27  
  28      /** @var string[] Define colour codes. */
  29      public $colornames = [
  30          'aliceblue' => 'f0f8ff',
  31          'antiquewhite' => 'faebd7',
  32          'aqua' => '00ffff',
  33          'aquamarine' => '7fffd4',
  34          'azure' => 'f0ffff',
  35          'beige' => 'f5f5dc',
  36          'bisque' => 'ffe4c4',
  37          'black' => '000000',
  38          'blanchedalmond' => 'ffebcd',
  39          'blue' => '0000ff',
  40          'blueviolet' => '8a2be2',
  41          'brown' => 'a52a2a',
  42          'burlywood' => 'deb887',
  43          'cadetblue' => '5f9ea0',
  44          'chartreuse' => '7fff00',
  45          'chocolate' => 'd2691e',
  46          'coral' => 'ff7f50',
  47          'cornflowerblue' => '6495ed',
  48          'cornsilk' => 'fff8dc',
  49          'crimson' => 'dc143c',
  50          'cyan' => '00ffff',
  51          'darkblue' => '00008b',
  52          'darkcyan' => '008b8b',
  53          'darkgoldenrod' => 'b8860b',
  54          'darkgray' => 'a9a9a9',
  55          'darkgreen' => '006400',
  56          'darkkhaki' => 'bdb76b',
  57          'darkmagenta' => '8b008b',
  58          'darkolivegreen' => '556b2f',
  59          'darkorange' => 'ff8c00',
  60          'darkorchid' => '9932cc',
  61          'darkred' => '8b0000',
  62          'darksalmon' => 'e9967a',
  63          'darkseagreen' => '8fbc8f',
  64          'darkslateblue' => '483d8b',
  65          'darkslategray' => '2f4f4f',
  66          'darkturquoise' => '00ced1',
  67          'darkviolet' => '9400d3',
  68          'deeppink' => 'ff1493',
  69          'deepskyblue' => '00bfff',
  70          'dimgray' => '696969',
  71          'dodgerblue' => '1e90ff',
  72          'firebrick' => 'b22222',
  73          'floralwhite' => 'fffaf0',
  74          'forestgreen' => '228b22',
  75          'fuchsia' => 'ff00ff',
  76          'gainsboro' => 'dcdcdc',
  77          'ghostwhite' => 'f8f8ff',
  78          'gold' => 'ffd700',
  79          'goldenrod' => 'daa520',
  80          'gray' => '808080',
  81          'green' => '008000',
  82          'greenyellow' => 'adff2f',
  83          'grey' => '808080',
  84          'honeydew' => 'f0fff0',
  85          'hotpink' => 'ff69b4',
  86          'indianred' => 'cd5c5c',
  87          'indigo' => '4b0082',
  88          'ivory' => 'fffff0',
  89          'khaki' => 'f0e68c',
  90          'lavender' => 'e6e6fa',
  91          'lavenderblush' => 'fff0f5',
  92          'lawngreen' => '7cfc00',
  93          'lemonchiffon' => 'fffacd',
  94          'lightblue' => 'add8e6',
  95          'lightcoral' => 'f08080',
  96          'lightcyan' => 'e0ffff',
  97          'lightgoldenrodyellow' => 'fafad2',
  98          'lightgrey' => 'd3d3d3',
  99          'lightgreen' => '90ee90',
 100          'lightpink' => 'ffb6c1',
 101          'lightsalmon' => 'ffa07a',
 102          'lightseagreen' => '20b2aa',
 103          'lightskyblue' => '87cefa',
 104          'lightslategray' => '778899',
 105          'lightsteelblue' => 'b0c4de',
 106          'lightyellow' => 'ffffe0',
 107          'lime' => '00ff00',
 108          'limegreen' => '32cd32',
 109          'linen' => 'faf0e6',
 110          'magenta' => 'ff00ff',
 111          'maroon' => '800000',
 112          'mediumaquamarine' => '66cdaa',
 113          'mediumblue' => '0000cd',
 114          'mediumorchid' => 'ba55d3',
 115          'mediumpurple' => '9370d8',
 116          'mediumseagreen' => '3cb371',
 117          'mediumslateblue' => '7b68ee',
 118          'mediumspringgreen' => '00fa9a',
 119          'mediumturquoise' => '48d1cc',
 120          'mediumvioletred' => 'c71585',
 121          'midnightblue' => '191970',
 122          'mintcream' => 'f5fffa',
 123          'mistyrose' => 'ffe4e1',
 124          'moccasin' => 'ffe4b5',
 125          'navajowhite' => 'ffdead',
 126          'navy' => '000080',
 127          'oldlace' => 'fdf5e6',
 128          'olive' => '808000',
 129          'olivedrab' => '6b8e23',
 130          'orange' => 'ffa500',
 131          'orangered' => 'ff4500',
 132          'orchid' => 'da70d6',
 133          'palegoldenrod' => 'eee8aa',
 134          'palegreen' => '98fb98',
 135          'paleturquoise' => 'afeeee',
 136          'palevioletred' => 'd87093',
 137          'papayawhip' => 'ffefd5',
 138          'peachpuff' => 'ffdab9',
 139          'peru' => 'cd853f',
 140          'pink' => 'ffc0cb',
 141          'plum' => 'dda0dd',
 142          'powderblue' => 'b0e0e6',
 143          'purple' => '800080',
 144          'red' => 'ff0000',
 145          'rosybrown' => 'bc8f8f',
 146          'royalblue' => '4169e1',
 147          'saddlebrown' => '8b4513',
 148          'salmon' => 'fa8072',
 149          'sandybrown' => 'f4a460',
 150          'seagreen' => '2e8b57',
 151          'seashell' => 'fff5ee',
 152          'sienna' => 'a0522d',
 153          'silver' => 'c0c0c0',
 154          'skyblue' => '87ceeb',
 155          'slateblue' => '6a5acd',
 156          'slategray' => '708090',
 157          'snow' => 'fffafa',
 158          'springgreen' => '00ff7f',
 159          'steelblue' => '4682b4',
 160          'tan' => 'd2b48c',
 161          'teal' => '008080',
 162          'thistle' => 'd8bfd8',
 163          'tomato' => 'ff6347',
 164          'turquoise' => '40e0d0',
 165          'violet' => 'ee82ee',
 166          'wheat' => 'f5deb3',
 167          'white' => 'ffffff',
 168          'whitesmoke' => 'f5f5f5',
 169          'yellow' => 'ffff00',
 170          'yellowgreen' => '9acd32'
 171      ];
 172  
 173      /**
 174       * Helper method that finds the luminosity between the provided
 175       * foreground and background parameters.
 176       * @param string $foreground The HEX value of the foreground color
 177       * @param string $background The HEX value of the background color
 178       * @return float The luminosity contrast ratio between the colors
 179       */
 180      public function get_luminosity(string $foreground, string $background): float {
 181          if ($foreground == $background) {
 182              return 0;
 183          }
 184          $forergb = $this->get_rgb($foreground);
 185          $backrgb = $this->get_rgb($background);
 186  
 187          // If get_rgb returns null for either, return 0.
 188          if ($forergb === null || $backrgb === null) {
 189              return 0;
 190          }
 191  
 192          return $this->luminosity($forergb['r'], $backrgb['r'],
 193              $forergb['g'], $backrgb['g'],
 194              $forergb['b'], $backrgb['b']);
 195      }
 196  
 197      /**
 198       * Returns the luminosity between two colors
 199       * @param string $r The first Red value
 200       * @param string $r2 The second Red value
 201       * @param string $g The first Green value
 202       * @param string $g2 The second Green value
 203       * @param string $b The first Blue value
 204       * @param string $b2 The second Blue value
 205       * @return float The luminosity contrast ratio between the colors
 206       */
 207      public function luminosity(string $r, string $r2, string $g, string $g2, string $b, string $b2): float {
 208          $rsrgb = $r / 255;
 209          $gsrgb = $g / 255;
 210          $bsrgb = $b / 255;
 211          $r3 = ($rsrgb <= 0.03928) ? $rsrgb / 12.92 : pow(($rsrgb + 0.055) / 1.055, 2.4);
 212          $g3 = ($gsrgb <= 0.03928) ? $gsrgb / 12.92 : pow(($gsrgb + 0.055) / 1.055, 2.4);
 213          $b3 = ($bsrgb <= 0.03928) ? $bsrgb / 12.92 : pow(($bsrgb + 0.055) / 1.055, 2.4);
 214  
 215          $rsrgb2 = $r2 / 255;
 216          $gsrgb2 = $g2 / 255;
 217          $bsrgb2 = $b2 / 255;
 218          $r4 = ($rsrgb2 <= 0.03928) ? $rsrgb2 / 12.92 : pow(($rsrgb2 + 0.055) / 1.055, 2.4);
 219          $g4 = ($gsrgb2 <= 0.03928) ? $gsrgb2 / 12.92 : pow(($gsrgb2 + 0.055) / 1.055, 2.4);
 220          $b4 = ($bsrgb2 <= 0.03928) ? $bsrgb2 / 12.92 : pow(($bsrgb2 + 0.055) / 1.055, 2.4);
 221  
 222          if ($r + $g + $b <= $r2 + $g2 + $b2) {
 223              $l2 = (.2126 * $r3 + 0.7152 * $g3 + 0.0722 * $b3);
 224              $l1 = (.2126 * $r4 + 0.7152 * $g4 + 0.0722 * $b4);
 225          } else {
 226              $l1 = (.2126 * $r3 + 0.7152 * $g3 + 0.0722 * $b3);
 227              $l2 = (.2126 * $r4 + 0.7152 * $g4 + 0.0722 * $b4);
 228          }
 229  
 230          $luminosity = round(($l1 + 0.05) / ($l2 + 0.05), 2);
 231          return $luminosity;
 232      }
 233  
 234  
 235      /**
 236       * Returns the decimal equivalents for a HEX color. Returns null if it cannot be determined.
 237       * @param string $color The hex color value
 238       * @return array|null An array where 'r' is the Red value, 'g' is Green, and 'b' is Blue
 239       */
 240      public function get_rgb(string $color): ?array {
 241          $color = $this->convert_color($color);
 242          $c = str_split($color, 2);
 243          if (count($c) != 3) {
 244              return null;
 245          }
 246          $results = ['r' => hexdec($c[0]), 'g' => hexdec($c[1]), 'b' => hexdec($c[2])];
 247          return $results;
 248      }
 249  
 250      /**
 251       * Converts multiple color or background styles into a simple hex string
 252       * @param string $color The color attribute to convert (this can also be a multi-value css background value)
 253       * @return string A standard CSS hex value for the color
 254       */
 255      public function convert_color(string $color): string {
 256          $color = trim($color);
 257          if (strpos($color, ' ') !== false) {
 258              $colors = explode(' ', $color);
 259              foreach ($colors as $backgroundpart) {
 260                  if (substr(trim($backgroundpart), 0, 1) == '#' ||
 261                      in_array(trim($backgroundpart), array_keys($this->colornames)) ||
 262                      strtolower(substr(trim($backgroundpart), 0, 3)) == 'rgb') {
 263                      $color = $backgroundpart;
 264                  }
 265              }
 266          }
 267          // Normal hex color.
 268          if (substr($color, 0, 1) == '#') {
 269              if (strlen($color) == 7) {
 270                  return str_replace('#', '', $color);
 271              } else if (strlen($color) == 4) {
 272                  return substr($color, 1, 1) . substr($color, 1, 1) .
 273                      substr($color, 2, 1) . substr($color, 2, 1) .
 274                      substr($color, 3, 1) . substr($color, 3, 1);
 275              } else {
 276                  return "000000";
 277              }
 278          }
 279          // Named Color.
 280          if (in_array($color, array_keys($this->colornames))) {
 281              return $this->colornames[$color];
 282          }
 283          // RGB values.
 284          if (strtolower(substr($color, 0, 3)) == 'rgb') {
 285              $colors = explode(',', trim(str_replace('rgb(', '', $color), '()'));
 286              if (count($colors) != 3) {
 287                  return false;
 288              }
 289              $r = intval($colors[0]);
 290              $g = intval($colors[1]);
 291              $b = intval($colors[2]);
 292  
 293              $r = dechex($r < 0 ? 0 : ($r > 255 ? 255 : $r));
 294              $g = dechex($g < 0 ? 0 : ($g > 255 ? 255 : $g));
 295              $b = dechex($b < 0 ? 0 : ($b > 255 ? 255 : $b));
 296  
 297              $color = (strlen($r) < 2 ? '0' : '') . $r;
 298              $color .= (strlen($g) < 2 ? '0' : '') . $g;
 299              $color .= (strlen($b) < 2 ? '0' : '') . $b;
 300              return $color;
 301          }
 302  
 303          return '';
 304      }
 305  
 306      /**
 307       * Returns the WAIERT contrast between two colors
 308       * @param string $foreground
 309       * @param string $background
 310       * @return array
 311       * @see get_luminosity
 312       */
 313      public function get_wai_ert_contrast(string $foreground, string $background): array {
 314          $forergb = $this->get_rgb($foreground);
 315          $backrgb = $this->get_rgb($background);
 316  
 317          // If get_rgb returns null for either, return 0.
 318          if ($forergb === null || $backrgb === null) {
 319              return [];
 320          }
 321  
 322          $diffs = $this->get_wai_diffs($forergb, $backrgb);
 323  
 324          return $diffs['red'] + $diffs['green'] + $diffs['blue'];
 325      }
 326  
 327      /**
 328       * Returns the WAI ERT Brightness between two colors
 329       * @param string $foreground
 330       * @param string $background
 331       * @return float|int
 332       */
 333      public function get_wai_ert_brightness(string $foreground, string $background): float {
 334          $forergb = $this->get_rgb($foreground);
 335          $backrgb = $this->get_rgb($background);
 336  
 337          // If get_rgb returns null for either, return 0.
 338          if ($forergb === null || $backrgb === null) {
 339              return 0;
 340          }
 341  
 342          $color = $this->get_wai_diffs($forergb, $backrgb);
 343          return (($color['red'] * 299) + ($color['green'] * 587) + ($color['blue'] * 114)) / 1000;
 344      }
 345  
 346      /**
 347       * Get the wai differences.
 348       * @param array $forergb
 349       * @param array $backrgb
 350       * @return array
 351       */
 352      public function get_wai_diffs(array $forergb, array $backrgb): array {
 353          $reddiff = ($forergb['r'] > $backrgb['r'])
 354              ? $forergb['r'] - $backrgb['r']
 355              : $backrgb['r'] - $forergb['r'];
 356          $greendiff = ($forergb['g'] > $backrgb['g'])
 357              ? $forergb['g'] - $backrgb['g']
 358              : $backrgb['g'] - $forergb['g'];
 359  
 360          $bluediff = ($forergb['b'] > $backrgb['b'])
 361              ? $forergb['b'] - $backrgb['b']
 362              : $backrgb['b'] - $forergb['b'];
 363          return ['red' => $reddiff, 'green' => $greendiff, 'blue' => $bluediff];
 364      }
 365  }