Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 311 and 401] [Versions 400 and 401]

   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      /** @var string[] Define estimated relative font-size codes to pt values. */
 174      public $fontsizenames = [
 175          'xx-small' => 9,
 176          'x-small' => 10,
 177          'small' => 11,
 178          'smaller' => 11,
 179          'medium' => 12,
 180          'large' => 14,
 181          'larger' => 14,
 182          'x-large' => 18,
 183          'xx-large' => 24,
 184      ];
 185  
 186      /**
 187       * Helper method that finds the luminosity between the provided
 188       * foreground and background parameters.
 189       * @param string $foreground The HEX value of the foreground color
 190       * @param string $background The HEX value of the background color
 191       * @return float The luminosity contrast ratio between the colors
 192       */
 193      public function get_luminosity(string $foreground, string $background): float {
 194          if ($foreground == $background) {
 195              return 0;
 196          }
 197          $forergb = $this->get_rgb($foreground);
 198          $backrgb = $this->get_rgb($background);
 199  
 200          // If get_rgb returns null for either, return 0.
 201          if ($forergb === null || $backrgb === null) {
 202              return 0;
 203          }
 204  
 205          return $this->luminosity($forergb['r'], $backrgb['r'],
 206              $forergb['g'], $backrgb['g'],
 207              $forergb['b'], $backrgb['b']);
 208      }
 209  
 210      /**
 211       * Returns the luminosity between two colors
 212       * @param string $r The first Red value
 213       * @param string $r2 The second Red value
 214       * @param string $g The first Green value
 215       * @param string $g2 The second Green value
 216       * @param string $b The first Blue value
 217       * @param string $b2 The second Blue value
 218       * @return float The luminosity contrast ratio between the colors
 219       */
 220      public function luminosity(string $r, string $r2, string $g, string $g2, string $b, string $b2): float {
 221          $rsrgb = $r / 255;
 222          $gsrgb = $g / 255;
 223          $bsrgb = $b / 255;
 224          $r3 = ($rsrgb <= 0.03928) ? $rsrgb / 12.92 : pow(($rsrgb + 0.055) / 1.055, 2.4);
 225          $g3 = ($gsrgb <= 0.03928) ? $gsrgb / 12.92 : pow(($gsrgb + 0.055) / 1.055, 2.4);
 226          $b3 = ($bsrgb <= 0.03928) ? $bsrgb / 12.92 : pow(($bsrgb + 0.055) / 1.055, 2.4);
 227  
 228          $rsrgb2 = $r2 / 255;
 229          $gsrgb2 = $g2 / 255;
 230          $bsrgb2 = $b2 / 255;
 231          $r4 = ($rsrgb2 <= 0.03928) ? $rsrgb2 / 12.92 : pow(($rsrgb2 + 0.055) / 1.055, 2.4);
 232          $g4 = ($gsrgb2 <= 0.03928) ? $gsrgb2 / 12.92 : pow(($gsrgb2 + 0.055) / 1.055, 2.4);
 233          $b4 = ($bsrgb2 <= 0.03928) ? $bsrgb2 / 12.92 : pow(($bsrgb2 + 0.055) / 1.055, 2.4);
 234  
 235          if ($r + $g + $b <= $r2 + $g2 + $b2) {
 236              $l2 = (.2126 * $r3 + 0.7152 * $g3 + 0.0722 * $b3);
 237              $l1 = (.2126 * $r4 + 0.7152 * $g4 + 0.0722 * $b4);
 238          } else {
 239              $l1 = (.2126 * $r3 + 0.7152 * $g3 + 0.0722 * $b3);
 240              $l2 = (.2126 * $r4 + 0.7152 * $g4 + 0.0722 * $b4);
 241          }
 242  
 243          // Increase round to 4 to avoid a 4.49 contrast being round up to a false pass of 4.5.
 244          $luminosity = round(($l1 + 0.05) / ($l2 + 0.05), 4);
 245          return $luminosity;
 246      }
 247  
 248  
 249      /**
 250       * Returns the decimal equivalents for a HEX color. Returns null if it cannot be determined.
 251       * @param string $color The hex color value
 252       * @return array|null An array where 'r' is the Red value, 'g' is Green, and 'b' is Blue
 253       */
 254      public function get_rgb(string $color): ?array {
 255          $color = $this->convert_color($color);
 256          $c = str_split($color, 2);
 257          if (count($c) != 3) {
 258              return null;
 259          }
 260          $results = ['r' => hexdec($c[0]), 'g' => hexdec($c[1]), 'b' => hexdec($c[2])];
 261          return $results;
 262      }
 263  
 264      /**
 265       * Converts multiple color or background styles into a simple hex string
 266       * @param string $color The color attribute to convert (this can also be a multi-value css background value)
 267       * @return string A standard CSS hex value for the color
 268       */
 269      public function convert_color(string $color): string {
 270          $color = trim($color);
 271          if (strpos($color, ' ') !== false) {
 272              $colors = explode(' ', $color);
 273              foreach ($colors as $backgroundpart) {
 274                  if (substr(trim($backgroundpart), 0, 1) == '#' ||
 275                      in_array(trim($backgroundpart), array_keys($this->colornames)) ||
 276                      strtolower(substr(trim($backgroundpart), 0, 3)) == 'rgb') {
 277                      $color = $backgroundpart;
 278                  }
 279              }
 280          }
 281          // Normal hex color.
 282          if (substr($color, 0, 1) == '#') {
 283              if (strlen($color) == 7) {
 284                  return str_replace('#', '', $color);
 285              } else if (strlen($color) == 4) {
 286                  return substr($color, 1, 1) . substr($color, 1, 1) .
 287                      substr($color, 2, 1) . substr($color, 2, 1) .
 288                      substr($color, 3, 1) . substr($color, 3, 1);
 289              } else {
 290                  return "000000";
 291              }
 292          }
 293          // Named Color.
 294          if (in_array($color, array_keys($this->colornames))) {
 295              return $this->colornames[$color];
 296          }
 297          // RGB values.
 298          if (strtolower(substr($color, 0, 3)) == 'rgb') {
 299              $colors = explode(',', trim(str_replace('rgb(', '', $color), '()'));
 300              if (count($colors) != 3) {
 301                  return false;
 302              }
 303              $r = intval($colors[0]);
 304              $g = intval($colors[1]);
 305              $b = intval($colors[2]);
 306  
 307              $r = dechex($r < 0 ? 0 : ($r > 255 ? 255 : $r));
 308              $g = dechex($g < 0 ? 0 : ($g > 255 ? 255 : $g));
 309              $b = dechex($b < 0 ? 0 : ($b > 255 ? 255 : $b));
 310  
 311              $color = (strlen($r) < 2 ? '0' : '') . $r;
 312              $color .= (strlen($g) < 2 ? '0' : '') . $g;
 313              $color .= (strlen($b) < 2 ? '0' : '') . $b;
 314              return $color;
 315          }
 316  
 317          return '';
 318      }
 319  
 320      /**
 321       * Returns the WAIERT contrast between two colors
 322       * @param string $foreground
 323       * @param string $background
 324       * @return array
 325       * @see get_luminosity
 326       */
 327      public function get_wai_ert_contrast(string $foreground, string $background): array {
 328          $forergb = $this->get_rgb($foreground);
 329          $backrgb = $this->get_rgb($background);
 330  
 331          // If get_rgb returns null for either, return 0.
 332          if ($forergb === null || $backrgb === null) {
 333              return [];
 334          }
 335  
 336          $diffs = $this->get_wai_diffs($forergb, $backrgb);
 337  
 338          return $diffs['red'] + $diffs['green'] + $diffs['blue'];
 339      }
 340  
 341      /**
 342       * Returns the WAI ERT Brightness between two colors
 343       * @param string $foreground
 344       * @param string $background
 345       * @return float|int
 346       */
 347      public function get_wai_ert_brightness(string $foreground, string $background): float {
 348          $forergb = $this->get_rgb($foreground);
 349          $backrgb = $this->get_rgb($background);
 350  
 351          // If get_rgb returns null for either, return 0.
 352          if ($forergb === null || $backrgb === null) {
 353              return 0;
 354          }
 355  
 356          $color = $this->get_wai_diffs($forergb, $backrgb);
 357          return (($color['red'] * 299) + ($color['green'] * 587) + ($color['blue'] * 114)) / 1000;
 358      }
 359  
 360      /**
 361       * Get the wai differences.
 362       * @param array $forergb
 363       * @param array $backrgb
 364       * @return array
 365       */
 366      public function get_wai_diffs(array $forergb, array $backrgb): array {
 367          $reddiff = ($forergb['r'] > $backrgb['r'])
 368              ? $forergb['r'] - $backrgb['r']
 369              : $backrgb['r'] - $forergb['r'];
 370          $greendiff = ($forergb['g'] > $backrgb['g'])
 371              ? $forergb['g'] - $backrgb['g']
 372              : $backrgb['g'] - $forergb['g'];
 373  
 374          $bluediff = ($forergb['b'] > $backrgb['b'])
 375              ? $forergb['b'] - $backrgb['b']
 376              : $backrgb['b'] - $forergb['b'];
 377          return ['red' => $reddiff, 'green' => $greendiff, 'blue' => $bluediff];
 378      }
 379  
 380      /**
 381       * Helper method that finds the estimated font-size for the provided
 382       * string font-size parameter.
 383       * @param string $fontsize The css font-size, in various formats
 384       * @return int The estimated font-size
 385       */
 386      public function get_fontsize(string $fontsize): int {
 387          $newfontsize = 12; // Default value, in pt, equivalent to 16px.
 388  
 389          // Search for rem, em, and px initially, typical font-size values.
 390          $pos1 = stripos($fontsize, 'rem');
 391          $pos2 = stripos($fontsize, 'em');
 392          $pos3 = stripos($fontsize, 'px');
 393          if ($pos1 !== false) {
 394              $rem = substr($fontsize, 0, -3);
 395              $newfontsize = $newfontsize * $rem;
 396          } else if ($pos2 !== false) {
 397              $em = substr($fontsize, 0, -2);
 398              $newfontsize = $newfontsize * $em;
 399          } else if ($pos3 !== false) {
 400              $px = substr($fontsize, 0, -2);
 401              $newfontsize = 0.75 * $px;
 402          } else if (in_array($fontsize, array_keys($this->fontsizenames))) {
 403              $newfontsize = $this->fontsizenames[$fontsize];
 404          } else {
 405              preg_match_all('!\d+!', $fontsize, $matches);
 406              $newfontsize = $matches[0][0] ?? $newfontsize;
 407          }
 408          return (int) $newfontsize;
 409      }
 410  }