<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[10730] sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-warnings/wporg-gp-custom-warnings.php: Translate: Expand upon the GlotPress tag warnings to validate the `href` values separately to the rest of the tags.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/10730">10730</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/10730","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dd32</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2021-03-01 06:05:09 +0000 (Mon, 01 Mar 2021)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Translate: Expand upon the GlotPress tag warnings to validate the `href` values separately to the rest of the tags.

This change also allows for the tags to be translated in any order, but doesn't validate the HTML is correctly nested (follow up change needed)

See <a href="http://meta.trac.wordpress.org/ticket/5155">#5155</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomwarningswporggpcustomwarningsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-warnings/wporg-gp-custom-warnings.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomwarningswporggpcustomwarningsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-warnings/wporg-gp-custom-warnings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-warnings/wporg-gp-custom-warnings.php    2021-02-26 21:42:37 UTC (rev 10729)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-warnings/wporg-gp-custom-warnings.php      2021-03-01 06:05:09 UTC (rev 10730)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -111,48 +111,109 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Extends the GlotPress tags warning to allow some URL changes.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Replaces the GlotPress tags warning to allow some URL changes.
+        * 
+        * Differences from GlotPress:
+        *  - URLs (href + src) are run through `self::warning_mismatching_urls()`
+        *    - The domain may change for some safe domains
+        *    - The protocol may change between https & http
+        *    - The URL may include/remove a trailing slash
+        *  - The value of translatable/url attributes is excluded from the error message if it's not related to the issue at hand.
+        *  - Tags are sorted, <em>One</em> <strong>Two</strong> can be translated as <strong>foo</strong> <em>bar</em> without generating warning.
+        *  - TODO: Tags are not validated to be nested correctly. GlotPress handles this by validating the ordering of the tags remained the same.
+        *  - TODO: Allow Japanese (and other locales?) remove certain style/formatting tags that don't apply in the locale.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param string    $original    The original string.
-        * @param string    $translation The translated string.
-        * @param GP_Locale $locale      The locale.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param string    $original    The source string.
+        * @param string    $translation The translation.
+        * @param GP_Locale $locale      The locale of the translation.
+        * @return string|true True if check is OK, otherwise warning message.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function warning_tags( $original, $translation, $locale ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Allow URL changes in `href` attributes by substituting the original URL when appropriate
-               // if that passes the checks, assume it's okay, otherwise throw the warning with the original payload.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $tag_pattern       = '(<[^>]*>)';
+               $tag_re            = "/$tag_pattern/Us";
+               $original_parts    = [];
+               $translation_parts = [];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $altered_translation = $translation;
-               foreach ( $this->allowed_domain_changes as $domain => $regex ) {
-                       if ( false === stripos( $original, '://' . $domain ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( preg_match_all( $tag_re, $original, $m ) ) {
+                       $original_parts = $m[1];
+               }
+               if ( preg_match_all( $tag_re, $translation, $m ) ) {
+                       $translation_parts = $m[1];
+               }
+
+               if ( count( $original_parts ) > count( $translation_parts ) ) {
+                       return 'Missing tags from translation. Expected: ' . implode( ' ', array_diff( $original_parts, $translation_parts ) );
+               }
+               if ( count( $original_parts ) < count( $translation_parts ) ) {
+                       return 'Too many tags in translation. Found: ' . implode( ' ', array_diff( $translation_parts, $original_parts ) );
+               }
+
+               // TODO: Validate nesting of HTML is same.
+               // GlotPress handled this by requiring the HTML be in the same order.
+
+               // Sort the tags, from this point out as long as all the tags are present is okay.
+               rsort( $original_parts );
+               rsort( $translation_parts );
+
+               $changeable_attributes = array(
+                       // We allow certain attributes to be different in translations.
+                       'title',
+                       'aria-label',
+                       // src and href will be checked separately.
+                       'src',
+                       'href',
+               );
+
+               $attribute_regex       = '/(\s*(?P<attr>%s))=([\'"])(?P<value>.+)\\3(\s*)/i';
+               $attribute_replace     = '$1=$3...$3$5';
+               $changeable_attr_regex = sprintf( $attribute_regex, implode( '|', $changable_attributes ) );
+               $link_attr_regex       = sprintf( $attribute_regex, 'href|src' );
+
+               // Items are sorted, so if all is well, will match up.
+               $parts_tags = array_combine( $original_parts, $translation_parts );
+
+               $warnings = [];
+               foreach ( $parts_tags as $original_tag => $translation_tag ) {
+                       if ( $original_tag === $translation_tag ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Make an assumption that the first protocol for the given domain is the protocol in use.
-                       $protocol = 'https';
-                       if ( preg_match( '!(https?)://' . $regex . '!', $original, $m ) ) {
-                               $protocol = $m[1];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Remove any attributes that can be expected to differ.
+                       $original_tag    = preg_replace( $changeable_attr_regex, $attribute_replace, $original_tag );
+                       $translation_tag = preg_replace( $changeable_attr_regex, $attribute_replace, $translation_tag );
+
+                       if ( $original_tag !== $translation_tag ) {
+                               $warnings[] = "Expected $original_tag, got $translation_tag.";
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $altered_translation = preg_replace_callback(
-                               '!(href=[\'"]?)(https?)://(' . $regex . ')!i',
-                               function( $m ) use( $protocol, $domain ) {
-                                       return $m[1] . $protocol . '://' . $domain;
-                               },
-                               $altered_translation
-                       );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Now check that the URLs mentioned within href & src tags match.
+               $original_links    = '';
+               $translation_links = '';
+
+               if ( preg_match_all( $link_attr_regex, implode( ' ', $original_parts ), $m ) ) {
+                       $original_links = implode( "\n", $m['value'] );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( preg_match_all( $link_attr_regex, implode( ' ', $translation_parts ), $m ) ) {
+                       $translation_links = implode( "\n", $m['value'] );
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $altered_translation !== $translation ) {
-                       $altered_warning = GP::$builtin_translation_warnings->warning_tags( $original, $altered_translation, $locale );
-                       if ( true === $altered_warning ) {
-                               return true;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Validate the URLs if present.
+               if ( $original_links || $translation_links ) {
+                       $url_warnings = $this->warning_mismatching_urls( $original_links, $translation_links );
+
+                       if ( true !== $url_warnings ) {
+                               $warnings[] = $url_warnings;
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Pass through to the core GlotPress warning method.
-               return GP::$builtin_translation_warnings->warning_tags( $original, $translation, $locale );
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( empty( $warnings ) ) {
+                       return true;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                return implode( "\n", $warnings );
+   }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Adds a warning for changing placeholders.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span></span></pre>
</div>
</div>

</body>
</html>