<!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>[11837] sites/trunk/wordpress.org/public_html/wp-content/plugins: Translate: Introduce the gp-translation-helpers plugin</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/11837">11837</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/11837","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>akirk</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-05-11 08:47:45 +0000 (Wed, 11 May 2022)</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: Introduce the gp-translation-helpers plugin
See <a href="http://meta.trac.wordpress.org/ticket/6310">#6310</a>, https://github.com/GlotPress/gp-translation-helpers/
props amieiro, spiraltee</pre>
<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/</li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperscssdiscussioncss">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/discussion.css</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperscsstranslationhelperscss">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/translation-helpers.css</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersgptranslationhelpersphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/gp-translation-helpers.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersbasehelperphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/base-helper.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelperotherlocalesphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-other-locales.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelpertranslationdiscussionphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-discussion.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelpertranslationhistoryphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-history.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/</li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/css/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetscsstranslationdiscussioncss">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/css/translation-discussion.css</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/js/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetsjstranslationdiscussionjs">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/js/translation-discussion.js</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/templates/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetstemplatestranslationdiscussioncommentsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/templates/translation-discussion-comments.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgpnotificationsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-notifications.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgproutetranslationhelpersphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-route-translation-helpers.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgptranslationhelpersphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-translation-helpers.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgthtemporarypostphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gth-temporary-post.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclasswporgnotificationsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-wporg-notifications.php</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersjsdiscussionjs">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/discussion.js</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersjsrejectfeedbackjs">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/reject-feedback.js</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersjstranslationhelpersjs">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/translation-helpers.js</a></li>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatescommentsectionphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/comment-section.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesdiscussionphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/discussion.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesoriginalpermalinktemplatephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink-template.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesoriginalpermalinkphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatestranslationhelpersphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/translation-helpers.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomizationstemplatessettingseditphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings-edit.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomizationstemplatessettingsphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<span class="cx" style="display: block; padding: 0 10px">Index: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers 2022-05-11 03:13:27 UTC (rev 11836)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers 2022-05-11 08:47:45 UTC (rev 11837)
</ins><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpers"></a>
<div class="propset"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Property changes: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnignore"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:ignore</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.github
+README.md
+composer.json
+composer.lock
+package-lock.json
+package.json
+phpcs.xml.dist
+.eslintrc.json
+tmp
</ins><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperscssdiscussioncss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/discussion.css</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/gp-translation-helpers/css/discussion.css (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/discussion.css 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,202 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+#original {
+ border: 1px solid black;
+}
+
+#original h1 {
+ margin: .5em 14px;
+ line-height: 34px;
+}
+
+#original p {
+ margin: 14px 26px 14px 14px;
+}
+
+#original ul#translation-list {
+ margin-left: 1rem;
+}
+
+.helper-translation-discussion {
+ position:relative;
+ min-height: 600px;
+}
+.discussion-list {
+ list-style:none;
+ max-width: 560px;
+}
+.comments-wrapper {
+ max-width: 600px;
+}
+article.comment {
+ margin: 15px 30px 15px 30px;
+ position: relative;
+ font-size: 0.9rem;
+}
+article.comment p {
+ margin-bottom: 0.5em;
+}
+article.comment footer {
+ overflow: hidden;
+ font-size: 0.8rem;
+ font-style: italic;
+}
+article.comment time {
+ font-style: italic;
+ opacity: 0.8;
+ display: inline-block;
+ padding-left: 4px;
+}
+.comment-locale {
+ opacity: 0.8;
+ float: right;
+}
+.comment-avatar {
+ margin-left: -45px;
+ margin-bottom: -25px;
+ width: 50px;
+ height: 26px;
+}
+.comment-avatar img {
+ display: block;
+ border-radius: 13px;
+}
+.comments-selector {
+ display: inline-block;
+ padding-left: 10px;
+ font-size: 0.9em;
+}
+.comment-content {
+ text-align: start;
+}
+.comment-form-comment label{
+ display: block;
+}
+h6 {
+ font-size: 1em;
+}
+.discussion-heading{
+ line-height: 1.2em;
+}
+.discussion-locales ul li{
+ display: inline;
+}
+.active-link{
+ font-weight: bold;
+}
+.discussion-wrapper textarea#comment {
+ width: 100%;
+ margin-top: 15px;
+}
+.discussion-wrapper .submit {
+ margin: 0 5px 0 0;
+ background: #2271b1;
+ border-color: #2271b1;
+ color: #fff;
+ text-decoration: none;
+ text-shadow: none;
+ font-size: 13px;
+ line-height: 2.15384615;
+ min-height: 30px;
+ padding: 0 10px;
+ cursor: pointer;
+ border-width: 1px;
+ border-style: solid;
+ -webkit-appearance: none;
+ border-radius: 3px;
+ white-space: nowrap;
+ box-sizing: border-box;
+}
+.discussion-wrapper .submit:hover {
+ background: #135e96;
+ border-color: #135e96;
+ color: #fff;
+}
+.modal-item {
+ display: inline-block;
+ width: 30%;
+ margin-top: 7px;
+}
+.modal-item label {
+ display: inherit;
+ margin-left: 8px;
+ cursor: pointer;
+}
+.modal-item input[type="checkbox"] {
+ transform: scale(1.5);
+ margin-right: 5px;
+}
+.modal-comment {
+ margin-top: 27px;
+}
+.modal-comment textarea {
+ width: 97%;
+ height: 170px;
+}
+.modal-btn {
+ float: right;
+ font-size: 15px;
+ height: 35px;
+ line-height: 1;
+ background: #0085ba;
+ border-color: #0073aa #006799 #006799;
+ box-shadow: 0 1px 0 #006799;
+ color: #fff;
+ text-decoration: none;
+ text-shadow: 0 -1px 1px #006799, 1px 0 1px #006799, 0 1px 1px #006799, -1px 0 1px #006799;
+ margin-top: 10px;
+ padding: 0 13px;
+ border-radius: 3px;
+}
+.modal-reason-title{
+ margin-top: 12px;
+ margin-bottom: 10px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+#TB_title {
+ background: #fcfcfc;
+ border-bottom: 1px solid #ddd;
+ height: 29px;
+ padding: 13px 0;
+}
+div#TB_ajaxWindowTitle {
+ font-size: 1.3em;
+}
+.meta summary.feedback-summary{
+ font-size: 1.2em;
+ font-weight: bold;
+ padding: 7px 0;
+}
+.gp-content ul.feedback-reason-list {
+ list-style-type: none;
+ display: inline-block;
+ padding-left: .5em;
+}
+.feedback-reason-list li {
+ width: 50%;
+ float: left;
+ padding-top: 4px;
+}
+.feedback-reason-list li label {
+ font-size: 1.1em;
+ cursor: pointer;
+}
+.feedback-reason-list li input[type="checkbox"] {
+ margin-right: 5px;
+}
+.feedback-comment label, .gp-content h3.feedback-reason-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin: 0;
+}
+.feedback-comment textarea {
+ height: 110px;
+ font-size: 1.1em;
+}
+.status-actions details {
+ margin-bottom: 5px;
+}
+
+.async-content #legend {
+ margin-top: 2em;
+ float: none;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/discussion.css
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperscsstranslationhelperscss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/translation-helpers.css</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/gp-translation-helpers/css/translation-helpers.css (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/translation-helpers.css 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,88 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.editor td {
+ vertical-align: top;
+}
+#translations .translation-helpers {
+ min-width: 400px;
+ padding: 0;
+ border: 0;
+ border-top:1px solid #eee;
+}
+.translation-helpers h4 {
+ margin-bottom: 0.5em;
+ font-size: 1.1em;
+ padding: .25em .5em;
+}
+.helpers-tabs {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+ border-bottom: 2px solid #ccc;
+ white-space: nowrap;
+}
+
+ul.helpers-tabs {
+ padding-left: 14px !important;
+}
+
+.helpers-tabs li {
+ background: #eee;
+ color: #222;
+ display: inline-block;
+ padding: 10px 15px;
+ cursor: pointer;
+ margin: 0 1px 0 0;
+ border-top: 1px solid #eee;
+}
+.helpers-tabs li.current {
+ background-color: transparent;
+ margin: 0 0 -2px -1px;
+ border: 1px solid #ccc;
+ border-bottom: 2px solid #f8ffec;
+ font-weight: bold;
+}
+.loading .helpers-tabs {
+ padding-right: 28px;
+ background: transparent url(https://s0.wp.com/wp-content/mu-plugins/notes/images/loading.gif) no-repeat right 4px center;
+ background-size: 20px;
+}
+.helper {
+ overflow-y: scroll;
+ max-height: 800px;
+ display: none;
+ padding: 1em;
+ min-height: 200px;
+}
+#original .helper {
+ overflow-y: initial;
+ max-height: fit-content;
+}
+.helper.current {
+ display: block;
+}
+.helpers-tabs li .count {
+ display: inline-block;
+ padding-left: 4px;
+ opacity: 0.6;
+}
+
+#translation-history-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+#translation-history-table thead tr th {
+ font-size: 1rem;
+ font-weight: 900 !important;
+ padding: 10px;
+ background: #f9f9f9;
+ border: 1px solid rgb(0 0 0 / 5%);
+}
+
+#translation-history-table tbody tr td {
+ padding: 10px;
+ border: 1px solid rgb(0 0 0 / 5%);
+}
+
+footer em {
+ margin-left: 14px;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/css/translation-helpers.css
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersgptranslationhelpersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/gp-translation-helpers.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/gp-translation-helpers/gp-translation-helpers.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/gp-translation-helpers.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,39 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * @package gp-translation-helpers
+ */
+
+/**
+ * Plugin name: GP Translation Helpers
+ * Plugin URI: https://github.com/GlotPress/gp-translation-helpers
+ * Description: GlotPress plugin to discuss the strings that are being translated in GlotPress.
+ * Version: 0.0.2
+ * Requires PHP: 7.4
+ * Author: the GlotPress team
+ * Author URI: https://glotpress.blog
+ * License: GPLv2 or later
+ * Text Domain: gp-translation-helpers
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+require_once __DIR__ . '/includes/class-gp-route-translation-helpers.php';
+require_once __DIR__ . '/includes/class-gp-translation-helpers.php';
+require_once __DIR__ . '/includes/class-gth-temporary-post.php';
+require_once __DIR__ . '/includes/class-gp-notifications.php';
+require_once __DIR__ . '/includes/class-wporg-notifications.php';
+
+add_action( 'gp_init', array( 'GP_Translation_Helpers', 'init' ) );
+add_action( 'gp_init', array( 'WPorg_GlotPress_Notifications', 'init' ) ); // todo: include this class in a different plugin.
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/gp-translation-helpers.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersbasehelperphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/base-helper.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/gp-translation-helpers/helpers/base-helper.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/base-helper.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,268 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Base class, extended by all other helpers
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class GP_Translation_Helper {
+
+ /**
+ * The folder where the assets are stored.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ public $assets_dir;
+
+ /**
+ * The data coming from the route.
+ *
+ * @since 0.0.1
+ * @var array
+ */
+ public $data;
+
+ /**
+ * GP_Translation_Helper constructor.
+ *
+ * Will throw a LogicException if the title property is not set.
+ *
+ * @since 0.0.1
+ *
+ * @throws LogicException If the class has not a must-have property.
+ */
+ final public function __construct() {
+ $this->assets_dir = dirname( dirname( __FILE__ ) ) . '/helpers-assets/';
+
+ $required_properties = array(
+ 'title',
+ );
+
+ foreach ( $required_properties as $prop ) {
+ if ( ! isset( $this->{$prop} ) ) {
+ throw new LogicException( get_class( $this ) . ' must have a property ' . $prop );
+ }
+ }
+
+ if ( method_exists( $this, 'after_constructor' ) ) {
+ $this->after_constructor();
+ }
+ }
+
+ /**
+ * Sets the data coming from the route.
+ *
+ * Sets values like project_id, locale_slug, set_slug, original_id, translation_id, etc.
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Data coming from the route.
+ */
+ public function set_data( array $args ) {
+ $this->data = $args;
+ }
+
+ /**
+ * Gets the priority of a helper. Defaults to 1 if not set.
+ *
+ * @since 0.0.1
+ *
+ * @return int
+ */
+ public function get_priority(): int {
+ return $this->priority ?? 1;
+ }
+
+ /**
+ * Indicates whether the helper loads asynchronous content or not.
+ *
+ * Defaults to false, but uses the class property if set.
+ *
+ * @since 0.0.1
+ *
+ * @return bool
+ */
+ public function has_async_content(): bool {
+ return $this->has_async_content ?? false;
+ }
+ /**
+ * Indicates whether the helper should initally load inline
+ *
+ * @since 0.0.2
+ *
+ * @return bool
+ */
+ public function load_inline(): bool {
+ return $this->load_inline ?? false;
+ }
+
+ /**
+ * Gets the class name for the helper div.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_div_classname(): string {
+ if ( isset( $this->classname ) ) {
+ return $this->classname;
+ }
+
+ return sanitize_html_class( str_replace( '_', '-', strtolower( get_class( $this ) ) ), 'default-translation-helper' );
+ }
+
+ /**
+ * Gets the HTML id for the div.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_div_id(): string {
+ $div_id = $this->get_div_classname() . '-' . $this->data['original_id'];
+
+ if ( isset( $this->data['translation_id'] ) ) {
+ $div_id .= '-' . $this->data['translation_id'];
+ } elseif ( isset( $this->data['translation'] ) ) {
+ $div_id .= '-' . $this->data['translation']->id;
+ }
+
+ return $div_id;
+ }
+
+ /**
+ * Returns the title of the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_title(): string {
+ return $this->title;
+ }
+
+ /**
+ * Indicates whether the helper should be active or not.
+ *
+ * Overwrite in the inheriting class to make this vary depending on class args.
+ *
+ * @since 0.0.1
+ *
+ * @return bool
+ */
+ public function activate() {
+ return true;
+ }
+
+ /**
+ * Sets the count of items returned by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @param mixed $list Elements to count.
+ */
+ public function set_count( $list ) {
+ if ( is_array( $list ) ) {
+ $this->count = count( $list );
+ } else {
+ $this->count = $list ? 1 : 0;
+ }
+ }
+
+ /**
+ * Gets the number of items returned by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return int
+ */
+ public function get_count(): int {
+ return $this->count ?? 0;
+ }
+
+ /**
+ * Gets the content/string to return when a helper has no results.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function empty_content() {
+ return __( 'No results found.' );
+ }
+
+ /**
+ * Default callback to render items returned by the helper.
+ *
+ * Gets an unordered list of the items that will be rendered by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @param array $items Elements to be grouped in an unordered list.
+ *
+ * @return string
+ */
+ public function async_output_callback( array $items ) {
+ $output = '<ul>';
+ foreach ( $items as $item ) {
+ $output .= '<li>' . $item . '</li>';
+ }
+ $output .= '</ul>';
+ return $output;
+ }
+
+ /**
+ * Gets content that is returned asynchronously.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_async_output(): string {
+ $items = $this->get_async_content();
+ $this->set_count( $items );
+
+ if ( ! $items ) {
+ return $this->empty_content();
+ }
+
+ return $this->async_output_callback( $items );
+ }
+
+ /**
+ * Gets the (non-async) output for the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_output(): string {
+ if ( ! $this->load_inline() ) {
+ return '<div class="loading">Loading…</div>';
+ }
+ return $this->async_output_callback( $this->get_async_content() );
+ }
+
+ /**
+ * Gets additional CSS required by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return bool|string
+ */
+ public function get_css() {
+ return false;
+ }
+
+ /**
+ * Gets additional JavaScript required by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return bool|string
+ */
+ public function get_js() {
+ return false;
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/base-helper.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelperotherlocalesphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-other-locales.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/gp-translation-helpers/helpers/helper-other-locales.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-other-locales.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,186 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Helper showing translations from other locales
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class Helper_Other_Locales extends GP_Translation_Helper {
+
+ /**
+ * Helper priority.
+ *
+ * @since 0.0.1
+ * @var int
+ */
+ public $priority = 3;
+
+ /**
+ * Helper title.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ public $title = 'Other locales';
+
+ /**
+ * Indicates whether the helper loads asynchronous content or not.
+ *
+ * @since 0.0.1
+ * @var bool
+ */
+ public $has_async_content = true;
+
+ /**
+ * Activates the helper.
+ *
+ * @since 0.0.2
+ *
+ * @return bool
+ */
+ public function activate(): bool {
+ if ( ! $this->data['project'] ) {
+ return false;
+ }
+
+ if ( ! isset( $this->data['translation_set_slug'] ) || ! isset( $this->data['locale_slug'] ) ) {
+ $this->title = 'Translations';
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets content that is returned asynchronously.
+ *
+ * @since 0.0.1
+ *
+ * @return array|void
+ */
+ public function get_async_content() {
+ if ( ! $this->data['project'] ) {
+ return;
+ }
+ $translation_set = null;
+ if ( isset( $this->data['translation_set_slug'] ) && isset( $this->data['locale_slug'] ) ) {
+ $translation_set = GP::$translation_set->by_project_id_slug_and_locale( $this->data['project_id'], $this->data['translation_set_slug'], $this->data['locale_slug'] );
+ }
+
+ $translations = GP::$translation->find_many_no_map(
+ array(
+ 'status' => 'current',
+ 'original_id' => $this->data['original_id'],
+ )
+ );
+ $translations_by_locale = array();
+ foreach ( $translations as $translation ) {
+ $_set = GP::$translation_set->get( $translation->translation_set_id );
+ if ( ! $_set || ( $translation_set && intval( $translation->translation_set_id ) === intval( $translation_set->id ) ) ) {
+ continue;
+ }
+ $translations_by_locale[ $_set->locale ] = $translation;
+ }
+
+ ksort( $translations_by_locale );
+
+ return $translations_by_locale;
+ }
+
+ /**
+ * Gets the items that will be rendered by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @param array $translations Translation history.
+ *
+ * @return string
+ */
+ public function async_output_callback( array $translations ): string {
+ $output = '<ul class="other-locales">';
+ $project = null;
+ foreach ( $translations as $locale => $translation ) {
+ $translation_set = GP::$translation_set->get( $translation->translation_set_id );
+ if ( is_null( $project ) ) {
+ $project = GP::$project->get( $translation_set->project_id );
+ }
+
+ $translation_permalink = GP_Route_Translation_Helpers::get_translation_permalink(
+ $project,
+ $translation_set->locale,
+ $translation_set->slug,
+ $translation->original_id,
+ $translation->id
+ );
+
+ if ( ( null === $translation->translation_1 ) && ( null === $translation->translation_2 ) &&
+ ( null === $translation->translation_3 ) && ( null === $translation->translation_4 ) &&
+ ( null === $translation->translation_5 ) ) {
+ $output .= sprintf( '<li><span class="locale unique">%s</span>%s</li>', $locale, gp_link_get( $translation_permalink, esc_translation( $translation->translation_0 ) ) );
+ } else {
+ $output .= sprintf( '<li><span class="locale">%s</span>', $locale );
+ $output .= '<ul>';
+ for ( $i = 0; $i <= 5; $i ++ ) {
+ if ( null !== $translation->{'translation_' . $i} ) {
+ $output .= sprintf( '<li>%s</li>', gp_link_get( $translation_permalink, esc_translation( $translation->{'translation_' . $i} ) ) );
+ }
+ }
+ $output .= '</ul>';
+ $output .= '</li>';
+ }
+ }
+ $output .= '</ul>';
+ return $output;
+ }
+
+ /**
+ * Gets the text to display when no other locales have translated this string yet.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function empty_content(): string {
+ return esc_html__( 'No other locales have translated this string yet.' );
+ }
+
+ /**
+ * Gets the CSS for this helper.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function get_css(): string {
+ return <<<CSS
+ .other-locales {
+ list-style: none;
+ }
+ ul.other-locales {
+ padding-left: 0;
+ }
+ .other-locales li {
+ clear:both;
+ }
+ ul.other-locales li {
+ display: flex;
+ }
+ ul.other-locales li ul li {
+ display: list-item;
+ list-style: disc;
+ }
+ span.locale.unique {
+ margin-right: 26px;
+ }
+ .other-locales .locale {
+ display: inline-block;
+ padding: 1px 6px 0 0;
+ margin: 1px 6px 1px 0;
+ background: #00DA12;
+ width: 5em;
+ text-align: right;
+ float: left;
+ color: #fff;
+ }
+CSS;
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-other-locales.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelpertranslationdiscussionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-discussion.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/gp-translation-helpers/helpers/helper-translation-discussion.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-discussion.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,957 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Helper that manages and shows the discussions
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class Helper_Translation_Discussion extends GP_Translation_Helper {
+
+ /**
+ * Helper priority.
+ *
+ * @since 0.0.1
+ * @var int
+ */
+ public $priority = 0;
+
+ /**
+ * Helper title.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ public $title = 'Discussion';
+
+ /**
+ * Indicates whether the helper loads asynchronous content or not.
+ *
+ * @since 0.0.1
+ * @var bool
+ */
+ public $has_async_content = true;
+
+ /**
+ * Indicates whether the helper should be loaded inline.
+ *
+ * @since 0.0.2
+ * @var bool
+ */
+ public $load_inline = true;
+
+ /**
+ * The post type used to store the comments.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ const POST_TYPE = 'gth_original';
+
+ /**
+ * The comment post status. Creates it as published.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ const POST_STATUS = 'publish';
+
+ /**
+ * The taxonomy key.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ const LINK_TAXONOMY = 'gp_original_id';
+
+ /**
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ const URL_SLUG = 'discuss';
+
+ /**
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ const ORIGINAL_ID_PREFIX = 'original-';
+
+ /**
+ * Registers the post type, its taxonomy, the comments' metadata and adds a filter to moderate the comments.
+ *
+ * Method executed just after the constructor.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function after_constructor() {
+ $this->register_post_type_and_taxonomy();
+ add_filter( 'pre_comment_approved', array( $this, 'comment_moderation' ), 10, 2 );
+ add_filter( 'map_meta_cap', array( $this, 'map_comment_meta_caps' ), 10, 4 );
+ add_filter( 'user_has_cap', array( $this, 'give_user_read_cap' ), 10, 3 );
+ add_filter( 'post_type_link', array( $this, 'rewrite_original_post_type_permalink' ), 10, 2 );
+ add_filter( 'comment_reply_link', array( $this, 'comment_reply_link' ), 10, 4 );
+ add_filter( 'wp_ajax_create_shadow_post', array( $this, 'ajax_create_shadow_post' ) );
+ }
+
+ /**
+ * Registers the post type with its taxonomy and the comments' metadata.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function register_post_type_and_taxonomy() {
+ register_taxonomy(
+ self::LINK_TAXONOMY,
+ array(),
+ array(
+ 'public' => false,
+ 'show_ui' => false,
+ 'rewrite' => false,
+ 'capabilities' => array(
+ 'assign_terms' => 'read',
+ ),
+ )
+ );
+
+ $post_type_args = array(
+ 'supports' => array( 'comments' ),
+ 'show_ui' => false,
+ 'show_in_menu' => false,
+ 'show_in_admin_bar' => false,
+ 'show_in_nav_menus' => false,
+ 'can_export' => false,
+ 'has_archive' => false,
+ 'show_in_rest' => true,
+ 'taxonomies' => array( self::LINK_TAXONOMY ),
+ 'rewrite' => false,
+ );
+
+ register_post_type( self::POST_TYPE, $post_type_args );
+
+ register_meta(
+ 'comment',
+ 'translation_id',
+ array(
+ 'description' => 'Translation that was displayed when the comment was posted',
+ 'single' => true,
+ 'show_in_rest' => true,
+ 'sanitize_callback' => array( $this, 'sanitize_translation_id' ),
+ )
+ );
+
+ register_meta(
+ 'comment',
+ 'locale',
+ array(
+ 'description' => 'Locale slug associated with a string comment',
+ 'single' => true,
+ 'show_in_rest' => true,
+ 'sanitize_callback' => array( $this, 'sanitize_comment_locale' ),
+ 'rewrite' => false,
+ )
+ );
+
+ register_meta(
+ 'comment',
+ 'comment_topic',
+ array(
+ 'description' => 'Reason for the comment',
+ 'single' => true,
+ 'show_in_rest' => true,
+ 'sanitize_callback' => array( $this, 'sanitize_comment_topic' ),
+ 'rewrite' => false,
+ )
+ );
+ }
+
+ /**
+ * Give subscribers permission to add our comment metas.
+ *
+ * @param array $caps The capabilities they need to have.
+ * @param string $cap The capability we're testing for.
+ * @param int $user_id The user id.
+ * @param array $args Other arguments.
+ *
+ * @return array The capabilities they need to have.
+ */
+ public function map_comment_meta_caps( $caps, $cap, $user_id, $args ) {
+ if ( 'edit_comment_meta' === $cap && isset( $args[1] ) && in_array( $args[1], array( 'translation_id', 'locale', 'comment_topic' ), true ) ) {
+ return array( 'read' );
+ }
+ return $caps;
+ }
+
+ /**
+ * Ensure that a user has the read capability on translate.wordpress.org.
+ *
+ * @param array $allcaps All capabilities of the uer.
+ * @param array $caps The capabilities requested.
+ * @param array $args Other arguments.
+ *
+ * @return array Potentially modified capabilities of the user.
+ */
+ public function give_user_read_cap( $allcaps, $caps, $args ) {
+ if ( ! defined( 'WPORG_TRANSLATE_BLOGID' ) || get_current_blog_id() !== WPORG_TRANSLATE_BLOGID ) {
+ return $allcaps;
+ }
+
+ if ( in_array( 'read', $caps, true ) && is_user_logged_in() && ! is_admin() ) {
+ $allcaps['read'] = true;
+ }
+
+ return $allcaps;
+ }
+
+ /**
+ * Gets the permalink and stores in the cache.
+ *
+ * @since 0.0.2
+ *
+ * @param string $post_link The post's permalink.
+ * @param WP_Post $post The post in question.
+ *
+ * @return mixed|string
+ */
+ public function rewrite_original_post_type_permalink( string $post_link, WP_Post $post ) {
+ static $cache = array();
+
+ if ( self::POST_TYPE !== $post->post_type ) {
+ return $post_link;
+ }
+
+ if ( isset( $cache[ $post->ID ] ) ) {
+ return $cache[ $post->ID ];
+ }
+
+ // Cache the error case and overwrite it later if we succeed.
+ $cache[ $post->ID ] = $post_link;
+
+ $original_id = self::get_original_from_post_id( $post->ID );
+ if ( ! $original_id ) {
+ return $cache[ $post->ID ];
+ }
+
+ $original = GP::$original->get( $original_id );
+ if ( ! $original ) {
+ return $cache[ $post->ID ];
+ }
+
+ $project = GP::$project->get( $original->project_id );
+ if ( ! $project ) {
+ return $cache[ $post->ID ];
+ }
+
+ // We were able to gather all information, let's put it in the cache.
+ $cache[ $post->ID ] = GP_Route_Translation_Helpers::get_permalink( $project->path, $original_id );
+
+ return $cache[ $post->ID ];
+ }
+
+ /**
+ * Updates the comment's approval status before it is set.
+ *
+ * It only updates the approved status if the user has previous translations.
+ *
+ * @since 0.0.1
+ *
+ * @param int|string|WP_Error $approved The approval status. Accepts 1, 0, 'spam', 'trash',
+ * or WP_Error.
+ * @param array $commentdata Comment data.
+ *
+ * @return bool|int|string|WP_Error|null
+ */
+ public function comment_moderation( $approved, array $commentdata ) {
+ global $wpdb;
+
+ // If the comment is already approved, we're good.
+ if ( $approved ) {
+ return $approved;
+ }
+
+ // We only care on comments on our specific post type.
+ if ( self::POST_TYPE !== get_post_type( $commentdata['comment_post_ID'] ) ) {
+ return $approved;
+ }
+
+ // We can't do much if the comment was posted logged out.
+ if ( empty( $commentdata['comment_author'] ) ) {
+ return $approved;
+ }
+
+ // If our user has already contributed translations, approve comment.
+ $user_current_translations = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->gp_translations WHERE user_id = %s AND status = 'current'", $commentdata['comment_author'] ) );
+ if ( $user_current_translations ) {
+ $approved = true;
+ }
+
+ return $approved;
+ }
+
+ /**
+ * Gets the slug for the post ID.
+ *
+ * @since 0.0.1
+ *
+ * @param $post_id The post ID.
+ *
+ * @return false|string
+ */
+ public static function get_original_from_post_id( $post_id ) {
+ if ( self::is_temporary_post_id( $post_id ) ) {
+ return self::get_original_id_from_temporary_post_id( $post_id );
+ }
+
+ $terms = wp_get_object_terms( $post_id, self::LINK_TAXONOMY, array( 'number' => 1 ) );
+ if ( empty( $terms ) ) {
+ return false;
+ }
+
+ return $terms[0]->slug;
+ }
+
+ /**
+ * Indicates whether the post id is a real one or a temporary one.
+ *
+ * @since 0.0.2
+ *
+ * @param int|string $post_id The post ID.
+ *
+ * @return bool
+ */
+ public static function is_temporary_post_id( $post_id ) {
+ return self::get_original_id_from_temporary_post_id( $post_id ) > 0;
+ }
+
+ /**
+ * Extract the original_id from the temporary post_id.
+ *
+ * @param int|string $post_id The post ID.
+ *
+ * @return int|null The original_id or null if the post_id is not a temporary one.
+ */
+ public static function get_original_id_from_temporary_post_id( $post_id ) {
+ if ( self::POST_TYPE !== substr( $post_id, 0, strlen( self::POST_TYPE ) ) ) {
+ return null;
+ }
+ $original_id = substr( $post_id, strlen( self::POST_TYPE ) );
+ if ( is_numeric( $original_id ) && $original_id > 0 ) {
+ return intval( $original_id );
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the post id of the shadow post and ensure its or create shadow post identifier.
+ *
+ * @param int $original_id The original identifier
+ *
+ * @return <type> The or create shadow post identifier.
+ */
+ public static function get_or_create_shadow_post_id( int $original_id ) {
+ return self::get_shadow_post_id( $original_id, true );
+ }
+
+ /**
+ * Get a Gth_Temporary_Post or a WP_Post, depending on the given post_id.
+ *
+ * @param int|string $post_id The post ID (could be a temporary one).
+ *
+ * @return WP_Post|Gth_Temporary_Post An object for use in wp_list_comments.
+ */
+ public static function maybe_get_temporary_post( $post_id ) {
+ if ( self::is_temporary_post_id( $post_id ) ) {
+ return new Gth_Temporary_Post( $post_id );
+ }
+
+ return get_post( $post_id );
+ }
+
+ /**
+ * Gets the post id for the comments and stores it in the cache.
+ *
+ * @since 0.0.1
+ *
+ * @param int $original_id The original id for the string to translate. E.g. "2440".
+ * @param bool $create Whether to create a post if it doesn't exist.
+ *
+ * @return int|WP_Error
+ */
+ public static function get_shadow_post_id( int $original_id, $create = false ) {
+ $cache_key = self::LINK_TAXONOMY . '_' . $original_id;
+
+ $post_id = wp_cache_get( $cache_key );
+ if ( false !== $post_id ) {
+ // Something was found in the cache.
+
+ if ( self::is_temporary_post_id( $post_id ) && $create ) {
+ // a fake post_id was stored in the cache but we need to create an entry.
+ // Let's pretend a cache fail, so that we get a chance to create an entry unless one already exists.
+ $post_id = false;
+ }
+ }
+
+ if ( 'production' !== wp_get_environment_type() || false === $post_id ) {
+ $post_id = null;
+ $gp_posts = get_posts(
+ array(
+ 'tax_query' => array(
+ array(
+ 'taxonomy' => self::LINK_TAXONOMY,
+ 'terms' => $original_id,
+ 'field' => 'slug',
+ ),
+ ),
+ 'post_type' => self::POST_TYPE,
+ 'posts_per_page' => 1,
+ 'post_status' => self::POST_STATUS,
+ 'suppress_filters' => false,
+ )
+ );
+
+ if ( ! empty( $gp_posts ) ) {
+ $post_id = $gp_posts[0]->ID;
+ } elseif ( $create ) {
+ $post_id = wp_insert_post(
+ array(
+ 'post_type' => self::POST_TYPE,
+ 'tax_input' => array(
+ self::LINK_TAXONOMY => array( strval( $original_id ) ),
+ ),
+ 'post_status' => self::POST_STATUS,
+ 'post_author' => 0,
+ 'comment_status' => 'open',
+ )
+ );
+ } else {
+ $post_id = self::POST_TYPE . strval( $original_id );
+ }
+ }
+
+ wp_cache_add( $cache_key, $post_id );
+ return $post_id;
+ }
+
+ /**
+ * Gets the comments for the post.
+ *
+ * @since 0.0.1
+ *
+ * @return array
+ */
+ public function get_async_content(): array {
+ $post_id = self::get_shadow_post_id( $this->data['original_id'] );
+ if ( self::is_temporary_post_id( $post_id ) ) {
+ return array();
+ }
+
+ return get_comments(
+ array(
+ 'post_id' => $post_id,
+ 'status' => 'approve',
+ 'type' => 'comment',
+ 'include_unapproved' => array( get_current_user_id() ),
+ )
+ );
+ }
+
+ /**
+ * Shows the discussion template with the comment form.
+ *
+ * @since 0.0.1
+ *
+ * @param array $comments The comments to display.
+ *
+ * @return false|string
+ */
+ public function async_output_callback( array $comments ) {
+ $this->set_count( $comments );
+
+ // Remove comment likes for now (or forever :) ).
+ remove_filter( 'comment_text', 'comment_like_button', 12 );
+
+ // Disable subscribe to posts.
+ add_filter( 'option_stb_enabled', '__return_false' );
+
+ // Disable subscribe to comments for now.
+ add_filter( 'option_stc_disabled', '__return_true' );
+
+ // Link comment author to their profile.
+ add_filter(
+ 'get_comment_author_link',
+ function( $return, $author, $comment_id ) {
+ $comment = get_comment( $comment_id );
+ if ( ! empty( $comment->user_id ) ) {
+ $user = get_userdata( $comment->user_id );
+ if ( $user ) {
+ return gp_link_user( $user );
+ }
+ }
+ return $return;
+ },
+ 10,
+ 3
+ );
+
+ add_filter(
+ 'comment_form_logged_in',
+ function( $logged_in_as, $commenter, $user_identity ) {
+ /* translators: Username with which the user is logged in */
+ return sprintf( '<p class="logged-in-as">%s</p>', sprintf( __( 'Logged in as %s.' ), $user_identity ) );
+ },
+ 10,
+ 3
+ );
+
+ add_filter(
+ 'comment_form_fields',
+ function( $comment_fields ) {
+ $comment_fields['comment'] = str_replace( '>Comment<', '>Please leave your comment about this string here:<', $comment_fields['comment'] );
+ return $comment_fields;
+ }
+ );
+
+ remove_action( 'comment_form_top', 'rosetta_comment_form_support_hint' );
+
+ $post = self::maybe_get_temporary_post( self::get_shadow_post_id( $this->data['original_id'] ) );
+
+ $output = gp_tmpl_get_output(
+ 'translation-discussion-comments',
+ array(
+ 'comments' => $comments,
+ 'post' => $post,
+ 'translation_id' => isset( $this->data['translation_id'] ) ? $this->data['translation_id'] : null,
+ 'locale_slug' => $this->data['locale_slug'],
+ 'original_permalink' => $this->data['original_permalink'],
+ 'original_id' => $this->data['original_id'],
+ 'project' => $this->data['project'],
+ 'translation_set_slug' => $this->data['translation_set_slug'],
+
+ ),
+ $this->assets_dir . 'templates'
+ );
+ return $output;
+ }
+
+ /**
+ * Gets the content/string to return when a helper has no results.
+ *
+ * @since 0.0.1
+ *
+ * @return false|string
+ */
+ public function empty_content() {
+ return $this->async_output_callback( array() );
+ }
+
+ /**
+ * Gets additional CSS required by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return bool|string
+ */
+ public function get_css() {
+ return file_get_contents( $this->assets_dir . 'css/translation-discussion.css' );
+ }
+
+ /**
+ * Gets additional JavaScript required by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @return bool|string
+ */
+ public function get_js() {
+ return file_get_contents( $this->assets_dir . 'js/translation-discussion.js' );
+ }
+
+ /**
+ * Sets the comment_topic meta_key as "unknown" if is not in the accepted values.
+ *
+ * Used as sanitize callback in the register_meta for the "comment" object type,
+ * 'comment_topic' meta_key
+ *
+ * @since 0.0.2
+ *
+ * @param string $comment_topic The meta_value for the meta_key "comment_topic".
+ *
+ * @return string
+ */
+ public function sanitize_comment_topic( string $comment_topic ): string {
+ if ( ! in_array( $comment_topic, array( 'typo', 'context', 'question' ), true ) ) {
+ $comment_topic = 'unknown';
+ }
+ return $comment_topic;
+
+ }
+
+ /**
+ * Sets the comment_topic meta_key as empty ("") if is not in the accepted values.
+ *
+ * Used as sanitize callback in the register_meta for the "comment" object type,
+ * "locale" meta_key
+ *
+ * @since 0.0.2
+ *
+ * @param string $comment_locale The meta_value for the meta_key "locale".
+ *
+ * @return string
+ */
+ public function sanitize_comment_locale( string $comment_locale ): string {
+ $gp_locales = new GP_Locales();
+ $all_gp_locales = array_keys( $gp_locales->locales );
+
+ if ( ! in_array( $comment_locale, $all_gp_locales, true ) ) {
+ $comment_locale = '';
+ }
+ return $comment_locale;
+ }
+
+ /**
+ * Throws an exception with an error message if the translation id is incorrect.
+ *
+ * Used as sanitize callback in the register_meta for the "comment" object type,
+ * "locale" meta_key
+ *
+ * The string type (input and output) is because it is the type if $translation_id is empty.
+ *
+ * @since 0.0.2
+ *
+ * @param int|string $translation_id The id for the translation showed when the comment was made.
+ *
+ * @return int|string
+ *
+ * @throws Exception Throws an exception with message if translation_id is invalid.
+ */
+ public function sanitize_translation_id( $translation_id ) {
+ if ( $translation_id > 0 && ! GP::$translation->get( $translation_id ) ) {
+ throw new Exception( 'Invalid translation ID' );
+ }
+ return $translation_id;
+ }
+
+ /**
+ * The comment reply link override.
+ *
+ * @param string $link The link.
+ * @param array $args The arguments.
+ * @param string $comment The comment.
+ * @param WP_Post $post The post.
+ *
+ * @return string Return the reply link HTML.
+ */
+ public function comment_reply_link( $link, $args, $comment, $post ) {
+ $data_attributes = array(
+ 'commentid' => $comment->comment_ID,
+ 'postid' => $post->ID,
+ 'belowelement' => $args['add_below'] . '-' . $comment->comment_ID,
+ 'respondelement' => $args['respond_id'],
+ 'replyto' => sprintf( $args['reply_to_text'], $comment->comment_author ),
+ );
+
+ $data_attribute_string = '';
+
+ foreach ( $data_attributes as $name => $value ) {
+ $data_attribute_string .= ' data-' . $name . '="' . esc_attr( $value ) . '"';
+ }
+
+ $data_attribute_string = trim( $data_attribute_string );
+
+ $link = sprintf(
+ "<a rel='nofollow' class='comment-reply-link' href='%s' %s aria-label='%s'>%s</a>",
+ esc_url(
+ add_query_arg(
+ array(
+ 'replytocom' => $comment->comment_ID,
+ 'unapproved' => false,
+ 'moderation-hash' => false,
+ ),
+ $args['original_permalink']
+ )
+ ) . '#' . $args['respond_id'],
+ $data_attribute_string,
+ esc_attr( sprintf( $args['reply_to_text'], $comment->comment_author ) ),
+ $args['reply_text']
+ );
+ return $args['before'] . $link . $args['after'];
+ }
+
+ /**
+ * Ajax callback for creating the shadow post.
+ *
+ * Returns the $post_id back to JS.
+ *
+ * @since 0.0.2
+ */
+ public function ajax_create_shadow_post() {
+ check_ajax_referer( 'wp_rest', 'nonce' );
+
+ $original_id = self::get_original_id_from_temporary_post_id( $_POST['data']['post'] );
+ $post_id = self::get_or_create_shadow_post_id( $original_id );
+ wp_send_json_success( $post_id );
+ }
+
+ /**
+ * Throws an exception with an error message if the original id is incorrect.
+ *
+ * Used as callback to validate the original_id passed on rejecting a string with feedback
+ *
+ * @since 0.0.2
+ *
+ * @param int|string $original_id The id of the original for the rejected translation.
+ *
+ * @return int|string
+ *
+ * @throws Exception Throws an exception with message if original_id is invalid.
+ */
+ public function sanitize_original_id( $original_id ) {
+ if ( $original_id > 0 && ! GP::$original->get( $original_id ) ) {
+ throw new Exception( 'Invalid Original ID' );
+ }
+
+ return $original_id;
+ }
+
+ /**
+ * Return an array of allowed rejection reasons
+ *
+ * @since 0.0.2
+ *
+ * @return array
+ */
+ public static function get_reject_reasons() {
+ return array(
+ 'style' => __( 'Style Guide' ),
+ 'grammar' => __( 'Grammar' ),
+ 'branding' => __( 'Branding' ),
+ 'glossary' => __( 'Glossary' ),
+ 'punctuation' => __( 'Punctuation' ),
+ 'typo' => __( 'Typo' ),
+ );
+ }
+
+}
+
+/**
+ * Gets the slug for the post ID.
+ *
+ * @since 0.0.1
+ *
+ * @param int $post_id The id of the post.
+ *
+ * @return false|string
+ */
+function gth_discussion_get_original_id_from_post( int $post_id ) {
+ return Helper_Translation_Discussion::get_original_from_post_id( $post_id );
+}
+
+/**
+ * Print a (linked) translation.
+ *
+ * @param int $comment_translation_id The comment translation identifier.
+ * @param array $args The arguments.
+ * @param string $prefix The prefix text.
+ */
+function gth_print_translation( $comment_translation_id, $args, $prefix = '' ) {
+ static $cache = array();
+ if ( ! isset( $cache[ $comment_translation_id ] ) ) {
+ $cache[ $comment_translation_id ] = GP::$translation->get( $comment_translation_id );
+ }
+ $translation = $cache[ $comment_translation_id ];
+ $translation_permalink = GP_Route_Translation_Helpers::get_translation_permalink(
+ $args['project'],
+ $args['locale_slug'],
+ $args['translation_set_slug'],
+ $args['original_id'],
+ $comment_translation_id
+ );
+ ?>
+ <em>
+ <?php
+ echo esc_html( $prefix );
+ if ( $translation_permalink ) {
+ echo wp_kses( gp_link( $translation_permalink, $translation->translation_0 ), array( 'a' => array( 'href' => true ) ) );
+ } else {
+ echo esc_html( $translation->translation_0 );
+ }
+ ?>
+ </em>
+ <?php
+}
+/**
+ * Callback for the wp_list_comments() function in the helper-translation-discussion.php template.
+ *
+ * @since 0.0.1
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $args Formatting options.
+ * @param int $depth The depth of the new comment.
+ *
+ * @return void
+ */
+function gth_discussion_callback( WP_Comment $comment, array $args, int $depth ) {
+ $GLOBALS['comment'] = $comment;// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+
+ $is_linking_comment = preg_match( '!^' . home_url( gp_url() ) . '[a-z0-9_/#-]+$!i', $comment->comment_content );
+
+ $comment_locale = get_comment_meta( $comment->comment_ID, 'locale', true );
+ $current_locale = $args['locale_slug'];
+
+ $current_translation_id = $args['translation_id'];
+ $comment_translation_id = get_comment_meta( $comment->comment_ID, 'translation_id', true );
+
+ $reject_reason = get_comment_meta( $comment->comment_ID, 'reject_reason', true );
+
+ $classes = array( 'comment-locale-' . $comment_locale );
+ if ( ! empty( $reject_reason ) ) {
+ $classes[] = 'rejection-feedback';
+ $classes[] = 'rejection-feedback-' . $comment_locale;
+ }
+ ?>
+ <li class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
+ <article id="comment-<?php comment_ID(); ?>" class="comment">
+ <div class="comment-avatar">
+ <?php echo get_avatar( $comment, 25 ); ?>
+ </div><!-- .comment-avatar -->
+ <?php printf( '<cite class="fn">%s</cite>', get_comment_author_link( $comment->comment_ID ) ); ?>
+ <a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
+ <?php
+ // Older than a week, show date; otherwise show __ time ago.
+ if ( time() - get_comment_time( 'U', true ) > 604800 ) {
+ /* translators: 1: Date , 2: Time */
+ $time = sprintf( _x( '%1$s at %2$s', '1: date, 2: time' ), get_comment_date(), get_comment_time() );
+ } else {
+ /* translators: Human readable time difference */
+ $time = sprintf( __( '%1$s ago' ), human_time_diff( get_comment_time( 'U' ), time() ) );
+ }
+ echo '<time datetime=" ' . get_comment_time( 'c' ) . '">' . esc_html( $time ) . '</time>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ ?>
+ </a>
+ <?php if ( $comment_locale ) : ?>
+ <div class="comment-locale">Locale:
+ <?php if ( ! $current_locale ) : ?>
+ <a href="<?php echo esc_attr( $comment_locale . '/default' ); ?>"><?php echo esc_html( $comment_locale ); ?></a>
+ <?php elseif ( $current_locale && $current_locale !== $comment_locale ) : ?>
+ <a href="<?php echo esc_attr( '../../' . $comment_locale . '/default' ); ?>"><?php echo esc_html( $comment_locale ); ?></a>
+ <?php else : ?>
+ <?php echo esc_html( $comment_locale ); ?>
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ <div class="comment-content" dir="auto">
+ <?php
+ if ( $is_linking_comment ) :
+ $linked_comment = $comment->comment_content;
+ $parts = wp_parse_url( $linked_comment );
+ $parts['path'] = rtrim( $parts['path'], '/' );
+ $path_parts = explode( '/', $parts['path'] );
+
+ $linking_comment_set_slug = array_pop( $path_parts );
+ $linking_comment_locale = array_pop( $path_parts );
+ if ( $current_locale && $current_locale !== $linking_comment_locale ) {
+ $linked_comment = str_replace( $parts['path'], $parts['path'] . '/' . $current_locale . '/default', $linked_comment );
+ }
+
+ if ( $reject_reason ) :
+ ?>
+ The translation <?php gth_print_translation( $comment_translation_id, $args ); ?> was rejected with <a href="<?php echo esc_url( $linked_comment ); ?>"><?php esc_html_e( 'a reason that is being discussed here' ); ?></a>.
+ <?php else : ?>
+ <a href="<?php echo esc_url( $linked_comment ); ?>"><?php esc_html_e( 'Please continue the discussion here' ); ?></a>
+ <?php endif; ?>
+ <?php else : ?>
+ <?php comment_text(); ?>
+ <?php if ( $reject_reason ) : ?>
+ <p>
+ <?php echo esc_html( _n( 'Rejection Reason: ', 'Rejection Reasons: ', count( $reject_reason ) ) ); ?>
+ <span><?php echo wp_kses( implode( '</span> | <span>', $reject_reason ), array( 'span' => array() ) ); ?></span>
+ </p>
+ <?php endif; ?>
+ <?php endif; ?>
+ </div>
+ <footer>
+ <div class="comment-author vcard">
+ <?php
+ if ( $comment->comment_parent ) {
+ printf(
+ '<a href="%1$s">%2$s</a>',
+ esc_url( get_comment_link( $comment->comment_parent ) ),
+ /* translators: The author of the current comment */
+ sprintf( esc_attr( __( 'in reply to %s' ) ), esc_html( get_comment_author( $comment->comment_parent ) ) )
+ );
+ }
+ if ( $is_linking_comment ) {
+ ?>
+ <span class="alignright">
+ <a href="<?php echo esc_url( $comment->comment_content ); ?>"><?php esc_html_e( 'Reply' ); ?></a>
+ </span>
+ <?php
+ } else {
+ comment_reply_link(
+ array_merge(
+ $args,
+ array(
+ 'depth' => $depth,
+ 'max_depth' => $args['max_depth'],
+ 'before' => '<span class="alignright">',
+ 'after' => '</span>',
+ )
+ )
+ );
+ }
+ ?>
+ </div><!-- .comment-author .vcard -->
+ <?php
+ if ( '0' === $comment->comment_approved ) {
+ ?>
+ <p><em><?php esc_html_e( 'Your comment is awaiting moderation.' ); ?></em></p>
+ <?php
+ }
+
+ if ( ! $is_linking_comment ) :
+ if ( $comment_translation_id && $comment_translation_id !== $current_translation_id ) {
+ gth_print_translation( $comment_translation_id, $args, empty( $reject_reason ) ? 'Translation: ' : 'Translation (Rejected): ' );
+ }
+
+ ?>
+ <div id="comment-reply-<?php echo esc_attr( $comment->comment_ID ); ?>" style="display: none;">
+ <?php
+ if ( is_user_logged_in() ) {
+ comment_form(
+ array(
+ 'title_reply' => esc_html__( 'Discuss this string' ),
+ /* translators: username */
+ 'title_reply_to' => esc_html__( 'Reply to %s' ),
+ 'title_reply_before' => '<h5 id="reply-title" class="discuss-title">',
+ 'title_reply_after' => '</h5>',
+ 'id_form' => 'commentform-' . $comment->comment_post_ID,
+ 'cancel_reply_link' => '<span></span>',
+ 'comment_notes_after' => implode(
+ "\n",
+ array(
+ '<input type="hidden" name="comment_parent" value="' . esc_attr( $comment->comment_ID ) . '" />',
+ '<input type="hidden" name="comment_locale" value="' . esc_attr( $args['locale_slug'] ) . '" />',
+ '<input type="hidden" name="translation_id" value="' . esc_attr( $args['translation_id'] ) . '" />',
+ '<input type="hidden" name="redirect_to" value="' . esc_url( $args['original_permalink'] ) . '" />',
+ )
+ ),
+ ),
+ $comment->comment_post_ID
+ );
+ } else {
+ /* translators: Log in URL. */
+ echo sprintf( __( 'You have to be <a href="%s">logged in</a> to comment.' ), esc_html( wp_login_url() ) );
+ }
+ ?>
+ </div>
+ <?php endif; ?>
+ </footer>
+ </article><!-- #comment-## -->
+</li>
+ <?php
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-discussion.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpershelpertranslationhistoryphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-history.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/gp-translation-helpers/helpers/helper-translation-history.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-history.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,203 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Helper that shows the history for a string in the current locale
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class Helper_History extends GP_Translation_Helper {
+
+ /**
+ * Helper priority.
+ *
+ * @since 0.0.1
+ * @var int
+ */
+ public $priority = 2;
+
+ /**
+ * Helper title.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ public $title = 'History';
+
+ /**
+ * Indicates whether the helper loads asynchronous content or not.
+ *
+ * @since 0.0.1
+ * @var bool
+ */
+ public $has_async_content = true;
+
+ /**
+ * Indicates whether the helper should be active or not.
+ *
+ * @since 0.0.2
+ *
+ * @return bool
+ */
+ public function activate(): bool {
+ if ( ! $this->data['translation_set_slug'] || ! isset( $this->data['translation_set_slug'] ) || ! isset( $this->data['locale_slug'] ) ) {
+ // Deactivate when translation set is available.
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets asynchronously the translation history of the string.
+ *
+ * @since 0.0.1
+ *
+ * @return mixed|void
+ */
+ public function get_async_content() {
+ $translation_set = GP::$translation_set->by_project_id_slug_and_locale( $this->data['project_id'], $this->data['translation_set_slug'], $this->data['locale_slug'] );
+
+ if ( ! $translation_set ) {
+ return;
+ }
+
+ $translations = GP::$translation->find_many_no_map(
+ array(
+ 'translation_set_id' => $translation_set->id,
+ 'original_id' => $this->data['original_id'],
+ )
+ );
+
+ usort(
+ $translations,
+ function ( $t1, $t2 ) {
+ $cmp_prop_t1 = $t1->date_modified ?? $t1->date_added;
+ $cmp_prop_t2 = $t2->date_modified ?? $t2->date_added;
+ return $cmp_prop_t1 < $cmp_prop_t2;
+ }
+ );
+
+ $this->set_count( $translations );
+
+ return $translations;
+ }
+
+ /**
+ * Gets the items that will be rendered by the helper.
+ *
+ * @since 0.0.1
+ *
+ * @param array $translations Translation history.
+ *
+ * @return string
+ */
+ public function async_output_callback( array $translations ): string {
+ if ( ! $translations ) {
+ return '';
+ }
+ $output = '<table id="translation-history-table" class="translations">';
+ $output .= '<thead>';
+ $output .= '<tr><th>Date</th><th>Translation</th><th>Added by</th><th>Last modified by</th>';
+ $output .= '</thead>';
+
+ foreach ( $translations as $key => $translation ) {
+ $date_and_time = is_null( $translation->date_modified ) ? $translation->date_added : $translation->date_modified;
+ $date_and_time = explode( ' ', $date_and_time );
+
+ $user = get_userdata( $translation->user_id );
+ $user_last_modified = get_userdata( $translation->user_id_last_modified );
+ $translation_permalink = GP_Route_Translation_Helpers::get_translation_permalink(
+ $this->data['project'],
+ $this->data['locale_slug'],
+ $this->data['translation_set_slug'],
+ $this->data['original_id'],
+ $translation->id
+ );
+
+ if (
+ is_null( $translation->translation_1 ) &&
+ is_null( $translation->translation_2 ) &&
+ is_null( $translation->translation_3 ) &&
+ is_null( $translation->translation_4 ) &&
+ is_null( $translation->translation_5 )
+ ) {
+ $output_translation = $translation->translation_0;
+ } else {
+ $output_translation = '<ul>';
+ for ( $i = 0; $i <= 5; $i ++ ) {
+ if ( null !== $translation->{'translation_' . $i} ) {
+ $output_translation .= sprintf( '<li>%s</li>', esc_translation( $translation->{'translation_' . $i} ) );
+ }
+ }
+ $output_translation .= '</ul>';
+ }
+
+ $output .= sprintf(
+ '<tr class="preview status-%1$s"><td title="%2$s">%3$s</td><td>%4$s</td><td>%5$s</td><td>%6$s</td></tr>',
+ esc_attr( $translation->status ),
+ esc_attr( $translation->date_modified ?? $translation->date_added ),
+ esc_html( $date_and_time[0] ),
+ $translation_permalink ? '<a href="' . esc_url( $translation_permalink ) . '">' . esc_html( $output_translation ) . '</a>' : esc_html( $output_translation ),
+ $user ? esc_html( $user->user_login ) : '—',
+ $user_last_modified ? esc_html( $user_last_modified->user_login ) : '—'
+ );
+ }
+
+ $output .= '</table>';
+
+ $output .= $this->get_translation_status_legend();
+
+ return $output;
+ }
+
+ /**
+ * Gets the translation status legend.
+ *
+ * @return string The translation status legend.
+ */
+ public function get_translation_status_legend() {
+ $legend = '<div id="legend" class="secondary clearfix">';
+ $legend .= '<div><strong>' . esc_html__( 'Legend:', 'glotpress' ) . '</strong></div>';
+ foreach ( GP::$translation->get_static( 'statuses' ) as $legend_status ) {
+ $legend .= '<div class="box status-' . esc_attr( $legend_status ) . '"></div>';
+ $legend .= '<div>';
+ switch ( $legend_status ) {
+ case 'current':
+ $legend .= esc_html__( 'Current', 'glotpress' );
+ break;
+ case 'waiting':
+ $legend .= esc_html__( 'Waiting', 'glotpress' );
+ break;
+ case 'fuzzy':
+ $legend .= esc_html__( 'Fuzzy', 'glotpress' );
+ break;
+ case 'old':
+ $legend .= esc_html__( 'Old', 'glotpress' );
+ break;
+ case 'rejected':
+ $legend .= esc_html__( 'Rejected', 'glotpress' );
+ break;
+ default:
+ $legend .= esc_html( $legend_status );
+ }
+ $legend .= '</div>';
+ }
+ $legend .= '<div class="box has-warnings"></div>';
+ $legend .= '<div>' . esc_html__( 'With warnings', 'glotpress' ) . '</div>';
+ $legend .= '</div>';
+ return $legend;
+ }
+
+ /**
+ * Gets the content/string to return when a helper has no results.
+ *
+ * @since 0.0.1
+ *
+ * @return string
+ */
+ public function empty_content(): string {
+ return esc_html__( 'No translation history for this string.' );
+ }
+
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers/helper-translation-history.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetscsstranslationdiscussioncss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/css/translation-discussion.css</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/gp-translation-helpers/helpers-assets/css/translation-discussion.css (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/css/translation-discussion.css 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,88 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.helper-translation-discussion {
+ position:relative;
+ min-height: 600px;
+}
+.discussion-list {
+ list-style:none;
+ width: 100%;
+}
+
+ul.discussion-list {
+ display: contents;
+}
+
+ul.discussion-list li {
+ border-top: 1px solid rgb(0 0 0 / 5%);
+}
+
+ul.discussion-list li:first-child {
+ margin-top: 15px;
+}
+
+ul.discussion-list li:last-child {
+ border-bottom: 1px solid rgb(0 0 0 / 5%);
+ margin-bottom: 15px;
+}
+
+ul.discussion-list cite.fn {
+ margin-left: 12px;
+}
+
+ul.discussion-list .comment-avatar {
+ margin-left: -30px;
+}
+
+ul.discussion-list article.comment {
+ margin-bottom: 0;
+}
+
+ul.discussion-list ul.children {
+ list-style: none;
+}
+
+article.comment {
+ margin: 15px 30px 15px 30px;
+ padding-bottom: 15px;
+ position: relative;
+ font-size: 0.9rem;
+}
+article.comment p {
+ margin-bottom: 0.5em;
+}
+article.comment footer {
+ overflow: hidden;
+ font-size: 0.8rem;
+ font-style: normal;
+}
+article.comment time {
+ font-style: italic;
+ opacity: 0.8;
+ display: inline-block;
+ padding-left: 4px;
+}
+.comment-locale {
+ opacity: 0.8;
+ float: right;
+}
+.comment-avatar {
+ margin-left: -45px;
+ margin-bottom: -25px;
+ width: 50px;
+ height: 26px;
+}
+.comment-avatar img {
+ display: block;
+ border-radius: 13px;
+}
+.comments-selector {
+ display: inline-block;
+ padding-left: 10px;
+ font-size: 0.9em;
+}
+.comment-content {
+ text-align: start;
+}
+
+.alignright {
+ float: right;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of file
</span><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/css/translation-discussion.css
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetsjstranslationdiscussionjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/js/translation-discussion.js</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/gp-translation-helpers/helpers-assets/js/translation-discussion.js (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/js/translation-discussion.js 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,104 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global $gp, document, wpApiSettings */
+jQuery( function( $ ) {
+ $( document ).on( 'click', '.helper-translation-discussion .comments-selector a', function( e ) {
+ var $comments, $selector;
+
+ e.preventDefault();
+ $( '.comments-selector a' ).removeClass( 'active-link' );
+ $( this ).addClass( 'active-link' );
+ $comments = jQuery( e.target ).parents( 'h6' ).next( '.discussion-list' );
+ $selector = $( e.target ).data( 'selector' );
+ if ( 'all' === $selector ) {
+ $comments.children().show();
+ } else if ( 'rejection-feedback' === $selector ) {
+ $comments.children().hide();
+ $comments.children( '.rejection-feedback' ).show();
+ } else {
+ $comments.children().hide();
+ $comments.children( '.comment-locale-' + $selector ).show();
+ $comments.children( '.comment-locale-' + $selector ).next( 'ul' ).show();
+ }
+ return false;
+ } );
+
+ function createShadowPost( formdata, submitComment ) {
+ var data = {
+ action: 'create_shadow_post',
+ data: formdata,
+ _ajax_nonce: wpApiSettings.nonce,
+ };
+
+ $.ajax(
+ {
+ type: 'POST',
+ url: wpApiSettings.admin_ajax_url,
+ data: data,
+ }
+ ).done(
+ function( response ) {
+ formdata.post = response.data;
+ submitComment( formdata );
+ }
+ );
+ }
+
+ $( document ).on( 'submit', '.helper-translation-discussion .comment-form', function( e ) {
+ var $commentform = $( e.target );
+ var postId = $commentform.attr( 'id' ).split( '-' )[ 1 ];
+ var submitComment = function( formdata ) {
+ $.ajax( {
+ url: wpApiSettings.root + 'wp/v2/comments',
+ method: 'POST',
+ beforeSend: function( xhr ) {
+ xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
+ },
+ data: formdata,
+ } ).done( function( response ) {
+ if ( 'undefined' !== typeof ( response.data ) ) {
+ // There's probably a better way, but response.data is only set for errors.
+ // TODO: error handling.
+ } else {
+ $commentform.find( 'textarea[name=comment]' ).val( '' );
+ $gp.translation_helpers.fetch( 'discussion' );
+ }
+ } );
+ };
+
+ var formdata = {
+ content: $commentform.find( 'textarea[name=comment]' ).val(),
+ parent: $commentform.find( 'input[name=comment_parent]' ).val(),
+ post: postId,
+ meta: {
+ translation_id: $commentform.find( 'input[name=translation_id]' ).val(),
+ locale: $commentform.find( 'input[name=comment_locale]' ).val(),
+ comment_topic: $commentform.find( 'select[name=comment_topic]' ).val(),
+ },
+ };
+ e.preventDefault();
+ e.stopImmediatePropagation();
+
+ $( 'input.submit' ).prop( 'disabled', true );
+
+ if ( ! formdata.meta.translation_id ) {
+ formdata.meta.translation_id = 0;
+ }
+
+ if ( formdata.meta.locale ) {
+ /**
+ * Set the locale to an empty string if option selected has value 'typo' or 'context'
+ * to force comment to be posted to the English discussion page
+ */
+ if ( formdata.meta.comment_topic === 'typo' || formdata.meta.comment_topic === 'context' ) {
+ formdata.meta.locale = '';
+ }
+ }
+
+ if ( isNaN( Number( postId ) ) ) {
+ createShadowPost( formdata, submitComment );
+ } else {
+ submitComment( formdata );
+ }
+
+ return false;
+ } );
+} );
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpershelpersassetstemplatestranslationdiscussioncommentsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/templates/translation-discussion-comments.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/gp-translation-helpers/helpers-assets/templates/translation-discussion-comments.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/templates/translation-discussion-comments.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,123 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * The template part for the comments and comment form on an original discussion
+ */
+?>
+<div class="discussion-wrapper">
+ <?php if ( $comments ) : ?>
+
+ <h6>
+ <?php
+ /* translators: number of comments. */
+ printf( _n( '%s Comment', '%s Comments', count( $comments ) ), number_format_i18n( count( $comments ) ) );
+ ?>
+ <?php if ( $locale_slug ) : ?>
+ (<?php echo esc_html( $locale_slug ); ?>)
+ <?php
+ $locale_comments_count = 0;
+ $count_rejection_feedback = 0;
+ foreach ( $comments as $_comment ) {
+ $comment_locale = get_comment_meta( $_comment->comment_ID, 'locale', true );
+ if ( $locale_slug == $comment_locale ) {
+
+ $reject_reason = get_comment_meta( $_comment->comment_ID, 'reject_reason', true );
+ if ( ! empty( $reject_reason ) ) {
+ $count_rejection_feedback++;
+ }
+ $locale_comments_count++;
+ }
+ }
+ ?>
+
+ <span class="comments-selector">
+ <a href="#" class="active-link" data-selector="all">Show all (<?php echo esc_html( count( $comments ) ); ?>)</a> |
+ <a href="#" data-selector="<?php echo esc_attr( $locale_slug ); ?>"><?php echo esc_html( $locale_slug ); ?> only (<?php echo esc_html( $locale_comments_count ); ?>)</a> |
+ <a href="#" data-selector="rejection-feedback">Rejection Feedback (<?php echo esc_html( $count_rejection_feedback ); ?>)</a>
+ </span>
+ <?php endif; ?>
+ </h6>
+ <?php else : ?>
+ <?php esc_html_e( 'No comments have been made on this yet.' ); ?>
+ <?php endif; ?>
+ <ul class="discussion-list">
+ <?php
+ wp_list_comments(
+ array(
+ 'style' => 'ul',
+ 'type' => 'comment',
+ 'callback' => 'gth_discussion_callback',
+ 'translation_id' => $translation_id,
+ 'locale_slug' => $locale_slug,
+ 'original_permalink' => $original_permalink,
+ 'original_id' => $original_id,
+ 'project' => $project,
+ 'translation_set_slug' => $translation_set_slug,
+ 'reverse_children' => false,
+ ),
+ $comments
+ );
+ ?>
+ </ul><!-- .discussion-list -->
+ <?php
+ add_action(
+ 'comment_form_logged_in_after',
+ function () use ( $locale_slug ) {
+ $language_question = '';
+
+ if ( $locale_slug ) {
+ $gp_locale = GP_Locales::by_slug( $locale_slug );
+ if ( $gp_locale ) {
+ $language_question = '<option value="question">Question about translating to ' . esc_html( $gp_locale->english_name ) . '</option>';
+ }
+ }
+
+ echo '<p class="comment-form-topic">
+ <label for="comment_topic">Topic <span class="required" aria-hidden="true">*</span></label>
+ <select required name="comment_topic" id="comment_topic">
+ <option value="">Select topic</option>
+ <option value="typo">Typo in the English text</option>
+ <option value="context">Where does this string appear? (more context)</option>' .
+ wp_kses( $language_question, array( 'option' => array( 'value' => true ) ) ) .
+ '</select>
+ </p>';
+ },
+ 10,
+ 2
+ );
+
+ if ( is_user_logged_in() && isset( $post ) ) {
+ if ( $post instanceof Gth_Temporary_Post ) {
+ $_post_id = $post->ID;
+ $post_obj = $post;
+ } else {
+ $post_obj = $post->ID;
+ $_post_id = $post->ID;
+ }
+ comment_form(
+ array(
+ 'title_reply' => __( 'Discuss this string' ),
+ /* translators: username */
+ 'title_reply_to' => __( 'Reply to %s' ),
+ 'title_reply_before' => '<h5 id="reply-title" class="discuss-title">',
+ 'title_reply_after' => '</h5>',
+ 'id_form' => 'commentform-' . $_post_id,
+ 'cancel_reply_link' => '<span></span>',
+ 'format' => 'html5',
+ 'comment_notes_after' => implode(
+ "\n",
+ array(
+ '<input type="hidden" name="comment_locale" value="' . esc_attr( $locale_slug ) . '" />',
+ '<input type="hidden" name="translation_id" value="' . esc_attr( $translation_id ) . '" />',
+ '<input type="hidden" name="redirect_to" value="' . esc_url( $original_permalink ) . '" />',
+ )
+ ),
+ ),
+ $post_obj
+ );
+ } else {
+ /* translators: Log in URL. */
+ echo sprintf( __( 'You have to be <a href="%s">logged in</a> to comment.' ), esc_html( wp_login_url() ) );
+ }
+
+ ?>
+</div><!-- .discussion-wrapper -->
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/helpers-assets/templates/translation-discussion-comments.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgpnotificationsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-notifications.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/gp-translation-helpers/includes/class-gp-notifications.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-notifications.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,476 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Routes: GP_Notifications class
+ *
+ * Manages the notifications of the plugin.
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.2
+ */
+class GP_Notifications {
+ /**
+ * Sends notifications when a new comment in the discussion is stored using the WP REST API.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment Inserted or updated comment object.
+ * @param WP_REST_Request $request Request object.
+ * @param bool $creating True when creating a comment, false when updating.
+ *
+ * @return void
+ */
+ public static function init( WP_Comment $comment, $request, $creating ) {
+ $post = get_post( $comment->comment_post_ID );
+ if ( Helper_Translation_Discussion::POST_TYPE === $post->post_type ) {
+ if ( ( '1' === $comment->comment_approved ) || ( 'approve' === $comment->comment_approved ) ) {
+ $comment_meta = get_comment_meta( $comment->comment_ID );
+ if ( ( '0' !== $comment->comment_parent ) ) { // Notify to the thread only if the comment is in a thread.
+ self::send_emails_to_thread_commenters( $comment, $comment_meta );
+ }
+ $root_comment = self::get_root_comment_in_a_thread( $comment );
+ $root_comment_meta = get_comment_meta( $root_comment->comment_ID );
+ if ( array_key_exists( 'comment_topic', $root_comment_meta ) ) {
+ switch ( $root_comment_meta['comment_topic'][0] ) {
+ case 'typo':
+ case 'context': // Notify to the GlotPress admins.
+ self::send_emails_to_gp_admins( $comment, $comment_meta );
+ break;
+ case 'question': // Notify to the project validator.
+ self::send_emails_to_validators( $comment, $comment_meta );
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends notifications when a comment changes its status to "approved".
+ *
+ * @since 0.0.2
+ *
+ * @param int|string $new_status The new comment status.
+ * @param int|string $old_status The old comment status.
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return void
+ */
+ public static function on_comment_status_change( $new_status, $old_status, WP_Comment $comment ) {
+ $post = get_post( $comment->comment_post_ID );
+ if ( Helper_Translation_Discussion::POST_TYPE === $post->post_type ) {
+ if ( $old_status != $new_status ) {
+ if ( ( 'approved' === $new_status ) ) {
+ self::init( $comment, '', '' );
+ }
+ }
+ }
+ }
+
+ /**
+ * Sends an email to the users that have commented on the thread, except to the last comment author.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return void
+ */
+ public static function send_emails_to_thread_commenters( WP_Comment $comment, array $comment_meta ) {
+ $parent_comments = self::get_parent_comments( $comment->comment_parent );
+ $email_addresses = self::get_commenters_email_addresses( $parent_comments, $comment->comment_author_email );
+ /**
+ * Filters the email addresses in a thread.
+ *
+ * @since 0.0.2
+ *
+ * @param array $email_addresses The email addresses in the thread.
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ */
+ $email_addresses = apply_filters( 'gp_notification_commenter_email_addresses', $email_addresses, $comment, $comment_meta );
+ self::send_emails( $comment, $comment_meta, $email_addresses );
+ }
+
+ /**
+ * Sends an email to the GlotPress admins.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return bool Whether the email has been sent or not.
+ */
+ public static function send_emails_to_gp_admins( WP_Comment $comment, array $comment_meta ) {
+ $email_addresses = self::get_admins_email_addresses( $comment, $comment_meta );
+ return self::send_emails( $comment, $comment_meta, $email_addresses );
+ }
+
+ /**
+ * Sends an email to the all the project validators.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return bool Whether the email has been sent or not.
+ */
+ public static function send_emails_to_validators( WP_Comment $comment, array $comment_meta ) {
+ $project = self::get_project_to_translate( $comment );
+
+ $email_addresses = self::get_validators_email_addresses( $project->path );
+ /**
+ * Filters the validators' email addresses.
+ *
+ * @since 0.0.2
+ *
+ * @param array $email_addresses The email addresses in the thread.
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ */
+ $email_addresses = apply_filters( 'gp_notification_validator_email_addresses', $email_addresses, $comment, $comment_meta );
+ $parent_comments = self::get_parent_comments( $comment->comment_ID ); // Includes the current comment.
+
+ $email_addresses_from_the_thread = self::get_commenters_email_addresses( $parent_comments );
+ // If one validator has a comment in the thread, we don't need to inform any other validators.
+ if ( true !== empty( array_intersect( $email_addresses, $email_addresses_from_the_thread ) ) ) {
+ $email_addresses = array();
+ }
+
+ return self::send_emails( $comment, $comment_meta, $email_addresses );
+ }
+
+ /**
+ * Returns the comments in the thread, including the last one.
+ *
+ * @since 0.0.2
+ *
+ * @param int $comment_id Last comment of the thread.
+ *
+ * @return array The comments in the thread.
+ */
+ public static function get_parent_comments( int $comment_id ): array {
+ $comments = array();
+ $comment = get_comment( $comment_id );
+ if ( $comment && $comment->comment_parent ) {
+ $comments = self::get_parent_comments( $comment->comment_parent );
+ }
+ if ( ! is_null( $comment ) ) {
+ $comments[] = $comment;
+ }
+ return $comments;
+ }
+
+ /**
+ * Gets the emails to be notified from the thread comments.
+ *
+ * Removes the second parameter from the returned array if it is found.
+ *
+ * @since 0.0.2
+ *
+ * @param array $comments Array with the parent comments to the posted comment.
+ * @param string $email_address_to_ignore Email from the posted comment.
+ *
+ * @return array The emails to be notified from the thread comments.
+ */
+ public static function get_commenters_email_addresses( array $comments, string $email_address_to_ignore = null ): array {
+ $email_addresses = array();
+ foreach ( $comments as $comment ) {
+ if ( $email_address_to_ignore !== $comment->comment_author_email ) {
+ $email_addresses[ $comment->comment_author_email ] = $comment->comment_author_email;
+ }
+ }
+
+ return $email_addresses;
+ }
+
+ /**
+ * Gets the emails of the validators of a project.
+ *
+ * @since 0.0.2
+ *
+ * @param string $project_path The project path.
+ *
+ * @return array The emails of the validators for the given project.
+ */
+ public static function get_validators_email_addresses( string $project_path ): array {
+ $email_addresses = array();
+
+ $project = GP::$project->by_path( $project_path );
+
+ $path_to_root = array_slice( $project->path_to_root(), 1 );
+ $permissions = GP::$validator_permission->by_project_id( $project->id );
+
+ $sort_by_locale_slug = function( $x, $y ) {
+ return strcmp( $x->locale_slug, $y->locale_slug );
+ };
+ usort( $permissions, $sort_by_locale_slug );
+ $parent_permissions = array();
+
+ foreach ( $path_to_root as $parent_project ) {
+ $this_parent_permissions = GP::$validator_permission->by_project_id( $parent_project->id );
+ usort( $this_parent_permissions, $sort_by_locale_slug );
+ foreach ( $this_parent_permissions as $permission ) {
+ $permission->project = $parent_project;
+ }
+ $parent_permissions = array_merge( $parent_permissions, (array) $this_parent_permissions );
+ }
+
+ // We can't join on users table.
+ foreach ( array_merge( (array) $permissions, (array) $parent_permissions ) as $permission ) {
+ $permission->user = get_user_by( 'id', $permission->user_id );
+ $email_addresses[] = $permission->user->data->user_email;
+ }
+
+ return $email_addresses;
+ }
+
+ /**
+ * Gets the email addresses of the GlotPress admins.
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return array The GlotPress admins' email addresses.
+ */
+ public static function get_admins_email_addresses( WP_Comment $comment, array $comment_meta ):array {
+ global $wpdb;
+ /**
+ * Filters the validators' emails.
+ *
+ * @since 0.0.2
+ *
+ * @param array $email_addresses The email addresses in the thread.
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ */
+ $email_addresses = apply_filters( 'gp_notification_admin_email_addresses', array(), $comment, $comment_meta );
+ if ( ! empty( $email_addresses ) ) {
+ return $email_addresses;
+ }
+
+ $admin_email_addresses = $wpdb->get_results(
+ "SELECT user_email FROM {$wpdb->users}
+ INNER JOIN {$wpdb->gp_permissions}
+ ON {$wpdb->users}.ID = {$wpdb->gp_permissions}.user_id
+ WHERE action='admin'"
+ );
+
+ foreach ( $admin_email_addresses as $admin ) {
+ $email_addresses[] = $admin->user_email;
+ }
+ $parent_comments = self::get_parent_comments( $comment->comment_parent );
+ $email_addresses_from_the_thread = self::get_commenters_email_addresses( $parent_comments );
+ // If an admin is already involved in the thread, don't notify the other admins.
+ if ( true !== empty( array_intersect( $email_addresses, $email_addresses_from_the_thread ) ) || in_array( $comment->comment_author_email, $email_addresses, true ) ) {
+ return array();
+ }
+
+ return array_unique( $email_addresses );
+ }
+
+ /**
+ * Sends an email to all the email addresses.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ * @param array $email_addresses The email addresses that will receive the notification.
+ *
+ * @return bool Whether the email has been sent or not.
+ */
+ public static function send_emails( WP_Comment $comment, array $comment_meta, array $email_addresses ): bool {
+ /**
+ * Filters the email addresses before sending the notifications.
+ *
+ * @since 0.0.2
+ *
+ * @param array $email_addresses The email addresses to be notified.
+ */
+ $email_addresses = apply_filters( 'gp_notification_before_send_emails', $email_addresses );
+ if ( ( null === $comment ) || ( null === $comment_meta ) || ( empty( $email_addresses ) ) ) {
+ return false;
+ }
+ $email_addresses = self::remove_commenter_email_address( $comment, $email_addresses );
+
+ $headers = array(
+ 'Content-Type: text/html; charset=UTF-8',
+ );
+ /**
+ * Filters the email headers.
+ *
+ * @since 0.0.2
+ *
+ * @param array $headers The email headers.
+ */
+ $headers = apply_filters( 'gp_notification_email_headers', $headers );
+ $subject = esc_html__( 'New comment in a translation discussion', 'glotpress' );
+ $body = self::get_email_body( $comment, $comment_meta );
+ foreach ( $email_addresses as $bcc ) {
+ $headers[] = 'Bcc: ' . $bcc;
+ }
+ wp_mail( '', $subject, $body, $headers );
+ return true;
+ }
+
+ /**
+ * Creates the email body message.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return string|null
+ */
+ public static function get_email_body( WP_Comment $comment, array $comment_meta ): string {
+ $project = self::get_project_to_translate( $comment );
+ $original = self::get_original( $comment );
+ $output = '';
+ /**
+ * Filters the content of the email at the beginning of the function that gets its content.
+ *
+ * @since 0.0.2
+ *
+ * @param string $output The content of the email.
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ */
+ $output = apply_filters( 'gp_notification_pre_email_body', $output, $comment, $comment_meta );
+ $output .= esc_html__( 'Hi there,', 'glotpress' );
+ $output .= '<br><br>';
+ $url = GP_Route_Translation_Helpers::get_permalink( $project->path, $original->id );
+ $output .= wp_kses(
+ /* translators: The discussion URL where the user can find the comment. */
+ sprintf( __( 'There is a new comment in a <a href="%1$s">GlotPress discussion</a> that may be of interest to you.', 'glotpress' ), $url ),
+ array(
+ 'a' => array( 'href' => array() ),
+ )
+ ) . '<br/>';
+ $output .= '<br>';
+ $output .= esc_html__( 'It would be nice if you have some time to review this comment and reply to it if needed.', 'glotpress' );
+ $output .= '<br><br>';
+ $output .= '- ' . wp_kses(
+ /* translators: The discussion URL where the user can find the comment. */
+ sprintf( __( '<strong>Discussion URL:</strong> <a href="%1$s">%1$s</a>', 'glotpress' ), $url ),
+ array(
+ 'strong' => array(),
+ 'a' => array( 'href' => array() ),
+ )
+ ) . '<br/>';
+ if ( array_key_exists( 'locale', $comment_meta ) && ( ! empty( $comment_meta['locale'][0] ) ) ) {
+ /* translators: The translation locale for the comment. */
+ $output .= '- ' . wp_kses( sprintf( __( '<strong>Locale:</strong> %s', 'glotpress' ), $comment_meta['locale'][0] ), array( 'strong' => array() ) ) . '<br/>';
+ }
+ /* translators: The original string to translate. */
+ $output .= '- ' . wp_kses( sprintf( __( '<strong>Original string:</strong> %s', 'glotpress' ), $original->singular ), array( 'strong' => array() ) ) . '<br/>';
+ if ( array_key_exists( 'translation_id', $comment_meta ) && $comment_meta['translation_id'][0] ) {
+ $translation_id = $comment_meta['translation_id'][0];
+ $translation = GP::$translation->get( $translation_id );
+ // todo: add the plurals.
+ if ( ! is_null( $translation ) ) {
+ /* translators: The translation string. */
+ $output .= '- ' . wp_kses( sprintf( __( '<strong>Translation string:</strong> %s', 'glotpress' ), $translation->translation_0 ), array( 'strong' => array() ) ) . '<br/>';
+ }
+ }
+ /* translators: The comment made. */
+ $output .= '- ' . wp_kses( sprintf( __( '<strong>Comment:</strong> %s', 'glotpress' ), $comment->comment_content ), array( 'strong' => array() ) );
+ $output .= '<br><br>';
+ $output .= esc_html__( 'Have a nice day!', 'glotpress' );
+ $output .= '<br><br>';
+ $output .= esc_html__( 'This is an automated message. Please, do not reply directly to this email.', 'glotpress' );
+ /**
+ * Filters the content of the email at the end of the function that gets its content.
+ *
+ * @since 0.0.2
+ *
+ * @param string $output The content of the email.
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ */
+ $output = apply_filters( 'gp_notification_post_email_body', $output, $comment, $comment_meta );
+ return $output;
+ }
+
+ /**
+ * Gets the root comment in a thread.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return WP_Comment The root comment in the thread.
+ */
+ public static function get_root_comment_in_a_thread( WP_Comment $comment ): WP_Comment {
+ $comments = self::get_parent_comments( $comment->comment_ID );
+ foreach ( $comments as $item ) {
+ if ( 0 === intval( $item->comment_parent ) ) {
+ return $item;
+ }
+ }
+ return $comment;
+ }
+
+ /**
+ * Removes the commenter email from the emails to be notified.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $email_addresses A list of emails.
+ *
+ * @return array The list of emails without the commenter's email.
+ */
+ public static function remove_commenter_email_address( WP_Comment $comment, array $email_addresses ): array {
+ $index = array_search( $comment->comment_author_email, $email_addresses, true );
+ if ( false !== $index ) {
+ unset( $email_addresses[ $index ] );
+ }
+ return array_values( $email_addresses );
+ }
+
+ /**
+ * Gets the project that the translated string belongs to.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return GP_Project|bool The project that the translated string belongs to.
+ */
+ private static function get_project_to_translate( WP_Comment $comment ) {
+ $post_id = $comment->comment_post_ID;
+ $terms = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
+ if ( empty( $terms ) ) {
+ return false;
+ }
+
+ $original = GP::$original->get( $terms[0]->slug );
+ $project_id = $original->project_id;
+ $project = GP::$project->get( $project_id );
+
+ return $project;
+ }
+
+ /**
+ * Gets the original string that the translated string belongs to.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return GP_Thing|false The original string that the translated string belongs to.
+ */
+ private static function get_original( WP_Comment $comment ) {
+ $post_id = $comment->comment_post_ID;
+ $terms = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
+ if ( empty( $terms ) ) {
+ return false;
+ }
+
+ return GP::$original->get( $terms[0]->slug );
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-notifications.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgproutetranslationhelpersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-route-translation-helpers.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/gp-translation-helpers/includes/class-gp-route-translation-helpers.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-route-translation-helpers.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,337 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Routes: GP_Route_Translation_Helpers class
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class GP_Route_Translation_Helpers extends GP_Route {
+
+ /**
+ * Stores an instance of each helper.
+ *
+ * @since 0.0.1
+ * @var array
+ */
+ private $helpers = array();
+
+ /**
+ * GP_Route_Translation_Helpers constructor.
+ *
+ * @since 0.0.1
+ */
+ public function __construct() {
+ $this->helpers = GP_Translation_Helpers::load_helpers();
+ $this->template_path = dirname( __FILE__ ) . '/../templates/';
+ }
+
+ /**
+ * Loads the 'original-permalink' template.
+ *
+ * @since 0.0.2
+ *
+ * @param string $project_path The project path. E.g. "wp/dev".
+ * @param int $original_id The original id. E.g. "2440".
+ * @param string|null $locale_slug Optional. The locale slug. E.g. "es".
+ * @param string $translation_set_slug The translation slug. E.g. "default".
+ * @param int|null $translation_id Optional. The translation id. E.g. "4525".
+ *
+ * @return void
+ */
+ public function original_permalink( $project_path, $original_id, $locale_slug = null, $translation_set_slug = null, $translation_id = null ) {
+ $original = GP::$original->get( $original_id );
+ if ( ! $original ) {
+ $this->die_with_404();
+ }
+ $project = GP::$project->by_path( $project_path );
+ if ( ! $project ) {
+ $this->die_with_404();
+ }
+
+ if ( $project->id !== $original->project_id ) {
+ $project = GP::$project->get( $original->project_id );
+
+ // Let's use the parameters that we have to create a URL in the right project.
+ $corrected_url = self::get_permalink( $project->path, $original_id, $locale_slug, $translation_set_slug );
+
+ wp_safe_redirect( $corrected_url );
+ exit;
+ }
+
+ if ( ! $original ) {
+ $this->die_with_404();
+ }
+
+ $args = array(
+ 'project_id' => $project->id,
+ 'locale_slug' => $locale_slug,
+ 'set_slug' => $translation_set_slug,
+ 'original_id' => $original_id,
+ 'translation_id' => $translation_id,
+ 'project' => $project,
+
+ );
+ $translation_set = GP::$translation_set->by_project_id_slug_and_locale( $project->id, $translation_set_slug, $locale_slug );
+
+ $all_translation_sets = GP::$translation_set->by_project_id( $project->id );
+
+ $row_id = $original_id;
+ $translation = null;
+ if ( $translation_id ) {
+ $row_id .= '-' . $translation_id;
+ $translation = GP::$translation->get( $translation_id );
+ }
+ $original_permalink = gp_url_project( $project, array( 'filters[original_id]' => $original_id ) );
+
+ $original_translation_permalink = false;
+ if ( $translation_set ) {
+ $original_translation_permalink = gp_url_project_locale( $project, $locale_slug, $translation_set->slug, array( 'filters[original_id]' => $original_id ) );
+ }
+
+ /** Get translation for this original */
+ $existing_translations = array();
+ if ( ! $translation && $translation_set && $original_id ) {
+ $translation = GP::$translation->find_one(
+ array(
+ 'status' => 'current',
+ 'original_id' => $original_id,
+ 'translation_set_id' => $translation_set->id,
+ )
+ );
+
+ if ( ! $translation ) {
+ $existing_translations = GP::$translation->find_many_no_map(
+ array(
+ 'original_id' => $original_id,
+ 'translation_set_id' => $translation_set->id,
+ )
+ );
+ usort(
+ $existing_translations,
+ function ( $t1, $t2 ) {
+ $cmp_prop_t1 = $t1->date_modified ?? $t1->date_added;
+ $cmp_prop_t2 = $t2->date_modified ?? $t2->date_added;
+ return $cmp_prop_t1 < $cmp_prop_t2;
+ }
+ );
+
+ // Something falsy is not enough.
+ $translation = null;
+ }
+ }
+
+ $priorities_key_value = $original->get_static( 'priorities' );
+ $priority = $priorities_key_value[ $original->priority ];
+
+ $args = compact( 'project', 'locale_slug', 'translation_set_slug', 'original_id', 'translation_id', 'translation', 'original_permalink' );
+ $sections = $this->get_translation_helper_sections( $args );
+
+ $translations = GP::$translation->find_many_no_map(
+ array(
+ 'status' => 'current',
+ 'original_id' => $original_id,
+ )
+ );
+ $no_of_translations = count( $translations );
+
+ add_action(
+ 'gp_head',
+ function() use ( $original, $no_of_translations ) {
+ echo '<meta property="og:title" content="' . esc_html( $original->singular ) . ' | ' . esc_html( $no_of_translations ) . ' translations" />';
+ }
+ );
+
+ $this->tmpl( 'original-permalink', get_defined_vars() );
+ }
+
+ /**
+ * Gets the sections of each active helper.
+ *
+ * @param array $data The data to be passed on to the sections.
+ *
+ * @return array The translation helper sections.
+ */
+ public function get_translation_helper_sections( $data ) {
+ $sections = array();
+ foreach ( $this->helpers as $helper => $translation_helper ) {
+ $translation_helper->set_data( $data );
+
+ if ( ! $translation_helper->activate() ) {
+ continue;
+ }
+
+ $sections[] = array(
+ 'title' => $translation_helper->get_title(),
+ 'content' => $translation_helper->get_output(),
+ 'classname' => $translation_helper->get_div_classname(),
+ 'id' => $translation_helper->get_div_id(),
+ 'priority' => $translation_helper->get_priority(),
+ 'has_async_content' => $translation_helper->has_async_content(),
+ 'count' => $translation_helper->get_count(),
+ 'load_inline' => $translation_helper->load_inline(),
+ 'helper' => $helper,
+ );
+ }
+
+ usort(
+ $sections,
+ function( $s1, $s2 ) {
+ return $s1['priority'] > $s2['priority'];
+ }
+ );
+
+ return $sections;
+ }
+
+ /**
+ * Returns the content of each section (tab).
+ *
+ * @since 0.0.2
+ *
+ * @param string $project_path The project path. E.g. "wp/dev".
+ * @param string $locale_slug The locale slug. E.g. "es".
+ * @param string $set_slug The translation set slug. E.g. "default".
+ * @param int $original_id The original id. E.g. "2440".
+ * @param int|null $translation_id Optional. The translation id. E.g. "4525".
+ *
+ * @return string JSON with the content of each section.
+ */
+ public function ajax_translation_helpers_locale( string $project_path, string $locale_slug, string $set_slug, int $original_id, int $translation_id = null ) {
+ return $this->ajax_translation_helpers( $project_path, $original_id, $translation_id, $locale_slug, $set_slug );
+ }
+
+ /**
+ * Returns the content of each section (tab).
+ *
+ * @since 0.0.1
+ *
+ * @param string $project_path The project path. E.g. "wp/dev".
+ * @param int $original_id The original id. E.g. "2440".
+ * @param int|null $translation_id Optional. The translation id. E.g. "4525".
+ * @param string|null $locale_slug The locale slug. E.g. "es".
+ * @param string|null $set_slug The translation set slug. E.g. "default".
+ *
+ * @return void Prints the JSON with the content of each section.
+ */
+ public function ajax_translation_helpers( string $project_path, int $original_id, int $translation_id = null, string $locale_slug = null, string $set_slug = null ): void {
+ $project = GP::$project->by_path( $project_path );
+ if ( ! $project ) {
+ $this->die_with_404();
+ }
+
+ $permalink = self::get_permalink( $project->path, $original_id, $set_slug, $locale_slug );
+
+ $args = array(
+ 'project_id' => $project->id,
+ 'locale_slug' => $locale_slug,
+ 'translation_set_slug' => $set_slug,
+ 'original_id' => $original_id,
+ 'translation_id' => $translation_id,
+ 'permalink' => $permalink,
+ 'project' => $project,
+ );
+
+ $selected = gp_get( 'helpers' );
+ if ( ! empty( $selected ) ) {
+ $helpers = array_filter(
+ $this->helpers,
+ function ( $key ) use ( $selected ) {
+ return in_array( $key, (array) $selected, true );
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+ } else {
+ $helpers = $this->helpers;
+ }
+
+ $sections = array();
+ foreach ( $helpers as $translation_helper ) {
+ $translation_helper->set_data( $args );
+ if ( $translation_helper->has_async_content() && $translation_helper->activate() ) {
+ $sections[ $translation_helper->get_div_id() ] = array(
+ 'content' => $translation_helper->get_async_output(),
+ 'count' => $translation_helper->get_count(),
+ );
+ };
+ }
+
+ echo wp_json_encode( $sections );
+ }
+
+ /**
+ * Gets the locales with comments.
+ *
+ * @since 0.0.2
+ *
+ * @param array|null $comments Array with comments.
+ *
+ * @return array Array with the locales with comments.
+ */
+ private function get_locales_with_comments( ?array $comments ): array {
+ $comment_locales = array();
+ if ( $comments ) {
+ foreach ( $comments as $comment ) {
+ $comment_meta = get_comment_meta( $comment->comment_ID, 'locale' );
+ $single_comment_locale = is_array( $comment_meta ) && ! empty( $comment_meta ) ? $comment_meta[0] : '';
+ if ( $single_comment_locale && ! in_array( $single_comment_locale, $comment_locales, true ) ) {
+ $comment_locales[] = $single_comment_locale;
+ }
+ }
+ }
+ return $comment_locales;
+ }
+
+ /**
+ * Gets the full permalink.
+ *
+ * @since 0.0.2
+ *
+ * @param string $project_path The project path. E.g. "wp/dev".
+ * @param int|null $original_id The original id. E.g. "2440".
+ * @param string|null $set_slug The translation set slug. E.g. "default".
+ * @param string|null $locale_slug Optional. The locale slug. E.g. "es".
+ *
+ * @return string The full permalink.
+ */
+ public static function get_permalink( string $project_path, ?int $original_id, string $set_slug = null, string $locale_slug = null ): string {
+ $permalink = $project_path . '/' . $original_id;
+ if ( $set_slug && $locale_slug ) {
+ $permalink .= '/' . $locale_slug . '/' . $set_slug;
+ }
+ return home_url( gp_url_project( $permalink ) );
+ }
+
+ /**
+ * Gets the translation permalink.
+ *
+ * @param GP_Project $project The project.
+ * @param string $locale_slug The locale slug.
+ * @param string $translation_set_slug The translation set slug.
+ * @param int $original_id The original id.
+ * @param int $translation_id The translation id.
+ *
+ * @return bool The translation permalink.
+ */
+ public static function get_translation_permalink( $project, $locale_slug, $translation_set_slug, $original_id, $translation_id = null ) {
+ if ( ! $project || ! $locale_slug || ! $translation_set_slug || ! $original_id ) {
+ return false;
+ }
+
+ $args = array(
+ 'filters[original_id]' => $original_id,
+ );
+
+ if ( $translation_id ) {
+ $args['filters[status]'] = 'either';
+ $args['filters[translation_id]'] = $translation_id;
+ }
+
+ $translation_permalink = gp_url_project_locale(
+ $project,
+ $locale_slug,
+ $translation_set_slug,
+ $args
+ );
+ return $translation_permalink;
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-route-translation-helpers.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgptranslationhelpersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-translation-helpers.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/gp-translation-helpers/includes/class-gp-translation-helpers.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-translation-helpers.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,446 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * GP_Translation_Helpers class
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.1
+ */
+class GP_Translation_Helpers {
+
+ /**
+ * Class id.
+ *
+ * @since 0.0.1
+ * @var string
+ */
+ public $id = 'translation-helpers';
+
+ /**
+ * Stores an instance of each helper.
+ *
+ * @since 0.0.1
+ * @var array
+ */
+ private $helpers = array();
+
+ /**
+ * Stores the self instance.
+ *
+ * @since 0.0.1
+ * @var object
+ */
+ private static $instance = null;
+
+ /**
+ * Inits the class.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public static function init() {
+ self::get_instance();
+ }
+
+ public static function get_instance() {
+ if ( is_null( self::$instance ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * GP_Translation_Helpers constructor.
+ *
+ * @since 0.0.1
+ */
+ public function __construct() {
+ add_action( 'template_redirect', array( $this, 'register_routes' ), 5 );
+ add_action( 'gp_before_request', array( $this, 'before_request' ), 10, 2 );
+ add_action( 'rest_after_insert_comment', array( 'GP_Notifications', 'init' ), 10, 3 );
+ add_action( 'transition_comment_status', array( 'GP_Notifications', 'on_comment_status_change' ), 10, 3 );
+ add_action( 'gp_pre_tmpl_load', array( $this, 'register_reject_feedback_js' ), 10, 2 );
+ add_action( 'wp_ajax_reject_with_feedback', array( $this, 'reject_with_feedback' ) );
+
+ add_thickbox();
+ gp_enqueue_style( 'thickbox' );
+
+ wp_register_style( 'gp-discussion-css', plugins_url( '/../css/discussion.css', __FILE__ ), array(), '0.0.1' );
+ gp_enqueue_style( 'gp-discussion-css' );
+
+ add_filter( 'gp_translation_row_template_more_links', array( $this, 'translation_row_template_more_links' ), 10, 5 );
+ add_filter( 'preprocess_comment', array( $this, 'preprocess_comment' ) );
+
+ $this->helpers = self::load_helpers();
+ }
+
+ /**
+ * Adds the action to load the CSS and JavaScript required only in the original-permalink template.
+ *
+ * @since 0.0.1
+ *
+ * @param string $class_name The class name of the route.
+ * @param string $last_method The route method that will be called.
+ *
+ * @return void
+ */
+ public function before_request( string $class_name, string $last_method ) {
+ if (
+ in_array(
+ $class_name . '::' . $last_method,
+ array(
+ // 'GP_Route_Translation::translations_get',
+ 'GP_Route_Translation_Helpers::original_permalink',
+ ),
+ true
+ )
+ ) {
+ add_action( 'gp_pre_tmpl_load', array( $this, 'pre_tmpl_load' ), 10, 2 );
+ }
+ }
+
+ /**
+ * Adds the link for the discussion in the main screen.
+ *
+ * @since 0.0.2
+ *
+ * @todo Move the inline CSS style to a CSS file when this plugin be integrated into GlotPress.
+ *
+ * @param array $more_links The links to be output.
+ * @param GP_Project $project Project object.
+ * @param GP_Locale $locale Locale object.
+ * @param GP_Translation_Set $translation_set Translation Set object.
+ * @param Translation_Entry $translation Translation object.
+ *
+ * @return array
+ */
+ public function translation_row_template_more_links( array $more_links, GP_Project $project, GP_Locale $locale, GP_Translation_Set $translation_set, Translation_Entry $translation ): array {
+ $permalink = GP_Route_Translation_Helpers::get_permalink( $project->path, $translation->original_id, $translation_set->slug, $translation_set->locale );
+
+ $links = '<a href="' . esc_url( $permalink ) . '">Discussion</a>';
+ $links .= '<a href="' . esc_url( $permalink ) . '" style="float:right" target="_blank"><span class="dashicons dashicons-external"></span></a>';
+ $more_links['discussion'] = $links;
+
+ return $more_links;
+ }
+
+ /**
+ * Prevents remote POST to comment forms.
+ *
+ * @since 0.0.2
+ *
+ * @param array $commentdata Comment data.
+ *
+ * @return array|void
+ */
+ public function preprocess_comment( array $commentdata ) {
+ if ( ! $commentdata['user_ID'] ) {
+ die( 'User not authorized!' );
+ }
+ return $commentdata;
+ }
+
+ /**
+ * Loads the CSS and JavaScript required only in the original-permalink template.
+ *
+ * @since 0.0.1
+ *
+ * @param string $template The template. E.g. "original-permalink".
+ * @param array $args Arguments passed to the template. Passed by reference.
+ *
+ * @return void
+ */
+ public function pre_tmpl_load( string $template, array $args ):void {
+ $allowed_templates = apply_filters( 'gp_translations_helpers_templates', array( 'original-permalink' ) );
+
+ if ( ! in_array( $template, $allowed_templates, true ) ) {
+ return;
+ }
+
+ $translation_helpers_settings = array(
+ 'th_url' => gp_url_project( $args['project'], gp_url_join( $args['locale_slug'], $args['translation_set_slug'], '-get-translation-helpers' ) ),
+ );
+
+ add_action( 'gp_head', array( $this, 'css_and_js' ), 10 );
+ add_action( 'gp_translation_row_editor_columns', array( $this, 'translation_helpers' ), 10, 2 );
+
+ add_filter(
+ 'gp_translation_row_editor_clospan',
+ function( $colspan ) {
+ return ( $colspan - 3 );
+ }
+ );
+
+ wp_register_style( 'gp-translation-helpers-css', plugins_url( 'css/translation-helpers.css', __DIR__ ), '', '0.0.1' ); // todo: add the version as global element.
+ gp_enqueue_style( 'gp-translation-helpers-css' );
+
+ wp_register_script( 'gp-translation-helpers', plugins_url( '/js/translation-helpers.js', __DIR__ ), array( 'gp-editor' ), '2017-02-09', true );
+ gp_enqueue_scripts( array( 'gp-translation-helpers' ) );
+
+ wp_localize_script( 'gp-translation-helpers', '$gp_translation_helpers_settings', $translation_helpers_settings );
+ wp_localize_script(
+ 'gp-translation-helpers',
+ 'wpApiSettings',
+ array(
+ 'root' => esc_url_raw( rest_url() ),
+ 'nonce' => wp_create_nonce( 'wp_rest' ),
+ 'admin_ajax_url' => admin_url( 'admin-ajax.php' ),
+ )
+ );
+ }
+
+ /**
+ * Gets the translation helpers.
+ *
+ * The returned array has the title helper as key and object of
+ * this class as value.
+ *
+ * @since 0.0.1
+ *
+ * @return array
+ */
+ public static function load_helpers(): array {
+ $base_dir = dirname( dirname( __FILE__ ) ) . '/helpers/';
+ require_once $base_dir . '/base-helper.php';
+
+ $helpers_files = array(
+ 'helper-translation-discussion.php',
+ 'helper-other-locales.php',
+ 'helper-translation-history.php',
+ );
+
+ foreach ( $helpers_files as $helper ) {
+ require_once $base_dir . $helper;
+ }
+
+ $helpers = array();
+
+ $classes = get_declared_classes();
+ foreach ( $classes as $declared_class ) {
+ $reflect = new ReflectionClass( $declared_class );
+ if ( $reflect->isSubclassOf( 'GP_Translation_Helper' ) ) {
+ $helpers[ sanitize_title_with_dashes( $reflect->getDefaultProperties()['title'] ) ] = new $declared_class();
+ }
+ }
+
+ return $helpers;
+ }
+
+ /**
+ * Loads the 'translation-helpers' template.
+ *
+ * @since 0.0.1
+ *
+ * @param GP_Translation $translation The current translation.
+ * @param GP_Translation_Set $translation_set The current translation set.
+ *
+ * @return void
+ */
+ public function translation_helpers( GP_Translation $translation, GP_Translation_Set $translation_set ) {
+ $args = array(
+ 'project_id' => $translation->project_id,
+ 'locale_slug' => $translation_set->locale,
+ 'translation_set_slug' => $translation_set->slug,
+ 'original_id' => $translation->original_id,
+ 'translation_id' => $translation->id,
+ 'translation' => $translation,
+ );
+
+ $sections = array();
+ foreach ( $this->helpers as $translation_helper ) {
+ $translation_helper->set_data( $args );
+
+ if ( ! $translation_helper->activate() ) {
+ continue;
+ }
+
+ $sections[] = array(
+ 'title' => $translation_helper->get_title(),
+ 'content' => $translation_helper->get_output(),
+ 'classname' => $translation_helper->get_div_classname(),
+ 'id' => $translation_helper->get_div_id(),
+ 'priority' => $translation_helper->get_priority(),
+ 'has_async_content' => $translation_helper->has_async_content(),
+ 'load_inline' => $translation_helper->load_inline(),
+ );
+ }
+ usort(
+ $sections,
+ function( $s1, $s2 ) {
+ return $s1['priority'] > $s2['priority'];
+ }
+ );
+
+ gp_tmpl_load( 'translation-helpers', array( 'sections' => $sections ), dirname( __FILE__ ) . '/templates/' );
+ }
+
+ /**
+ * Registers the routes and the methods that will respond to these routes.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function register_routes() {
+ $dir = '([^_/][^/]*)';
+ $path = '(.+?)';
+ $projects = 'projects';
+ $project = $projects . '/' . $path;
+ $locale = '(' . implode( '|', wp_list_pluck( GP_Locales::locales(), 'slug' ) ) . ')';
+ $set = "$project/$locale/$dir";
+ $id = '(\d+)-?(\d+)?';
+
+ GP::$router->prepend( "/$project/(\d+)(?:/$locale/$dir)?(/\d+)?", array( 'GP_Route_Translation_Helpers', 'original_permalink' ), 'get' );
+ GP::$router->prepend( "/$project/-get-translation-helpers/$id", array( 'GP_Route_Translation_Helpers', 'ajax_translation_helpers' ), 'get' );
+ GP::$router->prepend( "/$project/$locale/$dir/-get-translation-helpers/$id", array( 'GP_Route_Translation_Helpers', 'ajax_translation_helpers_locale' ), 'get' );
+ }
+
+ /**
+ * Prints inline CSS and JavaScript.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function css_and_js() {
+ ?>
+ <style>
+ <?php
+ foreach ( $this->helpers as $translation_helper ) {
+ $css = $translation_helper->get_css();
+ if ( $css ) {
+ echo '/* Translation Helper: ' . esc_js( $translation_helper->get_title() ) . ' */' . "\n";
+ echo esc_html( $css ) . "\n";
+ }
+ }
+ ?>
+ </style>
+ <script>
+ <?php
+ foreach ( $this->helpers as $translation_helper ) {
+ $js = $translation_helper->get_js();
+ if ( $js ) {
+ echo '/* Translation Helper: ' . esc_js( $translation_helper->get_title() ) . ' */' . "\n";
+ echo $js . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+ }
+ ?>
+ </script>
+ <?php
+ }
+
+ /**
+ * Registers and enqueues the reject-feedback.js script and also loads a few js variables on the page.
+ *
+ * @since 0.0.2
+ *
+ * @param string $template Template of the current page.
+ * @param string $translation_set Current translation set
+ *
+ * @return void
+ */
+ public function register_reject_feedback_js( $template, $translation_set ) {
+
+ if ( 'translations' !== $template ) {
+ return;
+ }
+
+ wp_register_script( 'gp-reject-feedback-js', plugins_url( '/../js/reject-feedback.js', __FILE__ ), array( 'jquery', 'gp-common', 'gp-editor', 'thickbox' ), '0.0.1' );
+ gp_enqueue_script( 'gp-reject-feedback-js' );
+
+ wp_localize_script(
+ 'gp-reject-feedback-js',
+ '$gp_reject_feedback_settings',
+ array(
+ 'url' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'gp_reject_feedback' ),
+ 'locale_slug' => $translation_set['locale_slug'],
+ 'reject_reasons' => Helper_Translation_Discussion::get_reject_reasons(),
+ )
+ );
+ }
+
+ /**
+ * Is called from the AJAX request in reject-feedback.js to submit a rejection feedback.
+ *
+ * @since 0.0.2
+ *
+ * @return void
+ */
+ public function reject_with_feedback() {
+ check_ajax_referer( 'gp_reject_feedback', 'nonce' );
+
+ $helper_discussion = new Helper_Translation_Discussion();
+ $locale_slug = $helper_discussion->sanitize_comment_locale( sanitize_text_field( $_POST['data']['locale_slug'] ) );
+ $translation_id_array = ! empty( $_POST['data']['translation_id'] ) ? array_map( array( $helper_discussion, 'sanitize_translation_id' ), $_POST['data']['translation_id'] ) : null;
+ $original_id_array = ! empty( $_POST['data']['original_id'] ) ? array_map( array( $helper_discussion, 'sanitize_original_id' ), $_POST['data']['original_id'] ) : null;
+ $reject_reason = ! empty( $_POST['data']['reason'] ) ? $_POST['data']['reason'] : array( 'other' );
+ $all_reject_reasons = array_keys( Helper_Translation_Discussion::get_reject_reasons() );
+ $reject_reason = array_filter(
+ $reject_reason,
+ function( $reason ) use ( $all_reject_reasons ) {
+ return in_array( $reason, $all_reject_reasons );
+ }
+ );
+ $reject_comment = sanitize_text_field( $_POST['data']['comment'] );
+
+ if ( ! $locale_slug || ! $translation_id_array || ! $original_id_array || ( ! $reject_reason && ! $reject_comment ) ) {
+ wp_send_json_error();
+ }
+
+ // Get original_id and translation_id of first string in the array
+ $first_original_id = array_shift( $original_id_array );
+ $first_translation_id = array_shift( $translation_id_array );
+
+ // Post comment on discussion page for the first string
+ $save_feedback = $this->insert_reject_comment( $reject_comment, $first_original_id, $reject_reason, $first_translation_id, $locale_slug, $_SERVER );
+
+ if ( ! empty( $original_id_array ) && ! empty( $translation_id_array ) ) {
+ // For other strings post link to the comment.
+ $reject_comment = get_comment_link( $save_feedback );
+ foreach ( $original_id_array as $index => $single_original_id ) {
+ $this->insert_reject_comment( $reject_comment, $single_original_id, $reject_reason, $translation_id_array[ $index ], $locale_slug, $_SERVER );
+ }
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Inserts rejection feedback as WordPress comment
+ *
+ * @since 0.0.2
+ *
+ * @param string $reject_comment Feedback entered by reviewer.
+ * @param int $original_id ID of the original where the comment will be added.
+ * @param array $reject_reason Reason(s) for rejection.
+ * @param string $translation_id ID of the rejected translation.
+ * @param string $locale_slug Locale of the rejected translation.
+ * @param array $server The $_SERVER array
+ *
+ * @return false|int
+ */
+ private function insert_reject_comment( $reject_comment, $original_id, $reject_reason, $translation_id, $locale_slug, $server ) {
+ $post_id = Helper_Translation_Discussion::get_or_create_shadow_post_id( $original_id );
+ $user = wp_get_current_user();
+ return wp_insert_comment(
+ array(
+ 'comment_post_ID' => $post_id,
+ 'comment_author' => $user->display_name,
+ 'comment_author_email' => $user->user_email,
+ 'comment_author_url' => $user->user_url,
+ 'comment_author_IP' => sanitize_text_field( $server['REMOTE_ADDR'] ),
+ 'comment_content' => $reject_comment,
+ 'comment_agent' => sanitize_text_field( $server['HTTP_USER_AGENT'] ),
+ 'user_id' => $user->ID,
+ 'comment_meta' => array(
+ 'reject_reason' => $reject_reason,
+ 'translation_id' => $translation_id,
+ 'locale' => $locale_slug,
+ ),
+ )
+ );
+ }
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gp-translation-helpers.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclassgthtemporarypostphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gth-temporary-post.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/gp-translation-helpers/includes/class-gth-temporary-post.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gth-temporary-post.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,14 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+class Gth_Temporary_Post {
+ public $ID;
+ public $comments_open = 'open';
+ public function __construct( $post_id ) {
+ $this->ID = $post_id;
+ }
+
+ public function __toString() {
+ return strval( $this->ID );
+ }
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-gth-temporary-post.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersincludesclasswporgnotificationsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-wporg-notifications.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/gp-translation-helpers/includes/class-wporg-notifications.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-wporg-notifications.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,408 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Routes: WPorg_Notifications class
+ *
+ * Manages the WPorg notifications in the translation notifications.
+ *
+ * @package gp-translation-helpers
+ * @since 0.0.2
+ */
+class WPorg_GlotPress_Notifications {
+ /**
+ * Emails to receive the comments about typos and asking for feedback in core, patterns, meta and apps.
+ *
+ * @todo Update these emails to the correct ones.
+ *
+ * @since 0.0.2
+ * @var array
+ */
+ private static $i18n_email = array(
+ 'i18n@wordpress.org',
+ 'i18n2@wordpress.org',
+ );
+
+ /**
+ * Adds the hooks to modify the email authors, validators and the email body.
+ *
+ * @since 0.0.2
+ *
+ * @return void
+ */
+ public static function init() {
+ if ( defined( 'WPORG_TRANSLATE_BLOGID' ) && ( get_current_blog_id() === WPORG_TRANSLATE_BLOGID ) ) {
+ add_filter(
+ 'gp_notification_admin_email_addresses',
+ function ( $email_addresses, $comment, $comment_meta ) {
+ return self::get_author_email_address( $comment, $comment_meta );
+ },
+ 10,
+ 3
+ );
+ add_filter(
+ 'gp_notification_validator_email_addresses',
+ function ( $email_addresses, $comment, $comment_meta ) {
+ return self::get_validator_email_addresses( $comment, $comment_meta );
+ },
+ 10,
+ 3
+ );
+ add_filter(
+ 'gp_notification_post_email_body',
+ function ( $output, $comment, $comment_meta ) {
+ return self::get_email_body( $comment, $comment_meta );
+ },
+ 10,
+ 3
+ );
+ add_filter(
+ 'gp_notification_before_send_emails',
+ function ( $email_addresses ) {
+ return self::get_opted_in_email_addresses( $email_addresses );
+ },
+ 10,
+ 1
+ );
+ add_filter(
+ 'gp_notification_email_headers',
+ function () {
+ return array(
+ 'Content-Type: text/html; charset=UTF-8',
+ 'From: Translating WordPress.org <no-reply@wordpress.org>',
+ );
+ }
+ );
+ }
+ }
+
+ /**
+ * Gets the email addresses of all project validators: GTE, PTE and CLPTE.
+ *
+ * Returns an empty array if one GTE/PTE/CLPTE has a comment in the thread,
+ * so only one validators is notified.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return array The validators' emails.
+ */
+ public static function get_validator_email_addresses( WP_Comment $comment, array $comment_meta ): array {
+ $locale = $comment_meta['locale'][0];
+ $email_addresses = self::get_gte_email_addresses( $locale );
+ $email_addresses = array_merge( $email_addresses, self::get_pte_email_addresses_by_project_and_locale( $comment, $locale ) );
+ $email_addresses = array_merge( $email_addresses, self::get_clpte_email_addresses_by_project( $comment ) );
+ $parent_comments = GP_Notifications::get_parent_comments( $comment->comment_parent );
+ $emails_from_the_thread = GP_Notifications::get_commenters_email_addresses( $parent_comments );
+ // Set the email addresses array as empty if one GTE/PTE/CLPTE has a comment in the thread.
+ if ( ! empty( array_intersect( $email_addresses, $emails_from_the_thread ) ) || in_array( $comment->comment_author_email, $email_addresses, true ) ) {
+ return array();
+ }
+ return $email_addresses;
+ }
+
+ /**
+ * Gets the general translation editors (GTE) emails for the given locale.
+ *
+ * @since 0.0.2
+ *
+ * @param string $locale The locale. E.g. 'zh-tw'.
+ *
+ * @return array The general translation editors (GTE) emails.
+ */
+ public static function get_gte_email_addresses( string $locale ): array {
+ $email_addresses = array();
+
+ $gp_locale = GP_Locales::by_field( 'slug', $locale );
+ if ( ( ! defined( 'WPORG_TRANSLATE_BLOGID' ) ) || ( false === $gp_locale ) ) {
+ return $email_addresses;
+ }
+ $result = get_sites(
+ array(
+ 'locale' => $gp_locale->wp_locale,
+ 'network_id' => WPORG_GLOBAL_NETWORK_ID,
+ 'path' => '/',
+ 'fields' => 'ids',
+ 'number' => '1',
+ )
+ );
+ $site_id = array_shift( $result );
+ if ( ! $site_id ) {
+ return $email_addresses;
+ }
+
+ $users = get_users(
+ array(
+ 'blog_id' => $site_id,
+ 'role' => 'general_translation_editor',
+ 'count_total' => false,
+ )
+ );
+ foreach ( $users as $user ) {
+ $email_addresses[] = $user->data->user_email;
+ }
+
+ return $email_addresses;
+ }
+
+ /**
+ * Gets the project translation editors (PTE) emails for the given translation_id (from a project) and locale.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param string $locale The locale. E.g. 'zh-tw'.
+ *
+ * @return array The project translation editors (PTE) emails.
+ */
+ public static function get_pte_email_addresses_by_project_and_locale( $comment, $locale ): array {
+ return self::get_pte_clpte_email_addresses_by_project_and_locale( $comment, $locale );
+ }
+
+ /**
+ * Gets the cross language project translation editors (CLPTE) emails for the given translation_id (from a project).
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return array The cross language project translation editors (CLPTE) emails.
+ */
+ public static function get_clpte_email_addresses_by_project( $comment ): array {
+ return self::get_pte_clpte_email_addresses_by_project_and_locale( $comment, 'all-locales' );
+ }
+
+ /**
+ * Gets the PTE/CLPTE emails for the given translation_id (from a project) and locale.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param string $locale The locale. E.g. 'zh-tw'.
+ *
+ * @return array The PTE/CLPTE emails for the project and locale.
+ */
+ private static function get_pte_clpte_email_addresses_by_project_and_locale( WP_Comment $comment, string $locale ): array {
+ global $wpdb;
+
+ if ( 'all-locales' === $locale ) {
+ $gp_locale = 'all-locales';
+ } else {
+ $gp_locale = GP_Locales::by_field( 'slug', $locale );
+ }
+
+ if ( ( ! defined( 'WPORG_TRANSLATE_BLOGID' ) ) || ( false === $gp_locale ) ) {
+ return $array();
+ }
+
+ $project = self::get_project_to_translate( $comment );
+
+ // todo: remove the deleted users in the SQL query.
+ $translation_editors = $wpdb->get_results(
+ $wpdb->prepare(
+ "
+ SELECT
+ {$wpdb->wporg_translation_editors}.user_id,
+ {$wpdb->wporg_translation_editors}.locale
+ FROM {$wpdb->wporg_translation_editors}
+ WHERE {$wpdb->wporg_translation_editors}.project_id = %d AND
+ {$wpdb->wporg_translation_editors}.locale = %s
+ ",
+ $project->id,
+ $locale
+ )
+ );
+
+ $email_addresses = array();
+ foreach ( $translation_editors as $pte ) {
+ $email_addresses[] = get_user_by( 'id', $pte->user_id )->user_email;
+ }
+ return $email_addresses;
+ }
+
+ /**
+ * Gets the email addresses for the author of a theme or a plugin.
+ *
+ * Themes: only one email.
+ * Plugins: all the plugin authors.
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array $comment_meta The meta values for the comment.
+ *
+ * @return array The email addresses for the author of a theme or a plugin.
+ */
+ public static function get_author_email_address( WP_Comment $comment, array $comment_meta ): array {
+ global $wpdb;
+
+ $email_addresses = array();
+ $project = self::get_project_to_translate( $comment );
+ if ( 'wp-themes' === substr( $project->path, 0, 9 ) ) {
+ $author = $wpdb->get_row(
+ $wpdb->prepare(
+ "SELECT post_author
+ FROM wporg_35_posts
+ WHERE
+ post_type = 'repopackage' AND
+ post_name = %s
+ ",
+ $project->slug
+ )
+ );
+ if ( $author ) {
+ $author = get_user_by( 'id', $author->post_author );
+ $email_addresses[] = $author->data->user_email;
+ }
+ }
+ if ( 'wp-plugins' === substr( $project->path, 0, 10 ) ) {
+ $committers = $wpdb->get_col(
+ $wpdb->prepare(
+ 'SELECT user FROM plugin_2_svn_access WHERE path = %s',
+ '/' . $project->slug
+ )
+ );
+ foreach ( $committers as $user_login ) {
+ $email_addresses[] = get_user_by( 'login', $user_login )->user_email;
+ }
+ }
+ if ( ! ( ( 'wp-themes' === substr( $project->path, 0, 9 ) ) || ( 'wp-plugins' === substr( $project->path, 0, 10 ) ) ) ) {
+ $email_addresses = self::$i18n_email;
+ }
+ $parent_comments = GP_Notifications::get_parent_comments( $comment->comment_parent );
+ $emails_from_the_thread = GP_Notifications::get_commenters_email_addresses( $parent_comments );
+ // If one author has a comment in the thread or if one validator is the commenter, we don't need to inform any other validator.
+ if ( ( true !== empty( array_intersect( $email_addresses, $emails_from_the_thread ) ) ) || in_array( $comment->comment_author_email, $email_addresses, true ) ) {
+ return array();
+ }
+ return $email_addresses;
+ }
+
+ /**
+ * Creates the email body message.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ * @param array|null $comment_meta The meta values for the comment.
+ *
+ * @return string|null The email body message.
+ */
+ public static function get_email_body( WP_Comment $comment, ?array $comment_meta ): ?string {
+ $project = self::get_project_to_translate( $comment );
+ $original = self::get_original( $comment );
+ $output = esc_html__( 'Hi:' );
+ $output .= '<br><br>';
+ $output .= esc_html__( 'There is a new comment in a discussion of the WordPress translation system that may be of interest to you.' );
+ $output .= '<br>';
+ $output .= esc_html__( 'It would be nice if you have some time to review this comment and reply to it if needed.' );
+ $output .= '<br><br>';
+ $url = GP_Route_Translation_Helpers::get_permalink( $project->path, $original->id );
+ $output .= '- <strong>' . esc_html__( 'Discussion URL: ' ) . '</strong><a href="' . $url . '">' . $url . '</a><br>';
+ if ( array_key_exists( 'locale', $comment_meta ) && ( ! empty( $comment_meta['locale'][0] ) ) ) {
+ $output .= '- <strong>' . esc_html__( 'Locale: ' ) . '</strong>' . esc_html( $comment_meta['locale'][0] ) . '<br>';
+ }
+ $output .= '- <strong>' . esc_html__( 'Original string: ' ) . '</strong>' . esc_html( $original->singular ) . '<br>';
+ if ( array_key_exists( 'translation_id', $comment_meta ) && $comment_meta['translation_id'][0] ) {
+ $translation_id = $comment_meta['translation_id'][0];
+ $translation = GP::$translation->get( $translation_id );
+ // todo: add the plurals.
+ if ( ! is_null( $translation ) ) {
+ $output .= '- <strong>' . esc_html__( 'Translation string: ' ) . '</strong>' . esc_html( $translation->translation_0 ) . '<br>';
+ }
+ }
+ $output .= '- <strong>' . esc_html__( 'Comment: ' ) . '</strong>' . esc_html( $comment->comment_content ) . '</pre>';
+ $output .= '<br><br>';
+ $output .= esc_html__( 'Have a nice day' );
+ $output .= '<br><br>';
+ $output .= esc_html__( 'This is an automated message. Please, do not reply directly to this email.' );
+
+ return $output;
+ }
+
+
+ /**
+ * Gets the project the translated string belongs to.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return GP_Project The project the translated string belongs to.
+ */
+ private static function get_project_to_translate( WP_Comment $comment ): GP_Project {
+ $post_id = $comment->comment_post_ID;
+ $terms = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
+ if ( empty( $terms ) ) {
+ return false;
+ }
+
+ $original = GP::$original->get( $terms[0]->slug );
+ $project_id = $original->project_id;
+ $project = GP::$project->get( $project_id );
+ $main_projects = self::get_main_projects();
+
+ // If the parent project is not a main project, get the parent project. We need to do this
+ // because we have 3 levels of projects. E.g. wp-plugins->akismet->stable and the PTE are
+ // assigned to the second level.
+ if ( ( ! is_null( $project->parent_project_id ) ) && ( ! in_array( $project->parent_project_id, $main_projects, true ) ) ) {
+ $project = GP::$project->get( $project->parent_project_id );
+ }
+ return $project;
+ }
+
+ /**
+ * Gets the id of the main projects without parent projects.
+ *
+ * @since 0.0.2
+ *
+ * @return array The id of the main projects.
+ */
+ private static function get_main_projects():array {
+ global $wpdb;
+
+ $main_projects = $wpdb->get_col( "SELECT id FROM {$wpdb->gp_projects} WHERE parent_project_id IS NULL" );
+
+ return $main_projects;
+ }
+
+ /**
+ * Gets the original string that the translated string belongs to.
+ *
+ * @since 0.0.2
+ *
+ * @param WP_Comment $comment The comment object.
+ *
+ * @return GP_Thing|false The original string that the translated string belongs to.
+ */
+ private static function get_original( WP_Comment $comment ) {
+ $post_id = $comment->comment_post_ID;
+ $terms = wp_get_object_terms( $post_id, Helper_Translation_Discussion::LINK_TAXONOMY, array( 'number' => 1 ) );
+ if ( empty( $terms ) ) {
+ return false;
+ }
+
+ return GP::$original->get( $terms[0]->slug );
+ }
+
+ /**
+ * Gets a list with the opt-in emails.
+ *
+ * @since 0.0.2
+ *
+ * @param array $email_addresses The list of emails to be notified.
+ *
+ * @return array The list of emails with the opt-in enabled.
+ */
+ private static function get_opted_in_email_addresses( array $email_addresses ): array {
+ foreach ( $email_addresses as $email_address ) {
+ $user = get_user_by( 'email', $email_address );
+ $gp_default_sort = get_user_option( 'gp_default_sort', $user->ID );
+ if ( 'on' != gp_array_get( $gp_default_sort, 'notifications_optin', 'off' ) ) {
+ $index = array_search( $email_address, $email_addresses, true );
+ if ( false !== $index ) {
+ unset( $email_addresses[ $index ] );
+ }
+ }
+ }
+ return array_values( $email_addresses );
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/includes/class-wporg-notifications.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersjsdiscussionjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/discussion.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/reject-feedback.js
===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/reject-feedback.js (rev 0)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/reject-feedback.js 2022-05-11 08:47:45 UTC (rev 11837)
</ins><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,193 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global $gp, $gp_reject_feedback_settings, document, tb_show */
+( function( $, $gp ) {
+ $( document ).ready(
+ function() {
+ var rowIds = [];
+ var translationIds = [];
+ var originalIds = [];
+ var feedbackForm = '<details><summary class="feedback-summary">Give feedback</summary>' +
+ '<div id="feedback-form">' +
+ '<form>' +
+ '<h3 class="feedback-reason-title">Reason</h3>' +
+ '<ul class="feedback-reason-list">' +
+ getReasonList( 'single' ) +
+ '</ul>' +
+ '<div class="feedback-comment">' +
+ '<label>Comment </label>' +
+ '<textarea name="feedback_comment"></textarea>' +
+ '</div>' +
+ '</form>' +
+ '</div>' +
+ '</details>';
+
+ var modalFeedbackForm =
+ '<div id="reject-feedback-form" style="display:none;">' +
+ '<form>' +
+ '<h3>Reason</h3>' +
+ getReasonList( 'bulk' ) +
+ '<div class="modal-comment">' +
+ '<label>Comment </label>' +
+ '<textarea name="modal_feedback_comment"></textarea>' +
+ '</div>' +
+ '<button id="modal-reject-btn" class="modal-btn">Reject</button>' +
+ '</form>' +
+ '</div>';
+
+ $( 'body' ).append( modalFeedbackForm );
+
+ // Remove click event added to <summary> by wporg-gp-customizations plugin
+ $( $gp.editor.table ).off( 'click', 'summary' );
+
+ $( 'button.reject' ).closest( 'dl,div.status-actions' ).prepend( feedbackForm );
+
+ $( '#bulk-actions-toolbar-top .button, #bulk-actions-toolbar .button' ).click( function( e ) {
+ rowIds = $( 'input:checked', $( 'table#translations th.checkbox' ) ).map( function() {
+ var selectedRow = $( this ).parents( 'tr.preview' );
+ if ( selectedRow.hasClass( 'status-current' ) ) {
+ return selectedRow.attr( 'row' );
+ }
+ $( this ).prop( 'checked', false );
+ return null;
+ } ).get();
+
+ rowIds.forEach( function( rowId ) {
+ var originalId = $gp.editor.original_id_from_row_id( rowId );
+ var translationId = $gp.editor.translation_id_from_row_id( rowId );
+
+ if ( originalId && translationId ) {
+ originalIds.push( originalId );
+ translationIds.push( translationId );
+ }
+ } );
+
+ if ( $( 'select[name="bulk[action]"]' ).val() === 'reject' ) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ if ( ! translationIds.length ) {
+ $( 'form.filters-toolbar.bulk-actions, form#bulk-actions-toolbar-top' ).submit();
+ return;
+ }
+
+ // eslint-disable-next-line no-undef
+ tb_show( 'Reject with Feedback', '#TB_inline?inlineId=reject-feedback-form' );
+ }
+ } );
+
+ $( 'body' ).on( 'click', '#modal-reject-btn', function( e ) {
+ var comment = '';
+ var rejectReason = [];
+ var rejectData = {};
+ var form = $( this ).closest( 'form' );
+
+ form.find( 'input[name="modal_feedback_reason"]:checked' ).each(
+ function() {
+ rejectReason.push( this.value );
+ }
+ );
+
+ comment = form.find( 'textarea[name="modal_feedback_comment"]' ).val();
+
+ if ( ( ! comment.trim().length && ! rejectReason.length ) || ( ! translationIds.length || ! originalIds.length ) ) {
+ $( 'form.filters-toolbar.bulk-actions, form#bulk-actions-toolbar-top' ).submit();
+ }
+
+ rejectData.locale_slug = $gp_reject_feedback_settings.locale_slug;
+ rejectData.reason = rejectReason;
+ rejectData.comment = comment;
+ rejectData.original_id = originalIds;
+ rejectData.translation_id = translationIds;
+ rejectData.is_bulk_reject = true;
+ rejectWithFeedback( rejectData );
+ e.preventDefault();
+ } );
+ }
+ );
+
+ $gp.editor.hooks.set_status_rejected = function() {
+ var button = $( this );
+ var rejectData = {};
+ var rejectReason = [];
+ var comment = '';
+ var div = button.closest( 'div.meta' );
+
+ div.find( 'input[name="feedback_reason"]:checked' ).each(
+ function() {
+ rejectReason.push( this.value );
+ }
+ );
+
+ comment = div.find( 'textarea[name="feedback_comment"]' ).val();
+
+ if ( ! comment.trim().length && ! rejectReason.length ) {
+ $gp.editor.set_status( button, 'rejected' );
+ return;
+ }
+
+ rejectData.locale_slug = $gp_reject_feedback_settings.locale_slug;
+ rejectData.reason = rejectReason;
+ rejectData.comment = comment;
+ rejectData.original_id = [ $gp.editor.current.original_id ];
+ rejectData.translation_id = [ $gp.editor.current.translation_id ];
+
+ rejectWithFeedback( rejectData, button );
+ };
+
+ function rejectWithFeedback( rejectData, button ) {
+ var data = {};
+ var div = {};
+ if ( button ) {
+ div = button.closest( 'div.meta' );
+ }
+
+ data = {
+ action: 'reject_with_feedback',
+ data: rejectData,
+
+ _ajax_nonce: $gp_reject_feedback_settings.nonce,
+ };
+
+ $.ajax(
+ {
+ type: 'POST',
+
+ url: $gp_reject_feedback_settings.url,
+ data: data,
+ }
+ ).done(
+ function() {
+ if ( rejectData.is_bulk_reject ) {
+ $( 'form.filters-toolbar.bulk-actions, form#bulk-actions-toolbar-top' ).submit();
+ } else {
+ $gp.editor.set_status( button, 'rejected' );
+ div.find( 'input[name="feedback_reason"]' ).prop( 'checked', false );
+ div.find( 'textarea[name="feedback_comment"]' ).val( '' );
+ }
+ }
+ );
+ }
+
+ function getReasonList( displayType ) {
+ var rejectReasons = $gp_reject_feedback_settings.reject_reasons;
+
+ var rejectList = '';
+ var prefix = '';
+ var suffix = '';
+ var inputName = '';
+ if ( displayType === 'single' ) {
+ prefix = '<li><label>';
+ suffix = '</label></li>';
+ inputName = 'feedback_reason';
+ } else {
+ prefix = '<div class="modal-item"><label>';
+ suffix = '</div></label>';
+ inputName = 'modal_feedback_reason';
+ }
+
+ // eslint-disable-next-line vars-on-top
+ for ( var reason in rejectReasons ) {
+ rejectList += prefix + '<input type="checkbox" name="' + inputName + '" value="' + reason + '" />' + rejectReasons[ reason ] + suffix;
+ }
+ return rejectList;
+ }
+}( jQuery, $gp )
+);
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelpersjstranslationhelpersjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/translation-helpers.js</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/gp-translation-helpers/js/translation-helpers.js (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/js/translation-helpers.js 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,121 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/* global $gp, window */
+$gp.translation_helpers = (
+ function( $ ) {
+ return {
+ init: function( table, fetchNow ) {
+ $gp.translation_helpers.table = table;
+ $gp.translation_helpers.install_hooks();
+ if ( fetchNow ) {
+ $gp.translation_helpers.fetch( false, $( '.translations' ) );
+ }
+ },
+ install_hooks: function() {
+ $( $gp.translation_helpers.table )
+ .on( 'beforeShow', '.editor', $gp.translation_helpers.hooks.initial_fetch )
+ .on( 'click', '.helpers-tabs li', $gp.translation_helpers.hooks.tab_select )
+ .on( 'click', 'a.comment-reply-link', $gp.translation_helpers.hooks.reply_comment_form );
+ },
+ initial_fetch: function( $element ) {
+ var $helpers = $element.find( '.translation-helpers' );
+
+ if ( $helpers.hasClass( 'loaded' ) || $helpers.hasClass( 'loading' ) ) {
+ return;
+ }
+
+ $gp.translation_helpers.fetch( false, $element );
+ },
+ fetch: function( which, $element ) {
+ var $helpers;
+ if ( $element ) {
+ $helpers = $element.find( '.translation-helpers' );
+ } else {
+ $helpers = $( '.editor:visible, .translations' ).find( '.translation-helpers' ).first();
+ }
+
+ var originalId = $helpers.parent().attr( 'row' ); // eslint-disable-line vars-on-top
+ var replytocom = $helpers.parent().attr( 'replytocom' ); // eslint-disable-line vars-on-top
+ var requestUrl = $gp_translation_helpers_settings.th_url + originalId + '?nohc'; // eslint-disable-line
+
+ if ( which ) {
+ requestUrl = requestUrl + '&helpers[]=' + which;
+ } else {
+ $helpers.find( 'div.helper:not(.loaded) ' ).each( function() {
+ requestUrl = requestUrl + '&helpers[]=' + $( this ).data( 'helper' );
+ } );
+ }
+ requestUrl = requestUrl + '&replytocom=' + replytocom;
+
+ if ( $helpers.find( 'div:first' ).is( ':not(.loaded)' ) ) {
+ $helpers.addClass( 'loading' );
+ }
+
+ $.getJSON(
+ requestUrl,
+ function( data ) {
+ $helpers.addClass( 'loaded' ).removeClass( 'loading' );
+ $.each( data, function( id, result ) {
+ jQuery( '.helpers-tabs li[data-tab="' + id + '"]' ).find( '.count' ).text( '(' + result.count + ')' );
+ $( '#' + id ).find( '.loading' ).remove();
+ $( '#' + id ).find( '.async-content' ).html( result.content );
+ } );
+ $( '.helper-translation-discussion' ).find( 'form.comment-form' ).removeAttr( 'novalidate' );
+ }
+ );
+ },
+ tab_select: function( $tab ) {
+ var tabId = $tab.attr( 'data-tab' );
+
+ $tab.siblings().removeClass( 'current' );
+ $tab.parents( '.translation-helpers ' ).find( '.helper' ).removeClass( 'current' );
+
+ $tab.addClass( 'current' );
+ $( '#' + tabId ).addClass( 'current' );
+ },
+ reply_comment_form: function( $comment ) {
+ var commentId = $comment.attr( 'data-commentid' );
+ $( '#comment-reply-' + commentId ).toggle().find( 'textarea' ).focus();
+ if ( 'Reply' === $comment.text() ) {
+ $comment.text( 'Cancel Reply' );
+ } else {
+ $comment.text( 'Reply' );
+ }
+ },
+ hooks: {
+ initial_fetch: function() {
+ $gp.translation_helpers.initial_fetch( $( this ) );
+ return false;
+ },
+ tab_select: function() {
+ $gp.translation_helpers.tab_select( $( this ) );
+ return false;
+ },
+ reply_comment_form: function( event ) {
+ event.preventDefault();
+ $gp.translation_helpers.reply_comment_form( $( this ) );
+ return false;
+ },
+ },
+ };
+ }( jQuery )
+);
+
+jQuery( function( $ ) {
+ var _oldShow = $.fn.show;
+ $gp.translation_helpers.init( $( '.translations' ), true );
+ if ( typeof window.newShowFunctionAttached === 'undefined' ) {
+ window.newShowFunctionAttached = true;
+ $.fn.show = function( speed, oldCallback ) {
+ return $( this ).each( function() {
+ var obj = $( this ),
+ newCallback = function() {
+ if ( $.isFunction( oldCallback ) ) {
+ oldCallback.apply( obj );
+ }
+ };
+
+ obj.trigger( 'beforeShow' );
+ _oldShow.apply( obj, [ speed, newCallback ] );
+ } );
+ };
+ }
+} );
</ins></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatescommentsectionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/comment-section.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/gp-translation-helpers/templates/comment-section.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/comment-section.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,37 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<ul class="discussion-list">
+ <?php
+ wp_list_comments(
+ array(
+ 'style' => 'ul',
+ 'type' => 'comment',
+ 'callback' => 'gth_discussion_callback',
+ 'translation_id' => $translation_id,
+ 'original_permalink' => $original_permalink,
+ 'locale_slug' => $locale_slug,
+ ),
+ $comments
+ );
+ ?>
+</ul><!-- .discussion-list -->
+<?php
+ comment_form(
+ $args = array(
+ 'title_reply' => __( 'Discuss this string' ),
+ /* translators: username */
+ 'title_reply_to' => __( 'Reply to %s' ),
+ 'title_reply_before' => '<h6 id="reply-title" class="discuss-title">',
+ 'title_reply_after' => '</h6>',
+ 'id_form' => 'commentform-' . $post_id,
+ 'comment_notes_after' => implode(
+ "\n",
+ array(
+ '<input type="hidden" name="comment_locale" value="' . esc_attr( $locale_slug ) . '" />',
+ '<input type="hidden" name="translation_id" value="' . esc_attr( $translation_id ) . '" />',
+ '<input type="hidden" name="redirect_to" value="' . esc_url( home_url( $_SERVER['REQUEST_URI'] ) ) . '" />',
+
+ )
+ ),
+ ),
+ $post_id
+ );
+ ?>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/comment-section.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesdiscussionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/discussion.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/gp-translation-helpers/templates/discussion.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/discussion.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,32 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<h1 class="discussion-heading"><?php echo esc_html( $original->singular ); ?></h1>
+
+<?php if ( $string_translation ) : ?>
+ <h2>Translation: <?php echo esc_html( $string_translation ); ?></h2>
+<?php endif; ?>
+
+<?php if ( $original_translation_permalink ) : ?>
+<a href="<?php echo esc_url( $original_translation_permalink ); ?>">View translation</a>
+<?php endif; ?>
+
+<div class="discussion-wrapper">
+ <?php if ( $number = count( $comments ) ) : ?>
+ <h4>
+ <?php
+ /* translators: number of comments. */
+ printf( _n( '%s Comment', '%s Comments', $number ), number_format_i18n( $number ) );
+ ?>
+ <?php if ( $original_translation_permalink ) : ?>
+ <span class="comments-selector">
+ <a href="<?php echo esc_html( $original_permalink ); ?>">Original Permalink page</a>
+ <?php foreach ( $locales_with_comments as $locale_with_comments ) : ?>
+
+ <a class="<?php echo esc_attr( $locale_with_comments == $locale_slug ? 'active-locale-link' : '' ); ?>" href="<?php echo esc_attr( $args['original_permalink'] . $locale_with_comments . '/default' ); ?>">
+ | <?php echo esc_html( $locale_with_comments ); ?>
+ </a>
+ <?php endforeach; ?>
+ </span>
+ <?php endif; ?>
+ </h4>
+ <?php endif; ?>
+ <?php gp_tmpl_load( 'comment-section', get_defined_vars(), dirname( __FILE__ ) ); ?>
+</div>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/discussion.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesoriginalpermalinktemplatephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink-template.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/gp-translation-helpers/templates/original-permalink-template.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink-template.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,93 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<table id="translations" class="translations clear">
+ <thead>
+
+ </thead>
+
+ <tbody>
+ <tr class="editor untranslated priority-normal no-warnings no-translations" id="editor-12561869" row="12561869" style="display: table-row;">
+ <td colspan="5">
+ <div class="editor-panel">
+ <div class="editor-panel__left">
+ <div class="panel-header">
+ <h3>Original</h3>
+ </div>
+ <div class="panel-content">
+ <div class="source-string strings">
+ <div class="source-string__singular">
+ <span class="original"><?php echo esc_html( $original->singular ); ?></span>
+ <span aria-hidden="true" class="original-raw"><?php echo esc_html( $original->singular ); ?></span>
+ </div>
+ </div>
+
+ <div class="source-details">
+ <details class="source-details__references" close="">
+ <summary>Comments all
+ <?php foreach ( $locales_with_comments as $locale_with_comments ) : ?>
+ <a class="<?php echo esc_attr( $locale_with_comments == $locale_slug ? 'active-locale-link' : '' ); ?>" href="<?php echo esc_attr( $args['original_permalink'] . $locale_with_comments . '/default1' ); ?>">
+ | <?php echo esc_html( $locale_with_comments ); ?>
+ </a>
+ <?php endforeach; ?>
+ </summary>
+
+ <?php gp_tmpl_load( 'comment-section', get_defined_vars(), dirname( __FILE__ ) ); ?>
+
+ </details>
+ </div>
+ <div class="suggestions-wrapper">
+ <details class="suggestions__other-languages initialized" data-nonce="b1ee0a8267" open="">
+ <summary>All Languages</summary>
+ <?php if ( $translations_by_locale ) : ?>
+ <ul class="suggestions-list">
+ <?php
+ foreach ( $translations_by_locale as $_locale => $translation ) :
+ ?>
+
+ <li>
+ <div class="translation-suggestion with-tooltip" tabindex="0" role="button" aria-pressed="false" aria-label="Copy translation">
+ <span class="translation-suggestion__translation">
+ <?php echo esc_html( strtoupper( $_locale ) ) . ' - ' . esc_html( $translation ); ?>
+ </span>
+ </div>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ <?php else : ?>
+ <p class="no-suggestions">No suggestions.</p>
+ <?php endif; ?>
+ </details>
+ </div>
+
+
+ </div>
+ </div>
+
+ <div class="editor-panel__right">
+ <div class="panel-header">
+ <h3>Meta</h3>
+ </div>
+ <div class="panel-content">
+ <div class="meta">
+ <dl>
+ <dt>Status:</dt>
+ <dd><?php echo esc_html( $no_of_translations ) . ' of ' . esc_html( count( $all_translation_sets ) ) . ' languages translated'; ?></dd>
+ </dl>
+ <dl>
+ <dt>Priority of the original:</dt>
+ <dd><?php echo esc_html( $priority ); ?></dd>
+ </dl>
+ <div class="source-details">
+ <details class="source-details__references">
+ <summary>References</summary>
+ <ul>
+ <li><a target="_blank" href="https://plugins.trac.wordpress.org/browser/friends/trunk/templates/frontend/messages/message-form.php#L36">templates/frontend/messages/message-form.php:36</a></li>
+ </ul>
+ </details>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink-template.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatesoriginalpermalinkphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink.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/gp-translation-helpers/templates/original-permalink.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,158 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+$breadcrumbs = array(
+ gp_project_links_from_root( $project ),
+);
+
+if ( $translation_set ) {
+ /* translators: 1: Translation set name, 2: Project name. */
+ gp_title( sprintf( __( 'Discussion < %1$s < %2$s < GlotPress', 'glotpress' ), $translation_set->name, $project->name ) );
+ $breadcrumbs[] = $translation_set->name;
+} else {
+ /* translators: Project name. */
+ gp_title( sprintf( __( 'Discussion < %s < GlotPress', 'glotpress' ), $project->name ) );
+}
+
+gp_breadcrumb( $breadcrumbs );
+gp_enqueue_scripts( array( 'gp-editor', 'gp-translations-page', 'gp-translation-discussion-js' ) );
+wp_localize_script(
+ 'gp-translations-page',
+ '$gp_translations_options',
+ array(
+ 'sort' => __( 'Sort', 'glotpress' ),
+ 'filter' => __(
+ 'Filter',
+ 'glotpress'
+ ),
+ )
+);
+gp_enqueue_style( 'gp-discussion-css' );
+gp_tmpl_header();
+
+?>
+
+<div id="original" class="clear">
+<h1>
+ <?php
+ if ( $original->plural ) {
+ esc_html_e( 'Singular: ' );
+ }
+
+ echo esc_translation( $original->singular );
+ if ( $original->plural ) {
+ echo '<br />';
+ esc_html_e( 'Plural: ' );
+ echo esc_translation( $original->plural );
+ }
+ ?>
+</h1>
+<?php
+ $generate_permalink = function ( $translation_id ) use ( $project, $locale_slug, $translation_set_slug, $original_id ) {
+ return GP_Route_Translation_Helpers::get_translation_permalink(
+ $project,
+ $locale_slug,
+ $translation_set_slug,
+ $original_id,
+ $translation_id
+ );
+ }
+ ?>
+<?php if ( $translation ) : ?>
+ <?php $translation_permalink = $generate_permalink( $translation->id ); ?>
+ <p>
+
+ <?php echo esc_html( ucfirst( $translation->status ) ); ?> translation:
+ <?php
+ if ( ( '' == $translation->translation_1 ) && ( '' == $translation->translation_2 ) &&
+ ( '' == $translation->translation_3 ) && ( '' == $translation->translation_4 ) &&
+ ( '' == $translation->translation_5 ) ) :
+ ?>
+ <strong><?php echo $translation_permalink ? gp_link( $translation_permalink, esc_translation( $translation->translation_0 ) ) : esc_translation( $translation->translation_0 ); ?></strong>
+ <?php else : ?>
+ <ul id="translation-list">
+ <?php for ( $i = 0; $i <= 5; $i++ ) : ?>
+ <?php if ( '' != $translation->{'translation_' . $i} ) : ?>
+ <li>
+ <?php echo $translation_permalink ? gp_link( $translation_permalink, esc_translation( $translation->{'translation_' . $i} ) ) : esc_translation( $translation->{'translation_' . $i} ); ?>
+ </li>
+ <?php endif ?>
+ <?php endfor ?>
+ </ul>
+ <?php endif ?>
+ </p>
+<?php elseif ( $existing_translations ) : ?>
+ <?php foreach ( $existing_translations as $e ) : ?>
+ <p>
+ <?php $translation_permalink = $generate_permalink( $e->id ); ?>
+ <?php echo esc_html( ucfirst( $e->status ) ); ?> translation:
+ <?php
+ if ( ( '' == $e->translation_1 ) && ( '' == $e->translation_2 ) &&
+ ( '' == $e->translation_3 ) && ( '' == $e->translation_4 ) &&
+ ( '' == $e->translation_5 ) ) :
+ ?>
+ <strong><?php echo $translation_permalink ? gp_link( $translation_permalink, esc_translation( $e->translation_0 ) ) : esc_translation( $e->translation_0 ); ?></strong>
+ <?php else : ?>
+ <ul id="translation-list">
+ <?php for ( $i = 0; $i <= 5; $i++ ) : ?>
+ <?php if ( '' != $e->{'translation_' . $i} ) : ?>
+ <li>
+ <?php echo $translation_permalink ? gp_link( $translation_permalink, esc_translation( $e->{'translation_' . $i} ) ) : esc_translation( $e->{'translation_' . $i} ); ?>
+ </li>
+ <?php endif ?>
+ <?php endfor ?>
+ </ul>
+ <?php endif ?>
+ </p>
+ <?php endforeach; ?>
+<?php elseif ( $translation_set ) : ?>
+ <?php
+ $translate_url = GP_Route_Translation_Helpers::get_translation_permalink(
+ $project,
+ $locale_slug,
+ $translation_set_slug,
+ $original_id
+ );
+ ?>
+ <p>
+ <a href="<?php echo esc_url( $translate_url ); ?>"><?php esc_html_e( 'This string has no translation in this language.' ); ?></a>
+ </p>
+<?php else : ?>
+ <p>
+ <?php esc_html_e( 'This string has no translation in this language.' ); ?>
+ </p>
+<?php endif; ?>
+<div class="translations" row="<?php echo esc_attr( $row_id . ( $translation ? '-' . $translation->id : '' ) ); ?>" replytocom="<?php echo esc_attr( gp_get( 'replytocom' ) ); ?>" >
+<div class="translation-helpers">
+ <nav>
+ <ul class="helpers-tabs">
+ <?php
+ $is_first_class = 'current';
+ foreach ( $sections as $section ) {
+ // TODO: printf.
+ echo "<li class='{$is_first_class}' data-tab='{$section['id']}'>" . esc_html( $section['title'] ) . '<span class="count">' . esc_html( $section['count'] ? ( '(' . $section['count'] . ')' ) : '' ) . '</span></li>'; // phpcs:ignore: XSS OK.
+ $is_first_class = '';
+ }
+ ?>
+ </ul>
+ </nav>
+ <?php
+ $is_first_class = 'current';
+ foreach ( $sections as $section ) {
+ printf( '<div class="%s helper %s %s" id="%s" data-helper="%s">', esc_attr( $section['classname'] ), esc_attr( $is_first_class ), $section['load_inline'] ? 'loaded' : '', esc_attr( $section['id'] ), esc_attr( $section['helper'] ) );
+ if ( $section['has_async_content'] ) {
+ echo '<div class="async-content">';
+ }
+
+ echo $section['content']; // phpcs:ignore XSS OK.
+ if ( $section['has_async_content'] ) {
+ echo '</div>';
+ }
+ echo '</div>';
+ $is_first_class = '';
+ }
+ ?>
+</div>
+</div>
+</div>
+<?php
+
+gp_tmpl_footer();
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/original-permalink.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsgptranslationhelperstemplatestranslationhelpersphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/translation-helpers.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/gp-translation-helpers/templates/translation-helpers.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/translation-helpers.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,26 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<td colspan="3" class="translation-helpers">
+ <nav>
+ <ul class="helpers-tabs">
+ <?php
+ $is_first_class = 'current';
+ foreach ( $sections as $section ) {
+ // TODO: printf.
+ echo "<li class='{$is_first_class}' data-tab='{$section['id']}'>" . esc_html( $section['title'] ) . '<span class="count"></span></li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $is_first_class = '';
+ }
+ ?>
+ </ul>
+ </nav>
+ <?php
+ $is_first_class = 'current';
+ foreach ( $sections as $section ) {
+ printf( '<div class="%s helper %s" id="%s">', esc_attr( $section['classname'] ), esc_attr( $is_first_class ), esc_attr( $section['id'] ) );
+ if ( ! $section['has_async_content'] ) {
+ echo '<div class="async-content"></div>';
+ }
+ echo $section['content']; // phpcs:ignore XSS OK.
+ echo '</div>';
+ $is_first_class = '';
+ }
+ ?>
+</td>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/gp-translation-helpers/templates/translation-helpers.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomizationstemplatessettingseditphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings-edit.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-customizations/templates/settings-edit.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings-edit.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,66 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * The user settings block
+ *
+ * A single table that contains all of the user settings, which is included as part of gp-templates/settings.php.
+ *
+ * @link http://glotpress.org
+ *
+ * @package GlotPress
+ * @since 2.0.0
+ */
+
+$gp_per_page = (int) get_user_option( 'gp_per_page' );
+if ( 0 === $gp_per_page ) {
+ $gp_per_page = 15;
+}
+
+$gp_default_sort = get_user_option( 'gp_default_sort' );
+if ( ! is_array( $gp_default_sort ) ) {
+ $gp_default_sort = array(
+ 'by' => 'priority',
+ 'how' => 'desc',
+ );
+}
+?>
+
+<table class="form-table">
+ <tr>
+ <th><label for="per_page"><?php _e( 'Number of items per page:', 'glotpress' ); ?></label></th>
+ <td><input type="number" id="per_page" name="per_page" value="<?php echo esc_attr( $gp_per_page ); ?>"/></td>
+ </tr>
+ <tr>
+ <th><label for="default_sort[by]"><?php _e( 'Default Sort By:', 'glotpress' ); ?></label></th>
+ <td>
+ <?php
+ $sort_bys = wp_list_pluck( gp_get_sort_by_fields(), 'title' );
+
+ echo gp_radio_buttons( 'default_sort[by]', $sort_bys, gp_array_get( $gp_default_sort, 'by', 'priority' ) );
+ ?>
+ </td>
+ </tr>
+ <tr>
+ <th><label for="default_sort[how]"><?php _e( 'Default Sort Order:', 'glotpress' ); ?></label></th>
+ <td>
+ <?php
+ echo gp_radio_buttons(
+ 'default_sort[how]',
+ array(
+ 'asc' => __( 'Ascending', 'glotpress' ),
+ 'desc' => __( 'Descending', 'glotpress' ),
+ ),
+ gp_array_get( $gp_default_sort, 'how', 'desc' )
+ );
+ ?>
+ </td>
+ </tr>
+ <!-- Including the "notifications_optin" in the "default_sort" array is a hack.
+ If we include it in the future in the GlotPress core, it would be interesting to put
+ this value in it own option item.
+ I do this because the post values are processed in the GP_Route_Settings->settings_post,
+ and I have to modify the GlotPress core to add a new configuration item. -->
+ <tr>
+ <th><label for="default_sort[notifications_optin]"><?php _e( 'I want to receive notifications of discussions:', 'glotpress' ); ?></label></th>
+ <td><input type="checkbox" id="default_sort[notifications_optin]" name="default_sort[notifications_optin]" <?php gp_checked( 'on' == gp_array_get( $gp_default_sort, 'notifications_optin', 'off' ) ); ?> /></td>
+ </tr>
+</table>
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings-edit.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginswporggpcustomizationstemplatessettingsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings.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-customizations/templates/settings.php (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings.php 2022-05-11 08:47:45 UTC (rev 11837)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,39 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * The settings page
+ *
+ * Displays the settings page for a user.
+ *
+ * @link http://glotpress.org
+ *
+ * @package GlotPress
+ * @since 2.0.0
+ */
+
+gp_title( __( 'Your Settings < GlotPress', 'glotpress' ) );
+gp_breadcrumb( array( __( 'Your Settings', 'glotpress' ) ) );
+gp_tmpl_header();
+
+$per_page = (int) get_user_option( 'gp_per_page' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+if ( 0 === $per_page ) {
+ $per_page = 15; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+}
+
+$default_sort = get_user_option( 'gp_default_sort' );
+if ( ! is_array( $default_sort ) ) {
+ $default_sort = array(
+ 'by' => 'priority',
+ 'how' => 'desc',
+ );
+}
+?>
+<h2><?php _e( 'Your Settings', 'glotpress' ); ?></h2>
+<form action="" method="post">
+ <?php require_once __DIR__ . '/settings-edit.php'; ?>
+ <br>
+ <?php gp_route_nonce_field( 'update-settings_' . get_current_user_id() ); ?>
+ <input type="submit" name="submit" value="<?php esc_attr_e( 'Save Settings', 'glotpress' ); ?>">
+</form>
+
+<?php
+gp_tmpl_footer();
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-customizations/templates/settings.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>
</body>
</html>