Changeset 3471105
- Timestamp:
- 02/27/2026 12:54:03 PM (4 weeks ago)
- Location:
- loco-translate/trunk
- Files:
-
- 4 edited
-
lib/compiled/gettext.php (modified) (8 diffs)
-
lib/compiled/locales.php (modified) (1 diff)
-
lib/compiled/phpunit.php (modified) (1 diff)
-
src/ajax/FsReferenceController.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
loco-translate/trunk/lib/compiled/gettext.php
r3471025 r3471105 1 1 <?php 2 2 /** 3 * Downgraded for PHP 7. 2compatibility. Do not edit.3 * Downgraded for PHP 7.4 compatibility. Do not edit. 4 4 * @noinspection ALL 5 5 */ … … 201 201 interface LocoTokensInterface extends Iterator { 202 202 public function advance(); 203 public function ignore( ...$symbols ):self; } 203 public function ignore( ...$symbols ):self; 204 public function allow( ...$symbols ):self; } 204 205 class LocoTokenizer implements LocoTokensInterface { const /*int*/T_LITERAL = 0; const /*int*/T_UNKNOWN = -1; 205 206 private /*string*/ $src; … … 217 218 public function init( string $src ):self { $this->src = $src; $this->rewind(); return $this; } 218 219 public function define( string $grep, /*mixed*/ $t = 0 ):self { if('^' !== $grep[1] ){ throw new InvalidArgumentException('Expression '.$grep.' isn\'t anchored'); } if( ! is_int($t) && ! is_callable($t) ){ throw new InvalidArgumentException('Non-integer token must be valid callback'); } $sniff = $grep[2]; if( $sniff === preg_quote($sniff,$grep[0]) ){ $this->rules[$sniff][] = [ $grep, $t ]; } else { $this->rules[''][] = [ $grep, $t ]; } return $this; } 219 public function ignore( ...$symbols ): LocoTokensInterface{ $this->skip += array_fill_keys( $symbols, true ); return $this; }220 public function ignore( ...$symbols ):self { $this->skip += array_fill_keys( $symbols, true ); return $this; } 220 221 public function allow( ...$symbols ):self { $this->skip = array_diff_key( $this->skip, array_fill_keys($symbols,true) ); return $this; } 221 222 #[ReturnTypeWillChange] … … 257 258 private /*array*/ $dom = []; 258 259 private /*string*/ $dflt = ''; 260 private /*int*/ $cap = 0; 259 261 public function extractSource( LocoExtractorInterface $ext, string $src, string $fileref = '' ):self { $ext->extract( $this, $ext->tokenize($src), $fileref ); return $this; } 260 262 public function export():array { return $this->exp; } … … 264 266 public function setDomain( string $default ):self { $this->dflt = $default; return $this; } 265 267 public function getDomain():string { return $this->dflt; } 268 public function isFull():bool { return $this->cap && count($this->exp) >= $this->cap; } 269 public function limit( int $cap ):void { $this->cap = $cap; } 266 270 private function key( array $entry ):string { $key = (string) $entry['source']; foreach( ['context','domain'] as $i => $prop ){ if( array_key_exists($prop,$entry) ) { $add = (string) $entry[$prop]; if( '' !== $add ){ $key .= ord($i).$add; } } } return $key; } 267 public function pushEntry( array $entry, string $domain ):int { if( '' === $domain || '*' === $domain ){ $domain = $this->dflt; } $entry['id'] = ''; $entry['target'] = ''; $entry['domain'] = $domain; $key = $this->key($entry); if( isset($this->reg[$key]) ){ $index = $this->reg[$key]; $clash = $this->exp[$index]; if( $value = $this->mergeField( $clash, $entry, 'refs', ' ') ){ $this->exp[$index]['refs'] = $value; } if( $value = $this->mergeField( $clash, $entry, 'notes', "\n") ){ $this->exp[$index]['notes'] = $value; } } else { $index = count($this->exp); $this->reg[$key] = $index; $this->exp[$index] = $entry; if( isset($this->dom[$domain]) ){ $this->dom[$domain]++; } else { $this->dom[$domain] = 1; } } return $index; }271 public function pushEntry( array $entry, string $domain ):int { if( '' === $domain || '*' === $domain ){ $domain = $this->dflt; } $entry['id'] = ''; $entry['target'] = ''; $entry['domain'] = $domain; $key = $this->key($entry); if( isset($this->reg[$key]) ){ $index = $this->reg[$key]; $clash = $this->exp[$index]; if( $value = $this->mergeField( $clash, $entry, 'refs', ' ') ){ $this->exp[$index]['refs'] = $value; } if( $value = $this->mergeField( $clash, $entry, 'notes', "\n") ){ $this->exp[$index]['notes'] = $value; } } else { $index = count($this->exp); if( $this->cap && $index === $this->cap ){ throw new OverflowException('Extraction limit reached'); } $this->reg[$key] = $index; $this->exp[$index] = $entry; if( isset($this->dom[$domain]) ){ $this->dom[$domain]++; } else { $this->dom[$domain] = 1; } } return $index; } 268 272 public function pushPlural( array $entry, int $sindex ):void { $parent = $this->exp[$sindex]; $domain = $parent['domain']; $pkey = $this->key($parent)."\2"; if( ! array_key_exists($pkey,$this->reg) ){ $pindex = count($this->exp); $this->reg[$pkey] = $pindex; $entry += [ 'id' => '', 'target' => '', 'plural' => 1, 'parent' => $sindex, 'domain' => $domain, ]; $this->exp[$pindex] = $entry; if( isset($entry['format']) && ! isset( $parent['format']) ) { $this->exp[$sindex]['format'] = $entry['format']; } if( $pindex !== $sindex + $entry['plural']) { $this->exp[$sindex]['child'] = $pindex; } } } 269 public function mergeField( array $old, array $new, string $field, string $glue ):string { $prev = isset($old[$field]) ? $old[$field] :''; if( isset($new[$field]) ){ $text = $new[$field]; if( '' !== $prev && $prev !== $text ){ if( 'notes' === $field && preg_match( '/^'.preg_quote( rtrim($text,'. '),'/').'[. ]*$/mu', $prev ) ) { $text = $prev; } else { $text = $prev.$glue.$text; } } return $text; } return $prev; }273 public function mergeField( array $old, array $new, string $field, string $glue ):string { $prev = $old[$field] ?? ''; if( isset($new[$field]) ){ $text = $new[$field]; if( '' !== $prev && $prev !== $text ){ if( 'notes' === $field && preg_match( '/^'.preg_quote( rtrim($text,'. '),'/').'[. ]*$/mu', $prev ) ) { $text = $prev; } else { $text = $prev.$glue.$text; } } return $text; } return $prev; } 270 274 public function filter( string $domain ):array { if( '' === $domain ){ $domain = $this->dflt; } $map = []; $newOffset = 1; $matchAll = '*' === $domain; $raw = [ [ 'id' => '', 'source' => '', 'target' => '', 'domain' => $matchAll ? '' : $domain, ] ]; foreach( $this->exp as $oldOffset => $r ){ if( isset($r['parent']) ){ if( isset($map[$r['parent']]) ){ $r['parent'] = $map[ $r['parent'] ]; $raw[ $newOffset++ ] = $r; } } else { if( $matchAll ){ $match = true; } else if( isset($r['domain']) ){ $match = $domain === $r['domain']; } else { $match = $domain === ''; } if( $match ){ $map[ $oldOffset ] = $newOffset; $raw[ $newOffset++ ] = $r; } } } return $raw; } } 271 275 abstract class LocoExtractor implements LocoExtractorInterface { … … 292 296 public function reset():void { $this->rewind(); $this->literal_tokens = []; $this->skip_tokens = []; } 293 297 public function literal( ...$symbols ):self { $this->literal_tokens += array_fill_keys($symbols,true); return $this; } 294 public function ignore( ...$symbols ):LocoTokensInterface { $this->skip_tokens += array_fill_keys($symbols,true); return $this; } 298 public function ignore( ...$symbols ):self { $this->skip_tokens += array_fill_keys($symbols,true); return $this; } 299 public function allow( ...$symbols ):self { $this->skip_tokens = array_diff_key( $this->skip_tokens, array_fill_keys($symbols,true) ); return $this; } 295 300 public function export():array { return array_values( iterator_to_array($this) ); } 296 301 public function advance() { if( $this->valid() ){ $tok = $this->current(); $this->next(); return $tok; } return null; } … … 322 327 protected function comment( string $comment ):string { return preg_replace('/^translators:\\s+/mi', '', loco_parse_php_comment($comment) ); } 323 328 public function define( string $name, string $value ):self { $this->defs[$name] = $value; return $this; } 324 public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, string $fileref = '' ):void { $tokens->ignore(T_WHITESPACE); $n = 0; $depth = 0; $comment = ''; $narg = 0; $args = []; $ref = ''; $rule = ''; $wp = $this->getHeaders(); $tokens->rewind(); while( $tok = $tokens->advance() ){ if( is_string($tok) ){ $s = $tok; $t = null; } else { $t = $tok[0]; $s = $tok[1]; if( T_NAME_FULLY_QUALIFIED === $t ){ $s = ltrim($s,'\\'); $t = T_STRING; } } if( $depth ){ if( ')' === $s || ']' === $s ){ if( 0 === --$depth ){ if( $this->push( $strings, $rule, $args, $comment, $ref ) ){ $n++; } $comment = ''; } } else if( '(' === $s || '[' === $s ){ $depth++; $args[$narg] = null; } else if( 1 === $depth ){ if( ',' === $s ){ $narg++; } else if( T_CONSTANT_ENCAPSED_STRING === $t ){ $s = self::utf8($s); $args[$narg] = $this->decapse($s); } else if( T_STRING === $t && array_key_exists($s,$this->defs) ){ $args[$narg] = $this->defs[$s]; } else { $args[$narg] = null; } } } else if( T_COMMENT === $t || T_DOC_COMMENT === $t ){ $was_header = false; $s = self::utf8($s); if( 0 === $n ){ if( false !== strpos($s,'* @package') ){ $was_header = true; } if( $wp && ( $header = loco_parse_wp_comment($s) ) ){ foreach( $wp as $domain => $tags ){ foreach( array_intersect_key($header,$tags) as $tag => $text ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $meta = $tags[$tag]; if( is_string($meta) ){ $meta = ['notes'=>$meta]; trigger_error( $tag.' header defaulted to "notes"',E_USER_DEPRECATED); } $strings->pushEntry( ['source'=>$text,'refs'=>$ref] + $meta, (string) $domain );$was_header = true; } } } } if( ! $was_header ) { $comment = $s; } } else if( T_STRING === $t && '(' === $tokens->advance() && ( $rule = $this->rule($s) ) ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $depth = 1; $args = []; $narg = 0; } else if( '' !== $comment && ! preg_match('!^[/* ]+(translators|xgettext):!im',$comment) ){ $comment = ''; } } } }329 public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, string $fileref = '' ):void { $tokens->ignore(T_WHITESPACE); $n = 0; $depth = 0; $comment = ''; $narg = 0; $args = []; $ref = ''; $rule = ''; $wp = $this->getHeaders(); $tokens->rewind(); while( $tok = $tokens->advance() ){ if( is_string($tok) ){ $s = $tok; $t = null; } else { $t = $tok[0]; $s = $tok[1]; if( T_NAME_FULLY_QUALIFIED === $t ){ $s = ltrim($s,'\\'); $t = T_STRING; } } if( $depth ){ if( ')' === $s || ']' === $s ){ if( 0 === --$depth ){ if( $this->push( $strings, $rule, $args, $comment, $ref ) ){ $n++; } if( $strings->isFull() ){ return; } $comment = ''; } } else if( '(' === $s || '[' === $s ){ $depth++; $args[$narg] = null; } else if( 1 === $depth ){ if( ',' === $s ){ $narg++; } else if( T_CONSTANT_ENCAPSED_STRING === $t ){ $s = self::utf8($s); $args[$narg] = $this->decapse($s); } else if( T_STRING === $t && array_key_exists($s,$this->defs) ){ $args[$narg] = $this->defs[$s]; } else { $args[$narg] = null; } } } else if( T_COMMENT === $t || T_DOC_COMMENT === $t ){ $was_header = false; $s = self::utf8($s); if( 0 === $n ){ if( false !== strpos($s,'* @package') ){ $was_header = true; } if( $wp && ( $header = loco_parse_wp_comment($s) ) ){ foreach( $wp as $domain => $tags ){ foreach( array_intersect_key($header,$tags) as $tag => $text ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $meta = $tags[$tag]; if( is_string($meta) ){ $meta = ['notes'=>$meta]; trigger_error( $tag.' header defaulted to "notes"',E_USER_DEPRECATED); } $strings->pushEntry( ['source'=>$text,'refs'=>$ref] + $meta, (string) $domain ); if( $strings->isFull() ){ return; } $was_header = true; } } } } if( ! $was_header ) { $comment = $s; } } else if( T_STRING === $t && '(' === $tokens->advance() && ( $rule = $this->rule($s) ) ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $depth = 1; $args = []; $narg = 0; } else if( '' !== $comment && ! preg_match('!^[/* ]+(translators|xgettext):!im',$comment) ){ $comment = ''; } } } } 325 330 class LocoJsExtractor extends LocoPHPExtractor { 326 331 public function tokenize( string $src ):LocoTokensInterface { return new LocoJsTokens($src); } … … 345 350 public function __construct( array $raw, stdClass $tpl ){ parent::__construct(); $this->walk( $tpl, $raw ); } 346 351 public function advance() { $tok = $this->current(); $this->next(); return $tok; } 347 public function ignore( ...$symbols ):LocoTokensInterface { return $this; } 352 public function ignore( ...$symbols ):self { throw new BadMethodCallException(); } 353 public function allow( ...$symbols ):self { throw new BadMethodCallException(); } 348 354 private function walk( /*mixed*/ $tpl, /*mixed*/ $raw ):void { if( is_string($tpl) && is_string($raw) ) { $this->offsetSet( null, [ 'context' => $tpl, 'source' => $raw, ] ); return; } if( is_array($tpl) && is_array($raw) ) { foreach ( $raw as $value ) { $this->walk( $tpl[0], $value ); } } else if( is_object($tpl) && is_array($raw) ) { $group_key = '*'; foreach ( $raw as $key => $value ) { if ( isset($tpl->$key) ) { $this->walk( $tpl->$key, $value ); } else if ( isset($tpl->$group_key) ) { $this->walk( $tpl->$group_key, $value ); } } } } } 349 355 function loco_wp_extractor( string $type = 'php', string $ext = '' ):LocoExtractorInterface { if( 'json' === $type ){ return new LocoWpJsonExtractor; } static $rules = [ '__' => 'sd', '_e' => 'sd', '_c' => 'sd', '_n' => 'sp_d', '_n_noop' => 'spd', '_nc' => 'sp_d', '__ngettext' => 'spd', '__ngettext_noop' => 'spd', '_x' => 'scd', '_ex' => 'scd', '_nx' => 'sp_cd', '_nx_noop' => 'spcd', 'esc_attr__' => 'sd', 'esc_html__' => 'sd', 'esc_attr_e' => 'sd', 'esc_html_e' => 'sd', 'esc_attr_x' => 'scd', 'esc_html_x' => 'scd', ]; if( 'php' === $type ){ return substr($ext,-9) === 'blade.php' ? new LocoBladeExtractor($rules) : new LocoPHPExtractor($rules); } if( 'js' === $type ){ return new LocoJsExtractor($rules); } if( 'twig' === $type ){ return new LocoTwigExtractor($rules); } throw new InvalidArgumentException('No extractor for '.$type); } -
loco-translate/trunk/lib/compiled/locales.php
r3297837 r3471105 1 1 <?php 2 2 /** 3 * Downgraded for PHP 7. 2compatibility. Do not edit.3 * Downgraded for PHP 7.4 compatibility. Do not edit. 4 4 * @noinspection ALL 5 5 */ -
loco-translate/trunk/lib/compiled/phpunit.php
r3368520 r3471105 1 1 <?php 2 2 /** 3 * Downgraded for PHP 7. 2compatibility. Do not edit.3 * Downgraded for PHP 7.4 compatibility. Do not edit. 4 4 * @noinspection ALL 5 5 */ -
loco-translate/trunk/src/ajax/FsReferenceController.php
r3471025 r3471105 137 137 138 138 // JSON is supported, but only if it parses as a valid i18n schema (e.g. blocks.json) 139 // No point highlighting this as blocks|theme.json usually have no line number.140 139 if( $extractor instanceof LocoWpJsonExtractor ){ 141 140 $source = $srcfile->getContents(); 142 141 $extractor->tokenize($source); 143 $tokens = preg_split( '/\\R/u',$source); 142 // No point highlighting this as blocks|theme.json usually have no line number. 143 foreach( preg_split( '/\\R/u',$source) as $line ){ 144 $code[] = '<code>'.htmlentities($line,ENT_COMPAT,'UTF-8').'</code>'; 145 } 144 146 } 145 147 // Else the file will be tokenized as JavaScript or PHP (including Twig and Blade) … … 153 155 else { 154 156 $tokens = $extractor->tokenize( $srcfile->getContents() ); 155 } 156 157 // highlighting on back end because tokenizer provides more control than highlight.js 158 if( $tokens instanceof LocoTokensInterface ){ 157 $strings = new LocoExtracted; 158 $strings->limit(1); 159 $extractor->extract( $strings, $tokens ); 160 if( 0 === $strings->count() ){ 161 throw new Loco_error_Exception('File access disallowed: No translatable strings found'); 162 } 159 163 $thisline = 1; 164 $tokens->rewind(); 165 $tokens->allow(T_WHITESPACE); 160 166 while( $tok = $tokens->advance() ){ 161 167 if( is_array($tok) ){ … … 189 195 } 190 196 } 191 // Tokens have been rendered including whitespace, but we will block files with no translatable strings192 // TODO Implement a limit to the strings extracted so we don't have to pull the whole file.193 $strings = new LocoExtracted;194 $extractor->extract( $strings, $tokens );195 if( 0 === $strings->count() ){196 throw new Loco_error_Exception('File access disallowed: No translatable strings found');197 }198 }199 // permit limited other file types, but without back end highlighting200 else if( is_iterable($tokens) ){201 foreach( $tokens as $line ){202 $code[] = '<code>'.htmlentities($line,ENT_COMPAT,'UTF-8').'</code>';203 }204 197 } 205 198
Note: See TracChangeset
for help on using the changeset viewer.