[ Index ] |
PHP Cross Reference of GlotPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Defines helper functions used by GlotPress. 4 * 5 * @package GlotPress 6 * @since 1.0.0 7 */ 8 9 /** 10 * Prepare an original string to be printed out in a translation row by adding encoding special 11 * characters, adding glossary entires and other markup. 12 * 13 * @param string $text A single style handle to enqueue or an array or style handles to enqueue. 14 * 15 * @return string The prepared string for output. 16 */ 17 function prepare_original( $text ) { 18 // Glossaries are injected into the translations prior to escaping and prepare_original() being run. 19 $glossary_entries = array(); 20 $text = preg_replace_callback( 21 '!(<span class="glossary-word"[^>]+>)!i', 22 function( $m ) use ( &$glossary_entries ) { 23 $item_number = count( $glossary_entries ); 24 $glossary_entries[ $item_number ] = $m[0]; 25 return "<span GLOSSARY={$item_number}>"; 26 }, 27 $text 28 ); 29 30 // Wrap full HTML tags with a notranslate class. 31 $text = preg_replace( '/(<.+?>)/', '<span class="notranslate">\\1</span>', $text ); 32 // Break out & back into notranslate for translatable attributes. 33 $text = preg_replace( '/(title|aria-label)=([\'"])([^\\2]+?)\\2/', '\\1=\\2</span>\\3<span class="notranslate">\\2', $text ); 34 // Wrap placeholders with notranslate. 35 $text = preg_replace( '/(%(\d+\$(?:\d+)?)?[bcdefgosuxEFGX])/', '<span class="notranslate">\\1</span>', $text ); 36 37 // Put the glossaries back! 38 $text = preg_replace_callback( 39 '!(<span GLOSSARY=(\d+)>)!', 40 function( $m ) use ( $glossary_entries ) { 41 return $glossary_entries[ $m[2] ]; 42 }, 43 $text 44 ); 45 46 $text = str_replace( array( "\r", "\n" ), "<span class='invisibles' title='" . esc_attr__( 'New line', 'glotpress' ) . "'>↵</span>\n", $text ); 47 $text = str_replace( "\t", "<span class='invisibles' title='" . esc_attr__( 'Tab character', 'glotpress' ) . "'>→</span>\t", $text ); 48 49 return $text; 50 } 51 52 /** 53 * Prepares a translation string to be printed out in a translation row by adding an 'extra' return/newline if 54 * it starts with one. 55 * 56 * @since 3.0.0 57 * 58 * @param string $text A single style handle to enqueue or an array or style handles to enqueue. 59 * @return string The prepared string for output. 60 */ 61 function gp_prepare_translation_textarea( $text ) { 62 if ( gp_startswith( $text, "\r\n" ) ) { 63 $text = "\r\n" . $text; 64 } elseif ( gp_startswith( $text, "\n" ) ) { 65 $text = "\n" . $text; 66 } 67 68 return $text; 69 } 70 71 /** 72 * Adds suffixes for use in map_glossary_entries_to_translation_originals(). 73 * 74 * @param array $glossary_entries An array of glossary entries to sort. 75 * 76 * @return array The suffixed entries. 77 */ 78 function gp_glossary_add_suffixes( $glossary_entries ) { 79 if ( empty( $glossary_entries ) ) { 80 return; 81 } 82 83 $glossary_entries_suffixes = array(); 84 85 // Create array of glossary terms, longest first. 86 foreach ( $glossary_entries as $key => $value ) { 87 $term = strtolower( $value->term ); 88 89 // Check if is multiple word term. 90 if ( preg_match( '/\s/', $term ) ) { 91 92 // Don't add suffix to terms with multiple words. 93 $glossary_entries_suffixes[ $term ] = array(); 94 continue; 95 } 96 97 $suffixes = array(); 98 if ( 'y' === substr( $term, -1 ) ) { 99 $term = substr( $term, 0, -1 ); 100 $suffixes[] = 'y'; 101 $suffixes[] = 'ies'; 102 $suffixes[] = 'ys'; 103 } elseif ( 'f' === substr( $term, -1 ) ) { 104 $term = substr( $term, 0, -1 ); 105 $suffixes[] = 'f'; 106 $suffixes[] = 'ves'; 107 $terms[] = substr( $term, 0, -1 ) . 'ves'; 108 } elseif ( 'fe' === substr( $term, -2 ) ) { 109 $term = substr( $term, 0, -2 ); 110 $suffixes[] = 'fe'; 111 $suffixes[] = 'ves'; 112 } elseif ( 'an' === substr( $term, -2 ) ) { 113 $term = substr( $term, 0, -2 ); 114 $suffixes[] = 'an'; 115 $suffixes[] = 'en'; 116 } else { 117 $suffixes[] = 's'; 118 $suffixes[] = 'es'; 119 $suffixes[] = 'ed'; 120 $suffixes[] = 'ing'; 121 } 122 123 $glossary_entries_suffixes[ $term ] = $suffixes; 124 } 125 126 // Sort by length in descending order. 127 uksort( 128 $glossary_entries_suffixes, 129 function( $a, $b ) { 130 return mb_strlen( $b ) <=> mb_strlen( $a ); 131 } 132 ); 133 134 return $glossary_entries_suffixes; 135 } 136 137 /** 138 * Add markup to a translation original to identify the glossary terms. 139 * 140 * @param GP_Translation $translation A GP Translation object. 141 * @param GP_Glossary $glossary A GP Glossary object. 142 * 143 * @return obj The marked up translation entry. 144 */ 145 function map_glossary_entries_to_translation_originals( $translation, $glossary ) { 146 static $terms_search, $glossary_entries_reference, $glossary_entries, $cached_glossary; 147 if ( isset( $terms_search ) && isset( $cached_glossary ) && $cached_glossary === $glossary->id ) { 148 if ( ! $terms_search ) { 149 return $translation; 150 } 151 } else { 152 // Build our glossary search. 153 $glossary_entries = $glossary->get_entries(); 154 $cached_glossary = $glossary->id; 155 if ( empty( $glossary_entries ) ) { 156 $terms_search = false; 157 return $translation; 158 } 159 160 $glossary_entries_suffixes = gp_glossary_add_suffixes( $glossary_entries ); 161 162 $glossary_entries_reference = array(); 163 foreach ( $glossary_entries as $id => $value ) { 164 $term = strtolower( $value->term ); 165 if ( ! isset( $glossary_entries_reference[ $term ] ) ) { 166 $glossary_entries_reference[ $term ] = array( $id ); 167 continue; 168 } 169 $glossary_entries_reference[ $term ][] = $id; 170 } 171 172 $terms_search = '\b('; 173 foreach ( $glossary_entries_suffixes as $term => $suffixes ) { 174 $terms_search .= preg_quote( $term, '/' ); 175 176 if ( ! empty( $suffixes ) ) { 177 $terms_search .= '(?:' . implode( '|', $suffixes ) . ')?'; 178 } 179 180 $terms_search .= '|'; 181 182 $referenced_term = $term; 183 if ( ! isset( $glossary_entries_reference[ $referenced_term ] ) ) { 184 foreach ( $suffixes as $suffix ) { 185 if ( isset( $glossary_entries_reference[ $term . $suffix ] ) ) { 186 $referenced_term = $term . $suffix; 187 } 188 } 189 if ( ! isset( $glossary_entries_reference[ $referenced_term ] ) ) { 190 // This should not happen but we don't want to access a non existing item below. 191 continue; 192 } 193 } 194 195 $referenced_term = $glossary_entries_reference[ $referenced_term ]; 196 // Add the suffixed terms to the lookup table. 197 foreach ( $suffixes as $suffix ) { 198 if ( isset( $glossary_entries_reference[ $term . $suffix ] ) ) { 199 $glossary_entries_reference[ $term . $suffix ] = array_values( array_unique( array_merge( $glossary_entries_reference[ $term . $suffix ], $referenced_term ) ) ); 200 } else { 201 $glossary_entries_reference[ $term . $suffix ] = $referenced_term; 202 } 203 } 204 } 205 206 // Remove the trailing |. 207 $terms_search = substr( $terms_search, 0, -1 ); 208 $terms_search .= ')\b'; 209 } 210 211 // Split the singular string on glossary terms boundaries. 212 $singular_split = preg_split( '/' . $terms_search . '/i', $translation->singular, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 213 214 // Loop through each chunk of the split to find glossary terms. 215 if ( is_array( $singular_split ) ) { 216 $singular_combined = ''; 217 218 foreach ( $singular_split as $chunk ) { 219 // Create an escaped version for use later on. 220 $escaped_chunk = esc_translation( $chunk ); 221 222 // Create a lower case version to compare with the glossary terms. 223 $lower_chunk = strtolower( $chunk ); 224 225 // Search the glossary terms for a matching entry. 226 if ( isset( $glossary_entries_reference[ $lower_chunk ] ) ) { 227 $glossary_data = array(); 228 229 // Add glossary data for each matching entry. 230 foreach ( $glossary_entries_reference[ $lower_chunk ] as $glossary_entry_id ) { 231 // Get the glossary entry based on the back reference we created earlier. 232 $glossary_entry = $glossary_entries[ $glossary_entry_id ]; 233 234 // If this is a locale glossary, make a note for the user. 235 $locale_entry = ''; 236 if ( $glossary_entry->glossary_id !== $glossary->id ) { 237 /* translators: Denotes an entry from the locale glossary in the tooltip */ 238 $locale_entry = _x( 'Locale Glossary', 'Bubble', 'glotpress' ); 239 } 240 241 // Create the data to be added to the span. 242 $glossary_data[] = array( 243 'translation' => $glossary_entry->translation, 244 'pos' => $glossary_entry->part_of_speech, 245 'comment' => $glossary_entry->comment, 246 'locale_entry' => $locale_entry, 247 ); 248 } 249 250 // Add the span and chunk to our output. 251 $singular_combined .= '<span class="glossary-word" data-translations="' . htmlspecialchars( wp_json_encode( $glossary_data ), ENT_QUOTES, 'UTF-8' ) . '">' . $escaped_chunk . '</span>'; 252 } else { 253 // No term was found so just add the escaped chunk to the output. 254 $singular_combined .= $escaped_chunk; 255 } 256 } 257 258 // Assign the output to the translation. 259 $translation->singular_glossary_markup = $singular_combined; 260 } else { 261 $translation->singular_glossary_markup = esc_translation( $translation->singular ); 262 } 263 264 // Add glossary terms to the plural if we have one. 265 if ( $translation->plural ) { 266 // Split the plural string on glossary terms boundaries. 267 $plural_split = @preg_split( '/' . $terms_search . '/i', $translation->plural, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 268 269 // Loop through each chunk of the split to find glossary terms. 270 if ( is_array( $plural_split ) ) { 271 $plural_combined = ''; 272 273 foreach ( $plural_split as $chunk ) { 274 // Create an escaped version for use later on. 275 $escaped_chunk = esc_translation( $chunk ); 276 277 // Create a lower case version to compare with the glossary terms. 278 $lower_chunk = strtolower( $chunk ); 279 280 // Search the glossary terms for a matching entry. 281 if ( isset( $glossary_entries_reference[ $lower_chunk ] ) ) { 282 $glossary_data = array(); 283 284 // Add glossary data for each matching entry. 285 foreach ( $glossary_entries_reference[ $lower_chunk ] as $glossary_entry_id ) { 286 // Get the glossary entry based on the back reference we created earlier. 287 $glossary_entry = $glossary_entries[ $glossary_entry_id ]; 288 289 // If this is a locale glossary, make a note for the user. 290 $locale_entry = ''; 291 if ( $glossary_entry->glossary_id !== $glossary->id ) { 292 /* translators: Denotes an entry from the locale glossary in the tooltip */ 293 $locale_entry = _x( 'Locale Glossary', 'Bubble', 'glotpress' ); 294 } 295 296 // Create the data to be added to the span. 297 $glossary_data[] = array( 298 'translation' => $glossary_entry->translation, 299 'pos' => $glossary_entry->part_of_speech, 300 'comment' => $glossary_entry->comment, 301 'locale_entry' => $locale_entry, 302 ); 303 } 304 305 // Add the span and chunk to our output. 306 $plural_combined .= '<span class="glossary-word" data-translations="' . htmlspecialchars( wp_json_encode( $glossary_data ), ENT_QUOTES, 'UTF-8' ) . '">' . $escaped_chunk . '</span>'; 307 } else { 308 // No term was found so just add the escaped chunk to the output. 309 $plural_combined .= $escaped_chunk; 310 } 311 } 312 313 // Assign the output to the translation. 314 $translation->plural_glossary_markup = $plural_combined; 315 } else { 316 $translation->plural_glossary_markup = esc_translation( $translation->plural ); 317 } 318 } 319 320 return $translation; 321 } 322 323 function textareas( $entry, $permissions, $index = 0 ) { 324 list( $can_edit, $can_approve ) = $permissions; 325 ?> 326 <div class="textareas"> 327 <?php 328 if ( isset( $entry->warnings[ $index ] ) ) : 329 $referenceable = $entry->warnings[ $index ]; 330 331 foreach ( $referenceable as $key => $value ) : 332 ?> 333 <div class="warning"> 334 <strong><?php _e( 'Warning:', 'glotpress' ); ?></strong> <?php echo esc_html( $value ); ?> 335 336 <?php if ( $can_approve ) : ?> 337 <a href="#" class="discard-warning" data-nonce="<?php echo esc_attr( wp_create_nonce( 'discard-warning_' . $index . $key ) ); ?>" data-key="<?php echo esc_attr( $key ); ?>" data-index="<?php echo esc_attr( $index ); ?>"><?php _e( 'Discard', 'glotpress' ); ?></a> 338 <?php endif; ?> 339 </div> 340 <?php 341 endforeach; 342 343 endif; 344 ?> 345 <blockquote class="translation"><?php echo prepare_original( esc_translation( gp_array_get( $entry->translations, $index ) ) ); ?></blockquote> 346 <textarea class="foreign-text" name="translation[<?php echo esc_attr( $entry->original_id ); ?>][]" id="translation_<?php echo esc_attr( $entry->original_id ); ?>_<?php echo esc_attr( $index ); ?>" <?php echo disabled( ! $can_edit ); ?>><?php echo gp_prepare_translation_textarea( esc_translation( gp_array_get( $entry->translations, $index ) ) ); ?></textarea> 347 348 <div> 349 <?php 350 if ( $can_edit ) { 351 gp_entry_actions(); 352 } elseif ( is_user_logged_in() ) { 353 _e( 'You are not allowed to edit this translation.', 'glotpress' ); 354 } else { 355 printf( 356 /* translators: %s: URL. */ 357 __( 'You <a href="%s">have to log in</a> to edit this translation.', 'glotpress' ), 358 esc_url( wp_login_url( gp_url_current() ) ) 359 ); 360 } 361 ?> 362 </div> 363 </div> 364 <?php 365 } 366 367 function display_status( $status ) { 368 $status_labels = array( 369 'current' => _x( 'current', 'Single Status', 'glotpress' ), 370 'waiting' => _x( 'waiting', 'Single Status', 'glotpress' ), 371 'fuzzy' => _x( 'fuzzy', 'Single Status', 'glotpress' ), 372 'old' => _x( 'old', 'Single Status', 'glotpress' ), 373 'rejected' => _x( 'rejected', 'Single Status', 'glotpress' ), 374 ); 375 if ( isset( $status_labels[ $status ] ) ) { 376 $status = $status_labels[ $status ]; 377 } 378 $status = preg_replace( '/^[+-]/', '', $status ); 379 return $status ? $status : _x( 'untranslated', 'Single Status', 'glotpress' ); 380 } 381 382 function references( $project, $entry ) { 383 384 /** 385 * Filter whether to show references of a translation string on a translation row. 386 * 387 * @since 1.0.0 388 * 389 * @param boolean $references Whether to show references. 390 * @param GP_Project $project The current project. 391 * @param Translation_Entry $entry Translation entry object. 392 */ 393 $show_references = apply_filters( 'gp_show_references', (bool) $entry->references, $project, $entry ); 394 395 if ( ! $show_references ) { 396 return; 397 } 398 ?> 399 <dl><dt> 400 <?php _e( 'References:', 'glotpress' ); ?> 401 <ul class="refs"> 402 <?php 403 foreach ( $entry->references as $reference ) : 404 list( $file, $line ) = array_pad( explode( ':', $reference ), 2, 0 ); 405 $source_url = $project->source_url( $file, $line ); 406 if ( $source_url ) : 407 ?> 408 <li> 409 <a target="_blank" tabindex="-1" href="<?php echo esc_url( $source_url ); ?>"> 410 <?php echo esc_html( $file . ':' . $line ); ?> 411 </a> 412 </li> 413 <?php 414 else : 415 echo '<li>' . esc_html( "$file:$line" ) . '</li>'; 416 endif; 417 endforeach; 418 ?> 419 </ul></dt></dl> 420 <?php 421 } 422 423 /** 424 * Output the bulk actions toolbar in the translations page. 425 * 426 * @param string $bulk_action The URL to submit the form to. 427 * @param string $can_write Can the current user write translations to the database. 428 * @param string $translation_set The current translation set. 429 * @param string $location The location of this toolbar, used to make id's unique for each instance on a page. 430 */ 431 function gp_translations_bulk_actions_toolbar( $bulk_action, $can_write, $translation_set, $location = 'top' ) { 432 ?> 433 <form id="bulk-actions-toolbar-<?php echo esc_attr( $location ); ?>" class="bulk-actions" action="<?php echo esc_attr( $bulk_action ); ?>" method="post"> 434 <div> 435 <select name="bulk[action]" id="bulk-action-<?php echo esc_attr( $location ); ?>" class="bulk-action"> 436 <option value="" selected="selected"><?php _e( 'Bulk Actions', 'glotpress' ); ?></option> 437 <option value="approve"><?php _ex( 'Approve', 'Action', 'glotpress' ); ?></option> 438 <option value="reject"><?php _ex( 'Reject', 'Action', 'glotpress' ); ?></option> 439 <option value="fuzzy"><?php _ex( 'Fuzzy', 'Action', 'glotpress' ); ?></option> 440 <?php if ( $can_write ) : ?> 441 <option value="set-priority" class="hide-if-no-js"><?php _e( 'Set Priority', 'glotpress' ); ?></option> 442 <?php endif; ?> 443 <?php 444 445 /** 446 * Fires inside the bulk action menu for translation sets. 447 * 448 * Printing out option elements here will add those to the translation 449 * bulk options drop down menu. 450 * 451 * @since 1.0.0 452 * 453 * @param GP_Translation_Set $set The translation set. 454 */ 455 do_action( 'gp_translation_set_bulk_action', $translation_set ); 456 ?> 457 </select> 458 <?php if ( $can_write ) : ?> 459 <select name="bulk[priority]" id="bulk-priority-<?php echo esc_attr( $location ); ?>" class="bulk-priority hidden"> 460 <?php 461 $labels = array( 462 'hidden' => _x( 'hidden', 'Priority', 'glotpress' ), 463 'low' => _x( 'low', 'Priority', 'glotpress' ), 464 'normal' => _x( 'normal', 'Priority', 'glotpress' ), 465 'high' => _x( 'high', 'Priority', 'glotpress' ), 466 ); 467 468 foreach ( GP::$original->get_static( 'priorities' ) as $value => $label ) { 469 if ( isset( $labels[ $label ] ) ) { 470 $label = $labels[ $label ]; 471 } 472 473 echo "\t<option value='" . esc_attr( $value ) . "' " . selected( 'normal', $value, false ) . '>' . esc_html( $label ) . "</option>\n"; 474 } 475 ?> 476 </select> 477 <?php endif; ?> 478 <input type="hidden" name="bulk[redirect_to]" value="<?php echo esc_attr( gp_url_current() ); ?>" id="bulk-redirect_to-<?php echo esc_attr( $location ); ?>" /> 479 <input type="hidden" name="bulk[row-ids]" value="" id="bulk-row-ids-<?php echo esc_attr( $location ); ?>" /> 480 <input type="submit" class="button" value="<?php esc_attr_e( 'Apply', 'glotpress' ); ?>" /> 481 </div> 482 <?php 483 $nonce = gp_route_nonce_field( 'bulk-actions', false ); 484 $nonce = str_replace( 'id="_gp_route_nonce"', 'id="_gp_route_nonce_' . esc_attr( $location ) . '"', $nonce ); 485 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 486 echo $nonce; 487 ?> 488 </form> 489 <?php 490 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:01:07 2024 | Cross-referenced by PHPXref 0.7.1 |