Add PSR HTTP Message Interfaces and Dependencies

- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards.
- Added getallheaders function to retrieve HTTP headers in a compatible manner.
- Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts.
- Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
2025-12-28 12:44:00 +01:00
parent cf600ae727
commit cd264483f8
410 changed files with 60841 additions and 16 deletions

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<polyline points="15 6 9 12 15 18" fill="none" stroke="#444" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 173 B

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<polyline points="9 6 15 12 9 18" fill="none" stroke="#444" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 172 B

View File

@@ -0,0 +1 @@
<svg width="16" height="8" viewBox="0 0 16 8" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.4497 7.77152L15.8438 4.37741C16.0521 4.16913 16.0521 3.83144 15.8438 3.62317L12.4497 0.229053C12.2414 0.0207729 11.9037 0.0207729 11.6954 0.229053C11.4872 0.437332 11.4872 0.775021 11.6954 0.9833L14.1791 3.46696H0.533333C0.238781 3.46696 0 3.70574 0 4.00029C0 4.29484 0.238781 4.53362 0.533333 4.53362H14.1791L11.6954 7.01728C11.4872 7.22556 11.4872 7.56324 11.6954 7.77152C11.9037 7.9798 12.2414 7.9798 12.4497 7.77152Z" fill="#666666"></path></svg>

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#222" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M22 12.5c0-.491-.005-1.483-.016-1.976c-.065-3.065-.098-4.598-1.229-5.733c-1.131-1.136-2.705-1.175-5.854-1.254a115 115 0 0 0-5.802 0c-3.149.079-4.723.118-5.854 1.254c-1.131 1.135-1.164 2.668-1.23 5.733a69 69 0 0 0 0 2.952c.066 3.065.099 4.598 1.23 5.733c1.131 1.136 2.705 1.175 5.854 1.254q1.204.03 2.401.036"></path><path d="m7 8.5l2.942 1.74c1.715 1.014 2.4 1.014 4.116 0L17 8.5m5 9h-8m8 0c0-.7-1.994-2.008-2.5-2.5m2.5 2.5c0 .7-1.994 2.009-2.5 2.5"></path></g></svg>

After

Width:  |  Height:  |  Size: 654 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M21.3334 17.056V21.3333H17.0667V17.056H21.3334ZM21.3334 10.6603V14.9227H17.0667V10.6603H21.3334ZM14.9334 10.6603H10.6667V14.9227H14.9334V10.6603ZM14.9334 17.056H10.6667V21.3333H14.9334V17.056Z" fill="#27AD95"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M2.1333 3.2C2.1333 2.35131 2.47044 1.53737 3.07056 0.937258C3.67068 0.337142 4.48461 0 5.3333 0L22.8416 0L29.8666 7.02507V28.8C29.8666 29.6487 29.5295 30.4626 28.9294 31.0627C28.3293 31.6629 27.5153 32 26.6666 32H5.3333C4.48461 32 3.67068 31.6629 3.07056 31.0627C2.47044 30.4626 2.1333 29.6487 2.1333 28.8V3.2ZM23.4666 8.52693H8.5333V23.4667H23.4666V8.52693Z" fill="#27AD95"></path></svg>

After

Width:  |  Height:  |  Size: 757 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><path fill="currentColor" d="M52 144H36a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8h16a36 36 0 0 0 0-72m0 56h-8v-40h8a20 20 0 0 1 0 40m169.53-4.91a8 8 0 0 1 .25 11.31A30.06 30.06 0 0 1 200 216c-17.65 0-32-16.15-32-36s14.35-36 32-36a30.06 30.06 0 0 1 21.78 9.6a8 8 0 0 1-11.56 11.06A14.24 14.24 0 0 0 200 160c-8.82 0-16 9-16 20s7.18 20 16 20a14.24 14.24 0 0 0 10.22-4.66a8 8 0 0 1 11.31-.25M128 144c-17.65 0-32 16.15-32 36s14.35 36 32 36s32-16.15 32-36s-14.35-36-32-36m0 56c-8.82 0-16-9-16-20s7.18-20 16-20s16 9 16 20s-7.18 20-16 20m-80-80a8 8 0 0 0 8-8V40h88v48a8 8 0 0 0 8 8h48v16a8 8 0 0 0 16 0V88a8 8 0 0 0-2.34-5.66l-56-56A8 8 0 0 0 152 24H56a16 16 0 0 0-16 16v72a8 8 0 0 0 8 8m112-68.69L188.69 80H160Z"/></svg>

After

Width:  |  Height:  |  Size: 789 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="#222" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="M21 14.016c-.046-.992-.723-1.016-1.643-1.016c-1.416 0-1.651.338-1.651 1.667v1.666c0 1.329.235 1.667 1.651 1.667c.92 0 1.597-.024 1.643-1.016M10.295 15.5c0 1.38-1.106 2.5-2.47 2.5c-.309 0-.463 0-.577-.067c-.275-.16-.247-.485-.247-.766v-3.334c0-.281-.028-.606.247-.766c.114-.067.268-.067.576-.067c1.365 0 2.47 1.12 2.47 2.5M14 18c-.776 0-1.165 0-1.406-.244s-.241-.637-.241-1.423v-1.666c0-.786 0-1.179.241-1.423S13.224 13 14 13s1.165 0 1.406.244s.241.637.241 1.423v1.666c0 .786 0 1.179-.241 1.423S14.776 18 14 18"></path><path d="M15 22h-4.273c-3.26 0-4.892 0-6.024-.798a4.1 4.1 0 0 1-.855-.805C3 19.331 3 17.797 3 14.727v-2.545c0-2.963 0-4.445.469-5.628c.754-1.903 2.348-3.403 4.37-4.113C9.095 2 10.668 2 13.818 2c1.798 0 2.698 0 3.416.252c1.155.406 2.066 1.263 2.497 2.35C20 5.278 20 6.125 20 7.818V10"></path><path d="M3 12a3.333 3.333 0 0 1 3.333-3.333c.666 0 1.451.116 2.098-.057A1.67 1.67 0 0 0 9.61 7.43c.173-.647.057-1.432.057-2.098A3.333 3.333 0 0 1 13 2"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="-2 -2 24 24"><g fill="currentColor"><path d="M8.695 6.937v1.377H7.687v1.683h1.008V15h2.072V9.997h1.39s.131-.807.194-1.69h-1.576v-1.15c0-.173.226-.404.45-.404h1.128V5h-1.535C8.644 5 8.695 6.685 8.695 6.937"/><path d="M4 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zm0-2h12a4 4 0 0 1 4 4v12a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4"/></g></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.83333 7.33529C7.83333 6.9671 8.13181 6.66862 8.5 6.66862C8.86819 6.66862 9.16667 6.9671 9.16667 7.33529V9.33529H11.1667C11.5349 9.33529 11.8333 9.63376 11.8333 10.002C11.8333 10.3701 11.5349 10.6686 11.1667 10.6686H9.16667V12.6686C9.16667 13.0368 8.86819 13.3353 8.5 13.3353C8.13181 13.3353 7.83333 13.0368 7.83333 12.6686V10.6686H5.83333C5.46514 10.6686 5.16667 10.3701 5.16667 10.002C5.16667 9.63376 5.46514 9.33529 5.83333 9.33529H7.83333V7.33529Z" fill="#373737"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M16.292 4.85151C16.288 4.84722 16.2838 4.84296 16.2796 4.83874L11.6491 0.208202L11.6435 0.202733C11.5225 0.0788515 11.3536 0.00195312 11.1667 0.00195312H1.16667C0.798477 0.00195312 0.5 0.30043 0.5 0.66862V18.002C0.5 18.3701 0.798477 18.6686 1.16667 18.6686H15.8333C16.2015 18.6686 16.5 18.3701 16.5 18.002V5.33529C16.5 5.14482 16.4201 4.973 16.292 4.85151ZM10.5 6.00195H15.1667V17.3353H1.83333V1.33529H10.5V6.00195ZM14.2239 4.66862H11.8333V2.2781L14.2239 4.66862Z" fill="#373737"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="-2 -2 24 24"><g fill="currentColor"><path d="M15 11.13v3.697h-2.143v-3.45c0-.866-.31-1.457-1.086-1.457c-.592 0-.945.398-1.1.784c-.056.138-.071.33-.071.522v3.601H8.456s.029-5.842 0-6.447H10.6v.913l-.014.021h.014v-.02c.285-.44.793-1.066 1.932-1.066c1.41 0 2.468.922 2.468 2.902M6.213 5.271C5.48 5.271 5 5.753 5 6.385c0 .62.466 1.115 1.185 1.115h.014c.748 0 1.213-.496 1.213-1.115c-.014-.632-.465-1.114-1.199-1.114m-1.086 9.556h2.144V8.38H5.127z"/><path d="M4 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zm0-2h12a4 4 0 0 1 4 4v12a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4"/></g></svg>

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1 @@
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_46_1321)"><path fill-rule="evenodd" clip-rule="evenodd" d="M32 16.5C32 25.3366 24.8366 32.5 16 32.5C7.16344 32.5 0 25.3366 0 16.5C0 7.66344 7.16344 0.5 16 0.5C24.8366 0.5 32 7.66344 32 16.5ZM9.0733 15.6079C8.65674 15.1913 8.65674 14.5159 9.0733 14.0994C9.48986 13.6828 10.1652 13.6828 10.5818 14.0994L15.3592 18.8768L24.2713 9.96466C24.6879 9.54811 25.3633 9.54811 25.7798 9.96466C26.1964 10.3812 26.1964 11.0566 25.7798 11.4732L16.124 21.129C15.8138 21.4392 15.36 21.5184 14.9765 21.3665C14.8324 21.3151 14.6971 21.2316 14.5816 21.1162L9.0733 15.6079Z" fill="#27AD95"></path><path d="M30.9333 16.5C30.9333 24.7475 24.2475 31.4333 16 31.4333C7.75255 31.4333 1.06667 24.7475 1.06667 16.5C1.06667 8.25255 7.75255 1.56667 16 1.56667C24.2475 1.56667 30.9333 8.25255 30.9333 16.5ZM26.5341 9.21042L25.7798 9.96466L26.5341 9.21042C25.701 8.3773 24.3502 8.3773 23.5171 9.21042L15.3592 17.3683L11.336 13.3451C10.5029 12.512 9.15217 12.512 8.31905 13.3451C7.48594 14.1783 7.48593 15.529 8.31905 16.3621L13.8274 21.8705C14.0523 22.0954 14.3174 22.261 14.6007 22.3649C15.362 22.6588 16.2612 22.5003 16.8783 21.8832L26.5341 12.2274C27.3672 11.3943 27.3672 10.0435 26.5341 9.21042Z" stroke="#27AD95" stroke-width="2.13333"></path></g><defs><clipPath id="clip0_46_1321"><rect width="32" height="32" fill="white" transform="translate(0 0.5)"></rect></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="currentColor"><path d="M2.5 12c0-4.478 0-6.718 1.391-8.109S7.521 2.5 12.001 2.5c4.478 0 6.717 0 8.108 1.391S21.5 7.521 21.5 12c0 4.478 0 6.718-1.391 8.109S16.479 21.5 12 21.5c-4.478 0-6.717 0-8.109-1.391c-1.39-1.392-1.39-3.63-1.39-8.109"/><path d="m7 17l4.194-4.193M17 7l-4.193 4.194m0 0L9.777 7H7l4.194 5.807m1.613-1.614L17 17h-2.778l-3.028-4.193"/></g></svg>

After

Width:  |  Height:  |  Size: 553 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path fill="#30b230" d="M20.433 14.173a.75.75 0 0 0-1.491-.16zm-16.12-.08l-.746.08zm-.184-1.705l.746-.08zm4.631-1.454l.655.365zm1.79-3.209l-.655-.365zm2.9 0l-.655.366zm1.79 3.209l.655-.365zm.764 1.025l-.303.687zm1.467-.714l-.53-.531zm-1.018.777l-.102-.743zm-9.923-.777l-.53.532zm1.017.777l.102-.743zm.45-.063l.301.687zm-2.285 8.194l.5-.559zm12.576 0l-.5-.559zm1.694-9.653l-.008.75zm-1.117-.52l.567-.49zm-5.957-3.197l-.341-.668zm-1.816 0l.341-.668zm1.996 13.467h-2.176v1.5h2.176zm-8.03-6.237l-.183-1.705l-1.491.16l.183 1.705zm4.357-2.714l1.79-3.208l-1.31-.73l-1.79 3.208zm3.38-3.208l1.79 3.208l1.31-.73l-1.79-3.209zm1.79 3.208c.162.29.31.56.455.765c.149.211.351.445.662.582l.604-1.373c.056.024.046.05-.039-.071a8 8 0 0 1-.372-.633zm2.356-.585c-.258.258-.412.41-.533.507c-.115.093-.117.066-.057.058l.205 1.486c.336-.047.595-.216.796-.378c.195-.158.412-.376.648-.61zm-1.24 1.932c.269.118.565.159.855.119l-.205-1.486a.1.1 0 0 1-.045-.006zm-9.7-.87c.235.235.452.453.647.61c.201.164.46.332.796.379l.205-1.486c.06.008.058.035-.057-.058a8 8 0 0 1-.533-.507zm2.104-1.207a8 8 0 0 1-.373.633c-.084.12-.094.095-.038.07l.604 1.374c.31-.137.514-.37.662-.582c.144-.206.293-.475.455-.765zm-.661 2.196c.29.04.586-.001.854-.12l-.604-1.372a.1.1 0 0 1-.045.006zm3.468 7.485c-1.438 0-2.445-.001-3.213-.1c-.748-.095-1.17-.273-1.487-.556l-1 1.118c.63.564 1.39.81 2.296.926c.886.113 2.006.112 3.404.112zm-7.345-6.077c.148 1.378.266 2.727.466 3.821c.101.552.229 1.072.405 1.523c.175.448.417.875.774 1.195l1-1.118c-.116-.104-.248-.294-.377-.623q-.19-.488-.326-1.247c-.188-1.022-.297-2.28-.45-3.711zm15.375-.16c-.154 1.431-.264 2.689-.45 3.71q-.138.76-.327 1.248c-.129.329-.261.52-.377.623l1 1.118c.357-.32.599-.747.774-1.195c.176-.451.304-.971.405-1.523c.2-1.094.318-2.443.466-3.82zm-5.854 7.737c1.398 0 2.518.001 3.404-.112c.907-.116 1.666-.362 2.296-.926l-1-1.118c-.317.283-.739.46-1.487.556c-.768.099-1.775.1-3.213.1zM10.75 5c0-.69.56-1.25 1.25-1.25v-1.5A2.75 2.75 0 0 0 9.25 5zM12 3.75c.69 0 1.25.56 1.25 1.25h1.5A2.75 2.75 0 0 0 12 2.25zM20.75 9a.75.75 0 0 1-.75.75v1.5A2.25 2.25 0 0 0 22.25 9zm-1.5 0a.75.75 0 0 1 .75-.75v-1.5A2.25 2.25 0 0 0 17.75 9zm.75-.75a.75.75 0 0 1 .75.75h1.5A2.25 2.25 0 0 0 20 6.75zM4 9.75A.75.75 0 0 1 3.25 9h-1.5A2.25 2.25 0 0 0 4 11.25zM3.25 9A.75.75 0 0 1 4 8.25v-1.5A2.25 2.25 0 0 0 1.75 9zM4 8.25a.75.75 0 0 1 .75.75h1.5A2.25 2.25 0 0 0 4 6.75zm16 1.5h-.009l-.017 1.5H20zm-.009 0a.75.75 0 0 1-.559-.26l-1.135.98c.406.47 1.006.772 1.677.78zm-.559-.26A.74.74 0 0 1 19.25 9h-1.5c0 .561.207 1.076.547 1.47zM18 11.777c.677-.675 1.026-1.015 1.258-1.159l-.787-1.276c-.42.26-.924.768-1.53 1.372zM4.75 9a.74.74 0 0 1-.182.49l1.135.98c.34-.394.547-.909.547-1.47zm2.309 1.714c-.606-.604-1.11-1.113-1.53-1.372l-.787 1.276c.232.144.58.484 1.258 1.159zM4.568 9.49a.75.75 0 0 1-.559.26l.017 1.5a2.25 2.25 0 0 0 1.677-.78zm-.559.26H4v1.5h.026zm.866 2.558a33 33 0 0 1-.109-1.116a3 3 0 0 1-.005-.592l-1.487-.2a4.6 4.6 0 0 0-.004.898c.023.328.065.72.114 1.17zM13.25 5c0 .485-.276.907-.683 1.115l.681 1.336A2.75 2.75 0 0 0 14.75 5zm-.683 1.115c-.17.086-.361.135-.567.135v1.5a2.74 2.74 0 0 0 1.249-.3zm1.538 1.245c-.206-.37-.391-.703-.561-.975l-1.272.795c.146.234.31.53.523.91zM12 6.25c-.206 0-.398-.05-.567-.135l-.681 1.336c.375.191.8.299 1.248.299zm-.567-.135A1.25 1.25 0 0 1 10.75 5h-1.5a2.75 2.75 0 0 0 1.502 2.45zm-.228 1.976c.212-.382.377-.677.523-.91l-1.272-.796c-.17.272-.355.605-.561.975z"></path><path stroke="#30b230" stroke-linecap="round" stroke-width="1.5" d="M5 17.5h14"></path></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">
<g fill="#30B230">
<path fill-rule="evenodd" d="M13.5 26C20.404 26 26 20.404 26 13.5S20.404 1 13.5 1S1 6.596 1 13.5S6.596 26 13.5 26m0-2C19.299 24 24 19.299 24 13.5S19.299 3 13.5 3S3 7.701 3 13.5S7.701 24 13.5 24" clip-rule="evenodd" opacity="0.2" />
<g opacity="0.2">
<path d="M19.568 14.058a1 1 0 0 1-.054 1.721l-8.033 4.408A1 1 0 0 1 10 19.311V9.817a1 1 0 0 1 1.535-.845z" />
<path fill-rule="evenodd" d="M17.067 14.841L12 11.633v5.988zm2.447.938a1 1 0 0 0 .054-1.721l-8.033-5.086A1 1 0 0 0 10 9.817v9.494a1 1 0 0 0 1.481.876z" clip-rule="evenodd" />
</g>
<path fill-rule="evenodd" d="m9 18.321l9.014-4.883L9 7.804zm9.49-4.003a1 1 0 0 0 .054-1.728L9.53 6.956A1 1 0 0 0 8 7.804v10.517a1 1 0 0 0 1.476.88z" clip-rule="evenodd" />
<path fill-rule="evenodd" d="M13 24.5c6.351 0 11.5-5.149 11.5-11.5S19.351 1.5 13 1.5S1.5 6.649 1.5 13S6.649 24.5 13 24.5m0 1c6.904 0 12.5-5.596 12.5-12.5S19.904.5 13 .5S.5 6.096.5 13S6.096 25.5 13 25.5" clip-rule="evenodd" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="180" viewBox="0 0 256 180"><path fill="#f00" d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134"/><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z"/></svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="-2 -2 24 24"><g fill="currentColor"><path d="M12.923 6.526H7.077C5.93 6.526 5 7.446 5 8.58v2.89c0 1.135.93 2.054 2.077 2.054h5.846c1.147 0 2.077-.92 2.077-2.054V8.58c0-1.135-.93-2.054-2.077-2.054m-1.404 3.64l-2.735 1.29a.11.11 0 0 1-.157-.099v-2.66a.11.11 0 0 1 .16-.097l2.734 1.37a.11.11 0 0 1-.002.196"/><path d="M4 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zm0-2h12a4 4 0 0 1 4 4v12a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4"/></g></svg>

After

Width:  |  Height:  |  Size: 539 B

View File

@@ -0,0 +1,284 @@
jQuery(function ($) {
// Constants
const SELECTORS = {
termsLink: ".atfpp-see-terms",
termsBox: "#termsBox",
refreshBtn: ".atfpp-refresh-btn",
licenseContainer: ".atfpp-dashboard-license-pro-container",
deactivateBtn: ".atfpp-dashboard-license-pro-container-deactivate-btn",
validityStrong: ".validity:has(strong) strong",
validitySpan: ".validity:not(:has(strong))",
licenseType: ".license-type",
containerUl: ".atfpp-dashboard-license-pro-container ul",
errorNotice: ".atfpp-dashboard-license-pro-container div.notice.notice-error",
};
const CSS_CLASSES = {
valid: "valid",
invalid: "invalid",
expired: "expired",
supportExpired: "support-expired",
noSupport: "no-support"
};
const MESSAGES = {
validLicense: "✅ Valid",
invalidLicense: "❌ Invalid",
expiredLicense: "❌ License Expired",
expiredSupport: "❌ Support Expired",
genericError: "An error occurred while refreshing the license. Please try again.",
checkingStatus: "⏳Checking....."
};
const URLS = {
renewLink: "https://my.coolplugins.net/account/subscriptions/"
};
// Helper Functions
const createNoticeElement = (type, message, isError = false) => {
const noticeClass = isError ? "notice-error" : "notice-success";
return $(`<div class="notice ${noticeClass} is-dismissible"><p>${message}</p></div>`);
};
const createRenewalNotice = (message) => {
return $(`<div style="margin-top: 10px; color: #d63638;" class="notice notice-error">${message}</div>`);
};
const isDateExpired = (dateString) => {
if (!dateString || dateString.toLowerCase() === "no expiry" || dateString.toLowerCase() === "unlimited") {
return false;
}
return new Date(dateString).getTime() < Date.now();
};
const updateValidityStatus = ($element, status, cssClass) => {
$element.html(status);
$element.removeClass(Object.values(CSS_CLASSES).join(" ")).addClass(cssClass);
};
const getRenewalMessage = (isLicenseExpired, market, versionMessage = '') => {
const baseMessage = isLicenseExpired
? "Your license has expired, "
: "Your support has expired, ";
const versionPrefix = versionMessage ? versionMessage + ' ' : '';
if (market === "E") {
return versionPrefix + baseMessage + "Renew now to continue receiving updates and priority support.";
}
const linkText = isLicenseExpired ? "Renew now" :
'<a style="color: #0073aa; text-decoration: underline;" href="' + URLS.renewLink + '" target="_blank">Renew now</a>';
return isLicenseExpired
? versionPrefix + baseMessage + `<a href="${URLS.renewLink}" target="_blank">Renew now</a> to continue receiving updates and priority support.`
: versionPrefix + baseMessage + linkText + " to continue receiving updates and priority support";
};
const getFormattedDate = (dateString) => {
if (dateString.toLowerCase() !== 'no expiry') {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
}
return dateString;
};
const showNoticeMessage = (element, delay = 2000) => {
element.insertBefore(SELECTORS.licenseContainer).delay(delay).fadeOut();
};
// Terms toggle functionality
const $termsLink = $(SELECTORS.termsLink);
const $termsBox = $(SELECTORS.termsBox);
$termsLink.on("click", function (e) {
e.preventDefault();
const isVisible = $termsBox.toggle().is(":visible");
$(this).html(isVisible ? "Hide Terms" : "See terms");
});
// License refresh functionality
const $refreshBtn = $(SELECTORS.refreshBtn);
$refreshBtn.on("click", function (e) {
e.preventDefault();
const $btn = $(this);
const originalText = $btn.text();
// Update button state
$btn.prop("disabled", true).text(MESSAGES.checkingStatus);
// AJAX request
$.ajax({
url: atfpp_ajax.ajaxurl,
type: "POST",
data: {
action: "atfpp_refresh_license_ajax",
nonce: atfpp_ajax.nonce,
},
success: function (response) {
if (response.success) {
const successNotice = createNoticeElement("success", response.data.message);
showNoticeMessage(successNotice);
if (response.data.license_info) {
const versionMessage = response.data.version_available_message || '';
updateLicenseInfo(response.data.license_info, versionMessage);
}
} else {
const errorNotice = createNoticeElement("error", response.data.message, true);
showNoticeMessage(errorNotice);
setTimeout(() => location.reload(), 2000);
}
},
error: function () {
const errorNotice = createNoticeElement("error", MESSAGES.genericError, true);
showNoticeMessage(errorNotice, 5000);
},
complete: function () {
$btn.prop("disabled", false).text("🔄" + originalText);
},
});
});
// License information update function
function updateLicenseInfo(licenseInfo, versionMessage = '') {
updateLicenseValidity(licenseInfo);
updateLicenseType(licenseInfo);
updateExpireDate(licenseInfo);
handleRenewalMessages(licenseInfo, versionMessage);
handleRefreshButtonVisibility(licenseInfo);
}
function updateLicenseValidity(licenseInfo) {
const $validity = $(SELECTORS.validityStrong);
if (!licenseInfo.is_valid) {
updateValidityStatus($validity, MESSAGES.invalidLicense, CSS_CLASSES.invalid);
return;
}
if (licenseInfo.is_valid === "license_expired") {
updateValidityStatus($validity, MESSAGES.expiredLicense, CSS_CLASSES.expired);
} else if (licenseInfo.support_end.toLowerCase() === "no support") {
updateValidityStatus($validity, MESSAGES.expiredSupport, CSS_CLASSES.noSupport);
} else if (licenseInfo.is_valid === "support_expired" &&
isDateExpired(licenseInfo.support_end)) {
updateValidityStatus($validity, MESSAGES.expiredSupport, CSS_CLASSES.supportExpired);
} else {
updateValidityStatus($validity, MESSAGES.validLicense, CSS_CLASSES.valid);
}
}
function updateLicenseType(licenseInfo) {
if (!licenseInfo.license_title) return;
const $licenseType = $(SELECTORS.licenseType);
if ($licenseType.length > 0) {
$licenseType.text(licenseInfo.license_title);
} else {
const $ul = $(SELECTORS.containerUl);
const $statusLi = $ul.find("li:first");
const $newLicenseTypeLi = $(`
<li>
<strong>License Type:</strong>
<span class="license-type">${licenseInfo.license_title}</span>
</li>
`);
$statusLi.after($newLicenseTypeLi);
}
}
function updateExpireDate(licenseInfo) {
if (!licenseInfo.expire_date) return;
const $expireDateSpan = $(SELECTORS.validitySpan);
const displayDate = getDisplayDate(licenseInfo);
if ($expireDateSpan.length > 0) {
$expireDateSpan.text(displayDate);
} else {
createExpireDateElement(displayDate);
}
}
function getDisplayDate(licenseInfo) {
const expireDateExpired = isDateExpired(licenseInfo.expire_date);
const supportEndExpired = isDateExpired(licenseInfo.support_end);
if (licenseInfo.support_end.toLowerCase() === "no support") {
return "No Support";
} else if (expireDateExpired) {
return getFormattedDate(licenseInfo.expire_date);
} else if (supportEndExpired) {
return getFormattedDate(licenseInfo.support_end);
} else {
return getFormattedDate(licenseInfo.expire_date);
}
}
function createExpireDateElement(displayDate) {
const $ul = $(SELECTORS.containerUl);
const $licenseTypeLi = $ul.find("li:has(.license-type)");
if ($licenseTypeLi.length > 0) {
const $newExpireDateLi = $(`
<li>
<strong>Plugin Updates & Support Validity:</strong>
<span class="validity">${displayDate}</span>
</li>
`);
$licenseTypeLi.after($newExpireDateLi);
}
}
function removeRenewalMessages() {
$(SELECTORS.errorNotice).remove();
}
function handleRenewalMessages(licenseInfo, versionMessage = '') {
const isLicenseExpired = licenseInfo.is_valid === "license_expired";
const isSupportExpired = licenseInfo.is_valid === "support_expired" ||
(licenseInfo.support_end.toLowerCase() === "no support" &&
isDateExpired(licenseInfo.support_end));
if (isLicenseExpired || isSupportExpired) {
updateRenewalMessage(licenseInfo, isLicenseExpired, versionMessage);
} else {
// Remove existing error notices when license is valid
removeRenewalMessages();
}
}
function updateRenewalMessage(licenseInfo, isLicenseExpired, versionMessage = '') {
const renewalMessage = getRenewalMessage(isLicenseExpired, licenseInfo.market, versionMessage);
const $existingLink = $(SELECTORS.errorNotice);
if ($existingLink.length) {
$existingLink.html(renewalMessage);
} else {
$(SELECTORS.errorNotice).remove();
const $renewalNotice = createRenewalNotice(renewalMessage);
$(SELECTORS.deactivateBtn).after($renewalNotice);
}
}
function handleRefreshButtonVisibility(licenseInfo) {
const isValidLicense = licenseInfo.is_valid &&
licenseInfo.is_valid !== "license_expired" &&
licenseInfo.is_valid !== "support_expired" &&
(licenseInfo.support_end.toLowerCase() === "unlimited" ||
!isDateExpired(licenseInfo.support_end));
if (isValidLicense) {
$(SELECTORS.refreshBtn).hide();
}
}
});

View File

@@ -0,0 +1,10 @@
jQuery(document).ready(function($) {
$('tr.active[data-plugin*="autopoly-ai-translation-for-polylang-pro"]').each(function() {
var $currentRow = $(this);
var $nextUpdateRow = $currentRow.nextAll('tr.plugin-update-tr.active.atfpp-pro').first();
if ($nextUpdateRow.length > 0) {
$currentRow.addClass('update');
}
});
});

View File

@@ -0,0 +1,58 @@
<div class="atfpp-dashboard-ai-translations">
<div class="atfpp-dashboard-ai-translations-container">
<div class="header">
<h1><?php echo esc_html__('AI Translations', $text_domain); ?></h1>
</div>
<p class="description">
<?php echo esc_html__('Experience the power of AI for faster, more accurate translations. Choose from multiple AI providers to translate your content efficiently.', $text_domain); ?>
</p>
<div class="atfpp-dashboard-translations">
<?php
$ai_translations = [
[
'logo' => 'geminiai-logo.png',
'alt' => 'Gemini AI',
'title' => esc_html__('AI Translations', $text_domain),
'description' => esc_html__('Leverage Gemini AI for seamless and context-aware translations.', $text_domain),
'icon' => 'gemini-translate.png',
'url' => 'https://docs.coolplugins.net/doc/translate-via-gemini-ai-polylang/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=ai_translations_gemini_pro'
],
[
'logo' => 'openai-translate-logo.png',
'alt' => 'OpenAI',
'title' => esc_html__('AI Translations', $text_domain),
'description' => esc_html__('Leverage OpenAI for seamless and context-aware translations.', $text_domain),
'icon' => 'open-ai-translate.png',
'url' => 'https://docs.coolplugins.net/doc/translate-via-open-ai-polylang/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=ai_translations_openai_pro'
],
[
'logo' => 'chrome-built-in-ai-logo.png',
'alt' => 'Chrome Built-in AI',
'title' => esc_html__('Chrome Built-in AI', $text_domain),
'description' => esc_html__('Utilize Chrome\'s built-in AI for seamless translation experience.', $text_domain),
'icon' => 'chrome-ai-translate.png',
'url' => 'https://docs.coolplugins.net/doc/chrome-ai-translation-polylang/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=ai_translations_chrome_pro'
]
];
foreach ($ai_translations as $translation) {
?>
<div class="atfpp-dashboard-translation-card">
<div class="logo">
<img src="<?php echo esc_url(ATFPP_URL . 'assets/images/' . $translation['logo']); ?>"
alt="<?php echo esc_attr($translation['alt']); ?>">
</div>
<h3><?php echo esc_html($translation['title']); ?></h3>
<p><?php echo esc_html($translation['description']); ?></p>
<div class="play-btn-container">
<a href="<?php echo esc_url($translation['url']); ?>" target="_blank">
<img src="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/' . $translation['icon']); ?>" alt="<?php echo esc_attr($translation['alt']); ?>">
</a>
</div>
</div>
<?php
}
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,119 @@
<?php
if(!defined('ABSPATH')) exit;
if(!class_exists('ATFP_Custom_Fields')) {
class ATFP_Custom_Fields {
private static $instance = null;
private $atfp_saved_fields = array();
private $atfp_allowed_fields = array();
public static function get_instance() {
if(null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
// wp:phpcs:ignore Wordpress.security Nonce verification is not required here
$tab=isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : '';
$page=isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
if('custom-fields' === $tab && 'polylang-atfpp-dashboard' === $page){
$this->atfpp_render_custom_fields_page();
$this->enqueue_editor_assets();
}
}
public function enqueue_editor_assets() {
wp_enqueue_script( 'atfp-datatable-script', ATFPP_URL . 'assets/js/dataTables.min.js', array(), ATFPP_V, true );
wp_enqueue_script( 'atfp-datatable-style', ATFPP_URL . 'assets/js/dataTables.min.js', array(), ATFPP_V, true );
wp_enqueue_style( 'atfp-editor-custom-fields', ATFPP_URL . 'assets/css/atfp-custom-data-table.css', array(), ATFPP_V );
wp_enqueue_script( 'atfp-editor-custom-fields', ATFPP_URL . 'assets/js/atfp-custom-data-table.js', array('atfp-datatable-script'), ATFPP_V, true );
wp_localize_script( 'atfp-editor-custom-fields', 'atfpCustomTableDataObject', array(
'admin_url' => esc_url(admin_url('admin-ajax.php')),
'save_button_handler' => 'atfp_update_custom_fields_content',
'save_button_nonce' => wp_create_nonce('atfp_save_custom_fields'),
'save_button_enabled'=>true,
'save_button_text'=>__('Save Fields', 'autopoly-ai-translation-for-polylang-pro'),
'save_button_class'=>'atfp-save-custom-fields',
) );
}
public function atfpp_render_custom_fields_page() {
$this->atfp_allowed_fields = ATFPP_Helper::get_instance()->get_allowed_custom_fields();
$s_no = 1;
?>
<div class="atfp-custom-data-table-wrapper atfp-custom-fields">
<h3><?php echo __('Custom Fields Translation Settings', 'autopoly-ai-translation-for-polylang-pro'); ?>
<br>
<p><?php echo sprintf(esc_html__('Select which custom fields will be translated by %s.', 'autopoly-ai-translation-for-polylang-pro'), 'AutoPoly'); ?></p>
</h3>
<button class="button button-primary atfp-save-custom-fields"><?php esc_html_e( 'Save Fields', 'autopoly-ai-translation-for-polylang-pro' ); ?></button>
<div class="atfp-custom-data-table-filters">
<div class="atfp-filter-tab" data-column="3" data-default="all">
<label for="atfp-fields-filter"><?php esc_html_e( 'Show Fields:', 'autopoly-ai-translation-for-polylang-pro' ); ?></label>
<select id="atfp-fields-filter" name="atfp_fields_filter">
<option value="all"><?php esc_html_e( 'All', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="supported"><?php esc_html_e( 'Translatable', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="unsupported"><?php esc_html_e( 'Non-Translatable', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
</select>
</div>
<div class="atfp-filter-tab" data-column="2" data-default="all">
<label for="atfp-fields-filter"><?php esc_html_e( 'Type:', 'autopoly-ai-translation-for-polylang-pro' ); ?></label>
<select id="atfp-fields-value-type-filter" name="atfp_fields_value_type_filter">
<option value="all"><?php esc_html_e( 'All', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="string"><?php esc_html_e( 'String', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="array"><?php esc_html_e( 'Array', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
</select>
</div>
</div>
<div class="atfp-custom-table-section">
<div class="atfp-custom-table-lists">
<table class="atfp-custom-data-table-table" id="atfp-custom-datatable">
<thead>
<tr>
<th><?php esc_html_e( 'Sr.No', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Field Name', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Type', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Status', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th align="center"><?php esc_html_e( 'Translate', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
</tr>
</thead>
<tbody>
<?php
$this->get_all_meta_fields_table();
?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}
public function get_all_meta_fields_table() {
$meta_fields=ATFPP_Helper::get_instance()->get_custom_fields_data();
if($meta_fields && is_array($meta_fields)) {
$s_no = 1;
foreach($meta_fields as $meta_field => $value) {
$checked=isset($this->atfp_allowed_fields[$meta_field]) && !empty($this->atfp_allowed_fields[$meta_field]['status']) ? 'checked' : '';
$status=isset($value['status']) && !empty($value['status']) ? $value['status'] : 'Unsupported';
$value_type=isset($value['type']) && !empty($value['type']) ? $value['type'] : 'string';
echo '<tr>';
echo '<td>' . $s_no++ . '</td>';
echo '<td>' . $meta_field . '</td>';
echo '<td>' . $value_type . '</td>';
echo '<td>' . $status . '</td>';
echo '<td align="center"><input type="checkbox" name="atfp_fields_status" value="' . $meta_field . '" ' . $checked . '></td>';
echo '</tr>';
}
}
}
}
}
ATFP_Custom_Fields::get_instance();

View File

@@ -0,0 +1,59 @@
<div class="atfpp-dashboard-left-section">
<!-- Welcome Section -->
<div class="atfpp-dashboard-welcome">
<div class="atfpp-dashboard-welcome-video">
<a href="https://docs.coolplugins.net/doc/ai-translation-polylang-video-tutorials/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_pro" target="_blank" class="atfpp-dashboard-video-link">
<img decoding="async" src="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/youtube-icon.svg'); ?>" class="play-icon" alt="play-icon">
<picture>
<source srcset="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/polylang-addon-video.png'); ?>" type="image/webp">
<img src="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/polylang-addon-video.png'); ?>" class="loco-video" alt="loco translate addon preview">
</picture>
</a>
</div>
</div>
<!-- Translation Providers -->
<div class="atfpp-dashboard-translation-providers">
<h3><?php echo esc_html__('Translation Providers', $text_domain); ?></h3>
<div class="atfpp-dashboard-providers-grid">
<?php
$providers = [
["Gemini AI", "geminiai-logo.png", "Pro", ["Unlimited Translations", "Fast Translations via AI", "Gemini API Key Required"], esc_url('https://docs.coolplugins.net/doc/translate-via-gemini-open-ai-openrouter/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_gemini_pro'), esc_url('admin.php?page=polylang-atfpp-dashboard&tab=settings')],
["OpenAI", "openai-translate-logo.png", "Pro", ["Unlimited Translations", "Fast Translations via AI", "OpenAI API Key Required"], esc_url('https://docs.coolplugins.net/doc/translate-via-gemini-open-ai-openrouter/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_openai_pro'), esc_url('admin.php?page=polylang-atfpp-dashboard&tab=settings')],
// ["Openrouter AI", "openrouter-translate-logo.png", "Pro", ["Unlimited Translations", "Fast Translations via AI", "Openrouter API Key Required"], esc_url('#'), esc_url('admin.php?page=polylang-atfpp-dashboard&tab=settings')],
["Google Translate", "google-translate-logo.png", "Pro", ["Unlimited Free Translations", "Fast & No API Key Required"], esc_url('https://docs.coolplugins.net/doc/google-translate-for-polylang/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_google_pro')],
["Chrome Built-in AI", "chrome-built-in-ai-logo.png", "Free", ["Fast AI Translations in Browser", "Unlimited Free Translations", "Use Translation Modals"], esc_url('https://docs.coolplugins.net/docs/ai-translation-for-polylang/how-to-automatically-translate-your-website-content-via-chrome-ai/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_chrome_pro')],
["Yandex Translate", "yandex-translate-logo.png", "Free", ["Unlimited Free Translations", "No API & No Extra Cost"], esc_url('https://docs.coolplugins.net/docs/ai-translation-for-polylang/how-to-automatically-translate-your-website-content-via-yandex/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_yandex_pro')],
];
foreach ($providers as $index => $provider) {
?>
<div class="atfpp-dashboard-provider-card">
<div class="atfpp-dashboard-provider-header">
<a href="<?php echo esc_url($provider[4]); ?>" target="_blank">
<img src="<?php echo esc_url(ATFPP_URL . 'assets/images/' . $provider[1]); ?>"
alt="<?php echo esc_html($provider[0]); ?>">
</a>
</div>
<h4><?php echo esc_html($provider[0]); ?></h4>
<ul>
<?php foreach ($provider[3] as $feature) { ?>
<li>✅ <?php echo esc_html($feature); ?></li>
<?php } ?>
</ul>
<div class="atfpp-dashboard-provider-buttons">
<a href="<?php echo esc_url($provider[4]); ?>" class="atfpp-dashboard-btn" target="_blank">Docs</a>
<?php if (isset($provider[5])) { ?>
<a href="<?php echo esc_url($provider[5]); ?>" class="atfpp-dashboard-btn">Settings</a>
<?php } ?>
</div>
</div>
<?php
}
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
<div class="atfpp-dashboard-info">
<div class="atfpp-dashboard-info-links">
<p>
<?php echo esc_html__('Made with ❤️ by', $text_domain); ?>
<span class="logo">
<a href="https://coolplugins.net/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=author_page&utm_content=dashboard_footer_pro" target="_blank">
<img src="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/cool-plugins-logo-black.svg'); ?>" alt="<?php esc_attr_e('Cool Plugins Logo', $text_domain); ?>">
</a>
</span>
</p>
<a href="https://coolplugins.net/support/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=support&utm_content=dashboard_footer_pro" target="_blank"><?php echo esc_html__('Support', $text_domain); ?></a> |
<a href="https://docs.coolplugins.net/docs/ai-translation-for-polylang/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=docs&utm_content=dashboard_footer_pro" target="_blank"><?php echo esc_html__('Docs', $text_domain); ?></a>
<div class="atfpp-dashboard-social-icons">
<?php
$social_links = [
['https://www.facebook.com/coolplugins/', 'facebook.svg', esc_html__('Facebook', $text_domain)],
['https://linkedin.com/company/coolplugins', 'linkedin.svg', esc_html__('Linkedin', $text_domain)],
['https://x.com/cool_plugins', 'twitter.svg', esc_html__('Twitter', $text_domain)],
['https://www.youtube.com/@cool_plugins', 'youtube.svg', esc_html__('YouTube Channel', $text_domain)]
];
foreach ($social_links as $link) {
echo '<a href="' . esc_url($link[0]) . '" target="_blank">
<img src="' . esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/' . $link[1]) . '" alt="' . esc_attr($link[2]) . '">
</a>';
}
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,505 @@
<?php
$glossary_data = get_option('atfpp_glossary_data', []);
if (!is_array($glossary_data)) {
$glossary_data = [];
}
// --- Build $languages array ONCE at the top and reuse everywhere ---
$pll_languages_serialized = get_option('_transient_pll_languages_list');
$pll_languages = [];
if ($pll_languages_serialized) {
$pll_languages = maybe_unserialize($pll_languages_serialized);
}
$languages = [];
if (is_array($pll_languages)) {
foreach ($pll_languages as $pll_lang) {
$languages[] = [
'code' => $pll_lang['slug'],
'img' => $pll_lang['flag_url'],
'alt' => $pll_lang['name'],
'flag' => isset($pll_lang['flag']) ? $pll_lang['flag'] : '',
];
}
}
// Build a map for quick lookup by code
$language_map = [];
foreach ($languages as $lang) {
$language_map[$lang['code']] = $lang;
}
$grouped_entries = [];
foreach ($glossary_data as $entry) {
$term = $entry['original_term'];
$lang = $entry['original_language_code'];
$key = $term . '||' . $lang; // Composite key
if (!isset($grouped_entries[$key])) {
$grouped_entries[$key] = [
'term' => $term,
'original_language_code' => $lang,
'desc' => $entry['description'],
'type' => $entry['kind'],
'type_label' => ucfirst($entry['kind']),
'translations' => [],
];
}
if (!empty($entry['translations']) && is_array($entry['translations'])) {
foreach ($entry['translations'] as $translation) {
if (
$translation['target_language_code'] !== $lang &&
!empty($translation['translated_term']) &&
trim($translation['translated_term']) !== ''
) {
$grouped_entries[$key]['translations'][$translation['target_language_code']] = trim($translation['translated_term']);
}
}
}
}
// Sort glossary entries alphabetically by term (case-insensitive)
uksort($grouped_entries, 'strnatcasecmp');
// Collect unique original_language_codes and their counts
$language_codes = [];
foreach ($glossary_data as $entry) {
if (!empty($entry['original_language_code'])) {
$code = $entry['original_language_code'];
if (!isset($language_codes[$code])) {
$language_codes[$code] = 0;
}
$language_codes[$code]++;
}
}
$unique_language_codes = array_keys($language_codes);
// Compute the unique original language code
$unique_original_language_code = '';
if (count($unique_language_codes) === 1) {
$unique_original_language_code = reset($unique_language_codes);
}
// Add this for default selected language (first in the list)
$default_selected_lang = $unique_language_codes[0] ?? '';
// --- ADD THIS BLOCK ---
$term_original_lang = [];
foreach ($glossary_data as $entry) {
$term = $entry['original_term'];
$lang = $entry['original_language_code'];
// Initialize as array if not already
if (!isset($term_original_lang[$term])) {
$term_original_lang[$term] = [];
}
// Add only if not already present
if (!in_array($lang, $term_original_lang[$term])) {
$term_original_lang[$term][] = $lang;
}
}
// --- END ADD ---
// Count how many languages have entries
$language_codes_with_entries = [];
foreach ($grouped_entries as $entry) {
$code = $entry['original_language_code'];
if (!in_array($code, $language_codes_with_entries, true)) {
$language_codes_with_entries[] = $code;
}
}
$single_language_mode = count($language_codes_with_entries) === 1;
$single_language_code = $single_language_mode ? $language_codes_with_entries[0] : '';
?>
<style>
<?php foreach ($languages as $lang):
$code = esc_attr($lang['code']);
?>
.atfpp-glossary-table.atfpp-hide-lang-<?php echo $code; ?> th[data-lang="<?php echo $code; ?>"],
.atfpp-glossary-table.atfpp-hide-lang-<?php echo $code; ?> td[data-lang="<?php echo $code; ?>"] {
display: none !important;
}
<?php endforeach; ?>
</style>
<div class="atfpp-glossary">
<div class="atfpp-glossary-container">
<?php wp_nonce_field('atfpp_glossary_nonce', 'atfpp_glossary_nonce'); ?>
<header class="atfpp-header">
<h1><?php esc_html_e('Glossary', $text_domain); ?></h1>
<p><?php esc_html_e('Define how you want to translate or not translate important words and phrases.', $text_domain); ?></p>
<ul>
<li><?php esc_html_e('Specific translations you want to use;', $text_domain); ?></li>
<li><?php esc_html_e('Terms you want to exclude from being translated;', $text_domain); ?></li>
<li><?php esc_html_e('Additional context for each term.', $text_domain); ?></li>
</ul>
<a href="#"><?php esc_html_e('Learn more about adding and managing glossary terms.', $text_domain); ?></a>
</header>
<div class="atfpp-controls">
<input type="text" class="atfpp-search" placeholder="<?php esc_attr_e('Search', $text_domain); ?>" />
<select class="atfpp-glossary-type" name="glossary_type">
<option value=""><?php esc_html_e('Glossary Type', $text_domain); ?></option>
<option value="name"><?php esc_html_e('Name', $text_domain); ?></option>
<option value="general"><?php esc_html_e('General', $text_domain); ?></option>
</select>
<button class="atfpp-add-btn button button-primary">
<?php esc_html_e('Add glossary entry', $text_domain); ?>
</button>
<button class="atfpp-import-btn button button-primary">
<?php esc_html_e('Import glossary', $text_domain); ?>
</button>
<button class="atfpp-export-btn button button-primary">
<?php esc_html_e('Export glossary', $text_domain); ?>
</button>
<!-- Modal -->
<div id="atfpp-glossary-modal-add" class="atfpp-glossary-modal atfpp-hidden">
<div class="atfpp-glossary-modal-content-wrapper">
<button type="button" class="atfpp-modal-close-btn" aria-label="Close">&times;</button>
<div class="atfpp-glossary-modal-content">
<h2><?php esc_html_e('Add New Glossary Term', $text_domain); ?></h2>
<form id="atfpp-add-glossary-form">
<label for="atfpp-add-term"><?php esc_html_e('Term', $text_domain); ?></label>
<textarea id="atfpp-add-term" name="term" class="atfpp-add-term" required placeholder="<?php esc_attr_e('Enter the term to be translated', $text_domain); ?>"></textarea>
<div class="atfpp-translation-error"></div>
<label for="atfpp-add-desc"><?php esc_html_e('Description', $text_domain); ?></label>
<textarea id="atfpp-add-desc" name="description" class="atfpp-add-desc" rows="3" placeholder="<?php esc_attr_e('Add a description to provide context for translators', $text_domain); ?>"></textarea>
<div class="atfpp-translation-error"></div>
<label for="atfpp-add-source-lang"><?php esc_html_e('Original Language', $text_domain); ?></label>
<select id="atfpp-add-source-lang" name="source_lang" class="atfpp-add-source-lang" required>
<option value=""><?php esc_html_e('Select language', $text_domain); ?></option>
<?php foreach ($languages as $lang): ?>
<option value="<?php echo esc_attr($lang['code']); ?>">
<?php echo esc_html($lang['alt']); ?>
</option>
<?php endforeach; ?>
</select>
<label for="atfpp-add-type"><?php esc_html_e('Type', $text_domain); ?></label>
<select id="atfpp-add-type" name="type" class="atfpp-add-type" required>
<option value="general"><?php esc_html_e('General', $text_domain); ?></option>
<option value="name"><?php esc_html_e('Name', $text_domain); ?></option>
</select>
<div class="atfpp-add-translations atfpp-translations-grid">
<?php foreach ($languages as $lang): ?>
<div class="atfpp-translation-field atfpp-translation-field-<?php echo esc_attr($lang['code']); ?>">
<label>
<div class="atfpp-translation-label-row">
<?php if (!empty($lang['img'])): ?>
<img src="<?php echo esc_attr($lang['img']); ?>" alt="<?php echo esc_attr($lang['alt']); ?>" class="atfpp-lang-flag">
<?php endif; ?>
<span class="atfpp-lang-name"><?php echo esc_html($lang['alt']); ?></span>
<span class="atfpp-lang-translation-label"><?php esc_html_e('Translation', $text_domain); ?></span>
</div>
<textarea name="translation_<?php echo esc_attr($lang['code']); ?>" class="atfpp-add-translation" rows="2" placeholder="<?php esc_attr_e('Custom Translation', $text_domain); ?>"></textarea>
<div class="atfpp-translation-error"><?php esc_html_e('Too long, must be less than 240 characters', $text_domain); ?></div>
</label>
</div>
<?php endforeach; ?>
</div>
<div class="atfpp-glossary-modal-actions" style="margin-top: 18px;">
<span class="atfpp-glossary-modal-actions-left" style="cursor:pointer;"><?php esc_html_e('Cancel', $text_domain); ?></span>
<button type="submit" id="add-glossary-term-btn" class="button button-primary"><?php esc_html_e('Add Term', $text_domain); ?></button>
</div>
</form>
<div id="add-glossary-success" class="atfpp-import-success atfpp-hidden">
<div class="import-success-icon">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/success.svg'; ?>" alt="Success Icon" />
</div>
<div class="atfpp-import-success-message">
<?php esc_html_e('Glossary term added successfully!', $text_domain); ?>
</div>
<button id="atfpp-glossary-success-close" class="atfpp-close-button" type="button"><?php esc_html_e('Close', $text_domain); ?></button>
</div>
</div>
</div>
</div>
<!-- End Modal -->
<div id="atfpp-glossary-modal-import" class="atfpp-glossary-modal atfpp-hidden">
<div class="atfpp-glossary-modal-content-wrapper">
<button type="button" class="atfpp-modal-close-btn" aria-label="Close">&times;</button>
<div class="atfpp-glossary-modal-content">
<div class="atfpp-import-glossary" id="atfpp-import-glossary-ui">
<h2 class="atfpp-title"><?php esc_html_e( 'Import glossary', $text_domain ); ?></h2>
<label class="atfpp-upload-box" id="upload-label">
<input type="file" accept=".csv" id="atfpp-csv-upload" hidden>
<div class="atfpp-upload-area">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/csv.svg'; ?>" alt="CSV Icon" />
<span id="file-name-display"><?php esc_html_e( 'Select a CSV file to upload', $text_domain ); ?></span>
</div>
</label>
<a
href="<?php echo esc_url( ATFPP_URL . 'admin/atfpp-dashboard/sample-glossary.csv' ); ?>"
class="atfpp-download-link"
download="sample-glossary.csv">
<?php esc_html_e( 'Download sample glossary CSV file', $text_domain ); ?>
</a>
</div>
<!-- Success UI (hidden by default) -->
<div id="atfpp-import-success-ui" class="atfpp-hidden">
<div id="atfpp-import-success" class="atfpp-import-success">
<div class="import-success-icon">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/success.svg'; ?>" alt="Success Icon" />
</div>
<div class="import-success-file">
<span id="importing-file-label"><?php esc_html_e('Importing:', $text_domain); ?></span>
<span id="importing-file-name"></span>
</div>
<div class="atfpp-import-success-message">
<?php esc_html_e('Glossary terms imported successfully', $text_domain); ?>
</div>
<button class="atfpp-import-close-btn atfpp-close-button" type="button">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
<nav class="atfpp-alphabet" aria-label="<?php esc_attr_e('Glossary Alphabet Navigation', $text_domain); ?>">
<?php
$alphabet = array_merge(['123'], range('A', 'Z'), ['#&à']);
// Build a set of enabled letters based on $glossary_data
$enabled_letters = [];
foreach ($glossary_data as $entry) {
if (!empty($entry['original_term'])) {
$first = mb_substr(trim($entry['original_term']), 0, 1, 'UTF-8');
if (is_numeric($first)) {
$enabled_letters['123'] = true;
} elseif (preg_match('/[A-Za-z]/u', $first)) {
$enabled_letters[strtoupper($first)] = true;
} else {
$enabled_letters['#&à'] = true;
}
}
}
$first_active_set = false;
foreach ($alphabet as $char) {
$enabled = isset($enabled_letters[$char]) ? '' : 'disabled';
$active = '';
if ($enabled === '' && !$first_active_set) {
$first_active_set = true;
}
printf(
'<button class="atfpp-alphabet-btn%s" %s data-letter="%s">%s</button>',
$active,
$enabled,
esc_attr($char),
esc_html($char)
);
}
?>
</nav>
<!-- Language Filter Buttons -->
<?php
if (count($language_codes_with_entries) > 1): ?>
<div class="atfpp-language-filters">
<?php foreach ($language_codes_with_entries as $i => $code): ?>
<?php
// Skip if language code not in map
if (!isset($language_map[$code])) {
continue;
}
$lang = $language_map[$code];
?>
<button class="atfpp-lang-filter-btn<?php echo $i === 0 ? ' active' : ''; ?>" data-lang="<?php echo esc_attr($code); ?>">
<?php if (!empty($lang['img'])): ?>
<img src="<?php echo esc_url($lang['img']); ?>" alt="<?php echo esc_attr($lang['alt']); ?>" />
<?php endif; ?>
<?php echo esc_html($lang['alt']) . ' Terms'; ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="atfpp-glossary-table-wrapper">
<?php if (!empty($grouped_entries)): ?>
<table class="atfpp-glossary-table" data-default-lang="<?php echo esc_attr($default_selected_lang); ?>">
<thead>
<tr>
<th colspan="2"></th>
<?php foreach ($languages as $lang): ?>
<?php if ($single_language_mode && $lang['code'] === $single_language_code) continue; ?>
<th colspan="2" title="<?php echo esc_attr($lang['alt']); ?>"
class="atfpp-lang-header atfpp-lang-col-<?php echo esc_attr($lang['code']); ?>"
data-lang="<?php echo esc_attr($lang['code']); ?>">
<?php
echo !empty($lang['flag'])
? $lang['flag']
: '<img src="' . esc_attr($lang['img']) . '" alt="' . esc_attr($lang['alt']) . '" />';
?>
</th>
<?php endforeach; ?>
<th class="atfpp-actions-cell">
<div class="atfpp-action-buttons-header">
<button class="atfpp-actions-header-btn" id="atfpp-actions-header-btn-left" title="Scroll Left">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/arrow-left.svg'; ?>" />
</button>
<button class="atfpp-actions-header-btn" id="atfpp-actions-header-btn-right" title="Scroll Right">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/arrow-right.svg'; ?>" />
</button>
</div>
</th>
</tr>
<tr>
<th>Glossary Entry</th>
<th>Type</th>
<?php foreach ($languages as $lang): ?>
<?php if ($single_language_mode && $lang['code'] === $single_language_code) continue; ?>
<th colspan="2" data-lang="<?php echo esc_attr($lang['code']); ?>">
<?php echo esc_html($lang['alt']); ?>
</th>
<?php endforeach; ?>
<th colspan="2">Actions</th>
</tr>
</thead>
<tbody>
<?php
// UTF-8 safe truncate helper
if ( ! function_exists('atfpp_truncate')) {
function atfpp_truncate( $str, $limit = 10 ) {
$str = (string) $str;
if (mb_strlen($str, 'UTF-8') > $limit) {
return mb_substr($str, 0, $limit, 'UTF-8') . '…';
}
return $str;
}
}
$editing_term = isset($_GET['edit']) ? $_GET['edit'] : null;
foreach ($grouped_entries as $composite_key => $data):
list($term, $original_language_code) = explode('||', $composite_key);
$first = mb_substr(trim($term), 0, 1, 'UTF-8');
$row_letter = is_numeric($first) ? '123' :
(preg_match('/[A-Za-z]/u', $first) ? strtoupper($first) : '#&à');
?>
<tr data-type="<?php echo esc_attr($data['type']); ?>"
data-original-language="<?php echo esc_attr($original_language_code); ?>"
data-term="<?php echo esc_attr($term); ?>"
data-letter="<?php echo esc_attr($row_letter); ?>">
<td>
<div class="atfpp-entry-title">
<?php echo esc_html($term); ?>
</div>
<div class="atfpp-entry-desc">
<?php echo esc_html($data['desc']); ?>
</div>
</td>
<td>
<span class="atfpp-type-badge <?php echo esc_attr($data['type']); ?>">
<?php echo esc_html(ucfirst($data['type'])); ?>
</span>
</td>
<?php
foreach ($languages as $lang):
if ($single_language_mode && $lang['code'] === $single_language_code) continue;
$translation = '';
$is_source = ($lang['code'] === $original_language_code);
?>
<td colspan="2" class="atfpp-lang-col-<?php echo esc_attr($lang['code']); ?>"
data-lang="<?php echo esc_attr($lang['code']); ?>"
data-is-source="<?php echo $is_source ? 'true' : 'false'; ?>">
<?php if ($is_source): ?>
<span class="atfpp-source-term">
<?php echo esc_html($term); ?>
</span>
<?php else: ?>
<?php
// Get translation from the new data structure
$translation = isset($data['translations'][$lang['code']]) ? $data['translations'][$lang['code']] : '';
?>
<?php if (!empty($translation) && trim($translation) !== ''): ?>
<?php $truncated = atfpp_truncate($translation, 7); ?>
<span class="atfpp-translated-term"
title="<?php echo esc_attr($translation); ?>"
data-full-text="<?php echo esc_attr($translation); ?>">
<?php echo esc_html($truncated); ?>
</span>
<?php else: ?>
<span class="atfpp-no-translation">
<button type="button" class="atfpp-edit-btn-svg"
data-term="<?php echo esc_attr(sanitize_text_field($term)); ?>"
data-source-lang="<?php echo esc_attr(sanitize_key($original_language_code)); ?>">
<img src="<?php echo ATFPP_URL . 'admin/atfpp-dashboard/images/file.svg'; ?>"
alt="<?php esc_attr_e('No translation', $text_domain); ?>" />
</button>
</span>
<?php endif; ?>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td class="atfpp-actions-cell">
<div class="atfpp-action-buttons">
<button type="button" class="atfpp-edit-btn"
data-term="<?php echo esc_attr(sanitize_text_field($term)); ?>"
data-source-lang="<?php echo esc_attr(sanitize_key($original_language_code)); ?>">
<?php esc_html_e('Edit', $text_domain); ?>
</button>
<button type="button" class="atfpp-delete-btn"
data-term="<?php echo esc_attr(sanitize_text_field($term)); ?>"
data-source-lang="<?php echo esc_attr(sanitize_key($original_language_code)); ?>">
<?php esc_html_e('Delete', $text_domain); ?>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div id="atfpp-no-results">No glossary entries found.</div>
<?php endif; ?>
</div>
<!-- Move the template outside the table wrapper so it is always present -->
<script type="text/template" id="atfpp-glossary-edit-row-template">
<tr class="atfpp-glossary-edit-row">
<td>
<textarea class="atfpp-edit-term" rows="3" placeholder="<?php esc_attr_e('String Translation', $text_domain); ?>"><%= term %></textarea>
<textarea class="atfpp-edit-desc" rows="4" placeholder="<?php esc_attr_e('Example: The name of the add-on that allows translating strings', $text_domain); ?>"><%= desc %></textarea>
</td>
<td>
<select class="atfpp-edit-type">
<option value="general" <%= type === 'general' ? 'selected' : '' %>><?php esc_html_e('General', $text_domain); ?></option>
<option value="name" <%= type === 'name' ? 'selected' : '' %>><?php esc_html_e('Name', $text_domain); ?></option>
</select>
</td>
<% for (var i = 0; i < languages.length; i++) {
if (languages[i].code === source_lang) continue;
%>
<td colspan="2">
<textarea class="atfpp-edit-translation" data-lang="<%= languages[i].code %>" placeholder="<?php esc_attr_e('Custom Translation', $text_domain); ?>" rows="9"><%= translations[languages[i].code] || '' %></textarea>
<div class="atfpp-translation-error"><?php esc_html_e('Too long, must be less than 220 characters', $text_domain); ?></div>
</td>
<% } %>
<td colspan="2" class="atfpp-actions-cell">
<div class="atfpp-action-buttons">
<button type="button" class="atfpp-save-edit-btn button button-primary">
<?php esc_html_e('Save', $text_domain); ?>
</button>
<button type="button" class="atfpp-cancel-edit-btn">
<?php esc_html_e('Cancel', $text_domain); ?>
</button>
</div>
</td>
</tr>
</script>
</div>
</div>

View File

@@ -0,0 +1,287 @@
<?php
/**
* Renders the license activation page
*/
function atfpp_render_license_page() {
$text_domain = 'autopoly-ai-translation-for-polylang-pro';
$purchase_email = get_option('AIAutomaticTranslationsForPolylang_lic_email', get_bloginfo('admin_email'));
// Escape early for security
$admin_url = esc_url(admin_url('admin-post.php'));
$purchase_email_escaped = esc_attr($purchase_email);
?>
<div class="atfpp-dashboard-license">
<div class="atfpp-dashboard-license-container">
<div class="header">
<h1>🔑 <?php echo esc_html__('Activate License', $text_domain); ?></h1>
</div>
<div class="atfpp-dashboard-license-form">
<form method="post" action="<?php echo $admin_url; ?>">
<input type="hidden" name="action" value="AIAutomaticTranslationsForPolylang_el_activate_license"/>
<?php wp_nonce_field('el-atfpp-license'); ?>
<div class="atfpp-dashboard-license-field">
<label for="license_code"><?php esc_html_e('License Key', $text_domain); ?></label>
<input type="text" name="license_code" id="license_code" required
placeholder="<?php esc_attr_e('xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx', $text_domain); ?>">
</div>
<div class="atfpp-dashboard-license-field">
<label for="email"><?php esc_html_e('Email Address', $text_domain); ?></label>
<input type="email" name="email" id="email" required
value="<?php echo $purchase_email_escaped; ?>">
<small class="atfpp-dashboard-activation-note"><?php esc_html_e("Plugin updates news will be sent to this email. Don't worry, we hate spam.", $text_domain); ?></small>
</div>
<button type="submit" class="button button-primary">
<?php echo esc_html__('Activate License', $text_domain); ?>
</button>
</form>
<p class="activation-note">
<?php echo esc_html__('Activate to receive automatic plugin updates and support.', $text_domain); ?>
</p>
<?php atfpp_render_license_help_buttons($text_domain); ?>
</div>
</div>
</div>
<?php
}
/**
* Renders the license information page for Pro users
*
* @param object|null $license_info License information object
*/
function atfpp_render_license_page_pro($license_info = null) {
$text_domain = 'autopoly-ai-translation-for-polylang-pro';
// Early return if invalid license info
if (!$license_info) {
$license_info = AI_Automatic_Translations_For_Polylang_Base::GetRegisterInfo();
}
if (!is_object($license_info) || !isset($license_info->is_valid) || !isset($license_info->license_title) || !isset($license_info->expire_date)) {
wp_die(__('Error: Invalid license information', $text_domain));
return;
}
// Sanitize license key before masking
$license_key = sanitize_text_field(get_option('AIAutomaticTranslationsForPolylang_lic_Key', ''));
$masked_key = !empty($license_key) ?
esc_html(substr($license_key, 0, 8) . '-XXXXXXXX-XXXXXXXX-' . substr($license_key, -8)) :
'';
$admin_url = esc_url(admin_url('admin-post.php'));
?>
<div class="atfpp-dashboard-license">
<div class="atfpp-dashboard-license-pro-container">
<div class="atfpp-dashboard-license-header-container" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h1>🔒 <?php esc_html_e('Your License Info', $text_domain); ?></h1>
<?php if (atfpp_needs_refresh($license_info)): ?>
<button type="button" class="atfpp-refresh-btn" id="atfpp-refresh-license-btn">
<?php esc_html_e('🔄Check license status', $text_domain); ?>
</button>
<?php endif; ?>
</div>
<ul>
<li><strong><?php esc_html_e('Status:', $text_domain); ?></strong>
<span class="validity">
<?php if ($license_info->is_valid): ?>
<?php if (atfpp_is_license_expired($license_info)): ?>
<strong>❌ <?php esc_html_e('License Expired', $text_domain); ?></strong>
<?php elseif (atfpp_is_support_expired($license_info)): ?>
<strong>❌ <?php esc_html_e('Support Expired', $text_domain); ?></strong>
<?php else: ?>
<strong class="valid">✅ <?php esc_html_e('Valid', $text_domain); ?></strong>
<?php endif; ?>
<?php else: ?>
<strong>❌ <?php esc_html_e('Invalid', $text_domain); ?></strong>
<?php endif; ?>
</span>
</li>
<li><strong><?php esc_html_e('License Type:', $text_domain); ?></strong> <span class="license-type"><?php echo esc_html($license_info->license_title); ?></span></li>
<li><strong><?php esc_html_e('Plugin Updates & Support Validity:', $text_domain); ?></strong> <span class="validity">
<?php
$current_time = time();
// Handle "No expiry" case for expire_date
$expire_date_expired = false;
if (strtolower($license_info->expire_date) !== 'no expiry') {
$expire_date_timestamp = strtotime($license_info->expire_date);
$expire_date_expired = $expire_date_timestamp && $expire_date_timestamp < $current_time;
}
// Handle "no support" case for support_end
if (strtolower($license_info->support_end) === 'no support') {
esc_html_e('No Support', $text_domain);
} else {
// Handle "unlimited" case for support_end
$support_end_expired = false;
if (strtolower($license_info->support_end) !== 'unlimited') {
$support_end_timestamp = strtotime($license_info->support_end);
$support_end_expired = $support_end_timestamp && $support_end_timestamp < $current_time;
}
if ($expire_date_expired) {
echo esc_html(atfpp_pro_formatLicenseDate($license_info->expire_date));
} elseif ($support_end_expired) {
echo esc_html(atfpp_pro_formatLicenseDate($license_info->support_end));
} else {
echo esc_html(atfpp_pro_formatLicenseDate($license_info->expire_date));
}
}
?>
</span>
</li>
<li><strong><?php echo esc_html__('Your License Key:', $text_domain); ?></strong> <span class="license-key"><?php echo esc_html($masked_key); ?></span></li>
</ul>
<div class="atfpp-dashboard-license-pro-container-deactivate-btn">
<p><?php esc_html_e('Want to deactivate the license for any reason?', $text_domain); ?></p>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<input type="hidden" name="action" value="AIAutomaticTranslationsForPolylang_el_deactivate_license" />
<?php wp_nonce_field('el-atfpp-license'); ?>
<button type="submit" class="deactivate-btn">
<?php echo esc_html__('Deactivate License', $text_domain); ?>
</button>
</form>
</div>
<?php if (atfpp_is_license_expired($license_info)): ?>
<div class="notice notice-error" style="margin-top: 10px; color: #d63638;">
<?php atfpp_render_expiry_message($license_info, 'license'); ?>
</div>
<?php elseif (atfpp_is_support_expired($license_info)): ?>
<div class="notice notice-error" style="margin-top: 10px; color: #d63638;">
<?php atfpp_render_expiry_message($license_info, 'support'); ?>
</div>
<?php endif; ?>
<?php atfpp_render_license_help_buttons($text_domain); ?>
</div>
</div>
<?php
}
/**
* Renders the license buttons section
* @param string $text_domain The text domain for translations
*/
function atfpp_render_license_help_buttons($text_domain) {
?>
<div class="atfpp-dashboard-license-pro-container-buttons">
<p><?php esc_html_e('Want to know more about the license key?', $text_domain); ?></p>
<div class="btns">
<a href="https://my.coolplugins.net/account/" target="_blank" class="atfpp-dashboard-btn">
<?php echo esc_html__('Check Account', $text_domain); ?>
</a>
<a href="https://coolplugins.net/support/?utm_source=atfp_plugin&utm_medium=inside&utm_campaign=support&utm_content=dashboard_license" target="_blank" class="atfpp-dashboard-btn">
<?php echo esc_html__('Contact Support', $text_domain); ?>
</a>
</div>
</div>
<?php
}
function atfpp_is_license_expired($license_info) {
return $license_info->is_valid === 'license_expired';
}
function atfpp_is_support_expired($license_info) {
return $license_info->support_end === 'no support' ||
($license_info->is_valid === 'support_expired' ||
(strtolower($license_info->support_end) !== 'unlimited' &&
strtotime($license_info->support_end) < time()));
}
function atfpp_needs_refresh($license_info) {
return atfpp_is_license_expired($license_info) || atfpp_is_support_expired($license_info);
}
function atfpp_render_expiry_message($license_info, $type = 'license') {
$text_domain = 'autopoly-ai-translation-for-polylang-pro';
// Generate version available message using common helper
$version_available_message = AutoPolyPro::atfppGetVersionAvailableMessage();
if ($license_info->msg === 'limit_reached') {
$support_link = sprintf('<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc_url('https://my.coolplugins.net/account/support-tickets/'), esc_html__('clicking here', 'atfpp'));
echo wp_kses_post(sprintf(
/* translators: %s: link to support ticket page */
__('There was an issue with your account. Please contact our plugin support team by %s.', 'atfpp'),
$support_link
));
return;
}
$message = $type === 'license'
? __('Your license has expired,', 'atfpp')
: __('Your support has expired,', 'atfpp');
$renew_link = isset($license_info->market) && $license_info->market === 'E'
? ''
: ' <a href="'.esc_url('https://my.coolplugins.net/account/subscriptions/').'" target="_blank" rel="noopener noreferrer">'.esc_html__('Renew now', 'atfpp').'</a>';
$final_message = '';
// Add version message if available
if (!empty($version_available_message)) {
wp_enqueue_script('thickbox');
wp_enqueue_style('thickbox');
$final_message .= wp_kses_post($version_available_message) . ' ';
}
// Add license expiry message
$final_message .= esc_html($message) . $renew_link . esc_html__(' to continue receiving updates and priority support.', 'atfpp');
echo $final_message;
}
function atfpp_pro_formatLicenseDate($dateString) {
if (!empty($dateString) && strtolower($dateString) !== 'no expiry') {
$date = new DateTime($dateString);
return $date->format('d M Y');
}
return $dateString;
}

View File

@@ -0,0 +1,141 @@
<!-- Right Sidebar -->
<div class="atfpp-dashboard-sidebar">
<div class="atfpp-dashboard-status">
<h3><?php echo esc_html__('Auto Translation Status', $text_domain); ?></h3>
<div class="atfpp-dashboard-sts-top">
<?php
$service_providers = array();
$all_data = get_option('cpt_dashboard_data', array());
$avilable_service_providers = array('google'=>'Google', 'yandex'=>'Yandex', 'localAiTranslator'=>'Chrome AI Translator', 'google_ai'=>'Gemini', 'openai_ai'=>'OpenAI', 'deepl_ai'=>'DeepL');
if (!is_array($all_data) || !isset($all_data['atfp'])) {
$all_data['atfp'] = []; // Ensure $all_data['atfp'] is an array
}
$totals = array_reduce($all_data['atfp'] ?? [], function($carry, $translation) use (&$service_providers, $avilable_service_providers) {
// Ensure $translation['string_count'] is numeric
// Ensure all values are properly handled
$carry['string_count'] += intval($translation['string_count'] ?? 0);
$carry['character_count'] += intval($translation['character_count'] ?? 0);
$carry['time_taken'] += intval($translation['time_taken'] ?? 0);
if(isset($translation['service_provider']) && !empty($translation['service_provider']) && !in_array($translation['service_provider'], $service_providers) && in_array($translation['service_provider'], array_keys($avilable_service_providers))){
$service_providers[] = $translation['service_provider'];
}
// Count total translations instead of unique post IDs
if (!empty($translation['post_id'])) {
$carry['translation_count']++;
}
return $carry;
}, ['string_count' => 0, 'character_count' => 0, 'time_taken' => 0, 'translation_count' => 0]);
// Update the time taken string using the new function
$time_taken_str = atfp_format_time_taken($totals['time_taken'] ,$text_domain);
?>
<span><?php echo esc_html(atfp_format_number($totals['character_count'], $text_domain)); ?></span>
<span><?php echo esc_html__('Total Characters Translated!', $text_domain); ?></span>
</div>
<ul class="atfpp-dashboard-sts-btm">
<li><span><?php echo esc_html__('Total Strings', $text_domain); ?></span> <span><?php echo esc_html(atfp_format_number($totals['string_count'], $text_domain)); ?></span></li>
<li><span><?php echo esc_html__('Total Pages / Posts', $text_domain); ?></span> <span><?php echo esc_html($totals['translation_count']); ?></span></li>
<li><span><?php echo esc_html__('Time Taken', $text_domain); ?></span> <span><?php echo esc_html($time_taken_str); ?></span></li>
<?php if(count($service_providers) > 0): ?>
<li class="atfpp-dashboard-sts-btm-service-providers"><span><?php echo esc_html__('Service Providers', $text_domain); ?></span> <div class="atfpp-dashboard-sts-btm-service-providers-list"><?php foreach($service_providers as $service_provider): ?>
<span><?php echo esc_html($avilable_service_providers[$service_provider]); ?></span>
<?php endforeach; ?></div></li>
<?php endif; ?>
</ul>
</div>
<div class="atfpp-dashboard-translate-full">
<h3><?php echo esc_html__('Automatically Translate Plugins & Themes', $text_domain); ?></h3>
<div class="atfpp-dashboard-addon first">
<div class="atfpp-dashboard-addon-l">
<strong><?php echo esc_html(atfp_get_plugin_display_name('automatic-translator-addon-for-loco-translate', $text_domain)); ?></strong>
<span class="addon-desc"><?php echo esc_html__('Loco addon to translate plugins and themes.', $text_domain); ?></span>
<?php if (atfp_is_plugin_installed('automatic-translator-addon-for-loco-translate')): ?>
<span class="installed"><?php echo esc_html__('Installed', $text_domain); ?></span>
<?php else: ?>
<a href="<?php echo esc_url(admin_url('plugin-install.php?s=LocoAI++Auto+Translate+for+Loco+Translate+by+CoolPlugins&tab=search&type=term') ); ?>" class="atfpp-dashboard-btn" target="_blank"><?php _e('Install', $text_domain); ?></a>
<?php endif; ?>
</div>
<div class="atfpp-dashboard-addon-r">
<img src="<?php echo esc_url(ATFPP_URL . 'admin/atfpp-dashboard/images/atlt-logo.png'); ?>" alt="<?php echo esc_html__('TranslatePress Addon', $text_domain); ?>">
</div>
</div>
</div>
<div class="atfpp-dashboard-rate-us">
<h3><?php echo esc_html__('Rate Us ⭐⭐⭐⭐⭐', $text_domain); ?></h3>
<p><?php echo esc_html__('We\'d love your feedback! Hope this addon made auto-translations easier for you.', $text_domain); ?></p>
<a href="https://wordpress.org/support/plugin/automatic-translations-for-polylang/reviews/#new-post" class="review-link" target="_blank"><?php echo esc_html__('Submit a Review →', $text_domain); ?></a>
</div>
</div>
<?php
function atfp_format_time_taken($time_taken, $text_domain) {
if ($time_taken === 0) return esc_html__('0', $text_domain);
if ($time_taken < 60) return sprintf(esc_html__('%d sec', $text_domain), $time_taken);
if ($time_taken < 3600) {
$min = floor($time_taken / 60);
$sec = $time_taken % 60;
return sprintf(esc_html__('%d min %d sec', $text_domain), $min, $sec);
}
$hours = floor($time_taken / 3600);
$min = floor(($time_taken % 3600) / 60);
return sprintf(esc_html__('%d hours %d min', $text_domain), $hours, $min);
}
function atfp_is_plugin_installed($plugin_slug) {
$plugins = get_plugins();
// Check if the plugin is installed
if ($plugin_slug === 'automatic-translator-addon-for-loco-translate') {
return isset($plugins['automatic-translator-addon-for-loco-translate/automatic-translator-addon-for-loco-translate.php']) || isset($plugins['loco-automatic-translate-addon-pro/loco-automatic-translate-addon-pro.php']);
}
return false; // Return false if no match found
}
function atfp_get_plugin_display_name($plugin_slug, $text_domain) {
$plugins = get_plugins();
// Define free and pro plugin paths
$plugin_paths = [
'automatic-translator-addon-for-loco-translate' => [
'free' => 'automatic-translator-addon-for-loco-translate/automatic-translator-addon-for-loco-translate.php',
'pro' => 'loco-automatic-translate-addon-pro/loco-automatic-translate-addon-pro.php',
'free_name' => esc_html__('LocoAI Auto Translate For Loco Translate', $text_domain),
'pro_name' => esc_html__('LocoAI Auto Translate for Loco Translate (Pro)', $text_domain),
],
];
// Check if the provided plugin slug exists
if (!isset($plugin_paths[$plugin_slug])) {
return $plugin_slug['free_name'];
}
$free_installed = isset($plugins[$plugin_paths[$plugin_slug]['free']]);
$pro_installed = isset($plugins[$plugin_paths[$plugin_slug]['pro']]);
// Determine which version is installed
if ($pro_installed) {
return $plugin_paths[$plugin_slug]['pro_name'];
} elseif ($free_installed) {
return $plugin_paths[$plugin_slug]['free_name'];
} else {
return $plugin_paths[$plugin_slug]['free_name'];
}
}
function atfp_format_number($number, $text_domain) {
if ($number >= 1000000000) {
return round($number / 1000000000, 1) . esc_html__('B', $text_domain);
} elseif ($number >= 1000000) {
return round($number / 1000000, 1) . esc_html__('M', $text_domain);
} elseif ($number >= 1000) {
return round($number / 1000, 1) . esc_html__('K', $text_domain);
}
return $number;
}

View File

@@ -0,0 +1,213 @@
<?php
/**
* Do not access the page directly
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'ATFP_Supported_Blocks' ) ) {
/**
* Class ATFP_Supported_Blocks
*
* This class handles the supported blocks for the AutoPoly - AI Translation For Polylang plugin.
*
* @package ATFPP
*/
class ATFP_Supported_Blocks {
/**
* Singleton instance.
*
* @var ATFP_Supported_Blocks
*/
private static $instance = null;
/**
* ATFPP plugin category.
*
* @var array
*/
private $atfpp_plugin_category = array();
/**
* Get the singleton instance of the class.
*
* @return ATFP_Supported_Blocks
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor for the ATFP_Supported_Blocks class.
*/
private function __construct() {
// wp:phpcs:ignore Wordpress.security Nonce verification is not required here
$tab=isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : '';
$page=isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
if('support-blocks' === $tab && 'polylang-atfpp-dashboard' === $page){
$this->atfpp_render_support_blocks_page();
$this->enqueue_editor_assets();
}
}
/**
* Enqueue editor CSS for the supported blocks page.
*/
public function enqueue_editor_assets( ) {
wp_enqueue_script( 'atfp-datatable-script', ATFPP_URL . 'assets/js/dataTables.min.js', array(), ATFPP_V, true );
wp_enqueue_script( 'atfp-datatable-style', ATFPP_URL . 'assets/js/dataTables.min.js', array(), ATFPP_V, true );
wp_enqueue_style( 'atfp-custom-data-table', ATFPP_URL . 'assets/css/atfp-custom-data-table.min.css', array(), ATFPP_V );
wp_enqueue_script( 'atfp-custom-data-table', ATFPP_URL . 'assets/js/atfp-custom-data-table.min.js', array('atfp-datatable-script'), ATFPP_V, true );
}
/**
* Add submenu page under the Polylang menu.
*/
public function atfpp_add_submenu_page() {
add_submenu_page(
'mlang', // Parent slug
__( 'Support Blocks', 'autopoly-ai-translation-for-polylang-pro' ), // Page title
__( '↳ Support Blocks', 'autopoly-ai-translation-for-polylang-pro' ), // Menu title
'manage_options', // Capability
'atfp-supported-blocks', // Menu slug
array( $this, 'atfpp_render_support_blocks_page' ) // Callback function
);
}
/**
* Render the support blocks page.
*/
public function atfpp_render_support_blocks_page() {
?>
<div class="atfp-custom-data-table-wrapper">
<h3><?php echo __('Supported Blocks Translation Settings', 'autopoly-ai-translation-for-polylang-pro'); ?>
<br>
<p><?php echo sprintf(esc_html__('Manage Gutenberg blocks to make them translation-ready with %s.', 'autopoly-ai-translation-for-polylang-pro'), 'AutoPoly'); ?></p>
</h3>
<div class="atfp-custom-data-table-filters">
<div class="atfp-filter-tab" data-column="1" data-default="all">
<label for="atfp-blocks-category"><?php esc_html_e( 'Block Type Category:', 'autopoly-ai-translation-for-polylang-pro' ); ?></label>
<select id="atfp-blocks-category" name="atfp_blocks_category">
<option value="all"><?php esc_html_e( 'All', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="core">Core</option>
<?php $this->atfpp_get_blocks_category(); ?>
</select>
</div>
<div class="atfp-filter-tab" data-column="3" data-default="all">
<label for="atfp-blocks-filter"><?php esc_html_e( 'Show Blocks:', 'autopoly-ai-translation-for-polylang-pro' ); ?></label>
<select id="atfp-blocks-filter" name="atfp_blocks_filter">
<option value="all"><?php esc_html_e( 'All', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="supported"><?php esc_html_e( 'Supported Blocks', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
<option value="unsupported"><?php esc_html_e( 'Unsupported Blocks', 'autopoly-ai-translation-for-polylang-pro' ); ?></option>
</select>
</div>
</div>
<div class="atfp-custom-table-section">
<div class="atfp-custom-table-lists">
<table class="atfp-custom-data-table-table" id="atfp-custom-datatable">
<thead>
<tr>
<th><?php esc_html_e( 'Sr.No', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Block Name', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Block Title', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Status', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
<th><?php esc_html_e( 'Modify', 'autopoly-ai-translation-for-polylang-pro' ); ?></th>
</tr>
</thead>
<tbody>
<?php
$this->atfpp_get_supported_blocks_table()
?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}
/**
* Get the blocks category.
*/
public function atfpp_get_blocks_category() {
$blocks_data = WP_Block_Type_Registry::get_instance()->get_all_registered();
$filter_blocks_data = array_filter( $blocks_data, function( $block ) {
return !in_array($block->category, array( 'media', 'reusable' ));
} );
foreach ( $filter_blocks_data as $block ) {
$plugin_name = explode('/', $block->name);
$plugin_name = isset($plugin_name[0]) ? $plugin_name[0] : '';
if(!empty($plugin_name)){
$filter_plugin_name = $this->atfpp_supported_block_name($plugin_name);
$filter_plugin_name=str_replace('-',' ',$filter_plugin_name);
$filter_plugin_name=ucwords($filter_plugin_name);
if(in_array($plugin_name, $this->atfpp_plugin_category) || $plugin_name === 'core'){
continue;
}
$this->atfpp_plugin_category[] = $plugin_name;
echo '<option value="' . esc_attr( $plugin_name ) . '">' . esc_html( $filter_plugin_name ) . '</option>';
}
}
}
/**
* Get the supported blocks.
*/
public function atfpp_get_supported_blocks_table() {
if ( class_exists( 'WP_Block_Type_Registry' ) && method_exists( 'WP_Block_Type_Registry', 'get_all_registered' ) ) {
$atfp_block_parse_rules = ATFPP_Helper::get_instance()->get_block_parse_rules();
$blocks_data = WP_Block_Type_Registry::get_instance()->get_all_registered();
$atfp_supported_blocks = isset($atfp_block_parse_rules['AtfpBlockParseRules']) ? $atfp_block_parse_rules['AtfpBlockParseRules'] : array();
$atfp_supported_blocks_names = array_keys( $atfp_supported_blocks );
$s_no = 1;
$atfp_post_id = ATFPP_Helper::get_custom_block_post_id();
$filter_blocks_data=$blocks_data;
foreach ( $filter_blocks_data as $block ) {
$block_name = esc_html( $block->name );
$block_title = esc_html( $block->title );
$status = ! in_array( $block_name, $atfp_supported_blocks_names ) ? 'Unsupported' : 'Supported'; // You can modify this logic based on your requirements
$modify_text = ! in_array( $block_name, $atfp_supported_blocks_names ) ? esc_html__( 'Add', 'autopoly-ai-translation-for-polylang-pro' ) : esc_html__( 'Edit', 'autopoly-ai-translation-for-polylang-pro' );
$modify_link = '<a href="' . esc_url( admin_url( 'post.php?post=' . esc_attr( $atfp_post_id ) . '&action=edit&atfp_new_block=' ) . esc_attr( $block_name ) ) . '">' . $modify_text . '</a>'; // Modify link
$modify_link = '<a href="' . esc_url( admin_url( 'post.php?post=' . esc_attr( $atfp_post_id ) . '&action=edit&atfp_new_block=' ) . esc_attr( $block_name ) ) . '">' . $modify_text . '</a>'; // Modify link
echo '<tr data-block-name="' . esc_attr( strtolower( $block_name ) ) . '" data-block-status="' . esc_attr( strtolower( $status ) ) . '" >';
echo '<td>' . esc_html($s_no++) . '</td>';
echo '<td>' . esc_html($block_name) . '</td>';
echo '<td>' . esc_html($block_title) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '<td>' . wp_kses($modify_link, array('a' => array('href' => array(), 'target' => array(), 'rel' => array()))) . '</td>';
echo '</tr>';
}
}
}
private function atfpp_supported_block_name($block_name){
$predfined_blocks = array(
'ub' => 'Ultimate Blocks',
'uagb' => 'Spectra',
'themeisle-blocks' => 'Otter Blocks'
);
if(array_key_exists($block_name, $predfined_blocks)){
return $predfined_blocks[$block_name];
}
return $block_name;
}
}
ATFP_Supported_Blocks::get_instance();
}