From ceed4d73e3d18db51e7dc7f6f3c28a4650da7c06 Mon Sep 17 00:00:00 2001 From: Stephen Cameron Date: Thu, 6 May 2021 20:24:09 +0200 Subject: [PATCH] Initial version of Cube Translate for Elementor plugin. WIP #3211 @30 --- .gitignore | 4 + composer.json | 15 + composer.lock | 1107 +++++++++++++++++++++++++++++++++ index.php | 32 + src/Admin.php | 19 + src/Elementor/Translation.php | 767 +++++++++++++++++++++++ src/Init.php | 40 ++ 7 files changed, 1984 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 index.php create mode 100644 src/Admin.php create mode 100644 src/Elementor/Translation.php create mode 100644 src/Init.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f3397c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.idea +*.log +/vendor diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..dea1437 --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "cubedesigners/wp-translate", + "description": "WordPress translation tools", + "type": "wordpress-plugin", + "require": { + "atomastic/arrays": "^2.2", + "phpoffice/phpspreadsheet": "^1.17", + "jfcherng/php-diff": "^6.10" + }, + "autoload": { + "psr-4": { + "CubeTranslate\\": "./src" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..72eef70 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1107 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a031b3dd0741b3f1beb0710e0d939746", + "packages": [ + { + "name": "atomastic/arrays", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/atomastic/arrays.git", + "reference": "77b60ad6abdfdd61730c42a336ddb5332d15f82a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/atomastic/arrays/zipball/77b60ad6abdfdd61730c42a336ddb5332d15f82a", + "reference": "77b60ad6abdfdd61730c42a336ddb5332d15f82a", + "shasum": "" + }, + "require": { + "atomastic/macroable": "^1.0", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "8.1.0", + "pestphp/pest": "^0.3.3", + "phpstan/phpstan": "^0.12.42" + }, + "type": "library", + "autoload": { + "psr-4": { + "Atomastic\\Arrays\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sergey Romanenko", + "email": "sergey.romanenko@flextype.org", + "homepage": "https://github.com/Awilum" + } + ], + "description": "Arrays Component provide a fluent, object-oriented interface for working with arrays, allowing you to chain multiple arrays operations together using a more readable syntax compared to traditional PHP arrays functions.", + "keywords": [ + "arrays", + "atomastic", + "php", + "php-arrays" + ], + "support": { + "issues": "https://github.com/atomastic/arrays/issues", + "source": "https://github.com/atomastic/arrays" + }, + "time": "2020-12-10T13:47:43+00:00" + }, + { + "name": "atomastic/macroable", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/atomastic/macroable.git", + "reference": "f614e5908792ddcc1342949165f6b004f13e2edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/atomastic/macroable/zipball/f614e5908792ddcc1342949165f6b004f13e2edb", + "reference": "f614e5908792ddcc1342949165f6b004f13e2edb", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "8.1.0", + "pestphp/pest": "^0.3.3", + "phpstan/phpstan": "^0.12.42" + }, + "type": "library", + "autoload": { + "psr-4": { + "Atomastic\\Macroable\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sergey Romanenko", + "email": "sergey.romanenko@flextype.org", + "homepage": "https://github.com/Awilum" + } + ], + "description": "Macroable Component is a trait that, gives you the ability in effect to add new methods to a class at runtime.", + "keywords": [ + "atomastic", + "macroable", + "php" + ], + "support": { + "issues": "https://github.com/atomastic/macroable/issues", + "source": "https://github.com/atomastic/macroable" + }, + "time": "2020-12-05T08:19:25+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.13.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/08e27c97e4c6ed02f37c5b2b20488046c8d90d75", + "reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ], + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/master" + }, + "time": "2020-06-29T00:56:53+00:00" + }, + { + "name": "jfcherng/php-color-output", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/jfcherng/php-color-output.git", + "reference": "2673074597eca9682d2fdfaee39a22418d4cc2f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jfcherng/php-color-output/zipball/2673074597eca9682d2fdfaee39a22418d4cc2f6", + "reference": "2673074597eca9682d2fdfaee39a22418d4cc2f6", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "phan/phan": "^2.2", + "phpunit/phpunit": "^7.2 || ^8.2 || ^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jfcherng\\Utility\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jack Cherng", + "email": "jfcherng@gmail.com" + } + ], + "description": "Make your PHP command-line application colorful.", + "keywords": [ + "ansi-colors", + "color", + "command-line", + "str-color" + ], + "support": { + "issues": "https://github.com/jfcherng/php-color-output/issues", + "source": "https://github.com/jfcherng/php-color-output/tree/2.0.2" + }, + "time": "2020-05-27T19:24:44+00:00" + }, + { + "name": "jfcherng/php-diff", + "version": "6.10.0", + "source": { + "type": "git", + "url": "https://github.com/jfcherng/php-diff.git", + "reference": "808f042a3dc97e1070d9e1d825bf596cd9d020b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/808f042a3dc97e1070d9e1d825bf596cd9d020b4", + "reference": "808f042a3dc97e1070d9e1d825bf596cd9d020b4", + "shasum": "" + }, + "require": { + "jfcherng/php-color-output": "^2.0", + "jfcherng/php-mb-string": "^1.3", + "jfcherng/php-sequence-matcher": "^3.2.5", + "php": ">=7.1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "liip/rmt": "^1.6", + "phan/phan": "^2.5 || ^3 || ^4", + "phpunit/phpunit": ">=7 <10", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jfcherng\\Diff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jack Cherng", + "email": "jfcherng@gmail.com" + }, + { + "name": "Chris Boulton", + "email": "chris.boulton@interspire.com" + } + ], + "description": "A comprehensive library for generating differences between two strings in multiple formats (unified, side by side HTML etc).", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/jfcherng/php-diff/issues", + "source": "https://github.com/jfcherng/php-diff/tree/6.10.0" + }, + "funding": [ + { + "url": "https://www.paypal.me/jfcherng/5usd", + "type": "custom" + } + ], + "time": "2021-03-19T09:20:25+00:00" + }, + { + "name": "jfcherng/php-mb-string", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/jfcherng/php-mb-string.git", + "reference": "ff8dacb993d83b5e8e2d8e325b5a015f3fb2da7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jfcherng/php-mb-string/zipball/ff8dacb993d83b5e8e2d8e325b5a015f3fb2da7d", + "reference": "ff8dacb993d83b5e8e2d8e325b5a015f3fb2da7d", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "phan/phan": "^2 || ^3 || ^4", + "phpunit/phpunit": "^7.2 || ^8 || ^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jfcherng\\Utility\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jack Cherng", + "email": "jfcherng@gmail.com" + } + ], + "description": "A high performance multibytes sting implementation for frequently reading/writing operations.", + "support": { + "issues": "https://github.com/jfcherng/php-mb-string/issues", + "source": "https://github.com/jfcherng/php-mb-string/tree/1.4.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/jfcherng/5usd", + "type": "custom" + } + ], + "time": "2021-01-16T11:51:16+00:00" + }, + { + "name": "jfcherng/php-sequence-matcher", + "version": "3.2.6", + "source": { + "type": "git", + "url": "https://github.com/jfcherng/php-sequence-matcher.git", + "reference": "369933b7cfbc31979fd94bf6391451468f49c693" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jfcherng/php-sequence-matcher/zipball/369933b7cfbc31979fd94bf6391451468f49c693", + "reference": "369933b7cfbc31979fd94bf6391451468f49c693", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phan/phan": "^2 || ^3", + "phpunit/phpunit": ">=7 <10", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jfcherng\\Diff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jack Cherng", + "email": "jfcherng@gmail.com" + }, + { + "name": "Chris Boulton", + "email": "chris.boulton@interspire.com" + } + ], + "description": "A longest sequence matcher. The logic is primarily based on the Python difflib package.", + "support": { + "issues": "https://github.com/jfcherng/php-sequence-matcher/issues", + "source": "https://github.com/jfcherng/php-sequence-matcher/tree/master" + }, + "funding": [ + { + "url": "https://www.paypal.me/jfcherng/5usd", + "type": "custom" + } + ], + "time": "2020-09-03T13:48:27+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/master" + }, + "funding": [ + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "markbaker/complex", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "9999f1432fae467bc93c53f357105b4c31bb994c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/9999f1432fae467bc93c53f357105b4c31bb994c", + "reference": "9999f1432fae467bc93c53f357105b4c31bb994c", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + }, + "files": [ + "classes/src/functions/abs.php", + "classes/src/functions/acos.php", + "classes/src/functions/acosh.php", + "classes/src/functions/acot.php", + "classes/src/functions/acoth.php", + "classes/src/functions/acsc.php", + "classes/src/functions/acsch.php", + "classes/src/functions/argument.php", + "classes/src/functions/asec.php", + "classes/src/functions/asech.php", + "classes/src/functions/asin.php", + "classes/src/functions/asinh.php", + "classes/src/functions/atan.php", + "classes/src/functions/atanh.php", + "classes/src/functions/conjugate.php", + "classes/src/functions/cos.php", + "classes/src/functions/cosh.php", + "classes/src/functions/cot.php", + "classes/src/functions/coth.php", + "classes/src/functions/csc.php", + "classes/src/functions/csch.php", + "classes/src/functions/exp.php", + "classes/src/functions/inverse.php", + "classes/src/functions/ln.php", + "classes/src/functions/log2.php", + "classes/src/functions/log10.php", + "classes/src/functions/negative.php", + "classes/src/functions/pow.php", + "classes/src/functions/rho.php", + "classes/src/functions/sec.php", + "classes/src/functions/sech.php", + "classes/src/functions/sin.php", + "classes/src/functions/sinh.php", + "classes/src/functions/sqrt.php", + "classes/src/functions/tan.php", + "classes/src/functions/tanh.php", + "classes/src/functions/theta.php", + "classes/src/operations/add.php", + "classes/src/operations/subtract.php", + "classes/src/operations/multiply.php", + "classes/src/operations/divideby.php", + "classes/src/operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/PHP8" + }, + "time": "2020-08-26T10:42:07+00:00" + }, + { + "name": "markbaker/matrix", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/361c0f545c3172ee26c3d596a0aa03f0cef65e6a", + "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + }, + "files": [ + "classes/src/Functions/adjoint.php", + "classes/src/Functions/antidiagonal.php", + "classes/src/Functions/cofactors.php", + "classes/src/Functions/determinant.php", + "classes/src/Functions/diagonal.php", + "classes/src/Functions/identity.php", + "classes/src/Functions/inverse.php", + "classes/src/Functions/minors.php", + "classes/src/Functions/trace.php", + "classes/src/Functions/transpose.php", + "classes/src/Operations/add.php", + "classes/src/Operations/directsum.php", + "classes/src/Operations/subtract.php", + "classes/src/Operations/multiply.php", + "classes/src/Operations/divideby.php", + "classes/src/Operations/divideinto.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/2.1.2" + }, + "time": "2021-01-23T16:37:31+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "46cf3d8498b095bd33727b13fd5707263af99421" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/46cf3d8498b095bd33727b13fd5707263af99421", + "reference": "46cf3d8498b095bd33727b13fd5707263af99421", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-02-15T16:11:48+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.17.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "c55269cb06911575a126dc225a05c0e4626e5fb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/c55269cb06911575a126dc225a05c0e4626e5fb4", + "reference": "c55269cb06911575a126dc225a05c0e4626e5fb4", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.13", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^1.5||^2.0", + "markbaker/matrix": "^1.2||^2.0", + "php": "^7.2||^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dompdf/dompdf": "^0.8.5", + "friendsofphp/php-cs-fixer": "^2.18", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "^8.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^8.5||^9.3", + "squizlabs/php_codesniffer": "^3.5", + "tecnickcom/tcpdf": "^6.3" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.17.1" + }, + "time": "2021-03-02T17:54:11+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..a9a56ed --- /dev/null +++ b/index.php @@ -0,0 +1,32 @@ + 'cube-translate_xls-download', + 'upload' => 'cube-translate_xls-upload', + 'confirm-import' => 'cube-translate_confirm-import', + ]; + protected const TYPE_TEXT = 'text'; + protected const TYPE_TEXTAREA = 'textarea'; + protected const TYPE_HTML = 'html'; + + public function register() { + + // Add menu item to side menu in dashboard under Cube Translate + add_action('admin_menu', function() { + add_submenu_page(Admin::$menu_slug, 'Elementor Bulk Translate', 'Elementor Translate', 'administrator', 'cube-translate-elementor', [$this, 'admin_page']); + }); + + // Add shortcut link to current page's translate menu + add_action('admin_bar_menu', function($admin_bar) { + + global $pagenow; + + // Only add the link when on admin page or on the frontend page + if (!($pagenow === 'post.php' && isset($_GET['post'])) && !is_page()) return false; + + // Check if current page is an Elementor page + $elementor = new \WP_Query([ + 'page_id' => get_the_ID(), + 'post_type' => 'page', + 'meta_key' => '_elementor_edit_mode', + 'meta_value' => 'builder', + ]); + + // Check if user has correct permissions and if this is an Elementor page + if (in_array('administrator', wp_get_current_user()->roles) && $elementor->post_count > 0) { + + $admin_bar->add_menu(array( + 'id' => 'wp-admin-bar-cube-translate-elementor', + 'title' => __('Cube Translate'), + 'href' => admin_url('admin.php') . '?page=cube-translate-elementor&id=' . get_the_ID(), + )); + } + + }, 200 ); + + // Catch loading of this plugin page (wp-admin/admin.php?page=cube-translate-elementor) + // Action tag comes from menu slugs: Cube Translate (cube-translate) + _page_ + Elementor Translate (cube-translate-elementor) + // Ref: https://codex.wordpress.org/Plugin_API/Action_Reference#Actions_Run_During_an_Admin_Page_Request + // Ref: https://developer.wordpress.org/reference/hooks/load-page-php/#comment-4340 + add_action('load-cube-translate_page_cube-translate-elementor', function() { + + // This function will run as the admin page loads (wp-admin/admin.php?page=cube-translate-elementor) + // but before there is any output, making it suitable for triggering the XLS download function. + // The other way to do this is using the 'admin_post' action: + // Ref: https://wordpress.stackexchange.com/a/342643 + // add_action('admin_post_cube-translate-download', [$this, 'export_XLS']); + // HTML form: + //
+ // + // + // + //
+ // However, this requires a POST request, which is inconvenient because we just want to be able to link + // to this from anywhere and have it trigger the XLS download after checking permissions... + + if (!$this->security_check()) { + wp_die("Sorry, you don't have permission to access this feature."); + } + + if (isset($_GET['id']) && isset($_GET['download'])) { + + // Ensure that the nonce is valid for this link + if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], $this->actions['download'])) { + wp_die('Error validating download link. Please go back and try again.'); + } + + $this->export_XLS($_GET['id']); + } + }); + + } + + public function __construct() { + + // TODO: add a hook here so the translatable and ignored widgets can be overridden by individual themes / sites + + // Definition of translatable widgets, which settings are translatable and what type of data it is + // Some widgets contain repeaters, which require special handling for sub-content + sub _id field + $this->translatable_widgets = [ + 'heading' => [ + 'title' => self::TYPE_TEXT, + ], + 'text-editor' => [ + 'editor' => self::TYPE_HTML, + ], + 'button' => [ + 'text' => self::TYPE_TEXT, + 'link.url' => self::TYPE_TEXT, + ], + 'image-box' => [ + 'title_text' => self::TYPE_TEXT, + 'description_text' => self::TYPE_TEXTAREA, + ], + 'cube-text' => [ + 'title' => self::TYPE_TEXT, + 'body' => self::TYPE_HTML, + 'cta_text' => self::TYPE_TEXT, + 'cta_link.url' => self::TYPE_TEXT, + ], + 'cube-picto-grid' => [ + 'items' => [ // Repeater + 'title' => self::TYPE_TEXT, + 'description' => self::TYPE_TEXTAREA, + ], + ], + 'cube-timeline' => [ + 'items' => [ // Repeater + 'title' => self::TYPE_TEXT, + 'details' => self::TYPE_HTML, + 'cta_text' => self::TYPE_TEXT, + 'cta_link.url' => self::TYPE_TEXT, + ], + ], + 'cube-timeline-horizontal' => [ + 'items' => [ // Repeater + 'title' => self::TYPE_TEXT, + 'cta_text' => self::TYPE_TEXT, + 'cta_link.url' => self::TYPE_TEXT, + ], + ], + 'cube-testimonial-carousel' => [ + 'testimonials' => [ // Repeater + 'name' => self::TYPE_TEXT, + 'testimonial' => self::TYPE_TEXTAREA, + 'notes' => self::TYPE_TEXT, + ], + ], + 'cube-dynamic-table' => [ + 'column_headings' => self::TYPE_TEXTAREA, + 'rows' => [ + 'cells' => self::TYPE_TEXTAREA + ], + ], + 'cube-fancy-list' => [ + 'title' => self::TYPE_TEXT, + 'items' => [ + 'content' => self::TYPE_HTML + ], + ], + 'cube-justified-list' => [ + 'items' => [ + 'title' => self::TYPE_HTML, + 'value' => self::TYPE_HTML, + ], + ], + 'cube-header-slideshow' => [ + 'title' => self::TYPE_TEXTAREA, + 'body' => self::TYPE_TEXTAREA, + ], + 'cube-link-carousel' => [ + 'links' => [ + 'title' => self::TYPE_TEXT, + 'url' => self::TYPE_TEXT, + ], + ], + 'cube-photo-grid' => [ + 'items' => [ + 'caption' => self::TYPE_TEXT, + ], + ], + + ]; + + // Widgets that shouldn't be translated + $this->ignored_widgets = [ + 'image', + 'image-carousel', + 'spacer', + 'google_maps', + 'cube-bg-image', + 'cube-form', + 'cube-scientific-news', + 'cube-news-banner', + ]; + } + + public function init($post_ID) { + // Elementor stores the content in the wp_postmeta table as JSON in _elementor_data + // This JSON contains the an array of "sections" that have child "elements" of "columns" + // and then inside those, individual "widgets". + // Section > Column > Widget > Settings (content, possibly a multidimensional array for repeaters) + + // Instead of getting the Elementor data from the database directly, we use the core Elementor functions + // because they handle all special cases for reading and saving the data (handling revisions etc). + // READING: See get_builder() function in elementor/includes/db.php + // SAVING: See ajax_save() function in elementor/core/documents-manager.php + + $this->document = Plugin::$instance->documents->get($post_ID); + $this->post = $this->document->get_post(); + $this->data = $this->document ? $this->document->get_elements_raw_data(null, false) : []; + } + + // Check that user has correct permissions for this plugin + public function security_check() { + // Todo: consider also checking for a custom capability like "cube_translate" so that non-administrator users could be granted access + return in_array('administrator', wp_get_current_user()->roles); + } + + public function admin_page() { + if (!isset($_REQUEST['id'])) { + + $pages = new \WP_Query( + [ + 'post_type' => 'page', + 'posts_per_page' => -1, + 'meta_key' => '_elementor_edit_mode', + 'meta_value' => 'builder', + ] + ); + + echo '

Select a page to translate

'; + echo ''; + + return true; + } + + $id = $_REQUEST['id']; + + if (empty(get_post_meta($id, '_elementor_data', true))) { + wp_die('Error: this post does not contain any Elementor data.'); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { + + $action = $_POST['action']; + + // Verify POST requests for security before acting on them. + // Only checks nonce and referrer because privilege check is done at a higher level + check_admin_referer($action); + + + switch($action) { + case $this->actions['upload']: + return $this->import_XLS(); + case $this->actions['confirm-import']: + return $this->apply_updates(); + } + } + + $this->init($id); + $preview = $this->generate_HTML($id); + $language = function_exists('pll_get_post_language') ? pll_get_post_language($id, 'flag') : ''; + + echo '

Cube Translate for Elementor

'; + echo "

+ $language + {$this->post->post_title} + post->ID}' class='button' style='vertical-align: middle'>Edit Page +

"; + + + echo '

+ + + + +

+ '; + + wp_nonce_field($this->actions['upload']); // Include nonce and referrer fields + + echo '
'; + + echo '

Translatable Content Preview

'; + echo '

'. count($this->translatable_data) .' translatable items found

'; + + $download_link = admin_url('admin.php') .'?page=cube-translate-elementor&download=xls&id='. $id; + $download_link = wp_nonce_url($download_link, $this->actions['download']); + + echo 'Download Spreadsheet '; + + echo $preview; + + } + + public function generate_HTML($ID = null) { + $spreadsheet = $this->prepare_spreadsheet($ID); + $writer = new Html($spreadsheet); + + $res = ''; + $res .= $writer->generateSheetData(); + + return $res; + } + + public function get_data() { + return $this->data; + } + + public function get_translatable_content() { + $this->process_data($this->data); + return $this->translatable_data; + } + + public function get_untranslated_widgets() { + return collect($this->untranslated_widgets)->unique(); + } + + + // Find and extract translatable items from Elementor data structure + // This function is called recursively + protected function process_data($data) { + if (!is_array($data)) return false; + + foreach ($data as $element) { + if ($element['elType'] === 'widget') { + $this->extract_translatable_content($element); + } + + if (isset($element['elements']) && !empty($element['elements'])) { + $this->process_data($element['elements']); + } + } + + } + + protected function extract_translatable_content($widget) { + // Only process Elementor widgets that we have a definition for + if (!array_key_exists($widget['widgetType'], $this->translatable_widgets)) { + // Keep track of any widgets that aren't handled for translation + if (!in_array($widget['widgetType'], $this->ignored_widgets)) { + $this->untranslated_widgets[] = $widget['widgetType']; + } + return false; + } + + // Get widget data into Arrays object for easier handling + $widget_data = Arrays::create($widget['settings']); + + foreach ($this->translatable_widgets[$widget['widgetType']] as $field => $format) { + + //=== Repeater fields + if (is_array($format)) { // Special case: when the field is a repeater, it will contain an array of details + $repeater_fields = $format; + + // Loop over repeater items + foreach ($widget_data->get($field, []) as $repeater_item) { + $repeater_data = Arrays::create($repeater_item); // Use Arrays library so we can use dot notation for nested data + + // Next, a loop for each translatable field + foreach ($repeater_fields as $repeater_field => $repeater_field_format) { + if ($repeater_data->get($repeater_field)) { // Invalid or empty fields will be skipped + $this->translatable_data[] = [ + 'id' => $widget['id'], + 'widget' => $widget['widgetType'], + 'field' => "{$field}#{$repeater_item['_id']}.{$repeater_field}", + 'content' => $repeater_data->get($repeater_field), // Handles dot notation access + ]; + } + } + } + + //=== Normal fields (empty fields will be skipped) + } elseif ($widget_data->get($field)) { + $this->translatable_data[] = [ + 'id' => $widget['id'], + 'widget' => $widget['widgetType'], + 'field' => $field, + 'content' => $widget_data->get($field), // Handles dot notation access + ]; + } + } + } + + public function update_contents($rows) { + + foreach ($rows as $row) { + + if (!key_exists('id', $row) || !key_exists('widget', $row) || !key_exists('field', $row) || !key_exists('translation', $row)) continue; + + $element_ID = trim($row['id']); + $widget_type = trim($row['widget']); + $field_name = trim($row['field']); + $content = trim($row['translation']); + $this->update_element($this->data, $element_ID, $widget_type, $field_name, $content); + } + + // dump("UPDATE COUNT", $this->update_count); + // dump(json_encode($this->data)); + + + } + + // Recursive function to search through the Elementor data structure and update content based on IDs + public function update_element(&$data, $element_ID, $widget_type, $field_name, $contents) { + + $match = false; + + // Search through all data until we find the item we're looking for + foreach ($data as &$element) { + + if ($element['id'] === $element_ID && isset($element['widgetType']) && $element['widgetType'] === $widget_type) { + + // Match found! Now update data... + $element = $this->update_field($element, $field_name, $contents); + + // End loop and recursion + return $element; + } + + // No match found so keep going recursively... + $match = $this->update_element($element['elements'], $element_ID, $widget_type, $field_name, $contents); + + if ($match) { + break; // Once a match is found stop searching at this level + } + } + + return $match; + } + + protected function update_field($element, $field_name, $contents) { + + // Special case: repeater elements have a different structure to normal element data + // The repeater subfield name and ID is stored with the field_name in the spreadsheet + // The format is: field_name#itemID.subfield (itemID and subfield are both optional) + // We can use dot notation to target nested data so both field_name and subfield can + // contain dots: eg. image.caption OR items#c697edd.cta_link.url + // Regex test: https://regex101.com/r/YLND5s/4 + preg_match('/(?[a-z0-9\-_\.]+)(?:#(?[a-z0-9]+))?(?:\.(?[a-z\-_\.]+))?/', $field_name, $matches); + extract($matches); // For easier access to $field, $id and $subfield + + if (empty($field)) return $element; // Bail out if at least the field isn't found + + //=== Find the field and update the value according to field pattern above + if (!empty($id) && !empty($subfield)) { // Repeater item (field name has both ID and subfield present) + foreach ($element['settings'][$field] as &$repeater_item) { + + $repeater_data = Arrays::create($repeater_item); // Allows updates using dot notation + + // Only perform update if contents have changed + if ($id === $repeater_data->get('_id') && $repeater_data->has($subfield) + && $this->has_changed($repeater_data->get($subfield), $contents)) { + $repeater_item = $repeater_data->set($subfield, $contents)->toArray(); + return $element; // Match found, end loop + } + } + + } else { // Normal field or field.subfield + $element_data = Arrays::create($element['settings']); // Allow updates using dot notation + + // Only perform an update if the contents have actually changed + if ($element_data->has($field) && $this->has_changed($element_data->get($field), $contents)) { + $element['settings'] = $element_data->set($field, $contents)->toArray(); // Convert back to normal array after update + } + } + + return $element; + } + + protected function has_changed($old_value, $new_value) { + + if ($old_value === $new_value) { + return false; + } + + // Log the changes + $this->updated_content[] = [ + 'old' => $old_value, + 'new' => $new_value, + ]; + + return true; + } + + public function get_changes() { + return $this->updated_content; + } + + public function count_changes() { + return count($this->get_changes()); + } + + public function prepare_spreadsheet($id = null) { + if ($id) $this->init($id); + $data = $this->get_translatable_content(); + + // Create new Spreadsheet object + $spreadsheet = new Spreadsheet(); + + // Set post title as document title + $spreadsheet->getProperties()->setTitle($this->post->post_title); + + // Create header row + $headers = ['ID', 'Widget', 'Field', 'Content', 'Translation']; + $spreadsheet->setActiveSheetIndex(0)->fromArray([$headers]); + $spreadsheet->getActiveSheet()->getStyle('A1:E1')->applyFromArray( + [ + 'font' => [ + 'bold' => true, + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['argb' => 'FFDDDDDD'], // AARRGGBB (Alpha Red Green Blue) + ], + ] + ); + + $sheet = $spreadsheet->getActiveSheet(); + + // Now populate the rest of the fields + for ($i = 0; $i < count($data); $i++) { + $x = $i + 2; // Offset the row number to allow for header row (i is zero-indexed but rows start from 1) + $sheet + ->setCellValue('A' . $x, $data[$i]['id']) + ->setCellValue('B' . $x, $data[$i]['widget']) + ->setCellValue('C' . $x, $data[$i]['field']) + ->setCellValue('D' . $x, $data[$i]['content'])->getStyle('D' . $x)->getAlignment()->setWrapText(true); + + $sheet->getStyle('E' . $x)->getAlignment()->setWrapText(true); + } + + // Set widths + $spreadsheet->getActiveSheet()->getColumnDimension('A')->setAutoSize(true); + $spreadsheet->getActiveSheet()->getColumnDimension('B')->setAutoSize(true); + $spreadsheet->getActiveSheet()->getColumnDimension('C')->setAutoSize(true); + $spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(70); // Original Content + $spreadsheet->getActiveSheet()->getColumnDimension('E')->setWidth(70); // Translation + + // Rename worksheet + $spreadsheet->getActiveSheet()->setTitle(substr($this->post->post_name,0,30)); // 31 char maximum for sheet title + + // Set active sheet index to the first sheet, so Excel opens this as the first sheet + $spreadsheet->setActiveSheetIndex(0); + + return $spreadsheet; + } + + public function import_XLS() { + + echo "

Importing Translations

"; + + // Make sure upload exists and there aren't any errors reported + if (!isset($_FILES['xls_upload']) || $_FILES['xls_upload']['error'] !== UPLOAD_ERR_OK) { + wp_die('There was an error uploading the spreadsheet. Please check the file and try again.'); + } + + // Check that upload is of the expected MIME type + $allowed_types = [ + 'application/vnd.ms-excel', // XLS + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // XLSX + ]; + + if (!in_array($_FILES['xls_upload']['type'], $allowed_types)) { + wp_die('Error: unexpected file format ('. $_FILES['xls_upload']['type'] .'). File must be XLS or XLSX format'); + } + + $id = $_REQUEST['id']; + $spreadsheet = IOFactory::load($_FILES['xls_upload']['tmp_name']); + $highest = $spreadsheet->getActiveSheet()->getHighestRowAndColumn(); + $data = $spreadsheet->getActiveSheet()->rangeToArray("A1:{$highest['column']}{$highest['row']}"); + + $keys = array_shift($data); // Take the first row to use as array key names + $keys = array_map('strtolower', array_map('trim', $keys)); // lowercase and trim key names + + // Apply heading keys to each row's data + foreach ($data as &$row) { + $row = array_combine($keys, $row); + } + + $this->init($id); + $this->update_contents($data); + + if ($this->count_changes() > 0) { + + echo "
{$this->count_changes()} updates were found. Please confirm the changes before applying them.
"; + + //=== Show a diff of changes + $differOptions = [ + 'context' => 0, // How many neighbour lines to show + 'ignoreCase' => false, // Case-sensitive + 'ignoreWhitespace' => false, // Don't ignore whitespace differences + ]; + + // Diff renderer class options + $rendererOptions = [ + 'detailLevel' => 'word', // how detailed the rendered HTML in-line diff is? (none, line, word, char) + 'lineNumbers' => false, // show line numbers in HTML renderers + 'separateBlock' => false, // show a separator between different diff hunks in HTML renderers + 'showHeader' => true, // show the (table) header + 'spacesToNbsp' => false, // Convert spaces to HTML   Alternative is to use CSS whitespace: pre + 'tabSize' => 4, // HTML renderer tab width (negative = do not convert into spaces) + // this option is currently only for the Combined renderer. + // it determines whether a replace-type block should be merged or not + // depending on the content changed ratio, which values between 0 and 1. + 'mergeThreshold' => 0.8, + // this option is currently only for the Unified and the Context renderers. + // RendererConstant::CLI_COLOR_AUTO = colorize the output if possible (default) + // RendererConstant::CLI_COLOR_ENABLE = force to colorize the output + // RendererConstant::CLI_COLOR_DISABLE = force not to colorize the output + 'cliColorization' => RendererConstant::CLI_COLOR_AUTO, + // this option is currently effective when the "detailLevel" is "word" + // characters listed in this array can be used to make diff segments into a whole + // for example, making "good-looking" into "good-looking" + // this should bring better readability but set this to empty array if you do not want it + 'wordGlues' => [' ', '-'], + // change this value to a string as the returned diff if the two input strings are identical + 'resultForIdenticals' => null, + // extra HTML classes added to the DOM of the diff container + 'wrapperClasses' => ['translate-diff-wrapper'], + ]; + + $old = array_column($this->get_changes(), 'old'); // Get all "old" text in a single array + $new = array_column($this->get_changes(), 'new'); // Get all "new" text in a single array + $diff = DiffHelper::calculate($old, $new, 'SideBySide', $differOptions, $rendererOptions); + // Hack: The "diff" class in the output conflicts with WordPress styling so remove it... + echo str_replace('translate-diff-wrapper diff diff-html diff-side-by-side', 'translate-diff-wrapper diff-html diff-side-by-side', $diff); + // Styling for diff table + echo ''; + + //=== Confirmation form + echo '

+ + + + '; + + wp_nonce_field($this->actions['confirm-import']); // Include nonce and referrer fields + + echo '
'; + + + } else { + // No applicable changes found + echo "
No possible updates were found in the uploaded file. Please check that you uploaded the correct spreadsheet or that changes haven't already been applied.
"; + echo '« Go Back'; + } + + return true; + } + + public function apply_updates() { + + echo "

Updating Translations

"; + + $id = $_POST['id']; + $data = unserialize(base64_decode($_POST['import_data'])); + + $this->init($id); + $this->update_contents($data); + + if ($this->count_changes() > 0) { + + // Tell Elementor to update the page content + $saved = $this->document->save([ + 'elements' => $this->get_data() + ]); + + if ($saved) { + echo "
Success! {$this->count_changes()} updates applied. View page.
"; + } else { + echo "
Error: the changes couldn't be applied.
"; + } + + } + + return true; + } + + public function export_XLS($id) { + + $spreadsheet = $this->prepare_spreadsheet($id); + + // Redirect output to the client's web browser (xls) + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment;filename="'. $this->post->post_name .'.xls"'); + header('Cache-Control: max-age=0'); + + $writer = IOFactory::createWriter($spreadsheet, 'Xls'); + $writer->save('php://output'); + exit; + } + +} diff --git a/src/Init.php b/src/Init.php new file mode 100644 index 0000000..61fa51f --- /dev/null +++ b/src/Init.php @@ -0,0 +1,40 @@ +register(); + } + } + } + + /** + * @param class $class Class from the services array + * @return class instance New instance of the class + */ + private static function instantiate($class) { + return new $class(); + } +} -- 2.39.5