├── .DS_Store ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── contributors.yml │ ├── dependabot-auto-merge.yml │ ├── main.yml │ ├── php-cs-fixer.yml │ ├── phpstan.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── art ├── .DS_Store └── screenshot.png ├── composer.json ├── composer.lock ├── config └── filament-import.php ├── phpstan-baseline.neon ├── phpstan.neon.dist ├── phpunit.xml.dist ├── phpunit.xml.dist.bak ├── resources └── lang │ ├── ar │ ├── actions.php │ └── validators.php │ ├── en │ ├── actions.php │ └── validators.php │ ├── es │ ├── actions.php │ └── validators.php │ ├── fa │ ├── actions.php │ └── validators.php │ └── pt_BR │ ├── actions.php │ └── validators.php ├── src ├── Actions │ ├── ImportAction.php │ └── ImportField.php ├── Concerns │ ├── HasActionMutation.php │ ├── HasActionUniqueField.php │ ├── HasColumnMatching.php │ ├── HasFieldHelper.php │ ├── HasFieldLabel.php │ ├── HasFieldMutation.php │ ├── HasFieldPlaceholder.php │ ├── HasFieldRequire.php │ ├── HasFieldValidation.php │ └── HasTemporaryDisk.php ├── FilamentImportServiceProvider.php ├── Import.php └── ImportField.php └── tests ├── Features └── ImportTest.php ├── Migrations └── post_migration.php ├── Pest.php ├── Resources ├── Models │ └── Post.php ├── Pages │ ├── AlternativeColumnsNamesList.php │ ├── CommonTestList.php │ ├── HandleCreationList.php │ ├── NonRequiredTestList.php │ ├── ValidateTestList.php │ └── WithoutMassCreateTestList.php ├── Panels │ └── TestPanelProvider.php └── PostResource.php └── TestCase.php /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnco/filament-import/c9963f09762144f90c4965579785f41ff8a07aac/.DS_Store -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/konnco/filament-import/discussions/new?category=q-a 5 | about: Ask the community for help 6 | - name: Request a feature 7 | url: https://github.com/konnco/filament-import/discussions/new?category=ideas 8 | about: Share ideas for new features 9 | - name: Report a security issue 10 | url: https://github.com/konnco/filament-import/security/policy 11 | about: Learn how to notify us for sensitive bugs 12 | - name: Report a bug 13 | url: https://github.com/konnco/filament-import/issues/new 14 | about: Report a reproducible bug 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed changes 2 | 3 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 4 | 5 | ## Types of changes 6 | 7 | What types of changes does your code introduce to Filament Import? 8 | _Put an `x` in the boxes that apply_ 9 | 10 | - [ ] ✨ New feature (non-breaking change which adds functionality) 11 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) 12 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) 13 | - [ ] 🧹 Code refactor 14 | - [ ] ✅ Build configuration change 15 | - [ ] 📝 Documentation 16 | - [ ] 🗑️ Chore 17 | 18 | ## Checklist 19 | 20 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 21 | 22 | - [ ] I have read the [CONTRIBUTING](https://github.com/appium/appium/blob/master/CONTRIBUTING.md) doc 23 | - [ ] I have signed the [CLA](https://cla.js.foundation/appium/appium) 24 | - [ ] Lint and unit tests pass locally with my changes 25 | - [ ] I have added tests that prove my fix is effective or that my feature works 26 | - [ ] I have added necessary documentation (if appropriate) 27 | - [ ] Any dependent changes have been merged and published in downstream modules 28 | 29 | ## Further comments 30 | 31 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | labels: 12 | - "dependencies" -------------------------------------------------------------------------------- /.github/workflows/contributors.yml: -------------------------------------------------------------------------------- 1 | name: Add contributors 2 | on: 3 | schedule: 4 | - cron: '20 20 * * *' 5 | # push: 6 | # branches: 7 | # - master 8 | 9 | jobs: 10 | add-contributors: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: BobAnkh/add-contributors@master 15 | with: 16 | CONTRIBUTOR: '### Contributors' 17 | COLUMN_PER_ROW: '6' 18 | ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | IMG_WIDTH: '100' 20 | FONT_SIZE: '14' 21 | PATH: '/README.md' 22 | COMMIT_MESSAGE: 'docs(README): update contributors' 23 | AVATAR_SHAPE: 'round' 24 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | 14 | - name: Dependabot metadata 15 | id: metadata 16 | uses: dependabot/fetch-metadata@v1.6.0 17 | with: 18 | github-token: "${{ secrets.GITHUB_TOKEN }}" 19 | 20 | - name: Auto-merge Dependabot PRs for semver-minor updates 21 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} 22 | run: gh pr merge --auto --merge "$PR_URL" 23 | env: 24 | PR_URL: ${{github.event.pull_request.html_url}} 25 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | 27 | - name: Auto-merge Dependabot PRs for semver-patch updates 28 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 29 | run: gh pr merge --auto --merge "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | contrib-readme-job: 8 | runs-on: ubuntu-latest 9 | name: A job to automate contrib in readme 10 | steps: 11 | - name: Contribute List 12 | uses: akhilmhdh/contributors-readme-action@v2.3.6 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Fix PHP code style issues 2 | 3 | on: [push] 4 | 5 | jobs: 6 | php-code-styling: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v3 12 | with: 13 | ref: ${{ github.head_ref }} 14 | 15 | - name: Fix PHP code style issues 16 | uses: aglipanci/laravel-pint-action@2.3.0 17 | 18 | - name: Commit changes 19 | uses: stefanzweifel/git-auto-commit-action@v4 20 | with: 21 | commit_message: Fix styling 22 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: PHPStan 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.php" 7 | - "phpstan.neon.dist" 8 | 9 | jobs: 10 | phpstan: 11 | name: phpstan 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: "8.1" 20 | coverage: none 21 | 22 | - name: Install composer dependencies 23 | uses: ramsey/composer-install@v1 24 | 25 | - name: Run PHPStan 26 | run: ./vendor/bin/phpstan --error-format=github 27 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | os: [ubuntu-latest, windows-latest] 16 | php: [8.1] 17 | laravel: [9.*] 18 | stability: [prefer-stable] 19 | include: 20 | - laravel: 9.* 21 | testbench: 7.* 22 | 23 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 34 | coverage: none 35 | 36 | - name: Setup problem matchers 37 | run: | 38 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 39 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 40 | - name: Install dependencies 41 | run: | 42 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 43 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 44 | - name: Execute tests 45 | run: vendor/bin/pest 46 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | with: 15 | ref: main 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: main 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .phpunit.result.cache 3 | 4 | build/ 5 | .idea/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `konnco/filament-import` will be documented in this file. 4 | 5 | ## 1.6.0 - 2023-06-18 6 | 7 | ### What's Changed 8 | 9 | - Bump aglipanci/laravel-pint-action from 1.0.0 to 2.3.0 by @dependabot in https://github.com/konnco/filament-import/pull/95 10 | - add alternative column names for automatic matching by @alex552 in https://github.com/konnco/filament-import/pull/77 11 | 12 | ### New Contributors 13 | 14 | - @alex552 made their first contribution in https://github.com/konnco/filament-import/pull/77 15 | 16 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.5.3...1.6.0 17 | 18 | ## 1.5.3 - 2023-05-30 19 | 20 | ### What's Changed 21 | 22 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.0 by @dependabot in https://github.com/konnco/filament-import/pull/92 23 | - Bump dependabot/fetch-metadata from 1.5.0 to 1.5.1 by @dependabot in https://github.com/konnco/filament-import/pull/94 24 | - AR Translations. by @Saifallak in https://github.com/konnco/filament-import/pull/96 25 | 26 | ### New Contributors 27 | 28 | - @Saifallak made their first contribution in https://github.com/konnco/filament-import/pull/96 29 | 30 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.5.2...1.5.3 31 | 32 | ## 1.5.2 - 2023-05-03 33 | 34 | ### What's Changed 35 | 36 | - feat: Add pt_BR support by @jeffersonGlemos in https://github.com/konnco/filament-import/pull/89 37 | 38 | ### New Contributors 39 | 40 | - @jeffersonGlemos made their first contribution in https://github.com/konnco/filament-import/pull/89 41 | 42 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.5.1...1.5.2 43 | 44 | ## 1.5.1 - 2023-05-03 45 | 46 | ### What's Changed 47 | 48 | - add psr/simple-cache ^3.0 by @ster in https://github.com/konnco/filament-import/pull/83 49 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/konnco/filament-import/pull/90 50 | - Adds FA translations. by @fsamapoor in https://github.com/konnco/filament-import/pull/86 51 | 52 | ### New Contributors 53 | 54 | - @ster made their first contribution in https://github.com/konnco/filament-import/pull/83 55 | - @fsamapoor made their first contribution in https://github.com/konnco/filament-import/pull/86 56 | 57 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.5...1.5.1 58 | 59 | ## 1.5 - 2023-03-17 60 | 61 | ### What's Changed 62 | 63 | - Translates labels by @archilex in https://github.com/konnco/filament-import/pull/82 64 | 65 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.4.3...1.5 66 | 67 | ## 1.4.3 - 2023-02-24 68 | 69 | ### What's Changed 70 | 71 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/konnco/filament-import/pull/72 72 | - Adds spanish translation by @archilex in https://github.com/konnco/filament-import/pull/76 73 | - Support Laravel 10 by @mtawil in https://github.com/konnco/filament-import/pull/81 74 | 75 | ### New Contributors 76 | 77 | - @archilex made their first contribution in https://github.com/konnco/filament-import/pull/76 78 | - @mtawil made their first contribution in https://github.com/konnco/filament-import/pull/81 79 | 80 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.4.2...1.4.3 81 | 82 | ## 1.4.2 - 2023-01-19 83 | 84 | ### What's Changed 85 | 86 | - Excel Blank Rows Calls mutateBeforeCreate (#66) by @jaetoole in https://github.com/konnco/filament-import/pull/67 87 | 88 | ### New Contributors 89 | 90 | - @jaetoole made their first contribution in https://github.com/konnco/filament-import/pull/67 91 | 92 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.4.1...1.4.2 93 | 94 | ## 1.4.1 - 2022-12-18 95 | 96 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.4.0...1.4.1 97 | 98 | ## 1.4.0 - 2022-11-15 99 | 100 | ### What's Changed 101 | 102 | - Updated readme by @frankyso in https://github.com/konnco/filament-import/pull/52 103 | - docs(contributor): contributors readme action update by @github-actions in https://github.com/konnco/filament-import/pull/53 104 | - Added handle record creation function by @frankyso in https://github.com/konnco/filament-import/pull/54 105 | 106 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.3.0...1.4.0 107 | 108 | ## 1.3.0 - 2022-11-14 109 | 110 | ### What's Changed 111 | 112 | - feature/mutate after create by @arjendejong12 in https://github.com/konnco/filament-import/pull/44 113 | - feature/unique field by @arjendejong12 in https://github.com/konnco/filament-import/pull/45 114 | - Bump dependabot/fetch-metadata from 1.3.4 to 1.3.5 by @dependabot in https://github.com/konnco/filament-import/pull/47 115 | 116 | ### New Contributors 117 | 118 | - @arjendejong12 made their first contribution in https://github.com/konnco/filament-import/pull/44 119 | 120 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.7...1.3.0 121 | 122 | ## 1.2.7 - 2022-10-28 123 | 124 | ### What's Changed 125 | 126 | - Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/konnco/filament-import/pull/40 127 | 128 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.6...1.2.7 129 | 130 | ## 1.2.6 - 2022-10-14 131 | 132 | ### What's Changed 133 | 134 | - docs(contributor): contributors readme action update by @github-actions in https://github.com/konnco/filament-import/pull/39 135 | 136 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.5...1.2.6 137 | 138 | ## 1.2.5 - 2022-10-11 139 | 140 | ### What's Changed 141 | 142 | - Fix/heading options by @rizkyanfasafm in https://github.com/konnco/filament-import/pull/38 143 | 144 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.4...1.2.5 145 | 146 | ## 1.2.4 - 2022-10-06 147 | 148 | ### What's Changed 149 | 150 | - Bump dependabot/fetch-metadata from 1.3.3 to 1.3.4 by @dependabot in https://github.com/konnco/filament-import/pull/35 151 | 152 | ### New Contributors 153 | 154 | - @dependabot made their first contribution in https://github.com/konnco/filament-import/pull/35 155 | 156 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.3...1.2.4 157 | 158 | ## 1.2.3 - 2022-09-27 159 | 160 | ### What's Changed 161 | 162 | - Feature/test by @frankyso in https://github.com/konnco/filament-import/pull/32 163 | 164 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.2...1.2.3 165 | 166 | ## 1.2.2 - 2022-09-23 167 | 168 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.1...1.2.2 169 | 170 | ## 1.2.1 - 2022-09-23 171 | 172 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.2.0...1.2.1 173 | 174 | ## 1.2.0 - 2022-09-22 175 | 176 | ### What's Changed 177 | 178 | - Added global data mutation 179 | 180 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.1.1...1.2.0 181 | 182 | ## 1.1.1 - 2022-09-21 183 | 184 | ### What's Changed 185 | 186 | - feature/documentation by @frankyso in https://github.com/konnco/filament-import/pull/24 187 | 188 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.1.0...1.1.1 189 | 190 | ## 1.1.0 - 2022-09-21 191 | 192 | ### What's Changed 193 | 194 | - Manipulate data based on another column by @abduromanov in https://github.com/konnco/filament-import/pull/18 195 | - Added basic validation by @frankyso in https://github.com/konnco/filament-import/pull/23 196 | 197 | ### New Contributors 198 | 199 | - @abduromanov made their first contribution in https://github.com/konnco/filament-import/pull/18 200 | 201 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.0.5...1.1.0 202 | 203 | ## 1.0.5 - 2022-09-19 204 | 205 | ### What's Changed 206 | 207 | - feature/common field import by @frankyso in https://github.com/konnco/filament-import/pull/21 208 | 209 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.0.4...1.0.5 210 | 211 | ## 1.0.4 - 2022-09-19 212 | 213 | ### What's Changed 214 | 215 | - Refactor masscreate and update into oneliner by @frankyso in https://github.com/konnco/filament-import/pull/17 216 | - JSON Column Support by @frankyso in https://github.com/konnco/filament-import/pull/20 217 | 218 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.0.3...1.0.4 219 | 220 | ## 1.0.3 - 2022-09-16 221 | 222 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.0.2...1.0.3 223 | 224 | ## 1.0.2 - 2022-09-15 225 | 226 | ### What's Changed 227 | 228 | - fix: undefined array key by @tryoasnafi in https://github.com/konnco/filament-import/pull/13 229 | - Fixed support csv & xls by @frankyso in https://github.com/konnco/filament-import/pull/14 230 | - docs(contributor): contributors readme action update by @github-actions in https://github.com/konnco/filament-import/pull/15 231 | 232 | ### New Contributors 233 | 234 | - @tryoasnafi made their first contribution in https://github.com/konnco/filament-import/pull/13 235 | - @github-actions made their first contribution in https://github.com/konnco/filament-import/pull/15 236 | 237 | **Full Changelog**: https://github.com/konnco/filament-import/compare/1.0.1...1.0.2 238 | 239 | ## 1.0.0 - 2022-09-11 240 | 241 | ### What's Changed 242 | 243 | - Feature/test by @frankyso in https://github.com/konnco/filament-import/pull/1 244 | - Feature/refactor by @frankyso in https://github.com/konnco/filament-import/pull/2 245 | 246 | ### New Contributors 247 | 248 | - @frankyso made their first contribution in https://github.com/konnco/filament-import/pull/1 249 | 250 | **Full Changelog**: https://github.com/konnco/filament-import/compare/0.0.2...1.0.0 251 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) :vendor_name 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Screenshot of Login](./art/screenshot.png) 2 | 3 | # Filament Plugin for Import CSV and XLS into Database 4 | 5 | 6 | FILAMENT 2.x 7 | 8 | 9 | Packagist 10 | 11 | 12 | Downloads 13 | 14 | 15 | [![Code Styles](https://github.com/konnco/filament-import/actions/workflows/php-cs-fixer.yml/badge.svg)](https://github.com/konnco/filament-import/actions/workflows/php-cs-fixer.yml) 16 | [![run-tests](https://github.com/konnco/filament-import/actions/workflows/run-tests.yml/badge.svg)](https://github.com/konnco/filament-import/actions/workflows/run-tests.yml) 17 | 18 | This package will make it easier for you to import from files to your model, very easily without the need to do templates. 19 | 20 | all you have to do is drag and drop and match the fields and columns of your file, and let magic happens! 21 | 22 | ## Installation 23 | 24 | You can install the package via composer: 25 | 26 | ```bash 27 | composer require konnco/filament-import 28 | ``` 29 | 30 | ## Publishing Config 31 | 32 | If you want to do the settings manually, please publish the existing config. 33 | 34 | ```bash 35 | php artisan vendor:publish --tag=filament-import-config 36 | ``` 37 | 38 | ## Usage 39 | 40 | import the actions into `ListRecords` page 41 | 42 | ```php 43 | use Konnco\FilamentImport\Actions\ImportAction; 44 | use Konnco\FilamentImport\Actions\ImportField; 45 | 46 | class ListCredentialDatabases extends ListRecords 47 | { 48 | protected static string $resource = CredentialDatabaseResource::class; 49 | 50 | protected function getActions(): array 51 | { 52 | return [ 53 | ImportAction::make() 54 | ->fields([ 55 | ImportField::make('project') 56 | ->label('Project') 57 | ->helperText('Define as project helper'), 58 | ImportField::make('manager') 59 | ->label('Manager'), 60 | ]) 61 | ]; 62 | } 63 | } 64 | ``` 65 | ### Required Field 66 | ```php 67 | protected function getActions(): array 68 | { 69 | return [ 70 | ImportAction::make() 71 | ->fields([ 72 | ImportField::make('project') 73 | ->label('Project') 74 | ->required(), 75 | ]) 76 | ]; 77 | } 78 | ``` 79 | 80 | ### Disable Mass Create 81 | if you still want to stick with the event model you might need this and turn off mass create 82 | ```php 83 | protected function getActions(): array 84 | { 85 | return [ 86 | ImportAction::make() 87 | ->massCreate(false) 88 | ->fields([ 89 | ImportField::make('project') 90 | ->label('Project') 91 | ->required(), 92 | ]) 93 | ]; 94 | } 95 | ``` 96 | 97 | ### Filter Out Blank Rows 98 | If you have a spreadsheet which includes blank data [click here to see more](https://thesoftwarepro.com/excel-tips-how-to-fill-blank-cells/), you can filter these out: 99 | ```php 100 | protected function getActions(): array 101 | { 102 | return [ 103 | ImportAction::make() 104 | ->handleBlankRows(true) 105 | ->fields([ 106 | ImportField::make('project') 107 | ->label('Project') 108 | ->required(), 109 | ]) 110 | ]; 111 | } 112 | ``` 113 | 114 | ### Field Data Mutation 115 | you can also manipulate data from row spreadsheet before saving to model 116 | ```php 117 | protected function getActions(): array 118 | { 119 | return [ 120 | ImportAction::make() 121 | ->fields([ 122 | ImportField::make('project') 123 | ->label('Project') 124 | ->mutateBeforeCreate(fn($value) => Str::of($value)->camelCase()) 125 | ->required(), 126 | ]) 127 | ]; 128 | } 129 | ``` 130 | otherwise you can manipulate data and getting all mutated data from field before its getting insert into the database. 131 | ```php 132 | protected function getActions(): array 133 | { 134 | return [ 135 | ImportAction::make() 136 | ->fields([ 137 | ImportField::make('email') 138 | ->label('Email') 139 | ->required(), 140 | ])->mutateBeforeCreate(function($row){ 141 | $row['password'] = bcrypt($row['email']); 142 | 143 | return $row; 144 | }) 145 | ]; 146 | } 147 | ``` 148 | it is also possible to manipulate data after it was inserted into the database 149 | ```php 150 | use Illuminate\Database\Eloquent\Model; 151 | 152 | protected function getActions(): array 153 | { 154 | return [ 155 | ImportAction::make() 156 | ->fields([ 157 | ImportField::make('email') 158 | ->label('Email') 159 | ->required(), 160 | ])->mutateAfterCreate(function(Model $model, $row){ 161 | // do something with the model 162 | 163 | return $model; 164 | }) 165 | ]; 166 | } 167 | ``` 168 | 169 | ### Grid Column 170 | Of course, you can divide the column grid into several parts to beautify the appearance of the data map 171 | ```php 172 | protected function getActions(): array 173 | { 174 | return [ 175 | ImportAction::make() 176 | ->fields([ 177 | ImportField::make('project') 178 | ->label('Project') 179 | ->required(), 180 | ], columns:2) 181 | ]; 182 | } 183 | ``` 184 | 185 | ### Json Format Field 186 | We also support the json format field, which you can set when calling the `make` function and separate the name with a dot annotation 187 | 188 | ```php 189 | protected function getActions(): array 190 | { 191 | return [ 192 | ImportAction::make() 193 | ->fields([ 194 | ImportField::make('project.en') 195 | ->label('Project In English') 196 | ->required(), 197 | ImportField::make('project.id') 198 | ->label('Project in Indonesia') 199 | ->required(), 200 | ], columns:2) 201 | ]; 202 | } 203 | ``` 204 | 205 | ### Static Field Data 206 | for the static field data you can use the common fields from filament 207 | 208 | ```php 209 | use Filament\Forms\Components\Select; 210 | 211 | protected function getActions(): array 212 | { 213 | return [ 214 | ImportAction::make() 215 | ->fields([ 216 | ImportField::make('name') 217 | ->label('Project') 218 | ->required(), 219 | Select::make('status') 220 | ->options([ 221 | 'draft' => 'Draft', 222 | 'reviewing' => 'Reviewing', 223 | 'published' => 'Published', 224 | ]) 225 | ], columns:2) 226 | ]; 227 | } 228 | ``` 229 | 230 | ### Unique field 231 | if your model should be unique, you can pass the name of the field, which will be used to check if a row already exists in the database. if it exists, skip that row (preventing an error about non unique row) 232 | 233 | ```php 234 | use Filament\Forms\Components\Select; 235 | 236 | protected function getActions(): array 237 | { 238 | return [ 239 | ImportAction::make() 240 | ->uniqueField('email') 241 | ->fields([ 242 | ImportField::make('email') 243 | ->label('Email') 244 | ->required(), 245 | ], columns:2) 246 | ]; 247 | } 248 | ``` 249 | 250 | ### Validation 251 | you can make the validation for import fields, for more information about the available validation please check laravel documentation 252 | 253 | ```php 254 | use Filament\Forms\Components\Select; 255 | 256 | protected function getActions(): array 257 | { 258 | return [ 259 | ImportAction::make() 260 | ->fields([ 261 | ImportField::make('name') 262 | ->label('Project') 263 | ->rules('required|min:10|max:255'), 264 | ], columns:2) 265 | ]; 266 | } 267 | ``` 268 | 269 | ### Create Record 270 | you can overide the default record creation closure and put your own code by using `handleRecordCreation` function 271 | 272 | ```php 273 | use Filament\Forms\Components\Select; 274 | 275 | protected function getActions(): array 276 | { 277 | return [ 278 | ImportAction::make() 279 | ->fields([ 280 | ImportField::make('name') 281 | ->label('Project') 282 | ->rules('required|min:10|max:255'), 283 | ], columns:2) 284 | ->handleRecordCreation(function($data){ 285 | return Post::create($data); 286 | }) 287 | ]; 288 | } 289 | ``` 290 | 291 | 292 | ## Testing 293 | 294 | ```bash 295 | composer test 296 | ``` 297 | 298 | ## Changelog 299 | 300 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 301 | 302 | ## Contributing 303 | 304 | Please see [CONTRIBUTING](https://github.com/konnco/.github/blob/main/CONTRIBUTING.md) for details. 305 | 306 | ## Security Vulnerabilities 307 | 308 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 309 | 310 | -------------------------------------------------------------------------------- /art/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnco/filament-import/c9963f09762144f90c4965579785f41ff8a07aac/art/.DS_Store -------------------------------------------------------------------------------- /art/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnco/filament-import/c9963f09762144f90c4965579785f41ff8a07aac/art/screenshot.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "konnco/filament-import", 3 | "description": "", 4 | "keywords": [ 5 | "import", 6 | "laravel", 7 | "filament-import" 8 | ], 9 | "autoload": { 10 | "psr-4": { 11 | "Konnco\\FilamentImport\\": "src/" 12 | } 13 | }, 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Franky So", 18 | "email": "frankyso.mail@gmail.com" 19 | } 20 | ], 21 | "require": { 22 | "php": "^8.0", 23 | "filament/filament": "^3", 24 | "filament/notifications": "^3.0", 25 | "illuminate/contracts": "^10|^11", 26 | "illuminate/support": "^10.0|^11", 27 | "livewire/livewire": "^3", 28 | "maatwebsite/excel": "^3.1.48", 29 | "psr/simple-cache": "^2.0|^3.0", 30 | "spatie/laravel-package-tools": "^1.16" 31 | }, 32 | "require-dev": { 33 | "laravel/pint": "^1.11", 34 | "nunomaduro/collision": "^6.4|^7.0", 35 | "larastan/larastan": "*", 36 | "orchestra/testbench": "^8.8", 37 | "pestphp/pest": "^2", 38 | "pestphp/pest-plugin-laravel": "^2", 39 | "pestphp/pest-plugin-livewire": "^2.1", 40 | "phpstan/extension-installer": "^1.3.1", 41 | "phpstan/phpstan-deprecation-rules": "^1.1.4", 42 | "phpstan/phpstan-phpunit": "^1.3.13", 43 | "phpunit/phpunit": "^9.6.10|^10.0" 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Konnco\\FilamentImport\\Tests\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "analyse": "vendor/bin/phpstan analyse", 52 | "test": "vendor/bin/pest", 53 | "test-coverage": "vendor/bin/pest --coverage", 54 | "format": "vendor/bin/pint" 55 | }, 56 | "config": { 57 | "sort-packages": true, 58 | "allow-plugins": { 59 | "pestphp/pest-plugin": true, 60 | "phpstan/extension-installer": true 61 | } 62 | }, 63 | "minimum-stability": "dev", 64 | "prefer-stable": true, 65 | "extra": { 66 | "laravel": { 67 | "providers": [ 68 | "Konnco\\FilamentImport\\FilamentImportServiceProvider" 69 | ] 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /config/filament-import.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'application/vnd.ms-excel', 6 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 7 | 'text/csv', 8 | 'text/plain', 9 | 'csv', 10 | 'txt', 11 | ], 12 | 'temporary_files' => [ 13 | 'disk' => 'local', 14 | 'directory' => 'filament-import', 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konnco/filament-import/c9963f09762144f90c4965579785f41ff8a07aac/phpstan-baseline.neon -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 4 6 | paths: 7 | - src 8 | tmpDir: build/phpstan 9 | checkOctaneCompatibility: true 10 | checkModelProperties: true 11 | checkMissingIterableValueType: false 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist.bak: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | tests 24 | 25 | 26 | 27 | 28 | ./src 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/lang/ar/actions.php: -------------------------------------------------------------------------------- 1 | 'استيراد', 5 | 'skip_header' => 'تخطي العنوان', 6 | 'match_to_column' => 'تطابق البيانات مع العمود', 7 | 'import_failed' => 'فشل الاستيراد ، يرجى التحقق من ملف الاستيراد والمحاولة مرة أخرى', 8 | 'import_failed_title' => 'فشل الاستيراد', 9 | 'import_succeeded' => 'استيراد:count الصفوف الناجحة ،:skipped تم تخطي صف (الصفوف)', 10 | 'import_succeeded_title' => 'نجح الاستيراد', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/ar/validators.php: -------------------------------------------------------------------------------- 1 | ':error بالسطر :line', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/en/actions.php: -------------------------------------------------------------------------------- 1 | 'Import', 5 | 'skip_header' => 'Skip header', 6 | 'match_to_column' => 'Match data to column', 7 | 'import_failed' => 'Import failed, please check your import file and try again', 8 | 'import_failed_title' => 'Import Failed', 9 | 'import_succeeded' => 'Import of :count row(s) succeeded, :skipped skipped row(s)', 10 | 'import_succeeded_title' => 'Import Succeeded', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/en/validators.php: -------------------------------------------------------------------------------- 1 | ':error at line :line', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/es/actions.php: -------------------------------------------------------------------------------- 1 | 'Importar', 5 | 'skip_header' => 'Omitir el encabezado', 6 | 'match_to_column' => 'Asignar los campos a sus columnas', 7 | 'import_failed' => 'La importación ha fallada. Revise el archivo y pruebe de nuevo.', 8 | 'import_failed_title' => 'Importación Fallida', 9 | 'import_succeeded' => 'Importación exitosa de :count fila(s). :skipped fila(s) omitida(s).', 10 | 'import_succeeded_title' => 'Importación Exitosa', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/es/validators.php: -------------------------------------------------------------------------------- 1 | 'Error en la línea :line. :error', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/fa/actions.php: -------------------------------------------------------------------------------- 1 | 'درون‌بُرد', 5 | 'skip_header' => 'پرش از سرآیند', 6 | 'match_to_column' => 'تطبیق داده با ستون', 7 | 'import_failed' => 'درون‌بُرد با خطا مواجه شد، لطفاً فایل خود را بررسی کرده و دوباره تلاش کنید', 8 | 'import_failed_title' => 'درون‌بُرد با خطا مواجه شد', 9 | 'import_succeeded' => 'درون‌بُرد :count ردیف با موفقیت انجام شد، از :skipped ردیف پریده شد', 10 | 'import_succeeded_title' => 'درون‌بُرد با موفقیت انجام شد', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/fa/validators.php: -------------------------------------------------------------------------------- 1 | ':error در خط :line', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/actions.php: -------------------------------------------------------------------------------- 1 | 'Importar', 5 | 'skip_header' => 'Ignorar cabeçalho', 6 | 'match_to_column' => 'Vincule os dados à coluna', 7 | 'import_failed' => 'Falha na importação, verifique seu arquivo de importação e tente novamente', 8 | 'import_failed_title' => 'Falha na Importação', 9 | 'import_succeeded' => 'Importação de :count linha(s) com sucesso, :skipped linha(s) ignorada(s)', 10 | 'import_succeeded_title' => 'Importação bem-sucedida', 11 | ]; 12 | -------------------------------------------------------------------------------- /resources/lang/pt_BR/validators.php: -------------------------------------------------------------------------------- 1 | ':error na linha :line', 5 | ]; 6 | -------------------------------------------------------------------------------- /src/Actions/ImportAction.php: -------------------------------------------------------------------------------- 1 | label(fn (): string => __('filament-import::actions.import')); 50 | 51 | $this->setInitialForm(); 52 | 53 | $this->button(); 54 | 55 | $this->groupedIcon('heroicon-s-plus'); 56 | 57 | $this->action(function (ComponentContainer $form): void { 58 | $model = $form->getModel(); 59 | 60 | $this->process(function (array $data) use ($model) { 61 | $selectedField = collect($data) 62 | ->except('fileRealPath', 'file', 'skipHeader'); 63 | 64 | Import::make(spreadsheetFilePath: $data['file']) 65 | ->fields($selectedField) 66 | ->formSchemas($this->fields) 67 | ->uniqueField($this->uniqueField) 68 | ->model($model) 69 | ->disk($this->getTemporaryDisk()) 70 | ->skipHeader((bool) $data['skipHeader']) 71 | ->massCreate($this->shouldMassCreate) 72 | ->handleBlankRows($this->shouldHandleBlankRows) 73 | ->mutateBeforeCreate($this->mutateBeforeCreate) 74 | ->mutateAfterCreate($this->mutateAfterCreate) 75 | ->handleRecordCreation($this->handleRecordCreation) 76 | ->execute(); 77 | }); 78 | }); 79 | } 80 | 81 | public function setInitialForm(): void 82 | { 83 | $this->form($this->getInitialFormSchema()); 84 | } 85 | 86 | public function massCreate($shouldMassCreate = true): static 87 | { 88 | $this->shouldMassCreate = $shouldMassCreate; 89 | 90 | return $this; 91 | } 92 | 93 | public function handleBlankRows($shouldHandleBlankRows = false): static 94 | { 95 | $this->shouldHandleBlankRows = $shouldHandleBlankRows; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @return $this 102 | */ 103 | public function fields(array $fields, int $columns = 1): static 104 | { 105 | $this->fields = collect($fields)->mapWithKeys(fn ($item) => [$item->getName() => $item])->toArray(); 106 | 107 | $fields = collect($fields); 108 | 109 | $fields = $fields->map(fn (ImportField|Field $field) => $this->getFields($field))->toArray(); 110 | 111 | $this->form( 112 | array_merge( 113 | $this->getInitialFormSchema(), 114 | [ 115 | Fieldset::make(__('filament-import::actions.match_to_column')) 116 | ->schema($fields) 117 | ->columns($columns) 118 | ->visible(function (callable $get) { 119 | return filled($get('file')); 120 | }), 121 | ] 122 | ) 123 | ); 124 | 125 | return $this; 126 | } 127 | 128 | protected function getInitialFormSchema(): array 129 | { 130 | return [ 131 | FileUpload::make('file') 132 | ->label('') 133 | ->required(! app()->environment('testing')) 134 | ->acceptedFileTypes(config('filament-import.accepted_mimes')) 135 | ->imagePreviewHeight('250') 136 | ->reactive() 137 | ->disk($this->getTemporaryDisk()) 138 | ->directory($this->getTemporaryDirectory()) 139 | ->afterStateUpdated(function (callable $set, TemporaryUploadedFile $state) { 140 | $set('fileRealPath', $state->getRealPath()); 141 | }), 142 | Hidden::make('fileRealPath'), 143 | Toggle::make('skipHeader') 144 | ->default(true) 145 | ->label(__('filament-import::actions.skip_header')), 146 | ]; 147 | } 148 | 149 | private function getFields(ImportField|Field $field): Field 150 | { 151 | if ($field instanceof Field) { 152 | return $field; 153 | } 154 | 155 | return Select::make($field->getName()) 156 | ->label($field->getLabel()) 157 | ->helperText($field->getHelperText()) 158 | ->required($field->isRequired()) 159 | ->placeholder($field->getPlaceholder()) 160 | ->options(options: function (callable $get, callable $set) use ($field) { 161 | $uploadedFile = last($get('file') ?? []); 162 | $filePath = is_string($uploadedFile) ? $uploadedFile : $uploadedFile?->getRealPath(); 163 | 164 | $options = $this->cachedHeadingOptions; 165 | 166 | if (count($options) === 0) { 167 | $options = $this->toCollection($filePath, $this->temporaryDiskIsRemote() ? $this->getTemporaryDisk() : null)->first()?->first()->filter(fn ($value) => $value != null)->map('trim')->toArray(); 168 | } 169 | 170 | $selected = array_search($field->getName(), $options); 171 | 172 | if ($selected !== false) { 173 | $set($field->getName(), $selected); 174 | } elseif (! empty($field->getAlternativeColumnNames())) { 175 | $alternativeNames = array_intersect($field->getAlternativeColumnNames(), $options); 176 | if (count($alternativeNames) > 0) { 177 | $set($field->getName(), array_search(current($alternativeNames), $options)); 178 | } 179 | } 180 | 181 | return $options; 182 | }); 183 | } 184 | 185 | public function handleRecordCreation(Closure $closure): static 186 | { 187 | $this->handleRecordCreation = $closure; 188 | $this->massCreate(false); 189 | 190 | return $this; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Actions/ImportField.php: -------------------------------------------------------------------------------- 1 | name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Concerns/HasActionMutation.php: -------------------------------------------------------------------------------- 1 | mutateBeforeCreate = $fn; 17 | 18 | return $this; 19 | } 20 | 21 | public function doMutateBeforeCreate(array $row) 22 | { 23 | $closure = $this->mutateBeforeCreate; 24 | 25 | if (! $closure) { 26 | return $row; 27 | } 28 | 29 | return $closure($row); 30 | } 31 | 32 | public function mutateAfterCreate(bool|Closure $fn): static 33 | { 34 | $this->mutateAfterCreate = $fn; 35 | 36 | return $this; 37 | } 38 | 39 | public function doMutateAfterCreate(Model $model, array $row) 40 | { 41 | $closure = $this->mutateAfterCreate; 42 | 43 | if (! $closure) { 44 | return $model; 45 | } 46 | 47 | return $closure($model, $row); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Concerns/HasActionUniqueField.php: -------------------------------------------------------------------------------- 1 | uniqueField = $uniqueField; 12 | 13 | return $this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Concerns/HasColumnMatching.php: -------------------------------------------------------------------------------- 1 | alternativeColumnNames = $alternativeColumnNames; 12 | 13 | return $this; 14 | } 15 | 16 | public function getAlternativeColumnNames(): array 17 | { 18 | return $this->alternativeColumnNames; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldHelper.php: -------------------------------------------------------------------------------- 1 | helperText = $text; 15 | 16 | return $this; 17 | } 18 | 19 | public function getHelperText(): ?string 20 | { 21 | return $this->helperText; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldLabel.php: -------------------------------------------------------------------------------- 1 | label = $label; 19 | 20 | return $this; 21 | } 22 | 23 | public function translateLabel(bool $shouldTranslateLabel = true): static 24 | { 25 | $this->shouldTranslateLabel = $shouldTranslateLabel; 26 | 27 | return $this; 28 | } 29 | 30 | public function getLabel(): string 31 | { 32 | $label = $this->label ?? (string) Str::of($this->name) 33 | ->afterLast('.') 34 | ->kebab() 35 | ->replace(['-', '_'], ' ') 36 | ->ucfirst(); 37 | 38 | return (is_string($label) && $this->shouldTranslateLabel) ? 39 | __($label) : 40 | $label; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldMutation.php: -------------------------------------------------------------------------------- 1 | mutateBeforeCreate = $fn; 15 | 16 | return $this; 17 | } 18 | 19 | public function doMutateBeforeCreate(mixed $state, Collection $row) 20 | { 21 | $closure = $this->mutateBeforeCreate; 22 | 23 | if (! $closure) { 24 | return $state; 25 | } 26 | 27 | return $closure($state, $row); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldPlaceholder.php: -------------------------------------------------------------------------------- 1 | placeholder = $placeholder; 15 | 16 | return $this; 17 | } 18 | 19 | public function getPlaceholder(): ?string 20 | { 21 | return $this->placeholder; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldRequire.php: -------------------------------------------------------------------------------- 1 | isRequired = true; 12 | 13 | return $this; 14 | } 15 | 16 | public function isRequired(): bool 17 | { 18 | return $this->isRequired; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Concerns/HasFieldValidation.php: -------------------------------------------------------------------------------- 1 | rules = $rules; 14 | $this->customMessages = $customMessages; 15 | 16 | return $this; 17 | } 18 | 19 | public function getValidationRules() 20 | { 21 | return $this->rules; 22 | } 23 | 24 | public function getCustomValidationMessages() 25 | { 26 | return $this->customMessages; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Concerns/HasTemporaryDisk.php: -------------------------------------------------------------------------------- 1 | temporaryDisk ?? config('filament-import.temporary_files.disk'); 17 | } 18 | 19 | public function temporaryDisk(string $temporaryDisk): void 20 | { 21 | $this->temporaryDisk = $temporaryDisk; 22 | } 23 | 24 | public function getTemporaryDirectory(): mixed 25 | { 26 | return $this->temporaryDirectory ?? config('filament-import.temporary_files.directory'); 27 | } 28 | 29 | public function temporaryDirectory(string $temporaryPath): void 30 | { 31 | $this->temporaryDirectory = $temporaryPath; 32 | } 33 | 34 | public function temporaryDiskIsRemote(): bool 35 | { 36 | $driver = config("filesystems.disks.{$this->getTemporaryDisk()}.driver"); 37 | 38 | return in_array($driver, ['s3', 'ftp', 'sftp']); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/FilamentImportServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filament-import') 13 | ->hasConfigFile() 14 | ->hasTranslations(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Import.php: -------------------------------------------------------------------------------- 1 | spreadsheet($spreadsheetFilePath); 49 | } 50 | 51 | public function fields(Collection $fields): static 52 | { 53 | $this->fields = $fields; 54 | 55 | return $this; 56 | } 57 | 58 | public function formSchemas(array $formSchemas): static 59 | { 60 | $this->formSchemas = $formSchemas; 61 | 62 | return $this; 63 | } 64 | 65 | public function spreadsheet($spreadsheet): static 66 | { 67 | $this->spreadsheet = $spreadsheet; 68 | 69 | return $this; 70 | } 71 | 72 | public function model(string $model): static 73 | { 74 | $this->model = $model; 75 | 76 | return $this; 77 | } 78 | 79 | public function disk($disk = 'local'): static 80 | { 81 | $this->disk = $disk; 82 | 83 | return $this; 84 | } 85 | 86 | public function skipHeader(bool $shouldSkipHeader): static 87 | { 88 | $this->shouldSkipHeader = $shouldSkipHeader; 89 | 90 | return $this; 91 | } 92 | 93 | public function massCreate($shouldMassCreate = true): static 94 | { 95 | $this->shouldMassCreate = $shouldMassCreate; 96 | 97 | return $this; 98 | } 99 | 100 | public function handleBlankRows($shouldHandleBlankRows = false): static 101 | { 102 | $this->shouldHandleBlankRows = $shouldHandleBlankRows; 103 | 104 | return $this; 105 | } 106 | 107 | public function getSpreadsheetData(): Collection 108 | { 109 | $data = $this->toCollection($this->temporaryDiskIsRemote() ? $this->spreadsheet : new UploadedFile(Storage::disk($this->disk)->path($this->spreadsheet), $this->spreadsheet)) 110 | ->first() 111 | ->skip((int) $this->shouldSkipHeader); 112 | if (! $this->shouldHandleBlankRows) { 113 | return $data; 114 | } 115 | 116 | return $data->filter(function ($row) { 117 | return $row->filter()->isNotEmpty(); 118 | }); 119 | } 120 | 121 | public function validated($data, $rules, $customMessages, $line) 122 | { 123 | $validator = Validator::make($data, $rules, $customMessages); 124 | 125 | try { 126 | if ($validator->fails()) { 127 | Notification::make() 128 | ->danger() 129 | ->title(trans('filament-import::actions.import_failed_title')) 130 | ->body(trans('filament-import::validators.message', ['line' => $line, 'error' => $validator->errors()->first()])) 131 | ->persistent() 132 | ->send(); 133 | 134 | return false; 135 | } 136 | } catch (\Exception $e) { 137 | return $data; 138 | } 139 | 140 | return $data; 141 | } 142 | 143 | public function handleRecordCreation(?Closure $closure): static 144 | { 145 | $this->handleRecordCreation = $closure; 146 | 147 | return $this; 148 | } 149 | 150 | public function execute() 151 | { 152 | $importSuccess = true; 153 | $skipped = 0; 154 | DB::transaction(function () use (&$importSuccess, &$skipped) { 155 | foreach ($this->getSpreadsheetData() as $line => $row) { 156 | $prepareInsert = collect([]); 157 | $rules = []; 158 | $validationMessages = []; 159 | 160 | foreach (Arr::dot($this->fields) as $key => $value) { 161 | $field = $this->formSchemas[$key]; 162 | $fieldValue = $value; 163 | 164 | if ($field instanceof ImportField) { 165 | // check if field is optional 166 | if (! $field->isRequired() && blank(@$row[$value])) { 167 | continue; 168 | } 169 | 170 | $fieldValue = $field->doMutateBeforeCreate($row[$value], collect($row)) ?? $row[$value]; 171 | $rules[$key] = $field->getValidationRules(); 172 | if (count($field->getCustomValidationMessages())) { 173 | $validationMessages[$key] = $field->getCustomValidationMessages(); 174 | } 175 | } 176 | 177 | $prepareInsert[$key] = $fieldValue; 178 | } 179 | 180 | $prepareInsert = $this->validated(data: Arr::undot($prepareInsert), rules: $rules, customMessages: $validationMessages, line: $line + 1); 181 | 182 | if (! $prepareInsert) { 183 | DB::rollBack(); 184 | $importSuccess = false; 185 | 186 | break; 187 | } 188 | 189 | $prepareInsert = $this->doMutateBeforeCreate($prepareInsert); 190 | 191 | if ($this->uniqueField !== false) { 192 | if (is_null($prepareInsert[$this->uniqueField] ?? null)) { 193 | DB::rollBack(); 194 | $importSuccess = false; 195 | 196 | break; 197 | } 198 | 199 | $exists = (new $this->model)->where($this->uniqueField, $prepareInsert[$this->uniqueField] ?? null)->first(); 200 | if ($exists instanceof $this->model) { 201 | $skipped++; 202 | 203 | continue; 204 | } 205 | } 206 | 207 | if (! $this->handleRecordCreation) { 208 | if (! $this->shouldMassCreate) { 209 | $model = (new $this->model)->fill($prepareInsert); 210 | $model = tap($model, function ($instance) { 211 | $instance->save(); 212 | }); 213 | } else { 214 | $model = $this->model::create($prepareInsert); 215 | } 216 | } else { 217 | $closure = $this->handleRecordCreation; 218 | $model = $closure($prepareInsert); 219 | } 220 | 221 | $this->doMutateAfterCreate($model, $prepareInsert); 222 | } 223 | }); 224 | 225 | if ($importSuccess) { 226 | Notification::make() 227 | ->success() 228 | ->title(trans('filament-import::actions.import_succeeded_title')) 229 | ->body(trans('filament-import::actions.import_succeeded', ['count' => count($this->getSpreadsheetData()), 'skipped' => $skipped])) 230 | ->persistent() 231 | ->send(); 232 | } 233 | 234 | if (! $importSuccess) { 235 | Notification::make() 236 | ->danger() 237 | ->title(trans('filament-import::actions.import_failed_title')) 238 | ->body(trans('filament-import::actions.import_failed')) 239 | ->persistent() 240 | ->send(); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/ImportField.php: -------------------------------------------------------------------------------- 1 | assertSuccessful(); 14 | }); 15 | 16 | it('can upload file', function () { 17 | $file = csvFiles(10); 18 | livewire()->mountAction('import') 19 | ->setActionData([ 20 | 'file' => $file, 21 | 'title' => 0, 22 | 'slug' => 1, 23 | 'body' => 2, 24 | 'skipHeader' => false, 25 | ]) 26 | ->callMountedAction() 27 | ->assertHasNoActionErrors() 28 | ->assertSuccessful(); 29 | 30 | assertDatabaseCount(Post::class, 11); 31 | }); 32 | 33 | it('can handling record creation', function () { 34 | $file = csvFiles(10); 35 | livewire(HandleCreationList::class)->mountAction('import') 36 | ->setActionData([ 37 | 'file' => $file, 38 | 'title' => 0, 39 | 'slug' => 1, 40 | 'body' => 2, 41 | 'skipHeader' => false, 42 | ]) 43 | ->callMountedAction() 44 | ->assertHasNoActionErrors() 45 | ->assertSuccessful(); 46 | 47 | assertDatabaseCount(Post::class, 11); 48 | }); 49 | 50 | it('can upload file and skip header', function () { 51 | $file = csvFiles(10); 52 | livewire()->mountAction('import') 53 | ->setActionData([ 54 | 'file' => $file, 55 | 'title' => 0, 56 | 'slug' => 1, 57 | 'body' => 2, 58 | 'skipHeader' => true, 59 | ]) 60 | ->callMountedAction() 61 | ->assertHasNoActionErrors() 62 | ->assertSuccessful(); 63 | 64 | assertDatabaseCount(Post::class, 10); 65 | }); 66 | 67 | it('can validate with laravel rules', function () { 68 | $file = csvFiles(10, ['hello', 'hello', 'hello']); 69 | 70 | livewire(ValidateTestList::class)->mountAction('import') 71 | ->setActionData([ 72 | 'file' => $file, 73 | 'title' => 0, 74 | 'slug' => 1, 75 | 'body' => 2, 76 | 'skipHeader' => true, 77 | ]) 78 | ->callMountedAction() 79 | ->assertHasNoActionErrors() 80 | ->assertSuccessful(); 81 | 82 | assertDatabaseCount(Post::class, 0); 83 | }); 84 | 85 | it('can disable mass create', function () { 86 | $file = csvFiles(10); 87 | livewire(WithoutMassCreateTestList::class)->mountAction('import') 88 | ->setActionData([ 89 | 'file' => $file, 90 | 'title' => 0, 91 | 'slug' => 1, 92 | 'body' => 2, 93 | 'skipHeader' => true, 94 | ]) 95 | ->callMountedAction() 96 | ->assertHasNoActionErrors() 97 | ->assertSuccessful(); 98 | 99 | assertDatabaseCount(Post::class, 10); 100 | }); 101 | 102 | it('can ignore non required fields', function () { 103 | $file = csvFiles(10); 104 | livewire(NonRequiredTestList::class)->mountAction('import') 105 | ->setActionData([ 106 | 'file' => $file, 107 | // 'title' => 0, 108 | 'slug' => 1, 109 | 'body' => 2, 110 | 'skipHeader' => true, 111 | ]) 112 | ->callMountedAction() 113 | ->assertHasNoActionErrors() 114 | ->assertSuccessful(); 115 | 116 | assertDatabaseCount(Post::class, 10); 117 | }); 118 | 119 | it('can match alternative column names', function () { 120 | $file = csvFiles(10); 121 | livewire(AlternativeColumnsNamesList::class)->mountAction('import') 122 | ->setActionData([ 123 | 'file' => $file, 124 | 'skipHeader' => false, 125 | ]) 126 | ->assertActionDataSet(['title' => 0, 'slug' => 1, 'body' => 2]) 127 | ->callMountedAction() 128 | ->assertHasNoActionErrors() 129 | ->assertSuccessful(); 130 | 131 | assertDatabaseCount(Post::class, 11); 132 | }); 133 | 134 | // it('can manipulate single field', function () { 135 | // expect(true)->toBeTrue(); 136 | // }); 137 | // 138 | // it('can manipulate mass field', function () { 139 | // expect(true)->toBeTrue(); 140 | // }); 141 | 142 | // it('can save json casting field', function () { 143 | // expect(true)->toBeTrue(); 144 | // }); 145 | -------------------------------------------------------------------------------- /tests/Migrations/post_migration.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('title'); 17 | $table->string('slug'); 18 | $table->string('body'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('posts'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 10 | 11 | function livewire($list = null) 12 | { 13 | return Livewire::test($list ?? CommonTestList::class); 14 | } 15 | 16 | function csvFiles($rows = 10, $extraRow = []) 17 | { 18 | Storage::fake('uploads'); 19 | 20 | $content = collect('Title,Slug,Body'); 21 | for ($i = 0; $i < $rows; $i++) { 22 | $content = $content->push(implode(',', [ 23 | fake()->title, 24 | fake()->slug, 25 | fake()->text(500), 26 | ])); 27 | } 28 | 29 | if (count($extraRow) > 0) { 30 | $content = $content->push(collect($extraRow)->join(',')); 31 | } 32 | 33 | return TemporaryUploadedFile::fake() 34 | ->createWithContent( 35 | name: 'file.csv', 36 | content: $content->join("\n") 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /tests/Resources/Models/Post.php: -------------------------------------------------------------------------------- 1 | fields([ 20 | ImportField::make('title')->alternativeColumnNames(['Title']), 21 | ImportField::make('slug')->alternativeColumnNames(['Slug']), 22 | ImportField::make('body')->alternativeColumnNames(['Body']), 23 | ]) 24 | ->handleRecordCreation(function ($data) { 25 | return Post::create($data); 26 | }), 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Resources/Pages/CommonTestList.php: -------------------------------------------------------------------------------- 1 | fields([ 19 | ImportField::make('title'), 20 | ImportField::make('slug'), 21 | ImportField::make('body'), 22 | ]), ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Resources/Pages/HandleCreationList.php: -------------------------------------------------------------------------------- 1 | fields([ 20 | ImportField::make('title'), 21 | ImportField::make('slug'), 22 | ImportField::make('body'), 23 | ]) 24 | ->handleRecordCreation(function ($data) { 25 | return Post::create($data); 26 | }), 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Resources/Pages/NonRequiredTestList.php: -------------------------------------------------------------------------------- 1 | fields([ 19 | ImportField::make('title'), 20 | ImportField::make('slug') 21 | ->rules('min:6') 22 | ->required(), 23 | ImportField::make('body') 24 | ->required(), 25 | ]) 26 | ->mutateBeforeCreate(function ($data) { 27 | $data['title'] = ''; 28 | 29 | return $data; 30 | }), 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Resources/Pages/ValidateTestList.php: -------------------------------------------------------------------------------- 1 | fields([ 19 | ImportField::make('title'), 20 | ImportField::make('slug') 21 | ->rules('min:6'), 22 | ImportField::make('body'), 23 | ]), ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Resources/Pages/WithoutMassCreateTestList.php: -------------------------------------------------------------------------------- 1 | massCreate(false) 19 | ->fields([ 20 | ImportField::make('title'), 21 | ImportField::make('slug') 22 | ->rules('min:6'), 23 | ImportField::make('body'), 24 | ]), ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Resources/Panels/TestPanelProvider.php: -------------------------------------------------------------------------------- 1 | id('test') 25 | ->path('test') 26 | ->default() 27 | ->resources([PostResource::class]) 28 | ->discoverPages(in: base_path(__DIR__.'/../Pages'), for: 'Konnco\FilamentImport\Tests\Resources\Pages') 29 | ->widgets([ 30 | // 31 | ]) 32 | ->middleware([ 33 | EncryptCookies::class, 34 | AddQueuedCookiesToResponse::class, 35 | StartSession::class, 36 | AuthenticateSession::class, 37 | ShareErrorsFromSession::class, 38 | VerifyCsrfToken::class, 39 | SubstituteBindings::class, 40 | DisableBladeIconComponents::class, 41 | DispatchServingFilamentEvent::class, 42 | ]) 43 | ->authMiddleware([ 44 | Authenticate::class, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Resources/PostResource.php: -------------------------------------------------------------------------------- 1 | schema([ 21 | Forms\Components\TextInput::make('title') 22 | ->required() 23 | ->maxLength(255), 24 | Forms\Components\TextInput::make('slug') 25 | ->required() 26 | ->maxLength(255), 27 | Forms\Components\TextInput::make('body') 28 | ->required() 29 | ->maxLength(255), 30 | ]); 31 | } 32 | 33 | public static function table(Table $table): Table 34 | { 35 | return $table 36 | ->columns([ 37 | Tables\Columns\TextColumn::make('title'), 38 | Tables\Columns\TextColumn::make('slug'), 39 | Tables\Columns\TextColumn::make('body'), 40 | Tables\Columns\TextColumn::make('created_at') 41 | ->dateTime(), 42 | Tables\Columns\TextColumn::make('updated_at') 43 | ->dateTime(), 44 | ]) 45 | ->filters([ 46 | // 47 | ]) 48 | ->actions([ 49 | Tables\Actions\EditAction::make(), 50 | ]) 51 | ->bulkActions([ 52 | Tables\Actions\DeleteBulkAction::make(), 53 | ]); 54 | } 55 | 56 | public static function getRelations(): array 57 | { 58 | return [ 59 | // 60 | ]; 61 | } 62 | 63 | public static function getPages(): array 64 | { 65 | return [ 66 | 'index' => ValidateTestList::route('/'), 67 | ]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('database.default', 'sqlite'); 51 | config()->set('database.connections.sqlite', [ 52 | 'driver' => 'sqlite', 53 | 'database' => ':memory:', 54 | 'prefix' => '', 55 | ]); 56 | 57 | $migration = include __DIR__.'/Migrations/post_migration.php'; 58 | $migration->up(); 59 | 60 | config()->set('filament.resources.namespace', 'Konnco\\FilamentImport\\Tests\\Resources'); 61 | config()->set('filament.resources.path', __DIR__.'/Resources'); 62 | 63 | config()->set('app.key', '6rE9Nz59bGRbeMATftriyQjrpF7DcOQm'); 64 | } 65 | } 66 | --------------------------------------------------------------------------------