1<?php 2// intlmsg.php -- HotCRP helper functions for message i18n 3// Copyright (c) 2006-2018 Eddie Kohler; see LICENSE. 4 5class IntlMsg { 6 public $context; 7 public $otext; 8 public $require; 9 public $priority = 0.0; 10 public $next; 11 12 private function arg(IntlMsgSet $ms, $args, $which) { 13 if (ctype_digit($which)) 14 return get($args, +$which); 15 else 16 return $ms->get($which); 17 } 18 function check_require(IntlMsgSet $ms, $args) { 19 if (!$this->require) 20 return 0; 21 $nreq = 0; 22 foreach ($this->require as $req) { 23 if (preg_match('/\A\s*\$(\w+)\s*([=!<>]=?|≠|≤|≥)\s*([-+]?(?:\d+\.?\d*|\.\d+))\s*\z/', $req, $m)) { 24 $arg = $this->arg($ms, $args, $m[1]); 25 if ((string) $arg === "" 26 || !CountMatcher::compare((float) $arg, $m[2], (float) $m[3])) 27 return false; 28 ++$nreq; 29 } else if (preg_match('/\A\s*\$(\w+)\s*([=!]=?|≠|!?\^=)\s*(\S+)\s*\z/', $req, $m)) { 30 $arg = $this->arg($ms, $args, $m[1]); 31 if ((string) $arg === "") 32 return false; 33 if ($m[2] === "^=" || $m[2] === "!^=") { 34 $have = str_starts_with($arg, $m[3]); 35 $weight = 0.9; 36 } else { 37 $have = $arg === $m[3]; 38 $weight = 1; 39 } 40 $want = ($m[2] === "=" || $m[2] === "==" || $m[2] === "^="); 41 if ($have !== $want) 42 return false; 43 $nreq += $weight; 44 } else if (preg_match('/\A\s*(|!)\s*\$(\w+)\s*\z/', $req, $m)) { 45 $arg = $this->arg($ms, $args, $m[2]); 46 $bool_arg = (string) $arg !== "" && $arg !== 0; 47 if ($bool_arg !== ($m[1] === "")) 48 return false; 49 ++$nreq; 50 } 51 } 52 return $nreq; 53 } 54} 55 56class IntlMsgSet { 57 private $ims = []; 58 private $defs = []; 59 private $_ctx; 60 private $_default_priority; 61 62 function set_default_priority($p) { 63 $this->_default_priority = (float) $p; 64 } 65 function clear_default_priority() { 66 $this->_default_priority = null; 67 } 68 69 function add($m, $ctx = null) { 70 if (is_string($m)) 71 $x = $this->addj(func_get_args()); 72 else if (!$ctx) 73 $x = $this->addj($m); 74 else { 75 $octx = $this->_ctx; 76 $this->_ctx = $ctx; 77 $x = $this->addj($m); 78 $this->_ctx = $octx; 79 } 80 return $x; 81 } 82 83 function addj($m) { 84 if (is_associative_array($m)) 85 $m = (object) $m; 86 if (is_object($m) && isset($m->members) && is_array($m->members)) { 87 $octx = $this->_ctx; 88 if (isset($m->context) && is_string($m->context)) 89 $this->_ctx = ((string) $this->_ctx === "" ? "" : $this->_ctx . "/") . $m->context; 90 foreach ($m->members as $mm) 91 $this->addj($mm); 92 $this->_ctx = $octx; 93 return true; 94 } 95 $im = new IntlMsg; 96 if ($this->_default_priority !== null) 97 $im->priority = $this->_default_priority; 98 if (is_array($m)) { 99 $n = count($m); 100 $p = false; 101 while ($n > 0 && !is_string($m[$n - 1])) { 102 if ((is_int($m[$n - 1]) || is_float($m[$n - 1])) && $p === false) 103 $p = $im->priority = (float) $m[$n - 1]; 104 else if (is_array($m[$n - 1]) && $im->require === null) 105 $im->require = $m[$n - 1]; 106 else 107 return false; 108 --$n; 109 } 110 if ($n < 2 || $n > 3 || !is_string($m[0]) || !is_string($m[1]) 111 || ($n === 3 && !is_string($m[2]))) 112 return false; 113 if ($n === 3) { 114 $im->context = $m[0]; 115 $itext = $m[1]; 116 $im->otext = $m[2]; 117 } else { 118 $itext = $m[0]; 119 $im->otext = $m[1]; 120 } 121 } else if (is_object($m)) { 122 if (isset($m->context) && is_string($m->context)) 123 $im->context = $m->context; 124 if (isset($m->id) && is_string($m->id)) 125 $itext = $m->id; 126 else if (isset($m->itext) && is_string($m->itext)) 127 $itext = $m->itext; 128 else 129 return false; 130 if (isset($m->otext) && is_string($m->otext)) 131 $im->otext = $m->otext; 132 else if (isset($m->itext) && is_string($m->itext)) 133 $im->otext = $m->itext; 134 else 135 return false; 136 if (isset($m->priority) && (is_float($m->priority) || is_int($m->priority))) 137 $im->priority = (float) $m->priority; 138 if (isset($m->require) && is_array($m->require)) 139 $im->require = $m->require; 140 } else 141 return false; 142 if ($this->_ctx) 143 $im->context = $this->_ctx . ($im->context ? "/" . $im->context : ""); 144 $im->next = get($this->ims, $itext); 145 $this->ims[$itext] = $im; 146 return true; 147 } 148 149 function set($name, $value) { 150 $this->defs[$name] = $value; 151 } 152 153 function get($name) { 154 return get($this->defs, $name); 155 } 156 157 private function find($context, $itext, $args) { 158 $match = null; 159 $matchnreq = $matchctxlen = 0; 160 for ($im = get($this->ims, $itext); $im; $im = $im->next) { 161 $ctxlen = $nreq = 0; 162 if ($context !== null && $im->context !== null) { 163 if ($context === $im->context) 164 $ctxlen = 10000; 165 else { 166 $ctxlen = (int) min(strlen($context), strlen($im->context)); 167 if (strncmp($context, $im->context, $ctxlen) !== 0 168 || ($ctxlen < strlen($context) && $context[$ctxlen] !== "/") 169 || ($ctxlen < strlen($im->context) && $im->context[$ctxlen] !== "/")) 170 continue; 171 } 172 } else if ($context === null && $im->context !== null) 173 continue; 174 if ($im->require 175 && ($nreq = $im->check_require($this, $args)) === false) 176 continue; 177 if (!$match 178 || $im->priority > $match->priority 179 || ($im->priority == $match->priority 180 && ($ctxlen > $matchctxlen 181 || ($ctxlen == $matchctxlen 182 && $nreq > $matchnreq)))) { 183 $match = $im; 184 $matchnreq = $nreq; 185 $matchctxlen = $ctxlen; 186 } 187 } 188 return $match; 189 } 190 191 private function expand($args) { 192 $pos = 0; 193 while (($pos = strpos($args[0], "%", $pos)) !== false) { 194 if (preg_match('/\A(?!\d+)\w+(?=[$%])/', substr($args[0], $pos + 1), $m) 195 && isset($this->defs[$m[0]])) { 196 $args[] = $this->defs[$m[0]]; 197 $t = substr($args[0], 0, $pos + 1) . (count($args) - 1); 198 $pos += 1 + strlen($m[0]); 199 if ($args[0][$pos] == "%") { 200 $t .= "\$s"; 201 ++$pos; 202 } 203 $args[0] = $t . substr($args[0], $pos); 204 $pos = strlen($t); 205 } else 206 $pos += 2; 207 } 208 return call_user_func_array("sprintf", $args); 209 } 210 211 function x($itext) { 212 $args = func_get_args(); 213 if (($im = $this->find(null, $itext, $args))) 214 $args[0] = $im->otext; 215 return $this->expand($args); 216 } 217 218 function xc($context, $itext) { 219 $args = array_slice(func_get_args(), 1); 220 if (($im = $this->find($context, $itext, $args))) 221 $args[0] = $im->otext; 222 return $this->expand($args); 223 } 224 225 function xi($id, $itext) { 226 $args = array_slice(func_get_args(), 1); 227 if (($im = $this->find(null, $id, $args))) 228 $args[0] = $im->otext; 229 return $this->expand($args); 230 } 231 232 function xci($context, $id, $itext) { 233 $args = array_slice(func_get_args(), 2); 234 if (($im = $this->find($context, $id, $args))) 235 $args[0] = $im->otext; 236 return $this->expand($args); 237 } 238} 239