├── .gitignore ├── .env ├── Dockerfile ├── config ├── provider │ ├── usermeta.yaml │ ├── comments.yaml │ ├── users.yaml │ ├── woocommerce_usermeta.yaml │ └── woocommerce_postmeta.yaml └── services.yaml ├── .dockerignore ├── src ├── Provider │ ├── AnonymizerProviderInterface.php │ ├── UserMetaProvider.php │ ├── WoocommerceUserMetaProvider.php │ ├── UserProvider.php │ ├── CommentProvider.php │ ├── UserMetaAnonymizerTrait.php │ ├── WoocommercePostMetaProvider.php │ └── AbstractAnonymizerProvider.php ├── Console │ └── Application.php ├── Anonymizer.php └── Command │ └── AnonymizeCommand.php ├── test ├── bootstrap.php ├── TestCase.php └── Provider │ ├── CommentProviderTest.php │ ├── UserMetaProviderTest.php │ ├── UserProviderTest.php │ ├── WoocommerceUserMetaProviderTest.php │ └── WoocommercePostMetaProviderTest.php ├── bin └── console ├── phpunit.xml.dist ├── LICENSE ├── docker-compose.yml ├── composer.json ├── .github └── workflows │ ├── test.yml │ └── docker.yml ├── README.md ├── ecs.php └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | *.local 3 | .phpunit.result.cache 4 | docker-compose.override.yml 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=mysql://test:test@127.0.0.1:6033/wp_test?serverVersion=8.0&charset=utf8mb4 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM williarin/php:8.0 2 | 3 | WORKDIR /srv/app 4 | COPY . . 5 | 6 | CMD ["php", "bin/console", "app:anonymize"] 7 | -------------------------------------------------------------------------------- /config/provider/usermeta.yaml: -------------------------------------------------------------------------------- 1 | first_name: firstName 2 | last_name: lastName 3 | nickname: firstName 4 | description: sentence 5 | -------------------------------------------------------------------------------- /config/provider/comments.yaml: -------------------------------------------------------------------------------- 1 | comment_author: userName 2 | comment_author_email: email 3 | comment_author_url: url 4 | comment_author_IP: ipv4 5 | -------------------------------------------------------------------------------- /config/provider/users.yaml: -------------------------------------------------------------------------------- 1 | user_login: userName 2 | user_pass: sha256 3 | user_nicename: userName 4 | user_email: email 5 | user_url: word 6 | display_name: name 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.github 3 | **/.idea 4 | **/test 5 | 6 | **/.gitignore 7 | **/*.lock 8 | **/*.cache 9 | **/*.dist 10 | **/*.md 11 | **/*.local 12 | **/.dockerignore 13 | **/docker-compose.* 14 | **/Dockerfile 15 | **/ecs.php 16 | **/Makefile 17 | -------------------------------------------------------------------------------- /src/Provider/AnonymizerProviderInterface.php: -------------------------------------------------------------------------------- 1 | usePutenv() 9 | ->bootEnv(dirname(__DIR__) . '/.env') 10 | ; 11 | } 12 | -------------------------------------------------------------------------------- /src/Provider/UserMetaProvider.php: -------------------------------------------------------------------------------- 1 | data = Yaml::parseFile(__DIR__ . '/../../config/provider/usermeta.yaml'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Console/Application.php: -------------------------------------------------------------------------------- 1 | add($command); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Provider/WoocommerceUserMetaProvider.php: -------------------------------------------------------------------------------- 1 | data = Yaml::parseFile(__DIR__ . '/../../config/provider/woocommerce_usermeta.yaml'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Anonymizer.php: -------------------------------------------------------------------------------- 1 | providers as $provider) { 22 | $provider->anonymize($useTransactions); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Provider/UserProvider.php: -------------------------------------------------------------------------------- 1 | connection->createQueryBuilder() 14 | ->select('ID', ...array_keys($this->data)) 15 | ->from($this->tablePrefix . 'users') 16 | ->executeQuery() 17 | ; 18 | 19 | $this->replaceValues($useTransactions, $users, 'users'); 20 | } 21 | 22 | protected function load(): void 23 | { 24 | $this->data = Yaml::parseFile(__DIR__ . '/../../config/provider/users.yaml'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | usePutenv()->bootEnv(dirname(__DIR__) . '/.env'); 15 | } 16 | 17 | $containerBuilder = new ContainerBuilder(); 18 | $loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__)); 19 | $loader->load(__DIR__ . '/../config/services.yaml'); 20 | $containerBuilder->compile(true); 21 | 22 | exit($containerBuilder->get(Application::class)->run()); 23 | -------------------------------------------------------------------------------- /src/Provider/CommentProvider.php: -------------------------------------------------------------------------------- 1 | connection->createQueryBuilder() 14 | ->select('comment_ID', ...array_keys($this->data)) 15 | ->from($this->tablePrefix . 'comments') 16 | ->executeQuery() 17 | ; 18 | 19 | $this->replaceValues($useTransactions, $comments, 'comments', 'comment_ID'); 20 | } 21 | 22 | protected function load(): void 23 | { 24 | $this->data = Yaml::parseFile(__DIR__ . '/../../config/provider/comments.yaml'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/provider/woocommerce_usermeta.yaml: -------------------------------------------------------------------------------- 1 | billing_first_name: firstName 2 | billing_last_name: lastName 3 | billing_company: company 4 | billing_address_1: streetAddress 5 | billing_address_2: secondaryAddress 6 | billing_city: city 7 | billing_postcode: postcode 8 | billing_state: state 9 | billing_country: countryCode 10 | billing_email: email 11 | billing_phone: phoneNumber 12 | shipping_first_name: firstName 13 | shipping_last_name: lastName 14 | shipping_company: company 15 | shipping_address_1: streetAddress 16 | shipping_address_2: secondaryAddress 17 | shipping_city: city 18 | shipping_postcode: postcode 19 | shipping_state: state 20 | shipping_country: countryCode 21 | _stripe_customer_id: ean13 22 | _customer_ip_address: ipv4 23 | payer_paypal_address: 24 | name: Payer PayPal address 25 | type: email 26 | payer_first_name: 27 | name: Payer first name 28 | type: firstName 29 | payer_last_name: 30 | name: Payer last name 31 | type: lastName 32 | -------------------------------------------------------------------------------- /config/provider/woocommerce_postmeta.yaml: -------------------------------------------------------------------------------- 1 | _billing_first_name: firstName 2 | _billing_last_name: lastName 3 | _billing_company: company 4 | _billing_address_1: streetAddress 5 | _billing_address_2: secondaryAddress 6 | _billing_city: city 7 | _billing_postcode: postcode 8 | _billing_state: state 9 | _billing_country: countryCode 10 | _billing_email: email 11 | _billing_phone: phoneNumber 12 | _shipping_first_name: firstName 13 | _shipping_last_name: lastName 14 | _shipping_company: company 15 | _shipping_address_1: streetAddress 16 | _shipping_address_2: secondaryAddress 17 | _shipping_city: city 18 | _shipping_postcode: postcode 19 | _shipping_state: state 20 | _shipping_country: countryCode 21 | _stripe_customer_id: ean13 22 | _customer_ip_address: ipv4 23 | payer_paypal_address: 24 | name: Payer PayPal address 25 | type: email 26 | payer_first_name: 27 | name: Payer first name 28 | type: firstName 29 | payer_last_name: 30 | name: Payer last name 31 | type: lastName 32 | -------------------------------------------------------------------------------- /src/Command/AnonymizeCommand.php: -------------------------------------------------------------------------------- 1 | anonymizer->anonymize(); 26 | 27 | $io = new SymfonyStyle($input, $output); 28 | $io->success('Database anonymized successfully.'); 29 | 30 | return Command::SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | test 21 | 22 | 23 | 24 | 25 | 26 | src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | env(TABLE_PREFIX): wp_ 3 | database_url: '%env(resolve:DATABASE_URL)%' 4 | table_prefix: '%env(resolve:TABLE_PREFIX)%' 5 | 6 | services: 7 | _instanceof: 8 | Symfony\Component\Console\Command\Command: 9 | tags: [ 'command' ] 10 | Williarin\WordpressAnonymizer\Provider\AnonymizerProviderInterface: 11 | tags: [ 'anonymizer_provider' ] 12 | 13 | _defaults: 14 | autowire: true 15 | autoconfigure: true 16 | public: true 17 | bind: 18 | iterable $commands: !tagged 'command' 19 | iterable $providers: !tagged 'anonymizer_provider' 20 | string $tablePrefix: '%table_prefix%' 21 | 22 | Williarin\WordpressAnonymizer\: 23 | resource: '../src/' 24 | 25 | Doctrine\DBAL\Connection: 26 | class: Doctrine\DBAL\Connection 27 | factory: ['Doctrine\DBAL\DriverManager', 'getConnection'] 28 | arguments: 29 | - { url: '%database_url%' } 30 | 31 | Faker\Generator: 32 | class: Faker\Generator 33 | factory: ['Faker\Factory', 'create'] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022, William Arin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | mysql: 5 | image: mysql:8 6 | ports: 7 | - '6033:3306' 8 | volumes: 9 | - db:/var/lib/mysql 10 | environment: 11 | - MYSQL_USER=test 12 | - MYSQL_PASSWORD=test 13 | - MYSQL_ROOT_PASSWORD=root 14 | - MYSQL_DATABASE=wp_test 15 | command: --default-authentication-plugin=mysql_native_password 16 | 17 | wordpress: 18 | image: wordpress:fpm-alpine 19 | environment: 20 | WORDPRESS_DB_HOST: mysql 21 | WORDPRESS_DB_USER: test 22 | WORDPRESS_DB_PASSWORD: test 23 | WORDPRESS_DB_NAME: wp_test 24 | volumes: 25 | - wordpress:/var/www/html 26 | 27 | wp-cli: 28 | image: wordpress:cli 29 | depends_on: 30 | - wordpress 31 | environment: 32 | WORDPRESS_DB_HOST: mysql 33 | WORDPRESS_DB_USER: test 34 | WORDPRESS_DB_PASSWORD: test 35 | WORDPRESS_DB_NAME: wp_test 36 | volumes: 37 | - wordpress:/var/www/html 38 | - ./assets/:/assets/:ro 39 | dns: 40 | - 1.1.1.1 41 | 42 | volumes: 43 | wordpress: 44 | db: 45 | -------------------------------------------------------------------------------- /test/TestCase.php: -------------------------------------------------------------------------------- 1 | container = new ContainerBuilder(); 23 | $loader = new YamlFileLoader($this->container, new FileLocator(__DIR__)); 24 | $loader->load(__DIR__ . '/../config/services.yaml'); 25 | $this->container->compile(true); 26 | 27 | $this->connection = $this->container->get(Connection::class); 28 | $this->tablePrefix = $this->container->getParameter('table_prefix'); 29 | 30 | $this->connection->beginTransaction(); 31 | } 32 | 33 | protected function tearDown(): void 34 | { 35 | $this->connection->rollBack(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Provider/UserMetaAnonymizerTrait.php: -------------------------------------------------------------------------------- 1 | connection->createQueryBuilder() 22 | ->select('user_id') 23 | ->from($this->tablePrefix . 'usermeta') 24 | ->groupBy('user_id') 25 | ; 26 | 27 | foreach (array_keys($this->data) as $key) { 28 | $queryBuilder 29 | ->addSelect(sprintf( 30 | "MAX(Case WHEN meta_key = '%s' THEN meta_value END) %s", 31 | is_array($this->data[$key]) ? $this->data[$key]['name'] : $key, 32 | $key, 33 | )) 34 | ; 35 | } 36 | 37 | $userMeta = $queryBuilder->executeQuery(); 38 | 39 | $this->replaceMetaValues($useTransactions, $userMeta, 'usermeta', 'user_id'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "williarin/wordpress-anonymizer", 3 | "description": "Anonymize a WordPress database without a WordPress installation", 4 | "license": "MIT", 5 | "type": "project", 6 | "authors": [ 7 | { 8 | "name": "William Arin", 9 | "email": "williamarin.dev@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=8.0", 14 | "doctrine/dbal": "^3.3", 15 | "fakerphp/faker": "^1.18", 16 | "symfony/config": "^6.0", 17 | "symfony/console": "^6.0", 18 | "symfony/dependency-injection": "^6.0", 19 | "symfony/dotenv": "^6.0", 20 | "symfony/string": "^6.0", 21 | "symfony/yaml": "^6.0" 22 | }, 23 | "require-dev": { 24 | "ergebnis/composer-normalize": "^2.23", 25 | "kubawerlos/php-cs-fixer-custom-fixers": "^3.7", 26 | "phpunit/phpunit": "^9.5", 27 | "roave/security-advisories": "dev-latest", 28 | "symfony/var-dumper": "^6.0", 29 | "symplify/coding-standard": "^10.0", 30 | "symplify/easy-coding-standard": "^10.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Williarin\\WordpressAnonymizer\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Williarin\\WordpressAnonymizer\\Test\\": "test/" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Provider/WoocommercePostMetaProvider.php: -------------------------------------------------------------------------------- 1 | connection->createQueryBuilder() 14 | ->select('post_id') 15 | ->from($this->tablePrefix . 'postmeta', 'pm') 16 | ->join('pm', $this->tablePrefix . 'posts', 'p', 'p.ID = pm.post_id') 17 | ->where("p.post_type = 'shop_order'") 18 | ->groupBy('post_id') 19 | ; 20 | 21 | foreach (array_keys($this->data) as $key) { 22 | $queryBuilder 23 | ->addSelect(sprintf( 24 | "MAX(Case WHEN meta_key = '%s' THEN meta_value END) %s", 25 | is_array($this->data[$key]) ? $this->data[$key]['name'] : $key, 26 | $key, 27 | )) 28 | ; 29 | } 30 | 31 | $postMeta = $queryBuilder->executeQuery(); 32 | 33 | $this->replaceMetaValues($useTransactions, $postMeta, 'postmeta', 'post_id'); 34 | } 35 | 36 | protected function load(): void 37 | { 38 | $this->data = Yaml::parseFile(__DIR__ . '/../../config/provider/woocommerce_postmeta.yaml'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**/README.md' 9 | pull_request: 10 | branches: 11 | - master 12 | paths-ignore: 13 | - '**/README.md' 14 | 15 | jobs: 16 | test: 17 | name: Test 18 | 19 | runs-on: ubuntu-20.04 20 | 21 | strategy: 22 | matrix: 23 | php: ['8.0', '8.1'] 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | 28 | - uses: FranzDiebold/github-env-vars-action@v2 29 | 30 | - name: Spin up Docker containers 31 | run: make reset-containers 32 | 33 | - name: Setup PHP 34 | id: setup-php 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php }} 38 | extensions: dom, curl, libxml, mbstring, zip 39 | tools: composer:v2 40 | 41 | - name: Validate composer.json and composer.lock 42 | run: composer validate 43 | 44 | - name: Get composer cache directory 45 | id: composer-cache 46 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 47 | 48 | - name: Cache dependencies 49 | uses: actions/cache@v2 50 | with: 51 | path: ${{ steps.composer-cache.outputs.dir }} 52 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 53 | restore-keys: ${{ runner.os }}-composer- 54 | 55 | - name: Install composer dependencies 56 | run: composer install 57 | 58 | - name: Install WordPress 59 | run: make install 60 | 61 | - name: Launch test suite 62 | run: make test 63 | -------------------------------------------------------------------------------- /test/Provider/CommentProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = $this->container->get(CommentProvider::class); 18 | } 19 | 20 | public function testAnonymizeComments(): void 21 | { 22 | $this->provider->anonymize(); 23 | $comments = $this->getComments(); 24 | 25 | $original = [ 26 | [ 27 | 'comment_author' => 'A WordPress Commenter', 28 | 'comment_author_email' => 'wapuu@wordpress.example', 29 | 'comment_author_url' => 'https://wordpress.org/', 30 | 'comment_author_IP' => '', 31 | ], 32 | [ 33 | 'comment_author' => 'Mr Robinson', 34 | 'comment_author_email' => 'robinson@wp.local', 35 | 'comment_author_url' => '', 36 | 'comment_author_IP' => '201.202.203.204', 37 | ], 38 | [ 39 | 'comment_author' => 'Jeff Park', 40 | 'comment_author_email' => 'jeff@wp.local', 41 | 'comment_author_url' => '', 42 | 'comment_author_IP' => '201.202.203.205', 43 | ], 44 | [ 45 | 'comment_author' => 'Linda Johnson', 46 | 'comment_author_email' => 'linda@wp.local', 47 | 'comment_author_url' => '', 48 | 'comment_author_IP' => '201.202.203.206', 49 | ], 50 | ]; 51 | 52 | foreach ($original as $i => $comment) { 53 | foreach ($comment as $key => $value) { 54 | self::assertNotSame($value, $comments[$i][$key]); 55 | } 56 | } 57 | } 58 | 59 | private function getComments(): array 60 | { 61 | return $this->connection->createQueryBuilder() 62 | ->select('comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP') 63 | ->from($this->tablePrefix . 'comments') 64 | ->executeQuery() 65 | ->fetchAllAssociative() 66 | ; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | jobs: 9 | build: 10 | name: Build Docker image 11 | 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: FranzDiebold/github-env-vars-action@v2 18 | 19 | - name: Setup PHP 20 | id: setup-php 21 | uses: shivammathur/setup-php@v2 22 | with: 23 | php-version: '8.0' 24 | extensions: dom, curl, libxml, mbstring, zip 25 | tools: composer:v2 26 | 27 | - name: Validate composer.json and composer.lock 28 | run: composer validate 29 | 30 | - name: Get composer cache directory 31 | id: composer-cache 32 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 33 | 34 | - name: Cache dependencies 35 | uses: actions/cache@v2 36 | with: 37 | path: ${{ steps.composer-cache.outputs.dir }} 38 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 39 | restore-keys: ${{ runner.os }}-composer- 40 | 41 | - name: Install composer dependencies 42 | run: composer install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction 43 | 44 | - name: Set major tag name 45 | run: echo "TAG_MAJOR=$(echo $CI_REF_NAME | cut -d. -f1)" >> $GITHUB_ENV 46 | 47 | - name: Set major tag name 48 | run: echo "TAG_MINOR=${{ env.TAG_MAJOR }}.$(echo $CI_REF_NAME | cut -d. -f2)" >> $GITHUB_ENV 49 | 50 | - name: Build Docker image 51 | run: | 52 | docker build -t williarin/wordpress-anonymizer:latest . 53 | docker tag williarin/wordpress-anonymizer:latest williarin/wordpress-anonymizer:$CI_REF_NAME 54 | docker tag williarin/wordpress-anonymizer:latest williarin/wordpress-anonymizer:${{ env.TAG_MAJOR }} 55 | docker tag williarin/wordpress-anonymizer:latest williarin/wordpress-anonymizer:${{ env.TAG_MINOR }} 56 | 57 | - name: Login to registry 58 | run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin 59 | 60 | - name: Push Docker image 61 | run: | 62 | docker push --all-tags williarin/wordpress-anonymizer 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress Anonymizer 2 | 3 | ## Introduction 4 | 5 | This repository can be used as a third party library or as a standalone Docker application. 6 | 7 | The main use case is to anonymize a database filled with user and customer data before committing it to a VCS repository. 8 | It will anonymize both WordPress base data and WooCommerce data. 9 | 10 | ## Docker standalone usage 11 | 12 | Run this command to automatically anonymize your WordPress database. 13 | 14 | **WARNING!** This operation is irreversible. Make a database backup before proceeding. 15 | To make automatic backups of your WordPress database, you can use [williarin/secure-mysql-backups](https://github.com/williarin/secure-mysql-backups). 16 | 17 | ```bash 18 | docker run --rm \ 19 | -e DATABASE_URL='mysql://user:user@127.0.0.1:3306/wp_mywebsite?serverVersion=8.0&charset=utf8mb4' \ 20 | williarin/wordpress-anonymizer 21 | ``` 22 | 23 | Variables: 24 | 25 | | Variable | Description | Default | 26 | |----------------|-------------------------------------|------------------------------------------------------------------------------| 27 | | `DATABASE_URL` | The database url to connect to. | `mysql://test:test@127.0.0.1:6033/wp_test?serverVersion=8.0&charset=utf8mb4` | 28 | | `TABLE_PREFIX` | The table prefix used by WordPress. | `wp_` | 29 | 30 | 31 | ## Installation as a library in your project 32 | 33 | To integrate this library to your project, install it with Composer: 34 | ```bash 35 | composer require williarin/wordpress-anonymizer 36 | ``` 37 | 38 | ### Usage 39 | 40 | ```php 41 | $faker = Faker\Factory::create(); 42 | $connection = DriverManager::getConnection(['url' => 'mysql://user:pass@localhost:3306/wp_mywebsite?serverVersion=8.0']); 43 | $tablePrefix = 'wp_'; 44 | 45 | $anonymizer = new Anonymizer([ 46 | new UserProvider($connection, $faker, $tablePrefix), 47 | new UserMetaProvider($connection, $faker, $tablePrefix), 48 | new CommentProvider($connection, $faker, $tablePrefix), 49 | new WoocommerceUserMetaProvider($connection, $faker, $tablePrefix), 50 | new WoocommercePostMetaProvider($connection, $faker, $tablePrefix), 51 | ]); 52 | 53 | // Anonymize the whole database at once 54 | $anonymizer->anonymize(); 55 | 56 | // or use a provider to anonymize only a part 57 | $commentProvider = new CommentProvider($connection, $faker, $tablePrefix); 58 | $commentProvider->anonymize(); 59 | ``` 60 | 61 | ## License 62 | 63 | [MIT](LICENSE) 64 | 65 | Copyright (c) 2022, William Arin 66 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | parameters(); 21 | $parameters->set(Option::PARALLEL, true); 22 | $parameters->set(Option::PATHS, [ 23 | __DIR__ . '/src', 24 | __DIR__ . '/test', 25 | ]); 26 | $parameters->set(Option::SKIP, [ 27 | PhpUnitInternalClassFixer::class, 28 | PhpUnitTestClassRequiresCoversFixer::class, 29 | ]); 30 | 31 | $containerConfigurator->import(SetList::SYMPLIFY); 32 | $containerConfigurator->import(SetList::PSR_12); 33 | $containerConfigurator->import(SetList::PHP_CS_FIXER); 34 | $containerConfigurator->import(SetList::DOCTRINE_ANNOTATIONS); 35 | $containerConfigurator->import(SetList::CLEAN_CODE); 36 | 37 | $services = $containerConfigurator->services(); 38 | 39 | $services->set(DocBlockLineLengthFixer::class) 40 | ->call('configure', [[ 41 | DocBlockLineLengthFixer::LINE_LENGTH => 120, 42 | ]]); 43 | 44 | $services->set(YodaStyleFixer::class) 45 | ->call('configure', [[ 46 | 'equal' => false, 47 | 'identical' => false, 48 | 'less_and_greater' => false, 49 | ]]); 50 | 51 | $services->set(PhpdocTypesOrderFixer::class) 52 | ->call('configure', [[ 53 | 'null_adjustment' => 'always_last', 54 | 'sort_algorithm' => 'none', 55 | ]]); 56 | 57 | $services->set(OrderedImportsFixer::class) 58 | ->call('configure', [[ 59 | 'imports_order' => ['class', 'function', 'const'], 60 | ]]); 61 | 62 | $services->set(ConcatSpaceFixer::class) 63 | ->call('configure', [[ 64 | 'spacing' => 'one', 65 | ]]); 66 | 67 | $services->set(LineLengthFixer::class) 68 | ->call('configure', [[ 69 | LineLengthFixer::LINE_LENGTH => 120, 70 | ]]); 71 | 72 | $services->set(NoUnusedImportsFixer::class); 73 | $services->set(NoDuplicatedImportsFixer::class); 74 | }; 75 | -------------------------------------------------------------------------------- /test/Provider/UserMetaProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = $this->container->get(UserMetaProvider::class); 18 | } 19 | 20 | public function testAnonymizeUserMeta(): void 21 | { 22 | $this->provider->anonymize(); 23 | $userMeta = $this->getUserMeta(); 24 | 25 | $original = [ 26 | [ 27 | 'first_name' => '', 28 | 'last_name' => '', 29 | 'nickname' => 'admin', 30 | 'description' => '', 31 | ], 32 | [ 33 | 'first_name' => 'Robert', 34 | 'last_name' => 'Carter', 35 | 'nickname' => 'bob', 36 | 'description' => 'Nice man', 37 | ], 38 | [ 39 | 'first_name' => 'Ann', 40 | 'last_name' => 'Jolly', 41 | 'nickname' => 'ann', 42 | 'description' => 'Nice woman', 43 | ], 44 | [ 45 | 'first_name' => 'William', 46 | 'last_name' => 'Arin', 47 | 'nickname' => 'will', 48 | 'description' => 'Very nice man', 49 | ], 50 | [ 51 | 'first_name' => '', 52 | 'last_name' => '', 53 | 'nickname' => 'shopmanager', 54 | 'description' => '', 55 | ], 56 | [ 57 | 'first_name' => '', 58 | 'last_name' => '', 59 | 'nickname' => 'justin', 60 | 'description' => '', 61 | ], 62 | [ 63 | 'first_name' => '', 64 | 'last_name' => '', 65 | 'nickname' => 'otis', 66 | 'description' => '', 67 | ], 68 | ]; 69 | 70 | foreach ($original as $i => $meta) { 71 | foreach ($meta as $key => $value) { 72 | self::assertNotSame($value, $userMeta[$i][$key]); 73 | } 74 | } 75 | } 76 | 77 | private function getUserMeta(): array 78 | { 79 | return $this->connection->createQueryBuilder() 80 | ->addSelect("MAX(Case WHEN meta_key = 'first_name' THEN meta_value END) first_name") 81 | ->addSelect("MAX(Case WHEN meta_key = 'last_name' THEN meta_value END) last_name") 82 | ->addSelect("MAX(Case WHEN meta_key = 'nickname' THEN meta_value END) nickname") 83 | ->addSelect("MAX(Case WHEN meta_key = 'description' THEN meta_value END) description") 84 | ->from($this->tablePrefix . 'usermeta') 85 | ->groupBy('user_id') 86 | ->executeQuery() 87 | ->fetchAllAssociative() 88 | ; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/Provider/UserProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = $this->container->get(UserProvider::class); 18 | } 19 | 20 | public function testAnonymizeUsers(): void 21 | { 22 | $this->provider->anonymize(); 23 | $users = $this->getUsers(); 24 | 25 | $original = [ 26 | [ 27 | 'user_login' => 'admin', 28 | 'user_pass' => '$P$B4GpYTuKoDZe3Weq0n6JeQtuFKVh/m.', 29 | 'user_nicename' => 'admin', 30 | 'user_email' => 'contact@example.com', 31 | 'user_url' => 'http://localhost', 32 | 'display_name' => 'admin', 33 | ], 34 | [ 35 | 'user_login' => 'bob', 36 | 'user_pass' => '$P$BXBzOCmXP3jgN8KFiIjCdFPsmWFajN/', 37 | 'user_nicename' => 'bob', 38 | 'user_email' => 'bob@wp.local', 39 | 'user_url' => '', 40 | 'display_name' => 'bob', 41 | ], 42 | [ 43 | 'user_login' => 'ann', 44 | 'user_pass' => '$P$BQNoq2BzYfmkLU5qYWUAUfqwCKBZQ90', 45 | 'user_nicename' => 'ann', 46 | 'user_email' => 'ann@wp.local', 47 | 'user_url' => '', 48 | 'display_name' => 'ann', 49 | ], 50 | [ 51 | 'user_login' => 'will', 52 | 'user_pass' => '$P$BM3muhw3Gr05x9wlJMcAEX8n/xSVHX.', 53 | 'user_nicename' => 'will', 54 | 'user_email' => 'will@wp.local', 55 | 'user_url' => '', 56 | 'display_name' => 'will', 57 | ], 58 | [ 59 | 'user_login' => 'shopmanager', 60 | 'user_pass' => '$P$Bhsc92ufyaPXpcdVq0M5BbMa2.Xwnf.', 61 | 'user_nicename' => 'shopmanager', 62 | 'user_email' => 'info@woocommerce.com', 63 | 'user_url' => '', 64 | 'display_name' => 'Shop Manager', 65 | ], 66 | [ 67 | 'user_login' => 'justin', 68 | 'user_pass' => '$P$BNkush6nTDlLyHfCz6GiKqeJvsRq9s/', 69 | 'user_nicename' => 'justin', 70 | 'user_email' => 'justin@woo.local', 71 | 'user_url' => '', 72 | 'display_name' => 'justin', 73 | ], 74 | [ 75 | 'user_login' => 'otis', 76 | 'user_pass' => '$P$BIsW3sWeHXDkYJx6CIbmHgzDVcUwdM/', 77 | 'user_nicename' => 'otis', 78 | 'user_email' => 'otis@woo.local', 79 | 'user_url' => '', 80 | 'display_name' => 'otis', 81 | ], 82 | ]; 83 | 84 | foreach ($original as $i => $comment) { 85 | foreach ($comment as $key => $value) { 86 | self::assertNotSame($value, $users[$i][$key]); 87 | } 88 | } 89 | } 90 | 91 | private function getUsers(): array 92 | { 93 | return $this->connection->createQueryBuilder() 94 | ->select('user_login', 'user_pass', 'user_nicename', 'user_email', 'user_url', 'display_name') 95 | ->from($this->tablePrefix . 'users') 96 | ->executeQuery() 97 | ->fetchAllAssociative() 98 | ; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Provider/AbstractAnonymizerProvider.php: -------------------------------------------------------------------------------- 1 | load(); 22 | } 23 | 24 | abstract protected function load(): void; 25 | 26 | protected function replaceValues( 27 | bool $useTransactions, 28 | Result $rows, 29 | string $tableName, 30 | string $idFieldName = 'ID' 31 | ): void { 32 | if ($useTransactions) { 33 | $this->connection->beginTransaction(); 34 | } 35 | 36 | while (($row = $rows->fetchAssociative()) !== false) { 37 | foreach ($row as $key => $value) { 38 | if (!array_key_exists($key, $this->data)) { 39 | continue; 40 | } 41 | 42 | $formatter = $this->data[$key]; 43 | 44 | $this->connection->createQueryBuilder() 45 | ->update($this->tablePrefix . $tableName) 46 | ->set($key, ':value') 47 | ->where(sprintf('%s = :id', $idFieldName)) 48 | ->setParameters([ 49 | 'id' => $row[$idFieldName], 50 | 'value' => $this->faker->{$formatter}, 51 | ]) 52 | ->executeStatement() 53 | ; 54 | } 55 | } 56 | 57 | if ($useTransactions) { 58 | try { 59 | $this->connection->commit(); 60 | } catch (\Exception $e) { 61 | $this->connection->rollBack(); 62 | 63 | throw $e; 64 | } 65 | } 66 | } 67 | 68 | protected function replaceMetaValues( 69 | bool $useTransactions, 70 | Result $rows, 71 | string $tableName, 72 | string $idFieldName 73 | ): void { 74 | if ($useTransactions) { 75 | $this->connection->beginTransaction(); 76 | } 77 | 78 | while (($row = $rows->fetchAssociative()) !== false) { 79 | foreach ($row as $key => $value) { 80 | if (!array_key_exists($key, $this->data)) { 81 | continue; 82 | } 83 | 84 | $formatter = is_array($this->data[$key]) ? $this->data[$key]['type'] : $this->data[$key]; 85 | 86 | $this->connection->createQueryBuilder() 87 | ->update($this->tablePrefix . $tableName) 88 | ->set('meta_value', ':value') 89 | ->where('meta_key = :key') 90 | ->andWhere(sprintf('%s = :id', $idFieldName)) 91 | ->setParameters([ 92 | 'id' => $row[$idFieldName], 93 | 'key' => is_array($this->data[$key]) ? $this->data[$key]['name'] : $key, 94 | 'value' => $this->faker->{$formatter}, 95 | ]) 96 | ->executeStatement() 97 | ; 98 | } 99 | } 100 | 101 | if ($useTransactions) { 102 | try { 103 | $this->connection->commit(); 104 | } catch (\Exception $e) { 105 | $this->connection->rollBack(); 106 | 107 | throw $e; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CLI=docker compose run --rm wp-cli wp 2 | 3 | .PHONY: install reset reset-containers reset-install reset-users reset-comments reset-woocommerce 4 | install: reset-install reset-users reset-woocommerce reset-comments 5 | 6 | reset: reset-containers reset-install reset-users reset-woocommerce reset-comments 7 | 8 | reset-containers: 9 | @echo "Preparing containers..." 10 | @docker compose down -v || true 11 | @docker compose up -d 12 | 13 | reset-install: 14 | @echo "Waiting 10 seconds for environment to be ready..." 15 | @sleep 10 16 | @echo "Installing WordPress..." 17 | @$(CLI) core install \ 18 | --url="http://localhost" \ 19 | --title="My Awesome WordPress Site" \ 20 | --admin_user=admin \ 21 | --admin_password=admin \ 22 | --admin_email=contact@example.com \ 23 | --skip-email 24 | @$(CLI) plugin install woocommerce --activate 25 | @$(CLI) plugin install wordpress-importer --activate 26 | 27 | reset-users: 28 | @echo "Create some sample users..." 29 | @$(CLI) user create bob bob@wp.local --first_name=Robert --last_name=Carter --nickname=Bob --description="Nice man" --role=author --porcelain 30 | @$(CLI) user create ann ann@wp.local --first_name=Ann --last_name=Jolly --nickname=Ann --description="Nice woman" --role=author --porcelain 31 | @$(CLI) user create will will@wp.local --first_name=William --last_name=Arin --nickname=Will --description="Very nice man" --role=author --porcelain 32 | 33 | reset-comments: 34 | @echo "Create some sample comments..." 35 | @$(CLI) comment create --comment_post_ID=1 --comment_content="hello blog" --comment_author="Mr Robinson" --comment_author_email="robinson@wp.local" --comment_author_IP="201.202.203.204" 36 | @$(CLI) comment create --comment_post_ID=10 --comment_content="nice website!" --comment_author="Jeff Park" --comment_author_email="jeff@wp.local" --comment_author_IP="201.202.203.205" 37 | @$(CLI) comment create --comment_post_ID=12 --comment_content="this is really impressive" --comment_author="Linda Johnson" --comment_author_email="linda@wp.local" --comment_author_IP="201.202.203.206" 38 | 39 | reset-woocommerce: 40 | @echo "Importing WooCommerce sample data and creating some more..." 41 | @$(CLI) import wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=create 42 | @$(CLI) wc customer create --email='justin@woo.local' --user=1 --password='he llo' \ 43 | --billing='{"first_name":"Justin","last_name":"Hills","company":"Google","address_1":"4571 Ersel Street","city":"Dallas","state":"Texas","postcode":"75204","country":"United States","email":"justin@woo.local","phone":"214-927-9108"}' \ 44 | --shipping='{"first_name":"Justin","last_name":"Hills","company":"Google","address_1":"4571 Ersel Street","city":"Dallas","state":"Texas","postcode":"75204","country":"United States","email":"justin@woo.local","phone":"214-927-9108"}' 45 | @$(CLI) wc customer create --email='otis@woo.local' --user=1 --password='he llo' \ 46 | --billing='{"first_name":"Ottis","last_name":"Bruen","company":"Facebook","address_1":"81 Spring St","city":"New York","state":"North Dakota","postcode":"10012","country":"United States","email":"ottis@woo.local","phone":"(646) 613-1367"}' \ 47 | --shipping='{"first_name":"Ottis","last_name":"Bruen","company":"Facebook","address_1":"81 Spring St","city":"New York","state":"North Dakota","postcode":"10012","country":"United States","email":"ottis@woo.local","phone":"(646) 613-1367"}' 48 | @$(CLI) wc shop_order create --user=1 --customer_id=6 --line_items='[{"product_id":17},{"product_id":23}]' 49 | @$(CLI) wc shop_order create --user=1 --customer_id=7 --line_items='[{"product_id":24}]' 50 | @$(CLI) wc shop_order create --user=1 --customer_id=0 --line_items='[{"product_id":20},{"product_id":22}]' \ 51 | --billing='{"first_name":"Trudie","last_name":"Metz","company":"Amazon","address_1":"135 Wyandot Ave","city":"Marion","state":"Ohio","postcode":"43302","country":"United States","email":"trudie@woo.local","phone":"(740) 383-4031"}' \ 52 | --shipping='{"first_name":"Trudie","last_name":"Metz","company":"Amazon","address_1":"135 Wyandot Ave","city":"Marion","state":"Ohio","postcode":"43302","country":"United States","email":"trudie@woo.local","phone":"(740) 383-4031"}' 53 | 54 | .PHONY: test 55 | test: 56 | @./vendor/bin/phpunit 57 | @./vendor/bin/ecs check 58 | 59 | .PHONY: fix 60 | fix: 61 | @./vendor/bin/ecs check --fix 62 | -------------------------------------------------------------------------------- /test/Provider/WoocommerceUserMetaProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = $this->container->get(WoocommerceUserMetaProvider::class); 19 | } 20 | 21 | public function testAnonymizeUserMeta(): void 22 | { 23 | $this->provider->anonymize(); 24 | $userMeta = $this->getWoocommerceUserMeta(); 25 | 26 | $original = [ 27 | ...array_fill(0, 5, [ 28 | 'billing_first_name' => null, 29 | 'billing_last_name' => null, 30 | 'billing_company' => null, 31 | 'billing_address_1' => null, 32 | 'billing_address_2' => null, 33 | 'billing_city' => null, 34 | 'billing_postcode' => null, 35 | 'billing_state' => null, 36 | 'billing_country' => null, 37 | 'billing_email' => null, 38 | 'billing_phone' => null, 39 | 'shipping_first_name' => null, 40 | 'shipping_last_name' => null, 41 | 'shipping_company' => null, 42 | 'shipping_address_1' => null, 43 | 'shipping_address_2' => null, 44 | 'shipping_city' => null, 45 | 'shipping_postcode' => null, 46 | 'shipping_state' => null, 47 | 'shipping_country' => null, 48 | '_stripe_customer_id' => null, 49 | '_customer_ip_address' => null, 50 | 'payer_paypal_address' => null, 51 | 'payer_first_name' => null, 52 | 'payer_last_name' => null, 53 | ]), 54 | [ 55 | 'billing_first_name' => 'Justin', 56 | 'billing_last_name' => 'Hills', 57 | 'billing_company' => 'Google', 58 | 'billing_address_1' => '4571 Ersel Street', 59 | 'billing_address_2' => null, 60 | 'billing_city' => 'Dallas', 61 | 'billing_postcode' => '75204', 62 | 'billing_state' => 'Texas', 63 | 'billing_country' => 'United States', 64 | 'billing_email' => 'justin@woo.local', 65 | 'billing_phone' => '214-927-9108', 66 | 'shipping_first_name' => 'Justin', 67 | 'shipping_last_name' => 'Hills', 68 | 'shipping_company' => 'Google', 69 | 'shipping_address_1' => '4571 Ersel Street', 70 | 'shipping_address_2' => null, 71 | 'shipping_city' => 'Dallas', 72 | 'shipping_postcode' => '75204', 73 | 'shipping_state' => 'Texas', 74 | 'shipping_country' => 'United States', 75 | '_stripe_customer_id' => null, 76 | '_customer_ip_address' => null, 77 | 'payer_paypal_address' => null, 78 | 'payer_first_name' => null, 79 | 'payer_last_name' => null, 80 | ], 81 | [ 82 | 'billing_first_name' => 'Ottis', 83 | 'billing_last_name' => 'Bruen', 84 | 'billing_company' => 'Facebook', 85 | 'billing_address_1' => '81 Spring St', 86 | 'billing_address_2' => null, 87 | 'billing_city' => 'New York', 88 | 'billing_postcode' => '10012', 89 | 'billing_state' => 'North Dakota', 90 | 'billing_country' => 'United States', 91 | 'billing_email' => 'ottis@woo.local', 92 | 'billing_phone' => '(646) 613-1367', 93 | 'shipping_first_name' => 'Ottis', 94 | 'shipping_last_name' => 'Bruen', 95 | 'shipping_company' => 'Facebook', 96 | 'shipping_address_1' => '81 Spring St', 97 | 'shipping_address_2' => null, 98 | 'shipping_city' => 'New York', 99 | 'shipping_postcode' => '10012', 100 | 'shipping_state' => 'North Dakota', 101 | 'shipping_country' => 'United States', 102 | '_stripe_customer_id' => null, 103 | '_customer_ip_address' => null, 104 | 'payer_paypal_address' => null, 105 | 'payer_first_name' => null, 106 | 'payer_last_name' => null, 107 | ], 108 | ]; 109 | 110 | foreach ($original as $i => $meta) { 111 | foreach ($meta as $key => $value) { 112 | if ($value === null && $userMeta[$i][$key] === null) { 113 | continue; 114 | } 115 | 116 | self::assertNotSame($value, $userMeta[$i][$key]); 117 | } 118 | } 119 | } 120 | 121 | private function getWoocommerceUserMeta(): array 122 | { 123 | $data = Yaml::parseFile(__DIR__ . '/../../config/provider/woocommerce_usermeta.yaml'); 124 | 125 | $queryBuilder = $this->connection->createQueryBuilder() 126 | ->from($this->tablePrefix . 'usermeta') 127 | ->groupBy('user_id') 128 | ; 129 | 130 | foreach (array_keys($data) as $key) { 131 | $queryBuilder 132 | ->addSelect(sprintf("MAX(Case WHEN meta_key = '%s' THEN meta_value END) %s", $key, $key)) 133 | ; 134 | } 135 | 136 | return $queryBuilder->executeQuery() 137 | ->fetchAllAssociative() 138 | ; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /test/Provider/WoocommercePostMetaProviderTest.php: -------------------------------------------------------------------------------- 1 | provider = $this->container->get(WoocommercePostMetaProvider::class); 19 | } 20 | 21 | public function testAnonymizePostMeta(): void 22 | { 23 | $this->provider->anonymize(); 24 | $postMeta = $this->getWoocommercePostMeta(); 25 | 26 | $original = [ 27 | [ 28 | '_billing_first_name' => null, 29 | '_billing_last_name' => null, 30 | '_billing_company' => null, 31 | '_billing_address_1' => null, 32 | '_billing_address_2' => null, 33 | '_billing_city' => null, 34 | '_billing_postcode' => null, 35 | '_billing_state' => null, 36 | '_billing_country' => null, 37 | '_billing_email' => 'justin@woo.local', 38 | '_billing_phone' => null, 39 | '_shipping_first_name' => null, 40 | '_shipping_last_name' => null, 41 | '_shipping_company' => null, 42 | '_shipping_address_1' => null, 43 | '_shipping_address_2' => null, 44 | '_shipping_city' => null, 45 | '_shipping_postcode' => null, 46 | '_shipping_state' => null, 47 | '_shipping_country' => null, 48 | '_stripe_customer_id' => null, 49 | '_customer_ip_address' => null, 50 | 'payer_paypal_address' => null, 51 | 'payer_first_name' => null, 52 | 'payer_last_name' => null, 53 | ], 54 | [ 55 | '_billing_first_name' => null, 56 | '_billing_last_name' => null, 57 | '_billing_company' => null, 58 | '_billing_address_1' => null, 59 | '_billing_address_2' => null, 60 | '_billing_city' => null, 61 | '_billing_postcode' => null, 62 | '_billing_state' => null, 63 | '_billing_country' => null, 64 | '_billing_email' => 'otis@woo.local', 65 | '_billing_phone' => null, 66 | '_shipping_first_name' => null, 67 | '_shipping_last_name' => null, 68 | '_shipping_company' => null, 69 | '_shipping_address_1' => null, 70 | '_shipping_address_2' => null, 71 | '_shipping_city' => null, 72 | '_shipping_postcode' => null, 73 | '_shipping_state' => null, 74 | '_shipping_country' => null, 75 | '_stripe_customer_id' => null, 76 | '_customer_ip_address' => null, 77 | 'payer_paypal_address' => null, 78 | 'payer_first_name' => null, 79 | 'payer_last_name' => null, 80 | ], 81 | [ 82 | '_billing_first_name' => 'Trudie', 83 | '_billing_last_name' => 'Metz', 84 | '_billing_company' => 'Amazon', 85 | '_billing_address_1' => '135 Wyandot Ave', 86 | '_billing_address_2' => null, 87 | '_billing_city' => 'Marion', 88 | '_billing_postcode' => '43302', 89 | '_billing_state' => 'Ohio', 90 | '_billing_country' => 'United States', 91 | '_billing_email' => 'trudie@woo.local', 92 | '_billing_phone' => '(740) 383-4031', 93 | '_shipping_first_name' => 'Trudie', 94 | '_shipping_last_name' => 'Metz', 95 | '_shipping_company' => 'Amazon', 96 | '_shipping_address_1' => '135 Wyandot Ave', 97 | '_shipping_address_2' => null, 98 | '_shipping_city' => 'Marion', 99 | '_shipping_postcode' => '43302', 100 | '_shipping_state' => 'Ohio', 101 | '_shipping_country' => 'United States', 102 | '_stripe_customer_id' => null, 103 | '_customer_ip_address' => null, 104 | 'payer_paypal_address' => null, 105 | 'payer_first_name' => null, 106 | 'payer_last_name' => null, 107 | ], 108 | ]; 109 | 110 | foreach ($original as $i => $meta) { 111 | foreach ($meta as $key => $value) { 112 | if ($value === null && $postMeta[$i][$key] === null) { 113 | continue; 114 | } 115 | 116 | self::assertNotSame($value, $postMeta[$i][$key]); 117 | } 118 | } 119 | } 120 | 121 | private function getWoocommercePostMeta(): array 122 | { 123 | $data = Yaml::parseFile(__DIR__ . '/../../config/provider/woocommerce_postmeta.yaml'); 124 | 125 | $queryBuilder = $this->connection->createQueryBuilder() 126 | ->from($this->tablePrefix . 'postmeta', 'pm') 127 | ->join('pm', $this->tablePrefix . 'posts', 'p', 'p.ID = pm.post_id') 128 | ->where("p.post_type = 'shop_order'") 129 | ->groupBy('post_id') 130 | ; 131 | 132 | foreach (array_keys($data) as $key) { 133 | $queryBuilder 134 | ->addSelect(sprintf("MAX(Case WHEN meta_key = '%s' THEN meta_value END) %s", $key, $key)) 135 | ; 136 | } 137 | 138 | return $queryBuilder->executeQuery() 139 | ->fetchAllAssociative() 140 | ; 141 | } 142 | } 143 | --------------------------------------------------------------------------------