[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.yml]\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/.travis.yml        export-ignore\n/phpunit.xml.dist   export-ignore\n/.scrutinizer.yml   export-ignore\n/tests              export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: ivanvermeyen\ncustom: https://paypal.me/ivanvermeyen\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\nIf you discover any security related issues, please email ivan@codezero.be instead of using the issue tracker.\n"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: Tests\n\non: [ push, pull_request ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        php: [ 8.0, 8.1, 8.2, 8.3 ]\n        laravel: [ 8.*, 9.*, 10.*, 11.* ]\n        dependency-version: [ prefer-stable ]\n        exclude:\n          - laravel: 10.*\n            php: 8.0\n          - laravel: 11.*\n            php: 8.0\n          - laravel: 11.*\n            php: 8.1\n        include:\n          - laravel: 6.*\n            php: 7.2\n            testbench: 4.*\n          - laravel: 6.*\n            php: 8.0\n            testbench: 4.*\n          - laravel: 7.*\n            php: 7.2\n            testbench: 5.*\n          - laravel: 7.*\n            php: 8.0\n            testbench: 5.*\n          - laravel: 8.*\n            php: 7.3\n            testbench: 6.*\n          - laravel: 8.*\n            testbench: 6.*\n          - laravel: 9.*\n            testbench: 7.*\n          - laravel: 10.*\n            testbench: 8.*\n          - laravel: 11.*\n            testbench: 9.*\n\n    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}\n\n    services:\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_ALLOW_EMPTY_PASSWORD: yes\n          MYSQL_DATABASE: testing\n        ports:\n          - 3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Cache dependencies\n        uses: actions/cache@v2\n        with:\n          path: ~/.composer/cache/files\n          key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick\n          coverage: pcov\n\n      - name: Install dependencies\n        run: composer update --with=\"orchestra/testbench:${{ matrix.testbench }}\" --prefer-dist --no-interaction --no-progress\n\n      - name: Execute tests\n        run: vendor/bin/phpunit --coverage-clover=coverage.xml\n        env:\n          DB_PORT: ${{ job.services.mysql.ports[3306] }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/vendor\n.idea\ncomposer.lock\nphpunit.xml\n/.phpunit.result.cache\n/.phpunit.cache\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# The MIT License (MIT)\n\nCopyright (c) 2017 Ivan Vermeyen (<ivan@codezero.be>)\n\n> Permission is hereby granted, free of charge, to any person obtaining a copy\n> of this software and associated documentation files (the \"Software\"), to deal\n> in the Software without restriction, including without limitation the rights\n> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n> copies of the Software, and to permit persons to whom the Software is\n> furnished to do so, subject to the following conditions:\n>\n> The above copyright notice and this permission notice shall be included in\n> all copies or substantial portions of the Software.\n>\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n> THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Laravel Unique Translation\n\n## IMPORTANT: March 2022\n\n[![Support Ukraine](https://raw.githubusercontent.com/hampusborgos/country-flags/main/png100px/ua.png)](https://github.com/hampusborgos/country-flags/blob/main/png100px/ua.png)\n\nIt's horrible to see what is happening now in Ukraine, as Russian army is\n[bombarding houses, hospitals and kindergartens](https://twitter.com/DavidCornDC/status/1501620037785997316).\n\nPlease [check out supportukrainenow.org](https://supportukrainenow.org/) for the ways how you can help people there.\nSpread the word.\n\nAnd if you are from Russia and you are against this war, please express your protest in some way.\nI know you can get punished for this, but you are one of the hopes of those innocent people.\n\n---\n\n[![GitHub release](https://img.shields.io/github/release/codezero-be/laravel-unique-translation.svg?style=flat-square)](https://github.com/codezero-be/laravel-unique-translation/releases)\n[![Laravel](https://img.shields.io/badge/laravel-11-red?style=flat-square&logo=laravel&logoColor=white)](https://laravel.com)\n[![License](https://img.shields.io/packagist/l/codezero/laravel-unique-translation.svg?style=flat-square)](LICENSE.md)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/codezero-be/laravel-unique-translation/run-tests.yml?style=flat-square&logo=github&logoColor=white&label=tests)](https://github.com/codezero-be/laravel-unique-translation/actions)\n[![Code Coverage](https://img.shields.io/codacy/coverage/bb5f876fb1a94aa0a426fd31a2656e5b/master?style=flat-square)](https://app.codacy.com/gh/codezero-be/laravel-unique-translation)\n[![Code Quality](https://img.shields.io/codacy/grade/bb5f876fb1a94aa0a426fd31a2656e5b/master?style=flat-square)](https://app.codacy.com/gh/codezero-be/laravel-unique-translation)\n[![Total Downloads](https://img.shields.io/packagist/dt/codezero/laravel-unique-translation.svg?style=flat-square)](https://packagist.org/packages/codezero/laravel-unique-translation)\n\n[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/R6R3UQ8V)\n\n#### Check if a translated value in a JSON column is unique in the database.\n\nImagine you want store a `slug` for a `Post` model in different languages.\n\nThe amazing [`spatie/laravel-translatable`](https://github.com/spatie/laravel-translatable) package makes this a cinch!\n\nBut then you want to make sure each translation is unique for its language.\n\nThat's where this package comes in to play.\n\nThis package also supports [`spatie/nova-translatable`](https://github.com/spatie/nova-translatable/) in case you are using [Laravel Nova](https://nova.laravel.com/) and [`filamentphp/spatie-laravel-translatable-plugin`](https://github.com/filamentphp/spatie-laravel-translatable-plugin) in case you are using [Filament](https://filamentphp.com/).\n\n## ✅ Requirements\n\n-   PHP ^7.2 or PHP ^8.0\n-   MySQL >= 5.7\n-   [Laravel](https://laravel.com/) >= 6\n-   [spatie/laravel-translatable](https://github.com/spatie/laravel-translatable) ^4.4|^5.0|^6.0\n-   [spatie/nova-translatable](https://github.com/spatie/nova-translatable/) ^3.0\n-   [filamentphp/spatie-laravel-translatable-plugin](https://github.com/filamentphp/spatie-laravel-translatable-plugin) ^3.0\n\n## 📦 Installation\n\nRequire the package via Composer:\n\n```\ncomposer require codezero/laravel-unique-translation\n```\nLaravel will automatically register the [ServiceProvider](https://github.com/codezero-be/laravel-unique-translation/blob/master/src/UniqueTranslationServiceProvider.php).\n\n## 🛠 Usage\n\nFor the following examples, I will use a `slug` in a `posts` table as the subject of our validation.\n\n### ☑️ Validate a Single Translation\n\nYour form can submit a single slug:\n\n```html\n<input name=\"slug\">\n```\n\nWe can then check if it is unique **in the current locale**:\n\n```php\n$attributes = request()->validate([\n    'slug' => 'required|unique_translation:posts',\n]);\n```\n\nYou could also use the Rule instance:\n\n```php\nuse CodeZero\\UniqueTranslation\\UniqueTranslationRule;\n\n$attributes = request()->validate([\n    'slug' => ['required', UniqueTranslationRule::for('posts')],\n]);\n```\n\n### ☑️ Validate an Array of Translations\n\nYour form can also submit an array of slugs.\n\n```html\n<input name=\"slug[en]\">\n<input name=\"slug[nl]\">\n```\n\nWe need to validate the entire array in this case. Mind the `slug.*` key.\n\n```php\n$attributes = request()->validate([\n    'slug.*' => 'unique_translation:posts',\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts'),\n]);\n```\n\n### ☑️ Specify a Column\n\nMaybe your form field has a name of `post_slug` and your database field `slug`:\n\n```php\n$attributes = request()->validate([\n    'post_slug.*' => 'unique_translation:posts,slug',\n    // or...\n    'post_slug.*' => UniqueTranslationRule::for('posts', 'slug'),\n]);\n```\n\n### ☑️ Specify a Database Connection\n\nIf you are using multiple database connections, you can specify which one to use by prepending it to the table name, separated by a dot:\n\n```php\n$attributes = request()->validate([\n    'slug.*' => 'unique_translation:db_connection.posts',\n    // or...\n    'slug.*' => UniqueTranslationRule::for('db_connection.posts'),\n]);\n```\n\n### ☑️ Ignore a Record with ID\n\nIf you're updating a record, you may want to ignore the post itself from the unique check.\n\n```php\n$attributes = request()->validate([\n    'slug.*' => \"unique_translation:posts,slug,{$post->id}\",\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->ignore($post->id),\n]);\n```\n\n### ☑️ Ignore Records with a Specific Column and Value\n\nIf your ID column has a different name, or you just want to use another column:\n\n```php\n$attributes = request()->validate([\n    'slug.*' => 'unique_translation:posts,slug,ignore_value,ignore_column',\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->ignore('ignore_value', 'ignore_column'),\n]);\n```\n\n### ☑️ Use Additional Where Clauses\n\nYou can add 4 types of where clauses to the rule.\n\n#### `where`\n\n```php\n$attributes = request()->validate([\n    'slug.*' => \"unique_translation:posts,slug,null,null,column,value\",\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->where('column', 'value'),\n]);\n```\n\n#### `whereNot`\n\n```php\n$attributes = request()->validate([\n    'slug.*' => \"unique_translation:posts,slug,null,null,column,!value\",\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->whereNot('column', 'value'),\n]);\n```\n\n#### `whereNull`\n\n```php\n$attributes = request()->validate([\n    'slug.*' => \"unique_translation:posts,slug,null,null,column,NULL\",\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->whereNull('column'),\n]);\n```\n\n#### `whereNotNull`\n\n```php\n$attributes = request()->validate([\n    'slug.*' => \"unique_translation:posts,slug,null,null,column,NOT_NULL\",\n    // or...\n    'slug.*' => UniqueTranslationRule::for('posts')->whereNotNull('column'),\n]);\n```\n\n### ☑️ Laravel Nova\n\nIf you are using [Laravel Nova](https://nova.laravel.com/) in combination with  [`spatie/nova-translatable`](https://github.com/spatie/nova-translatable/), then you can add the validation rule like this:\n\n```php\nText::make(__('Slug'), 'slug')\n  ->creationRules('unique_translation:posts,slug')\n  ->updateRules('unique_translation:posts,slug,{{resourceId}}');\n```\n\n### ☑️ Filament\n\nIf you are using [Filament](https://filamentphp.com/) in combination with [`filamentphp/spatie-laravel-translatable-plugin`](https://github.com/filamentphp/spatie-laravel-translatable-plugin), then you can add the validation rule like this:\n\n```php\nTextInput::make('slug')\n  ->title(__('Slug'))\n  ->rules([\n    UniqueTranslationRule::for('posts', 'slug')\n  ])\n```\n\n```php\nTextInput::make('slug')\n  ->title(__('Slug'))\n  ->rules([\n    fn (Get $get) => UniqueTranslationRule::for('posts', 'slug')->ignore($get('id'))\n  ])\n```\n\n## 🖥 Example\n\nYour existing `slug`  column (JSON) in a `posts` table:\n\n```json\n{\n  \"en\":\"not-abc\",\n  \"nl\":\"abc\"\n}\n```\n\nYour form input to create a new record:\n\n\n```html\n<input name=\"slug[en]\" value=\"abc\">\n<input name=\"slug[nl]\" value=\"abc\">\n```\n\nYour validation logic:\n\n```php\n$attributes = request()->validate([\n    'slug.*' => 'unique_translation:posts',\n]);\n```\n\nThe result is that `slug[en]` is valid, since the only `en` value in the database is `not-abc`.\n\nAnd `slug[nl]` would fail, because there already is a `nl` value of `abc`.\n\n## ⚠️ Error Messages\n\nYou can pass your own error messages as normal.\n\nWhen validating a single form field:\n\n```html\n<input name=\"slug\">\n```\n\n```php\n$attributes = request()->validate([\n    'slug' => 'unique_translation:posts',\n], [\n    'slug.unique_translation' => 'Your custom :attribute error.',\n]);\n```\n\nIn your view you can then get the error with `$errors->first('slug')`.\n\nOr when validation an array:\n\n```html\n<input name=\"slug[en]\">\n```\n\n```php\n$attributes = request()->validate([\n    'slug.*' => 'unique_translation:posts',\n], [\n    'slug.*.unique_translation' => 'Your custom :attribute error.',\n]);\n```\n\nIn your view you can then get the error with `$errors->first('slug.en')` (`en` being your array key).\n\n## 🚧 Testing\n\n```\nvendor/bin/phpunit\n```\n\n## ☕️ Credits\n\n- [Ivan Vermeyen](https://byterider.io)\n- [All contributors](../../contributors)\n\n## 🔓 Security\n\nIf you discover any security related issues, please [e-mail me](mailto:ivan@codezero.be) instead of using the issue tracker.\n\n## 📑 Changelog\n\nA complete list of all notable changes to this package can be found on the\n[releases page](https://github.com/codezero-be/laravel-unique-translation/releases).\n\n## 📜 License\n\nThe MIT License (MIT). Please see [License File](https://github.com/codezero-be/laravel-unique-translation/blob/master/LICENSE.md) for more information.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"codezero/laravel-unique-translation\",\n    \"description\": \"Check if a translated value in a JSON column is unique in the database.\",\n    \"keywords\": [\n        \"translation\",\n        \"json\",\n        \"mysql\",\n        \"php\",\n        \"laravel\",\n        \"validation\",\n        \"validator\",\n        \"unique\",\n        \"rule\",\n        \"language\",\n        \"database\"\n    ],\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Ivan Vermeyen\",\n            \"email\": \"ivan@codezero.be\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^7.2|^8.0\"\n    },\n    \"require-dev\": {\n        \"orchestra/testbench\": \"^4.0|^5.0|^6.0|^7.0|^8.0|^9.0\",\n        \"phpunit/phpunit\": \"^8.0|^9.0|^10.0\",\n        \"spatie/laravel-translatable\": \"^4.4|^5.0|^6.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"CodeZero\\\\UniqueTranslation\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"CodeZero\\\\UniqueTranslation\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"CodeZero\\\\UniqueTranslation\\\\UniqueTranslationServiceProvider\"\n            ]\n        }\n    },\n    \"config\": {\n        \"preferred-install\": \"dist\",\n        \"sort-packages\": true,\n        \"optimize-autoloader\": true\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         backupGlobals=\"false\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/10.5/phpunit.xsd\"\n         cacheDirectory=\".phpunit.cache\"\n         backupStaticProperties=\"false\">\n    <testsuites>\n        <testsuite name=\"CodeZero\">\n            <directory suffix=\"Test.php\">./tests</directory>\n        </testsuite>\n    </testsuites>\n    <php>\n        <env name=\"APP_ENV\" value=\"testing\"/>\n        <env name=\"CACHE_DRIVER\" value=\"array\"/>\n        <env name=\"SESSION_DRIVER\" value=\"array\"/>\n        <env name=\"QUEUE_DRIVER\" value=\"sync\"/>\n        <env name=\"DB_CONNECTION\" value=\"mysql\"/>\n        <env name=\"DB_DATABASE\" value=\"testing\"/>\n        <env name=\"DB_COLLATION\" value=\"utf8mb4_unicode_ci\"/>\n        <env name=\"DB_USERNAME\" value=\"root\"/>\n        <env name=\"DB_PASSWORD\" value=\"\"/>\n    </php>\n    <source>\n        <include>\n            <directory suffix=\".php\">./src</directory>\n        </include>\n    </source>\n</phpunit>\n"
  },
  {
    "path": "src/UniqueTranslationRule.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation;\n\nuse Illuminate\\Validation\\Rules\\DatabaseRule;\n\nclass UniqueTranslationRule\n{\n    use DatabaseRule;\n\n    /**\n     * The name of the validation rule.\n     *\n     * @var string\n     */\n    protected $rule = 'unique_translation';\n\n    /**\n     * The value of the the 'ignoreColumn' to ignore.\n     *\n     * @var mixed\n     */\n    protected $ignoreValue;\n\n    /**\n     * The name of the 'ignoreColumn'.\n     *\n     * @var string|null\n     */\n    protected $ignoreColumn;\n\n    /**\n     * Create a new rule instance.\n     *\n     * @param string $table\n     * @param string|null $column\n     *\n     * @return static\n     */\n    public static function for($table, $column = null)\n    {\n        return new static($table, $column);\n    }\n\n    /**\n     * Create a new rule instance.\n     *\n     * @param string $table\n     * @param string|null $column\n     */\n    public function __construct($table, $column = null)\n    {\n        $this->table = $table;\n        $this->column = $column;\n    }\n\n    /**\n     * Ignore any record that has a column with the given value.\n     *\n     * @param mixed $value\n     * @param string $column\n     *\n     * @return $this\n     */\n    public function ignore($value, $column = 'id')\n    {\n        $this->ignoreValue = $value;\n        $this->ignoreColumn = $column;\n\n        return $this;\n    }\n\n    /**\n     * Generate a string representation of the validation rule.\n     *\n     * @return string\n     */\n    public function __toString()\n    {\n        return rtrim(sprintf(\n            '%s:%s,%s,%s,%s,%s',\n            $this->rule,\n            $this->table,\n            $this->column ?: 'NULL',\n            $this->ignoreValue ?: 'NULL',\n            $this->ignoreColumn ?: 'NULL',\n            $this->formatWheres()\n        ), ',');\n    }\n}\n"
  },
  {
    "path": "src/UniqueTranslationServiceProvider.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation;\n\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass UniqueTranslationServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap any application services.\n     *\n     * @return void\n     */\n    public function boot()\n    {\n        Validator::extend('unique_translation', UniqueTranslationValidator::class.'@validate');\n    }\n\n    /**\n     * Register any application services.\n     *\n     * @return void\n     */\n    public function register()\n    {\n        //\n    }\n}\n"
  },
  {
    "path": "src/UniqueTranslationValidator.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation;\n\nuse Illuminate\\Support\\Facades\\App;\nuse Illuminate\\Support\\Facades\\Config;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Str;\nuse Illuminate\\Support\\Arr;\n\nclass UniqueTranslationValidator\n{\n    /**\n     * Check if the translated value is unique in the database.\n     *\n     * @param string $attribute\n     * @param string $value\n     * @param array $parameters\n     * @param \\Illuminate\\Validation\\Validator $validator\n     *\n     * @return bool\n     */\n    public function validate($attribute, $value, $parameters, $validator)\n    {\n        list ($name, $locale) = $this->isNovaTranslation($attribute)\n            ? $this->getNovaAttributeNameAndLocale($attribute)\n            : (\n\t\t\t\t$this->isFilamentTranslation($attribute)\n\t\t\t\t? $this->getFilamentAttributeNameAndLocale($attribute, $validator)\n\t\t\t\t: $this->getArrayAttributeNameAndLocale($attribute)\n\t\t\t);\n\n        if ($this->isUnique($value, $name, $locale, $parameters)) {\n            return true;\n        }\n\n        $this->setMissingErrorMessages($validator, $name, $locale);\n\n        return false;\n    }\n\n    /**\n     * Set any missing (custom) error messages for our validation rule.\n     *\n     * @param \\Illuminate\\Validation\\Validator $validator\n     * @param string $name\n     * @param string $locale\n     *\n     * @return void\n     */\n    protected function setMissingErrorMessages($validator, $name, $locale)\n    {\n        $rule = 'unique_translation';\n\n        $keys = [\n            \"{$name}.{$rule}\",\n            \"{$name}.*.{$rule}\",\n            \"{$name}.{$locale}.{$rule}\",\n            \"translations_{$name}_{$locale}.{$rule}\",\n        ];\n\n        foreach ($keys as $key) {\n            if ( ! array_key_exists($key, $validator->customMessages)) {\n                $validator->customMessages[$key] = trans('validation.unique');\n            }\n        }\n    }\n\n    /**\n     * Check if the attribute is a Nova translation field name.\n     *\n     * @param string $attribute\n     *\n     * @return bool\n     */\n    protected function isNovaTranslation($attribute)\n    {\n        return strpos($attribute, '.') === false && strpos($attribute, 'translations_') === 0;\n    }\n\n    /**\n     * Get the attribute name and locale of a Filament translation field.\n     *\n     * @param string $attribute\n     *\n     * @return array\n     */\n    protected function getNovaAttributeNameAndLocale($attribute)\n    {\n        $attribute = str_replace('translations_', '', $attribute);\n\n        return $this->getAttributeNameAndLocale($attribute, '_');\n    }\n\n    /**\n     * Check if the attribute is a Filament translation field name.\n     *\n     * @param string $attribute\n     *\n     * @return bool\n     */\n    protected function isFilamentTranslation($attribute)\n    {\n        return strpos($attribute, 'data.') === 0;\n    }\n\n    /**\n     * Get the attribute name and locale of a Filament translation field.\n     *\n     * @param string $attribute\n     *\n     * @return array\n     */\n    protected function getFilamentAttributeNameAndLocale($attribute, $validator)\n    {\n        $attribute = str_replace('data.', '', $attribute);\n\n        $dataValidator = $validator->getData();\n\n        @list($name, $locale) = @explode('.', $attribute);\n\n        if ($locale === null && Arr::exists($dataValidator, 'activeLocale')) {\n            $locale = $dataValidator['activeLocale'];\n        }\n\n        return [$name, $locale];\n    }\n\n    /**\n     * Get the attribute name and locale of an array field.\n     *\n     * @param string $attribute\n     *\n     * @return array\n     */\n    protected function getArrayAttributeNameAndLocale($attribute)\n    {\n        return $this->getAttributeNameAndLocale($attribute, '.');\n    }\n\n    /**\n     * Get the attribute name and locale.\n     *\n     * @param string $attribute\n     * @param string $delimiter\n     *\n     * @return array\n     */\n    protected function getAttributeNameAndLocale($attribute, $delimiter)\n    {\n        $locale = $this->getAttributeLocale($attribute, $delimiter);\n        $name = $this->getAttributeName($attribute, $locale, $delimiter);\n\n        return [$name, $locale ?: App::getLocale()];\n    }\n\n    /**\n     * Get the locale from the attribute name.\n     *\n     * @param string $attribute\n     * @param string $delimiter\n     *\n     * @return string|null\n     */\n    protected function getAttributeLocale($attribute, $delimiter)\n    {\n        $pos = strrpos($attribute, $delimiter);\n\n        return $pos > 0 ? substr($attribute, $pos +  1) : null;\n    }\n\n    /**\n     * Get the attribute name without the locale.\n     *\n     * @param string $attribute\n     * @param string|null $locale\n     * @param string $delimiter\n     *\n     * @return string\n     */\n    protected function getAttributeName($attribute, $locale, $delimiter)\n    {\n        return $locale ? str_replace(\"{$delimiter}{$locale}\", '', $attribute) : $attribute;\n    }\n\n    /**\n     * Get the database connection and table name.\n     *\n     * @param array $parameters\n     *\n     * @return array\n     */\n    protected function getConnectionAndTable($parameters)\n    {\n        $parts = explode('.', $this->getParameter($parameters, 0));\n\n        $connection = isset($parts[1])\n            ? $parts[0]\n            : Config::get('database.default');\n\n        $table = $parts[1] ?? $parts[0];\n\n        return [$connection, $table];\n    }\n\n    /**\n     * Get the parameter value at the given index.\n     *\n     * @param array $parameters\n     * @param int $index\n     *\n     * @return string|null\n     */\n    protected function getParameter($parameters, $index)\n    {\n        return $this->convertNullValue($parameters[$index] ?? null);\n    }\n\n    /**\n     * Convert any 'NULL' string value to null.\n     *\n     * @param string $value\n     *\n     * @return string|null\n     */\n    protected function convertNullValue($value)\n    {\n        return strtoupper($value) === 'NULL' ? null : $value;\n    }\n\n    /**\n     * Check if a translation is unique.\n     *\n     * @param mixed $value\n     * @param string $name\n     * @param string $locale\n     * @param array $parameters\n     *\n     * @return bool\n     */\n    protected function isUnique($value, $name, $locale, $parameters)\n    {\n        list ($connection, $table) = $this->getConnectionAndTable($parameters);\n\n        $column = $this->getParameter($parameters, 1) ?? $name;\n        $ignoreValue = $this->getParameter($parameters, 2);\n        $ignoreColumn = $this->getParameter($parameters, 3);\n\n        $query = $this->findTranslation($connection, $table, $column, $locale, $value);\n        $query = $this->ignore($query, $ignoreColumn, $ignoreValue);\n        $query = $this->addConditions($query, $this->getUniqueExtra($parameters));\n\n        $isUnique = $query->count() === 0;\n\n        return $isUnique;\n    }\n\n    /**\n     * Find the given translated value in the database.\n     *\n     * @param string $connection\n     * @param string $table\n     * @param string $column\n     * @param string $locale\n     * @param mixed $value\n     *\n     * @return \\Illuminate\\Database\\Query\\Builder\n     */\n    protected function findTranslation($connection, $table, $column, $locale, $value)\n    {\n        // Properly escape backslashes to work with LIKE queries...\n        // See: https://stackoverflow.com/questions/14926386/how-to-search-for-slash-in-mysql-and-why-escaping-not-required-for-wher\n        $escaped = DB::getDriverName() === 'sqlite' ? '\\\\\\\\' : '\\\\\\\\\\\\\\\\';\n        $value = str_replace('\\\\', $escaped, $value);\n\n        // Support PostgreSQL case insensitive queries with ILIKE\n        $operator = DB::getDriverName() === 'pgsql' ? 'ILIKE' : 'LIKE';\n\n        return DB::connection($connection)->table($table)\n            ->where(function ($query) use ($column, $operator, $locale, $value) {\n                $query->where($column, $operator, \"%\\\"{$locale}\\\": \\\"{$value}\\\"%\")\n                    ->orWhere($column, $operator, \"%\\\"{$locale}\\\":\\\"{$value}\\\"%\");\n            });\n    }\n\n    /**\n     * Ignore the column with the given value.\n     *\n     * @param \\Illuminate\\Database\\Query\\Builder $query\n     * @param string|null $column\n     * @param mixed $value\n     *\n     * @return \\Illuminate\\Database\\Query\\Builder\n     */\n    protected function ignore($query, $column = null, $value = null)\n    {\n        if ($value !== null && $column === null) {\n            $column = 'id';\n        }\n\n        if ($column !== null) {\n            $query = $query->where($column, '!=', $value);\n        }\n\n        return $query;\n    }\n\n    /**\n     * Get the extra conditions for a unique rule.\n     * Taken From: \\Illuminate\\Validation\\Concerns\\ValidatesAttributes\n     *\n     * @param array $parameters\n     *\n     * @return array\n     */\n    protected function getUniqueExtra($parameters)\n    {\n        if (isset($parameters[4])) {\n            return $this->getExtraConditions(array_slice($parameters, 4));\n        }\n\n        return [];\n    }\n\n    /**\n     * Get the extra conditions for a unique / exists rule.\n     * Taken from: \\Illuminate\\Validation\\Concerns\\ValidatesAttributes\n     *\n     * @param array $segments\n     *\n     * @return array\n     */\n    protected function getExtraConditions(array $segments)\n    {\n        $extra = [];\n\n        $count = count($segments);\n\n        for ($i = 0; $i < $count; $i += 2) {\n            $extra[$segments[$i]] = $segments[$i + 1];\n        }\n\n        return $extra;\n    }\n\n    /**\n     * Add the given conditions to the query.\n     * Adapted from: \\Illuminate\\Validation\\DatabasePresenceVerifier\n     *\n     * @param \\Illuminate\\Database\\Query\\Builder $query\n     * @param array $conditions\n     *\n     * @return \\Illuminate\\Database\\Query\\Builder\n     */\n    protected function addConditions($query, $conditions)\n    {\n        foreach ($conditions as $key => $value) {\n            $this->addWhere($query, $key, $value);\n        }\n\n        return $query;\n    }\n\n    /**\n     * Add a \"where\" clause to the given query.\n     * Taken from: \\Illuminate\\Validation\\DatabasePresenceVerifier\n     *\n     * @param \\Illuminate\\Database\\Query\\Builder $query\n     * @param string $key\n     * @param string $extraValue\n     *\n     * @return \\Illuminate\\Database\\Query\\Builder\n     */\n    protected function addWhere($query, $key, $extraValue)\n    {\n        if ($extraValue === 'NULL') {\n            return $query->whereNull($key);\n        }\n\n        if ($extraValue === 'NOT_NULL') {\n            return $query->whereNotNull($key);\n        }\n\n        $isNegative = Str::startsWith($extraValue, '!');\n        $operator = $isNegative ? '!=' : '=';\n        $value = $isNegative ? mb_substr($extraValue, 1) : $extraValue;\n\n        return $query->where($key, $operator, $value);\n    }\n}\n"
  },
  {
    "path": "tests/Stubs/Model.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation\\Tests\\Stubs;\n\nuse Illuminate\\Database\\Eloquent\\Model as EloquentModel;\nuse Spatie\\Translatable\\HasTranslations;\n\nclass Model extends EloquentModel\n{\n    use HasTranslations;\n\n    public $translatable = ['slug', 'name'];\n\n    protected $table = 'test_models';\n\n    protected $guarded = [];\n\n    public $timestamps = false;\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation\\Tests;\n\nuse CodeZero\\UniqueTranslation\\UniqueTranslationServiceProvider;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\App;\nuse Illuminate\\Support\\Facades\\Config;\nuse Illuminate\\Support\\Str;\nuse Orchestra\\Testbench\\TestCase as BaseTestCase;\n\nabstract class TestCase extends BaseTestCase\n{\n    /**\n     * Database table for the test models.\n     *\n     * @var string\n     */\n    protected $table = 'test_models';\n\n    /**\n     * Name of the validation rule.\n     *\n     * @var string\n     */\n    protected $rule = 'unique_translation';\n\n    /**\n     * Setup the test environment.\n     *\n     * @return void\n     */\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        Config::set('app.key', Str::random(32));\n\n        App::setLocale('en');\n\n        $this->setupDatabase();\n    }\n\n    /**\n     * Get the packages service providers.\n     *\n     * @param \\Illuminate\\Foundation\\Application $app\n     *\n     * @return array\n     */\n    protected function getPackageProviders($app)\n    {\n        return [\n            UniqueTranslationServiceProvider::class,\n        ];\n    }\n\n    /**\n     * Setup the test database.\n     *\n     * @return void\n     */\n    protected function setupDatabase()\n    {\n        $this->app['db']->getSchemaBuilder()->dropIfExists($this->table);\n\n        $this->app['db']->getSchemaBuilder()->create($this->table, function (Blueprint $table) {\n            $table->increments('id');\n            $table->json('slug')->nullable();\n            $table->text('name')->nullable();\n            $table->string('other_field')->nullable();\n        });\n\n        $this->beforeApplicationDestroyed(function () {\n            $this->app['db']->getSchemaBuilder()->drop($this->table);\n        });\n    }\n}\n"
  },
  {
    "path": "tests/UniqueTranslationTest.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation\\Tests;\n\nuse CodeZero\\UniqueTranslation\\Tests\\Stubs\\Model;\nuse CodeZero\\UniqueTranslation\\UniqueTranslationRule;\nuse Illuminate\\Support\\Facades\\Config;\nuse Illuminate\\Support\\Facades\\Validator;\n\nclass UniqueTranslationTest extends TestCase\n{\n    /** @test */\n    public function it_checks_if_the_translation_for_the_current_locale_is_unique()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$this->table}\",\n            'name' => UniqueTranslationRule::for($this->table),\n        ];\n\n        // The following validation fails, because the\n        // current locale is \"en\", so we actually set\n        // ['en' => 'existing-slug-en'] etc.\n\n        $validation = Validator::make([\n            'slug' => 'existing-slug-en',\n            'name' => 'existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        // The following validation passes, because the\n        // current locale is \"en\", so we actually set\n        // ['en' => 'existing-slug-nl'] etc.\n\n        $validation = Validator::make([\n            'slug' => 'existing-slug-nl',\n            'name' => 'existing-name-nl',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function search_is_case_insensitive()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$this->table}\",\n            'name' => UniqueTranslationRule::for($this->table),\n        ];\n\n        $validation = Validator::make([\n            'slug' => 'Existing-slug-en',\n            'name' => 'Existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n    }\n\n    /** @test */\n    public function it_checks_if_the_translation_for_a_specific_locale_is_unique()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table}\",\n            'name.*' => UniqueTranslationRule::for($this->table),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'different-slug-en'],\n            'name' => ['en' => 'different-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n\n        $validation = Validator::make([\n            'slug' => ['nl' => 'existing-slug-nl'],\n            'name' => ['nl' => 'existing-name-nl'],\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        $validation = Validator::make([\n            'slug' => ['nl' => 'different-slug-nl'],\n            'name' => ['nl' => 'different-name-nl'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function a_database_connection_can_be_specified()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $connection = Config::get('database.default');\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$connection}.{$this->table}\",\n            'name' => UniqueTranslationRule::for(\"{$connection}.{$this->table}\"),\n        ];\n\n        $validation = Validator::make([\n            'slug' => 'existing-slug-en',\n            'name' => 'existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        $validation = Validator::make([\n            'slug' => 'different-slug-en',\n            'name' => 'different-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function the_models_attribute_name_can_be_specified()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'form_slug' => \"{$this->rule}:{$this->table},slug\",\n            'form_name' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $validation = Validator::make([\n            'form_slug' => 'existing-slug-en',\n            'form_name' => 'existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        $rules = [\n            'form_slug.*' => \"{$this->rule}:{$this->table},slug\",\n            'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $validation = Validator::make([\n            'form_slug' => ['nl' => 'existing-slug-nl'],\n            'form_name' => ['nl' => 'existing-name-nl'],\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n    }\n\n    /** @test */\n    public function it_ignores_the_given_id()\n    {\n        $model = Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$this->table},null,{$model->id}\",\n            'name' => UniqueTranslationRule::for($this->table)->ignore($model->id),\n        ];\n\n        $validation = Validator::make([\n            'slug' => 'existing-slug-en',\n            'name' => 'existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,{$model->id}\",\n            'name.*' => UniqueTranslationRule::for($this->table)->ignore($model->id),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_ignores_a_specific_attribute_with_the_given_value()\n    {\n        $model = Model::create([\n            'slug' => ['en' => 'existing-slug-en', 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => 'existing-name-en', 'nl' => 'existing-name-nl'],\n            'other_field' => 'foobar',\n        ]);\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$this->table},null,{$model->other_field},other_field\",\n            'name' => UniqueTranslationRule::for($this->table)->ignore($model->other_field, 'other_field'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => 'existing-slug-en',\n            'name' => 'existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,{$model->other_field},other_field\",\n            'name.*' => UniqueTranslationRule::for($this->table)->ignore($model->other_field, 'other_field'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['nl' => 'existing-slug-nl'],\n            'name' => ['nl' => 'existing-name-nl'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_ignores_null_values()\n    {\n        Model::create([\n            'slug' => ['en' => null, 'nl' => 'existing-slug-nl'],\n            'name' => ['en' => null, 'nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table}\",\n            'name.*' => UniqueTranslationRule::for($this->table),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => null],\n            'name' => ['en' => null],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_validates_nova_translations()\n    {\n        Model::create([\n            'slug' => ['nl' => 'existing-slug-nl'],\n            'name' => ['nl' => 'existing-name-nl'],\n        ]);\n\n        $rules = [\n            'translations_slug_nl' => \"{$this->rule}:{$this->table},slug\",\n            'translations_name_nl' => UniqueTranslationRule::for($this->table, 'slug'),\n        ];\n\n        $validation = Validator::make([\n            'translations_slug_nl' => 'existing-slug-nl',\n            'translations_name_nl' => 'existing-name-nl',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        $validation = Validator::make([\n            'translations_slug_nl' => 'different-slug-nl',\n            'translations_name_nl' => 'different-name-nl',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_handles_backslashes_in_values()\n    {\n        Model::create([\n            'slug' => ['en' => '\\existing-slug-en', 'nl' => '\\existing-slug-nl'],\n            'name' => ['en' => '\\existing-name-en', 'nl' => '\\existing-name-nl'],\n        ]);\n\n        $rules = [\n            'slug' => \"{$this->rule}:{$this->table}\",\n            'name' => UniqueTranslationRule::for($this->table),\n        ];\n\n        // The following validation fails, because the\n        // current locale is \"en\", so we actually set\n        // ['en' => '\\existing-slug-en'] etc.\n\n        $validation = Validator::make([\n            'slug' => '\\existing-slug-en',\n            'name' => '\\existing-name-en',\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n\n        // The following validation passes, because the\n        // current locale is \"en\", so we actually set\n        // ['en' => '\\existing-slug-nl'] etc.\n\n        $validation = Validator::make([\n            'slug' => '\\existing-slug-nl',\n            'name' => '\\existing-name-nl',\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_handles_arabic_language()\n    {\n        Model::create([\n            'slug' => ['ar' => 'جديد'],\n            'name' => ['ar' => 'جديد'],\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table}\",\n            'name.*' => UniqueTranslationRule::for($this->table),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['ar' => 'جديد'],\n            'name' => ['ar' => 'جديد'],\n        ], $rules);\n\n        $this->assertTrue($validation->fails());\n    }\n}\n"
  },
  {
    "path": "tests/ValidationMessageTest.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation\\Tests;\n\nuse CodeZero\\UniqueTranslation\\Tests\\Stubs\\Model;\nuse CodeZero\\UniqueTranslation\\UniqueTranslationRule;\nuse Illuminate\\Support\\Facades\\Validator;\n\nclass ValidationMessageTest extends TestCase\n{\n    /** @test */\n    public function it_returns_a_default_error_message_when_validating_a_single_translation()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $formAttributes = [\n            'form_slug' => 'existing-slug-en',\n            'form_name' => 'existing-name-en',\n        ];\n\n        $rules = [\n            'form_slug' => \"{$this->rule}:{$this->table},slug\",\n            'form_name' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $expectedSlugError = trans('validation.unique', ['attribute' => 'form slug']);\n        $expectedNameError = trans('validation.unique', ['attribute' => 'form name']);\n\n        $this->assertNotEmpty($expectedSlugError);\n        $this->assertNotEmpty($expectedNameError);\n\n        $validation = Validator::make($formAttributes, $rules);\n\n        $this->assertEquals([\n            'form_slug' => [$expectedSlugError],\n            'form_name' => [$expectedNameError],\n        ], $validation->errors()->messages());\n    }\n\n    /** @test */\n    public function it_returns_a_default_error_message_when_validating_an_array()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $formAttributes = [\n            'form_slug' => ['en' => 'existing-slug-en'],\n            'form_name' => ['en' => 'existing-name-en'],\n        ];\n\n        $rules = [\n            'form_slug.*' => \"{$this->rule}:{$this->table},slug\",\n            'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $expectedSlugError = trans('validation.unique', ['attribute' => 'form_slug.en']);\n        $expectedNameError = trans('validation.unique', ['attribute' => 'form_name.en']);\n\n        $this->assertNotEmpty($expectedSlugError);\n        $this->assertNotEmpty($expectedNameError);\n\n        $validation = Validator::make($formAttributes, $rules);\n\n        $this->assertEquals([\n            'form_slug.en' => [$expectedSlugError],\n            'form_name.en' => [$expectedNameError],\n        ], $validation->errors()->messages());\n    }\n\n    /** @test */\n    public function it_returns_a_custom_error_message_when_validating_a_single_translation()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $formAttributes = [\n            'form_slug' => 'existing-slug-en',\n            'form_name' => 'existing-name-en',\n        ];\n\n        $rules = [\n            'form_slug' => \"{$this->rule}:{$this->table},slug\",\n            'form_name' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $messages = [\n            \"form_slug.{$this->rule}\" => 'Custom slug message for :attribute.',\n            \"form_name.{$this->rule}\" => 'Custom name message for :attribute.',\n        ];\n\n        $expectedSlugError = 'Custom slug message for form slug.';\n        $expectedNameError = 'Custom name message for form name.';\n\n        $validation = Validator::make($formAttributes, $rules, $messages);\n\n        $this->assertEquals([\n            'form_slug' => [$expectedSlugError],\n            'form_name' => [$expectedNameError],\n        ], $validation->errors()->messages());\n    }\n\n    /** @test */\n    public function it_returns_a_custom_error_message_when_validating_an_array()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $formAttributes = [\n            'form_slug' => ['en' => 'existing-slug-en'],\n            'form_name' => ['en' => 'existing-name-en'],\n        ];\n\n        $rules = [\n            'form_slug.*' => \"{$this->rule}:{$this->table},slug\",\n            'form_name.*' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $messages = [\n            \"form_slug.*.{$this->rule}\" => 'Custom slug message for :attribute.',\n            \"form_name.*.{$this->rule}\" => 'Custom name message for :attribute.',\n        ];\n\n        $expectedSlugError = 'Custom slug message for form_slug.en.';\n        $expectedNameError = 'Custom name message for form_name.en.';\n\n        $validation = Validator::make($formAttributes, $rules, $messages);\n\n        $this->assertEquals([\n            'form_slug.en' => [$expectedSlugError],\n            'form_name.en' => [$expectedNameError],\n        ], $validation->errors()->messages());\n    }\n\n    /** @test */\n    public function it_returns_a_default_error_message_when_validating_a_nova_translation()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ]);\n\n        $formAttributes = [\n            'translations_form_slug_en' => 'existing-slug-en',\n            'translations_form_name_en' => 'existing-name-en',\n        ];\n\n        $rules = [\n            'translations_form_slug_en' => \"{$this->rule}:{$this->table},slug\",\n            'translations_form_name_en' => UniqueTranslationRule::for($this->table, 'name'),\n        ];\n\n        $expectedSlugError = trans('validation.unique', ['attribute' => 'translations form slug en']);\n        $expectedNameError = trans('validation.unique', ['attribute' => 'translations form name en']);\n\n        $this->assertNotEmpty($expectedSlugError);\n        $this->assertNotEmpty($expectedNameError);\n\n        $validation = Validator::make($formAttributes, $rules);\n\n        $this->assertEquals([\n            'translations_form_slug_en' => [$expectedSlugError],\n            'translations_form_name_en' => [$expectedNameError],\n        ], $validation->errors()->messages());\n    }\n}\n"
  },
  {
    "path": "tests/WhereClauseTest.php",
    "content": "<?php\n\nnamespace CodeZero\\UniqueTranslation\\Tests;\n\nuse CodeZero\\UniqueTranslation\\Tests\\Stubs\\Model;\nuse CodeZero\\UniqueTranslation\\UniqueTranslationRule;\nuse Illuminate\\Support\\Facades\\Validator;\n\n// * * *\n// You can use any method defined in the DatabaseRule\n// trait, except the whereIn and whereNotIn methods.\n//\n// https://laravel.com/api/5.8/Illuminate/Validation/Rules/DatabaseRule.html\n//\n// This is because it uses a closure which cannot be converted into a string.\n// We need to convert the rule into a string to use it with the UniqueTranslationValidator.\n// The reason we use this kind of validator is because it has access to the Validator instance.\n// We need that instance to add custom error messages.\n// * * *\n\nclass WhereClauseTest extends TestCase\n{\n    /** @test */\n    public function it_accepts_where_clause()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n            'other_field' => 'foobar',\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,null,null,other_field,!foobar\",\n            'name.*' => UniqueTranslationRule::for($this->table)->where('other_field', 'not foobar'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_accepts_where_not_clauses()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n            'other_field' => 'foobar',\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,null,null,other_field,!foobar\",\n            'name.*' => UniqueTranslationRule::for($this->table)->whereNot('other_field', 'foobar'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_accepts_where_null_clause()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n            'other_field' => 'foobar',\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,null,null,other_field,NULL\",\n            'name.*' => UniqueTranslationRule::for($this->table)->whereNull('other_field'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n\n    /** @test */\n    public function it_accepts_where_not_null_clause()\n    {\n        Model::create([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n            'other_field' => null,\n        ]);\n\n        $rules = [\n            'slug.*' => \"{$this->rule}:{$this->table},null,null,null,other_field,NOT_NULL\",\n            'name.*' => UniqueTranslationRule::for($this->table)->whereNotNull('other_field'),\n        ];\n\n        $validation = Validator::make([\n            'slug' => ['en' => 'existing-slug-en'],\n            'name' => ['en' => 'existing-name-en'],\n        ], $rules);\n\n        $this->assertTrue($validation->passes());\n    }\n}\n"
  }
]