[
  {
    "path": ".github/workflows/appstore-build-publish.yml",
    "content": "# This workflow is provided via the organization template repository\n#\n# https://github.com/nextcloud/.github\n# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization\n#\n# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: MIT\n\nname: Build and publish app release\n\non:\n  release:\n    types: [published]\n\njobs:\n  build_and_publish:\n    runs-on: ubuntu-latest\n\n    # Only allowed to be run on nextcloud-releases repositories\n    if: ${{ github.repository_owner == 'nextcloud-releases' }}\n\n    steps:\n      - name: Check actor permission\n        uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0\n        with:\n          require: write\n\n      - name: Set app env\n        run: |\n          # Split and keep last\n          echo \"APP_NAME=${GITHUB_REPOSITORY##*/}\" >> $GITHUB_ENV\n          echo \"APP_VERSION=${GITHUB_REF##*/}\" >> $GITHUB_ENV\n\n      - name: Checkout\n        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7\n        with:\n          path: ${{ env.APP_NAME }}\n\n      - name: Get appinfo data\n        id: appinfo\n        uses: skjnldsv/xpath-action@7e6a7c379d0e9abc8acaef43df403ab4fc4f770c # master\n        with:\n          filename: ${{ env.APP_NAME }}/appinfo/info.xml\n          expression: \"//info//dependencies//nextcloud/@min-version\"\n\n      - name: Read package.json node and npm engines version\n        uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3\n        id: versions\n        # Continue if no package.json\n        continue-on-error: true\n        with:\n          path: ${{ env.APP_NAME }}\n          fallbackNode: '^20'\n          fallbackNpm: '^10'\n\n      - name: Set up node ${{ steps.versions.outputs.nodeVersion }}\n        # Skip if no package.json\n        if: ${{ steps.versions.outputs.nodeVersion }}\n        uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3\n        with:\n          node-version: ${{ steps.versions.outputs.nodeVersion }}\n\n      - name: Set up npm ${{ steps.versions.outputs.npmVersion }}\n        # Skip if no package.json\n        if: ${{ steps.versions.outputs.npmVersion }}\n        run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'\n\n      - name: Get php version\n        id: php-versions\n        uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1\n        with:\n          filename: ${{ env.APP_NAME }}/appinfo/info.xml\n\n      - name: Set up php ${{ steps.php-versions.outputs.php-min }}\n        uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1\n        with:\n          php-version: ${{ steps.php-versions.outputs.php-min }}\n          coverage: none\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Check composer.json\n        id: check_composer\n        uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0\n        with:\n          files: \"${{ env.APP_NAME }}/composer.json\"\n\n      - name: Install composer dependencies\n        if: steps.check_composer.outputs.files_exists == 'true'\n        run: |\n          cd ${{ env.APP_NAME }}\n          composer install --no-dev\n\n      - name: Build ${{ env.APP_NAME }}\n        # Skip if no package.json\n        if: ${{ steps.versions.outputs.nodeVersion }}\n        env:\n          CYPRESS_INSTALL_BINARY: 0\n        run: |\n          cd ${{ env.APP_NAME }}\n          npm ci\n          npm run build --if-present\n\n      - name: Check Krankerl config\n        id: krankerl\n        uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0\n        with:\n          files: ${{ env.APP_NAME }}/krankerl.toml\n\n      - name: Install Krankerl\n        if: steps.krankerl.outputs.files_exists == 'true'\n        run: |\n          wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb\n          sudo dpkg -i krankerl_0.14.0_amd64.deb\n\n      - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl\n        if: steps.krankerl.outputs.files_exists == 'true'\n        run: |\n          cd ${{ env.APP_NAME }}\n          krankerl package\n\n      - name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with makefile\n        if: steps.krankerl.outputs.files_exists != 'true'\n        run: |\n          cd ${{ env.APP_NAME }}\n          make appstore\n\n      - name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}\n        continue-on-error: true\n        id: server-checkout\n        run: |\n          NCVERSION='${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}'\n          wget --quiet https://download.nextcloud.com/server/releases/latest-$NCVERSION.zip\n          unzip latest-$NCVERSION.zip\n\n      - name: Checkout server master fallback\n        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7\n        if: ${{ steps.server-checkout.outcome != 'success' }}\n        with:\n          submodules: true\n          repository: nextcloud/server\n          path: nextcloud\n\n      - name: Sign app\n        run: |\n          # Extracting release\n          cd ${{ env.APP_NAME }}/build/artifacts\n          tar -xvf ${{ env.APP_NAME }}.tar.gz\n          cd ../../../\n          # Setting up keys\n          echo '${{ secrets.APP_PRIVATE_KEY }}' > ${{ env.APP_NAME }}.key\n          wget --quiet \"https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt\"\n          # Signing\n          php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}\n          # Rebuilding archive\n          cd ${{ env.APP_NAME }}/build/artifacts\n          tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}\n\n      - name: Attach tarball to github release\n        uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # v2\n        id: attach_to_release\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz\n          asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz\n          tag: ${{ github.ref }}\n          overwrite: true\n\n      - name: Upload app to Nextcloud appstore\n        uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1\n        with:\n          app_name: ${{ env.APP_NAME }}\n          appstore_token: ${{ secrets.APPSTORE_TOKEN }}\n          download_url: ${{ steps.attach_to_release.outputs.browser_download_url }}\n          app_private_key: ${{ secrets.APP_PRIVATE_KEY }}\n"
  },
  {
    "path": ".github/workflows/pr-feedback.yml",
    "content": "# This workflow is provided via the organization template repository\n#\n# https://github.com/nextcloud/.github\n# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization\n\n# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors\n# SPDX-FileCopyrightText: 2023 Marcel Klehr <mklehr@gmx.net>\n# SPDX-FileCopyrightText: 2023 Joas Schilling <213943+nickvergessen@users.noreply.github.com>\n# SPDX-FileCopyrightText: 2023 Daniel Kesselberg <mail@danielkesselberg.de>\n# SPDX-FileCopyrightText: 2023 Florian Steffens <florian.steffens@nextcloud.com>\n# SPDX-License-Identifier: MIT\n\nname: 'Ask for feedback on PRs'\non:\n  schedule:\n    - cron: '30 1 * * *'\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  pr-feedback:\n    if: ${{ github.repository_owner == 'nextcloud' }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: The get-github-handles-from-website action\n        uses: marcelklehr/get-github-handles-from-website-action@06b2239db0a48fe1484ba0bfd966a3ab81a08308 # v1.0.1\n        id: scrape\n        with:\n          website: 'https://nextcloud.com/team/'\n\n      - name: Get blocklist\n        id: blocklist\n        run: |\n          blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -)\n          echo \"blocklist=$blocklist\" >> \"$GITHUB_OUTPUT\"\n\n      - uses: marcelklehr/pr-feedback-action@1883b38a033fb16f576875e0cf45f98b857655c4\n        with:\n          feedback-message: |\n            Hello there,\n            Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.\n\n            We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.\n\n            Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6\n\n            Thank you for contributing to Nextcloud and we hope to hear from you soon!\n\n            (If you believe you should not receive this message, you can add yourself to the [blocklist](https://github.com/nextcloud/.github/blob/master/non-community-usernames.txt).)\n          days-before-feedback: 14\n          start-date: '2024-04-30'\n          exempt-authors: '${{ steps.blocklist.outputs.blocklist }},${{ steps.scrape.outputs.users }}'\n          exempt-bots: true\n"
  },
  {
    "path": ".github/workflows/reuse.yml",
    "content": "# This workflow is provided via the organization template repository\n#\n# https://github.com/nextcloud/.github\n# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization\n\n# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org>\n#\n# SPDX-License-Identifier: CC0-1.0\n\nname: REUSE Compliance Check\n\non: [pull_request]\n\npermissions:\n  contents: read\n\njobs:\n  reuse-compliance-check:\n    runs-on: ubuntu-latest-low\n    steps:\n      - name: Checkout\n        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n        with:\n          persist-credentials: false\n\n      - name: REUSE Compliance Check\n        uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5.0.0\n"
  },
  {
    "path": ".gitignore",
    "content": "# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: AGPL-3.0-or-later\n\\.idea/\nbuild/\nvendor/\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: AGPL-3.0-or-later\nimports:\n    - php\n    - javascript\n\nfilter:\n    excluded_paths:\n        - 'appinfo/application.php'\n        - 'appinfo/info.xml'\n        - 'l10n/*'\n        - 'vendor/*'\n        - 'js/vendor/*'\n        - 'templates/*'\n        - 'css/*'\n        - 'img/*'\n        - 'tests/*'\n        - 'build/*'\n        - 'documentation/*'\n\ntools:\n    sensiolabs_security_checker: true\n    php_sim: true\n    php_pdepend: true\n    php_analyzer: true\n\nchecks:\n    php:\n        line_length:\n            max_length: '100'\n        verify_access_scope_valid: true\n        require_scope_for_methods: true\n        no_underscore_prefix_in_methods: true\n        missing_arguments: true\n        method_calls_on_non_object: true\n        deprecated_code_usage: true\n        no_eval: true\n        parameter_doc_comments: true\n        return_doc_comments: true\n        fix_doc_comments: true\n        more_specific_types_in_doc_comments: true\n        code_rating: true\n        duplication: true\n        variable_existence: true\n        useless_calls: true\n        use_statement_alias_conflict: true\n        unused_variables: true\n        unused_properties: true\n        unused_parameters: true\n        unused_methods: true\n        unreachable_code: true\n        sql_injection_vulnerabilities: true\n        security_vulnerabilities: true\n        precedence_mistakes: true\n        precedence_in_conditions: true\n        parameter_non_unique: true\n        no_property_on_interface: true\n        no_non_implemented_abstract_methods: true\n        closure_use_not_conflicting: true\n        closure_use_modifiable: true\n        avoid_useless_overridden_methods: true\n        avoid_conflicting_incrementers: true\n        assignment_of_null_return: true\n        php5_style_constructor: true\n        one_class_per_file: true\n        require_php_tag_first: true\n        uppercase_constants: true\n        require_braces_around_control_structures: true\n        psr2_switch_declaration: true\n        psr2_control_structure_declaration: true\n        properties_in_camelcaps: true\n        parameters_in_camelcaps: true\n        optional_parameters_at_the_end: true\n        no_underscore_prefix_in_properties: true\n        no_space_inside_cast_operator: true\n        no_space_before_semicolon: true\n        no_short_open_tag: true\n        no_goto: true\n        lowercase_php_keywords: true\n        lowercase_basic_constants: true\n        function_in_camel_caps: true\n        classes_in_camel_caps: true\n        avoid_space_indentation: true\n        overriding_private_members: true\n        no_unnecessary_function_call_in_for_loop: true\n        simplify_boolean_return: true\n    javascript:\n        wrap_iife: true\n        no_process_exit: true\n        no_process_env: true\n        no_extra_semi: true\n        no_extra_bind: true\n        no_eval: true\n        no_else_return: true\n        dot_notation: true\n        camelcase: true\n        wrap_regex: true\n        valid_typeof: true\n        no_wrap_func: true\n        no_use_before_define: true\n        no_unreachable: true\n        no_undefined: true\n        no_trailing_spaces: true\n        no_reserved_keys: true\n        no_redeclare: true\n        no_obj_calls: true\n        no_loop_func: true\n        no_lonely_if: true\n        no_lone_blocks: true\n        no_inner_declarations: true\n        no_floating_decimal: true\n        no_extra_boolean_cast: true\n        no_empty: true\n        no_dupe_keys: true\n\ncoding_style:\n    php:\n        indentation:\n            general:\n                use_tabs: true\n                size: 4\n        spaces:\n            other:\n                after_type_cast: false\n        braces:\n            classes_functions:\n                class: end-of-line\n"
  },
  {
    "path": ".tx/config",
    "content": "[main]\nhost     = https://www.transifex.com\nlang_map = ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th\n\n[o:nextcloud:p:nextcloud:r:fulltextsearch]\nfile_filter = translationfiles/<lang>/fulltextsearch.po\nsource_file = translationfiles/templates/fulltextsearch.pot\nsource_lang = en\ntype        = PO\n\n"
  },
  {
    "path": "AUTHORS.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Authors\n\n- Allan Nordhøy <epost@anotheragency.no>\n- Andy Scherzinger <info@andy-scherzinger.de>\n- aronovgj <aronovgj@gmx.net>\n- Christian Ingenhaag <chrisingenhaag@users.noreply.github.com>\n- comradekingu <epost@anotheragency.no>\n- daita <maxence@pontapreta.net>\n- Daniel Hansson <daniel@techandme.se>\n- Ferdinand Thiessen <opensource@fthiessen.de>\n- Jakub Onderka <j.onderka@nukib.cz>\n- Jan Vonde <mail@jan-von.de>\n- Joas Schilling <coding@schilljs.com>\n- John Molakvoæ <skjnldsv@protonmail.com>\n- Julius Knorr <jus@bitgrid.net>\n- Jürgen Kellerer <juergen@k123.eu>\n- Marius Blüm <marius@lineone.io>\n- Maxence Lange <maxence@artificial-owl.com>\n- Morris Jobke <hey@morrisjobke.de>\n- Patrick Robertson <robertson.patrick@gmail.com>\n- rakekniven <2069590+rakekniven@users.noreply.github.com>\n- Robin Appelman <robin@icewind.nl>\n- Robin Windey <ro.windey@gmail.com>\n- SrzStephen <stephen@motts.id.au>\n- Valdnet <47037905+Valdnet@users.noreply.github.com>\n- Vincent Petry <vincent@nextcloud.com>\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Changelog\n\n### 32.0.0\n\n- compat nc32\n- manage 'since' search filter\n- manage 'until' search filter\n- search via occ returned as JSON\n- migration to config lexicon\n- cleaning some code\n- reconnect to database on lost connection\n\n### 31.0.0\n\n- improved index on live index\n- improved code logic on index update\n- reset collection endpoint\n- compat nc31\n\n### 30.0.0\n\n- compat nc30\n\n### 29.0.0\n\n- compat nc29\n\n### 28.0.0\n\n- compat nc28\n\n### 27.0.1\n\n- fix a database migration issue\n\n### 27.0.0\n\n- compat nc27\n\n\n### 26.0.0\n\n- compat nc26\n\n\n### 25.0.0\n\n- compat nc25\n\n\n### 24.0.0\n\n- collections\n\n\n### 23.0.0\n\n- better load of the libs\n\n\n### 20.0.1\n\n- compat nc21\n\n\n### 20.0.0\n\n- database migration\n- upgrade deps\n\n\n### 2.0.0\n\n- compat nc20\n\n\n### 1.4.2\n\n- compat nc19\n- error logs on missing Provider Options\n\n\n### 1.4.1\n\n- adding ./occ fulltextsearch:document:status\n- ability to set the index status to IGNORE\n\n\n### 1.4.0 (nc18)\n\n- compat nc18\n\n\n### 1.3.6\n\n- some compat nc17\n\n\n### 1.3.5\n\n- ignore live index on cron\n- live can be run as a service\n- simple queries\n\n\n### 1.3.4\n\n- some improvement in the index comparison\n\n\n### 1.3.2\n\n- adding a key to the list of indexes for old version of NC\n- no crash on missing provider \n- --no-readline can be passed with empty options\n\n\n### 1.3.1\n\n- issue with some providers.\n\n\n### 1.3.0\n\n- Chunks (NC 16)\n\n### 1.2.3\n\n- fixing issue with tests.\n- adding IndexService->createIndex() (NC 15.0.1)\n\n\n### 1.2.2\n\n- cleaning\n\n\n### 1.2.1\n\n- initiating some vars\n- adding the and: option\n\n\n### 1.2.0 (NC15)\n\n- Compat NC15 + full php7.\n- breaking index on Ctrl-C.\n- non interactive mode available during :index and :live\n\n\n### 1.0.3\n\n- improvement: display process advancement during compareWithCurrentIndex\n\n\n### 1.0.2\n\n- improvement: long process while indexing should not timeout (Force Quit).\n- misc: removing compat with NC12.\n\n\n### 1.0.1\n\nimprovement: some rework on the process wrapper.\n\n\n### 1.0.0\n\nFirst stable release\n\n\n\n### 0.99.1 Release Candidate 2\n\n- bugfix: crashing issue during :live\n- database: documentId is now a string\n- improvement: tags/metatags/subtags\n- improvement: no more chunks, documents are indexed one by one.\n- improvement: the :index command allow a navigation between results.\n\n\n### 0.99.0 Release Candidate\n\nCommand Line Interface:\n\n- The indexing process is now embedded in a new graphical wrapper, including an interactive interface for both the fulltextsearch:index and fulltextsearch:live commands.\n- Errors are now displayed during index/live execution with navigation. Errors can be managed and deleted while indexing.\n- new command: ./occ fulltextsearch:test to test the indexing and search platform.\n- new command: ./occ fulltextsearch:document:provider to get info about a document from a provider.\n- new command: ./occ fulltextsearch:document:platform to get info about a document from the search platform.\n- ./occ fulltextsearch:reset can now be done for a specific provider only.\n- ./occ fulltextsearch:index now accept users, providers, errors, chunk and paused options.\n- fixing some display glitch.\n\n\n### 0.8.2\n\n- debug, testing tools\n- get document\n- multi-host\n\n\n### 0.7.0\n\n- navigation app to search within all content from your providers\n- better navigation\n- content (index and search) can be splited in Parts \n- rework on the exchange between platform and providers\n- bugfixes\n\n\n\n### 0.6.1\n\n- bugfix: removing reset of the index on migration\n- bugfix: do not retrieve access on ignored document\n\n\n\n### 0.6.0\n\n- Nextcloud integration\n- Options panel\n- bugfixes\n \n\n### 0.5.1\n\n- bugfixes\n\n\n\n### 0.5.0\n\n- managing errors from platform\n- issues with JS\n\n\n\n### v0.4.0\n\n- fullnextsearch -> fulltextsearch\n- Pagination\n- settings panel\n\n\n\n### v0.3.2\n\n- UI: remove personal settings\n- DB: fill err field on new indexes\n\n\n\n### v0.3.1\n\n- bugfixes.\n\n\n\n### BETA v0.3.0\n\n- First Beta\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSES/AGPL-3.0-or-later.txt",
    "content": "GNU AFFERO GENERAL PUBLIC LICENSE\nVersion 3, 19 November 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n\nEveryone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.\n\n                            Preamble\n\nThe GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.\n\nThe licenses for most software and other practical works are designed to take away your freedom to share and change the works.  By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.\n\nWhen we speak of free software, we are referring to freedom, not price.  Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.\n\nDevelopers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.\n\nA secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate.  Many developers of free software are heartened and encouraged by the resulting cooperation.  However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.\n\nThe GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community.  It requires the operator of a network server to provide the source code of the modified version running there to the users of that server.  Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.\n\nAn older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals.  This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.\n\nThe precise terms and conditions for copying, distribution and modification follow.\n\n                       TERMS AND CONDITIONS\n\n0. Definitions.\n\n\"This License\" refers to version 3 of the GNU Affero General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this License.  Each licensee is addressed as \"you\".  \"Licensees\" and \"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy.  The resulting work is called a \"modified version\" of the earlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based on the Program.\n\nTo \"propagate\" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy.  Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other parties to make or receive copies.  Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License.  If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.\n\n1. Source Code.\nThe \"source code\" for a work means the preferred form of the work for making modifications to it.  \"Object code\" means any non-source form of a work.\n\nA \"Standard Interface\" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form.  A \"Major Component\", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities.  However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work.  For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.\n\nThe Corresponding Source for a work in source code form is that same work.\n\n2. Basic Permissions.\nAll rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met.  This License explicitly affirms your unlimited permission to run the unmodified Program.  The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work.  This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force.  You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright.  Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under the conditions stated below.  Sublicensing is not allowed; section 10 makes it unnecessary.\n\n3. Protecting Users' Legal Rights From Anti-Circumvention Law.\nNo covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.\n\nWhen you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.\n\n4. Conveying Verbatim Copies.\nYou may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.\n\n5. Conveying Modified Source Versions.\nYou may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7.  This requirement modifies the requirement in section 4 to \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy.  This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged.  This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.\n\nA compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an \"aggregate\" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit.  Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.\n\n6. Conveying Non-Source Forms.\nYou may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source.  This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.\n\n    d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge.  You need not require recipients to copy the Corresponding Source along with the object code.  If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source.  Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling.  In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage.  For a particular product received by a particular user, \"normally used\" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product.  A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source.  The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.\n\nIf you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information.  But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).\n\nThe requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed.  Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.\n\n7. Additional Terms.\n\"Additional permissions\" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law.  If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it.  (Additional permissions may be written to require their own removal in certain cases when you modify the work.)  You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further restrictions\" within the meaning of section 10.  If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term.  If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.\n\n8. Termination.\n\nYou may not propagate or modify a covered work except as expressly provided under this License.  Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).\n\nHowever, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.\n\nTermination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License.  If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.\n\n9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or run a copy of the Program.  Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance.  However, nothing other than this License grants you permission to propagate or modify any covered work.  These actions infringe copyright if you do not accept this License.  Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.\n\n10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License.  You are not responsible for enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations.  If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the rights granted or affirmed under this License.  For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.\n\n11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based.  The work thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version.  For purposes of this definition, \"control\" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement).  To \"grant\" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License.  You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.\n\n12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License.  If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.\n\n13. Remote Network Interaction; Use with the GNU General Public License.\n\nNotwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.  This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.\n\nNotwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work.  The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.\n\n14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time.  Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program specifies that a certain numbered version of the GNU Affero General Public License \"or any later version\" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation.  If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.\n\nIf the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.\n\nLater license versions may give you additional or different permissions.  However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.\n\n15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\n17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program.  It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the \"copyright\" line and a pointer to where the full notice is found.\n\n     <one line to give the program's name and a brief idea of what it does.>\n     Copyright (C) <year>  <name of author>\n\n     This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\n     This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.\n\n     You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source.  For example, if your program is a web application, its interface could display a \"Source\" link that leads users to an archive of the code.  There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.\n\nYou should also get your employer (if you work as a programmer) or school, if any, to sign a \"copyright disclaimer\" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "LICENSES/Apache-2.0.txt",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:\n\n     (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and\n\n     (b) You must cause any modified files to carry prominent notices stating that You changed the files; and\n\n     (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and\n\n     (d) If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.\n\n     You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets \"[]\" replaced with your own identifying information. (Don't include the brackets!)  The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same \"printed page\" as the copyright notice for easier identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "LICENSES/CC0-1.0.txt",
    "content": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n    INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n    HEREUNDER.\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclusive Copyright and Related Rights (defined below) upon the creator\nand subsequent owner(s) (each and all, an \"owner\") of an original work of\nauthorship and/or a database (each, a \"Work\").\n\nCertain owners wish to permanently relinquish those rights to a Work for\nthe purpose of contributing to a commons of creative, cultural and\nscientific works (\"Commons\") that the public can reliably and without fear\nof later claims of infringement build upon, modify, incorporate in other\nworks, reuse and redistribute as freely as possible in any form whatsoever\nand for any purposes, including without limitation commercial purposes.\nThese owners may contribute to the Commons to promote the ideal of a free\nculture and the further production of creative, cultural and scientific\nworks, or to gain reputation or greater distribution for their Work in\npart through the use and efforts of others.\n\nFor these and/or other purposes and motivations, and without any\nexpectation of additional consideration or compensation, the person\nassociating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\nis an owner of Copyright and Related Rights in the Work, voluntarily\nelects to apply CC0 to the Work and publicly distribute the Work under its\nterms, with knowledge of his or her Copyright and Related Rights in the\nWork and the meaning and intended legal effect of CC0 on those rights.\n\n1. Copyright and Related Rights. A Work made available under CC0 may be\nprotected by copyright and related or neighboring rights (\"Copyright and\nRelated Rights\"). Copyright and Related Rights include, but are not\nlimited to, the following:\n\n  i. the right to reproduce, adapt, distribute, perform, display,\n     communicate, and translate a Work;\n ii. moral rights retained by the original author(s) and/or performer(s);\niii. publicity and privacy rights pertaining to a person's image or\n     likeness depicted in a Work;\n iv. rights protecting against unfair competition in regards to a Work,\n     subject to the limitations in paragraph 4(a), below;\n  v. rights protecting the extraction, dissemination, use and reuse of data\n     in a Work;\n vi. database rights (such as those arising under Directive 96/9/EC of the\n     European Parliament and of the Council of 11 March 1996 on the legal\n     protection of databases, and under any national implementation\n     thereof, including any amended or successor version of such\n     directive); and\nvii. other similar, equivalent or corresponding rights throughout the\n     world based on applicable law or treaty, and any national\n     implementations thereof.\n\n2. Waiver. To the greatest extent permitted by, but not in contravention\nof, applicable law, Affirmer hereby overtly, fully, permanently,\nirrevocably and unconditionally waives, abandons, and surrenders all of\nAffirmer's Copyright and Related Rights and associated claims and causes\nof action, whether now known or unknown (including existing as well as\nfuture claims and causes of action), in the Work (i) in all territories\nworldwide, (ii) for the maximum duration provided by applicable law or\ntreaty (including future time extensions), (iii) in any current or future\nmedium and for any number of copies, and (iv) for any purpose whatsoever,\nincluding without limitation commercial, advertising or promotional\npurposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\nmember of the public at large and to the detriment of Affirmer's heirs and\nsuccessors, fully intending that such Waiver shall not be subject to\nrevocation, rescission, cancellation, termination, or any other legal or\nequitable action to disrupt the quiet enjoyment of the Work by the public\nas contemplated by Affirmer's express Statement of Purpose.\n\n3. Public License Fallback. Should any part of the Waiver for any reason\nbe judged legally invalid or ineffective under applicable law, then the\nWaiver shall be preserved to the maximum extent permitted taking into\naccount Affirmer's express Statement of Purpose. In addition, to the\nextent the Waiver is so judged Affirmer hereby grants to each affected\nperson a royalty-free, non transferable, non sublicensable, non exclusive,\nirrevocable and unconditional license to exercise Affirmer's Copyright and\nRelated Rights in the Work (i) in all territories worldwide, (ii) for the\nmaximum duration provided by applicable law or treaty (including future\ntime extensions), (iii) in any current or future medium and for any number\nof copies, and (iv) for any purpose whatsoever, including without\nlimitation commercial, advertising or promotional purposes (the\n\"License\"). The License shall be deemed effective as of the date CC0 was\napplied by Affirmer to the Work. Should any part of the License for any\nreason be judged legally invalid or ineffective under applicable law, such\npartial invalidity or ineffectiveness shall not invalidate the remainder\nof the License, and in such case Affirmer hereby affirms that he or she\nwill not (i) exercise any of his or her remaining Copyright and Related\nRights in the Work or (ii) assert any associated claims and causes of\naction with respect to the Work, in either case contrary to Affirmer's\nexpress Statement of Purpose.\n\n4. Limitations and Disclaimers.\n\n a. No trademark or patent rights held by Affirmer are waived, abandoned,\n    surrendered, licensed or otherwise affected by this document.\n b. Affirmer offers the Work as-is and makes no representations or\n    warranties of any kind concerning the Work, express, implied,\n    statutory or otherwise, including without limitation warranties of\n    title, merchantability, fitness for a particular purpose, non\n    infringement, or the absence of latent or other defects, accuracy, or\n    the present or absence of errors, whether or not discoverable, all to\n    the greatest extent permissible under applicable law.\n c. Affirmer disclaims responsibility for clearing rights of other persons\n    that may apply to the Work or any use thereof, including without\n    limitation any person's Copyright and Related Rights in the Work.\n    Further, Affirmer disclaims responsibility for obtaining any necessary\n    consents, permissions or other rights required for any use of the\n    Work.\n d. Affirmer understands and acknowledges that Creative Commons is not a\n    party to this document and has no duty or obligation with respect to\n    this CC0 or use of the Work.\n"
  },
  {
    "path": "LICENSES/MIT.txt",
    "content": "MIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: AGPL-3.0-or-later\napp_name=FullTextSearch\n\nbuild_dir=$(CURDIR)/build/artifacts\nappstore_dir=$(build_dir)/appstore\nsource_dir=$(build_dir)/source\nsign_dir=$(build_dir)/sign\npackage_name=$(shell echo $(app_name) | tr '[:upper:]' '[:lower:]')\ncert_dir=$(HOME)/.nextcloud/certificates\ngithub_account=nextcloud\nrelease_account=nextcloud-releases\nbranch=stable29\nversion=29.0.0\nsince_tag=\n\nall: appstore\n\nrelease: appstore github-release github-upload\n\ngithub-release:\n\tif [ -z \"$(release_account)\" ]; then \\\n\t\trelease_account=$(github_account); \\\n\t\trelease_branch=$(branch); \\\n\telse \\\n\t\trelease_account=$(release_account); \\\n\t\trelease_branch=master; \\\n\tfi; \\\n\tif [ -z \"$(since_tag)\" ]; then \\\n\t\tlatest_tag=$$(git describe --tags `git rev-list --tags --max-count=1`); \\\n\telse \\\n\t\tlatest_tag=$(since_tag); \\\n\tfi; \\\n\tcomparison=\"$$latest_tag..HEAD\"; \\\n\tif [ -z \"$$latest_tag\" ]; then comparison=\"\"; fi; \\\n\tchangelog=$$(git log $$comparison --oneline --no-merges | sed -e 's/^/$(github_account)\\/$(package_name)@/'); \\\n\tgithub-release release \\\n\t\t--user $$release_account \\\n\t\t--repo $(package_name) \\\n\t\t--target $$release_branch \\\n\t\t--tag $(version) \\\n\t\t--description \"**Changelog**<br/>$$changelog\" \\\n\t\t--name \"$(app_name) v$(version)\"; \\\n\tif [ $(github_account) != $$release_account ]; then \\\n\t        link=\"https://github.com/$$release_account/$(package_name)/releases/download/$(version)/$(package_name)-$(version).tar.gz\";\\\n\t\tgithub-release release \\\n\t\t\t--user $(github_account) \\\n\t\t\t--repo $(package_name) \\\n\t\t\t--target $(branch) \\\n\t\t\t--tag $(version) \\\n\t\t\t--description \"**Download**<br />$$link<br /><br />**Changelog**<br/>$$changelog<br />\" \\\n\t\t\t--name \"$(app_name) v$(version)\"; \\\n\tfi; \\\n\n\ngithub-upload:\n\tif [ -z \"$(release_account)\" ]; then \\\n\t\trelease_account=$(github_account); \\\n\telse \\\n\t\trelease_account=$(release_account); \\\n\tfi; \\\n\tgithub-release upload \\\n\t\t--user $$release_account \\\n\t\t--repo $(package_name) \\\n\t\t--tag $(version) \\\n\t\t--name \"$(package_name)-$(version).tar.gz\" \\\n\t\t--file $(build_dir)/$(package_name).tar.gz\n\n\nclean:\n\trm -rf $(build_dir)\n\trm -rf node_modules\n\n# composer packages\ncomposer:\n\tcomposer install --prefer-dist\n\tcomposer upgrade --prefer-dist\n\nappstore: clean composer\n\tmkdir -p $(sign_dir)\n\trsync -a \\\n\t--exclude=/build \\\n\t--exclude=/docs \\\n\t--exclude=/translationfiles \\\n\t--exclude=/.tx \\\n\t--exclude=/tests \\\n\t--exclude=.git \\\n\t--exclude=/.github \\\n\t--exclude=/l10n/l10n.pl \\\n\t--exclude=/CONTRIBUTING.md \\\n\t--exclude=/issue_template.md \\\n\t--exclude=/README.md \\\n\t--exclude=/composer.json \\\n\t--exclude=/testConfiguration.json \\\n\t--exclude=/composer.lock \\\n\t--exclude=/.gitattributes \\\n\t--exclude=/.gitignore \\\n\t--exclude=/.scrutinizer.yml \\\n\t--exclude=/.travis.yml \\\n\t--exclude=/Makefile \\\n\t./ $(sign_dir)/$(package_name)\n\ttar -czf $(build_dir)/$(package_name).tar.gz \\\n\t\t-C $(sign_dir) $(package_name)\n\t@if [ -f $(cert_dir)/$(package_name).key ]; then \\\n\t\techo \"Signing package…\"; \\\n\t\topenssl dgst -sha512 -sign $(cert_dir)/$(package_name).key $(build_dir)/$(package_name).tar.gz | openssl base64; \\\n\tfi\n"
  },
  {
    "path": "README.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Full text search\n\n[![REUSE status](https://api.reuse.software/badge/github.com/nextcloud/fulltextsearch)](https://api.reuse.software/info/github.com/nextcloud/fulltextsearch)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/nextcloud/fulltextsearch/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/nextcloud/fulltextsearch/?b=master)\n\n_Full text search_ is the core app of a full-text search framework for your Nextcloud. \nTo have it operate, and get content indexed, some other apps are needed: \n\n- Some **Providers Apps** to extract content from your Nextcloud. \n- A **Platform App** that communicate with a search platform _(ie. Elastic Search, Solr, …)_ in order to index the content provided by the **Providers**.   \n_Note: There is no limit to the number of platform-apps that can be installed, however only one can be selected from the admin interface_\n\n\n\n### Documentation\n\n[Can be found on the wiki](https://github.com/nextcloud/fulltextsearch/wiki)\n"
  },
  {
    "path": "REUSE.toml",
    "content": "# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: AGPL-3.0-or-later\nversion = 1\nSPDX-PackageName = \"fulltextsearch\"\nSPDX-PackageSupplier = \"Nextcloud <info@nextcloud.com>\"\nSPDX-PackageDownloadLocation = \"https://github.com/nextcloud/fulltextsearch\"\n\n[[annotations]]\npath = [\"l10n/**.js\", \"l10n/**.json\"]\nprecedence = \"aggregate\"\nSPDX-FileCopyrightText = \"2016 Nextcloud translators\"\nSPDX-License-Identifier = \"AGPL-3.0-or-later\"\n\n[[annotations]]\npath = [\"composer.json\", \"composer.lock\"]\nprecedence = \"aggregate\"\nSPDX-FileCopyrightText = \"2016 Nextcloud GmbH and Nextcloud contributors\"\nSPDX-License-Identifier = \"AGPL-3.0-or-later\"\n\n[[annotations]]\npath = [\"screenshots/0.3.0.png\", \"screenshots/configuration.png\"]\nprecedence = \"aggregate\"\nSPDX-FileCopyrightText = \"2017 Nextcloud GmbH and Nextcloud contributors\"\nSPDX-License-Identifier = \"AGPL-3.0-or-later\"\n\n[[annotations]]\npath = [\".tx/config\", \"screenshots/draw.io/Index.xml\", \"screenshots/draw.io/Search.xml\", \"screenshots/OccFulltextsearchIndex.png\", \"screenshots/OccFulltextsearchTest.png\", \"screenshots/SchemaIndexCommand.jpg\", \"screenshots/SchemaIndexLive.jpg\", \"screenshots/SchemaSearch.jpg\"]\nprecedence = \"aggregate\"\nSPDX-FileCopyrightText = \"2018 Nextcloud GmbH and Nextcloud contributors\"\nSPDX-License-Identifier = \"AGPL-3.0-or-later\"\n\n[[annotations]]\npath = [\"img/fulltextsearch.svg\", \"img/fulltextsearch_black.svg\", \"img/options.svg\", \"img/options_black.svg\", \"img/page-next.svg\", \"img/page-prev.svg\"]\nprecedence = \"aggregate\"\nSPDX-FileCopyrightText = \"2018-2024 Google LLC\"\nSPDX-License-Identifier = \"Apache-2.0\"\n"
  },
  {
    "path": "appinfo/info.xml",
    "content": "<?xml version=\"1.0\"?>\n<!--\n  - SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n<info xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t  xsi:noNamespaceSchemaLocation=\"https://apps.nextcloud.com/schema/apps/info.xsd\">\n\t<id>fulltextsearch</id>\n\t<name>Full text search</name>\n\t<summary>Core of the full-text search framework for Nextcloud</summary>\n\t<description><![CDATA[\n\nCore App of the full-text search framework for your Nextcloud.\n\n]]>\n\t</description>\n\t<version>32.0.0</version>\n\t<licence>agpl</licence>\n\t<author>Maxence Lange</author>\n\t<namespace>FullTextSearch</namespace>\n\t<documentation>\n\t\t<admin>https://github.com/nextcloud/fulltextsearch/wiki</admin>\n\t</documentation>\n\t<category>search</category>\n\t<website>https://github.com/nextcloud/fulltextsearch</website>\n\t<bugs>https://github.com/nextcloud/fulltextsearch/issues</bugs>\n\t<repository>https://github.com/nextcloud/fulltextsearch.git</repository>\n\t<screenshot>https://raw.githubusercontent.com/nextcloud/fulltextsearch/master/screenshots/0.3.0.png</screenshot>\n\t<dependencies>\n\t\t<nextcloud min-version=\"32\" max-version=\"33\"/>\n\t</dependencies>\n\n\t<background-jobs>\n\t\t<job>OCA\\FullTextSearch\\Cron\\Index</job>\n\t</background-jobs>\n\n\t<commands>\n\t\t<command>OCA\\FullTextSearch\\Command\\Check</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\CollectionInit</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\CollectionDelete</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\CollectionLink</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\CollectionList</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Configure</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\DocumentIndex</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\DocumentPlatform</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\DocumentProvider</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\DocumentStatus</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Index</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Live</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Reset</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Search</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Stop</command>\n\t\t<command>OCA\\FullTextSearch\\Command\\Test</command>\n\t</commands>\n\n\t<settings>\n\t\t<admin>OCA\\FullTextSearch\\Settings\\Admin</admin>\n\t\t<admin-section>OCA\\FullTextSearch\\Settings\\AdminSection</admin-section>\n\t</settings>\n\n</info>\n"
  },
  {
    "path": "appinfo/routes.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nreturn [\n\t'ocs' => [\n\t\t/** @see OCA\\FullTextSearch\\Controller\\CollectionController */\n\t\t['name' => 'Collection#resetCollection', 'url' => '/collection/{collection}/index', 'verb' => 'DELETE'],\n\t\t['name' => 'Collection#getQueue', 'url' => '/collection/{collection}/index', 'verb' => 'GET'],\n\t\t[\n\t\t\t'name' => 'Collection#indexDocument',\n\t\t\t'url' => '/collection/{collection}/document/{providerId}/{documentId}',\n\t\t\t'verb' => 'GET'\n\t\t],\n\t\t[\n\t\t\t'name' => 'Collection#updateStatusDone',\n\t\t\t'url' => '/collection/{collection}/document/{providerId}/{documentId}/done',\n\t\t\t'verb' => 'POST'\n\t\t]\n\t],\n\n\t'routes' => [\n\t\t['name' => 'Navigation#navigate', 'url' => '/', 'verb' => 'GET'],\n\t\t['name' => 'Settings#getSettingsAdmin', 'url' => '/admin/settings', 'verb' => 'GET'],\n\t\t['name' => 'Settings#setSettingsAdmin', 'url' => '/admin/settings', 'verb' => 'POST'],\n\t\t['name' => 'Api#search', 'url' => '/v1/search', 'verb' => 'GET'],\n\t\t['name' => 'Api#searchFromRemote', 'url' => '/v1/remote', 'verb' => 'GET'],\n\t\t['name' => 'Templates#getOptionsPanel', 'url' => '/options/{providerId}/', 'verb' => 'GET'],\n\t\t['name' => 'Templates#getNavigationPanels', 'url' => '/navigation/panels', 'verb' => 'GET']\n\t]\n];\n\n\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"nextcloud/fulltextsearch\",\n  \"description\": \"Full text search framework for Nextcloud\",\n  \"minimum-stability\": \"stable\",\n  \"license\": \"agpl\",\n  \"config\": {\n    \"optimize-autoloader\": true,\n    \"classmap-authoritative\": true,\n    \"autoloader-suffix\": \"FullTextSearch\"\n  },\n  \"authors\": [\n    {\n      \"name\": \"Maxence Lange\",\n      \"email\": \"maxence@artificial-owl.com\"\n    }\n  ],\n  \"autoload\": {\n    \"psr-4\": {\n      \"OCA\\\\FullTextSearch\\\\\": \"lib/\"\n    }\n  }\n}\n"
  },
  {
    "path": "css/admin.css",
    "content": "/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n \n.fnshidden {\n\tdisplay: none;\n}\n\nEM {\n\tfont-size: 12px;\n}\n\n.div-table {\n\tdisplay: table;\n\twidth: auto;\n\tborder-spacing: 5px;\n}\n\n.div-table-row {\n\tdisplay: table-row;\n\twidth: auto;\n\theight: 65px;\n\tclear: both;\n}\n\n.div-table-col {\n\tfloat: left;\n\tdisplay: table-column;\n\twidth: 300px;\n}\n\n.div-table-col-left {\n\twidth: 350px;\n\tmargin-right: 35px;\n\ttext-align: right;\n}\n\n.hsub {\n\tmargin-left: 20px;\n}\n\nSELECT, INPUT {\n\twidth: 350px;\n}\n\nINPUT.small {\n\twidth: 150px;\n}"
  },
  {
    "path": "css/fulltextsearch.css",
    "content": "/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n#fts-icon {\n\tbackground-size: 16px 16px;\n\tpadding: 14px;\n\tcursor: pointer;\n}\n\ndiv.icon-fulltextsearch-white {\n\tbackground-image: url('../img/fulltextsearch.svg');\n}\n\ndiv.icon-fulltextsearch {\n\tbackground-image: url('../img/fulltextsearch_black.svg');\n}\n\n#fts-error {\n\tcolor: #f00;\n\ttext-align: center;\n\tmargin: 20px;\n}\n\n#fts-popup {\n\twidth: 340px;\n\tright: 42px;\n\tmax-width: 85%;\n\tmax-height: 500px !important;\n}\n\n#fts-popup::after {\n\tright: 78px;\n}\n\n#fts-header {\n\twidth: 100%;\n}\n\n#fts-header div {\n\tleft: 0;\n\tright: 0;\n\ttop: 0;\n\tmargin: 7px;\n}\n\n#fts-input {\n\twidth: 100%;\n}\n\ninput.options_small {\n\twidth: 60px;\n}\n\n.fts_options_input {\n\tmargin-right: 10px !important;\n\topacity: 0.65;\n}\n\n.fts_options_input_small {\n\twidth: 70px;\n}\n\n/**\n\n */\n\n.provider_result {\n}\n\n.provider_navigation_right {\n\tposition: absolute;\n\tright: 0;\n\ttop: 0;\n\theight: 15px;\n\tmargin: auto 25px auto 0;\n\tbottom: 0;\n}\n\n.provider_navigation_close {\n\tmargin: 3px 20px 3px 3px;\n\topacity: 0.6;\n}\n\n.provider_navigation_prev, .provider_navigation_curr, .provider_navigation_next, .provider_navigation_close {\n\tfloat: left;\n}\n\n.provider_navigation_curr {\n\tfont-weight: normal;\n\tfont-size: 15px;\n\tletter-spacing: 6px;\n\tmargin: -3px 0 0 11px;\n}\n\n.provider_navigation_prev, .provider_navigation_next, .provider_navigation_close, .provider_navigation_page {\n\tcursor: pointer;\n}\n\n.provider_navigation_page {\n\tfont-size: 11px;\n\twhite-space: nowrap;\n\tmargin: -3px 16px -3px 16px;\n}\n\n.result_entry_default {\n\theight: 55px;\n\twidth: 100%;\n\tborder-bottom: solid 1px #d1d1d1;\n}\n\n.result_entry_left {\n\tfloat: left;\n\tposition: relative;\n\ttop: 45%;\n\tmargin-left: 16px;\n\ttransform: perspective(1px) translateY(-50%);\n}\n\n.result_entry_right {\n\tfloat: right;\n\tmargin-right: 10px;\n\tposition: relative;\n\ttop: 50%;\n\ttransform: perspective(1px) translateY(-50%);\n}\n\n.result_entry_default #title {\n\tfont-weight: bold;\n\theight: 14px;\n}\n\n.result_entry_default #line1, .result_entry_default #line2 {\n\tcolor: #717171;\n\tfont-size: 11px;\n\theight: 12px;\n}\n\n.result_entry_default #score {\n\t/*font-weight: bold;*/\n}\n\n/*.result_entry_default #info {*/\n/*font-weight: bold;*/\n/*}*/\n\n.next_search_div {\n\tposition: relative;\n\twidth: 286px;\n\theight: 36px;\n\tmargin-right: 20px;\n}\n\n.provider_navigation {\n\tposition: relative;\n\tfont-size: 13px;\n\tcolor: #898989;\n\tpadding-top: 13px;\n\tpadding-left: 20px;\n\tpadding-bottom: 13px;\n\tborder-bottom: solid 1px #eaeaea;\n}\n\n.icon-page-prev {\n\tbackground-image: url('../img/page-prev.svg');\n\tbackground-position: left;\n\tpadding-left: 18px;\n}\n\n.icon-page-next {\n\tbackground-image: url('../img/page-next.svg');\n\tbackground-position: right;\n\tpadding-right: 18px;\n}\n\n.icon-page-next, .icon-page-prev {\n\tbackground-repeat: no-repeat;\n\theight: 16px;\n\tfont-size: 11px;\n}\n\n.result_entry {\n\theight: 50px;\n\tposition: relative;\n\tborder-bottom: solid 1px #efefef;\n}\n\n.result_title {\n\tcolor: #000;\n}\n\n.result_extract {\n\tfont-size: 11px;\n\tcolor: #999;\n\tmargin-top: -5px;\n\tmargin-bottom: -5px;\n\tmargin-left: 5px;\n}\n\n.result_div_checkbox {\n\tleft: 0px;\n\twidth: 50px;\n}\n\n.result_div {\n\tposition: absolute;\n\ttop: 50%;\n\ttransform: translateY(-50%);\n}\n\n.result_div_checkbox {\n\tleft: 0px;\n\twidth: 50px;\n}\n\n.result_div_content {\n\tleft: 50px;\n}\n\n.result_div_right {\n\tright: 20px;\n\tcolor: #9a9a9a;\n}\n\n.icon-more-fulltextsearch {\n\tposition: absolute;\n\tright: 40px;\n\ttop: 10px;\n\tcursor: pointer;\n}\n\n#next_search_input {\n\tbackground: #fffc;\n\twidth: 212px;\n\theight: 30px;\n\tmargin: 2px !important;\n}\n\n.div-table {\n\tdisplay: table;\n\twidth: auto;\n\tborder-spacing: 5px;\n}\n\n.div-table-row {\n\tdisplay: table-row;\n\twidth: auto;\n\tclear: both;\n}\n\n.div-table-col {\n\tfloat: left;\n\tdisplay: table-column;\n}\n\n.div-table-col-left {\n\twidth: 170px;\n\tmargin-top: 8px;\n\tcolor: #707070;\n\tmargin-right: 15px;\n\ttext-align: right;\n}\n"
  },
  {
    "path": "css/navigate.css",
    "content": "/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n \n#search_header {\n\twidth: 100%;\n\tposition: absolute;\n}\n\n#search_header div {\n\tleft: 0;\n\tright: 0;\n\ttop: 0;\n\tmargin: 5px;\n}\n\n#search_input {\n\twidth: 100%;\n}\n\n#search_error {\n\ttext-align: center;\n\tcolor: #f00;\n\tmargin: 20px;\n}\n\n.search_checkbox {\n\tmargin-right: 10px !important;\n\topacity: 0.8;\n}\n\n.search_checkbox_sub {\n\tmargin-right: 10px !important;\n\topacity: 0.65;\n}\n\n.search_input_sub {\n\tmargin-right: 10px !important;\n\topacity: 0.65;\n}\n\n.search_tags_sub {\n\tmargin-right: 10px !important;\n\twidth: 200px;\n\topacity: 0.65;\n}\n\n.search_input_sub_small {\n\twidth: 70px;\n}\n\n.search_icon {\n\tbackground-image: url('/apps/fulltextsearch/img/fulltextsearch_black.svg');\n}\n\nA.icon-fts {\n\ttext-indent: 25px;\n}\n\na.ulsub {\n\tpadding: 0px 12px 0 12px !important;\n}\n\n/**\n\n*/\n#search_result {\n}\n\n.provider_header {\n\tmargin-bottom: 35px;\n}\n\n.provider_result {\n}\n\n#noresult {\n\tfont-style: italic;\n\tposition: absolute;\n\tleft: 100px;\n}\n\n"
  },
  {
    "path": "docs/api/collections.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Collections\n\n"
  },
  {
    "path": "docs/commands.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Commands\n\n"
  },
  {
    "path": "docs/configuration.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Configuration\n\n"
  },
  {
    "path": "docs/index.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Nextcloud FullTextSearch\n\n"
  },
  {
    "path": "docs/installation.md",
    "content": "<!--\n  - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n  - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n# Installation\n\n"
  },
  {
    "path": "js/admin.elements.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: fts_admin_settings */\n\n\n\nvar fts_admin_elements = {\n\tfts_div: null,\n\tfts_navigation: null,\n\tfts_platforms: null,\n\tfts_providers: null,\n\n\tinit: function () {\n\t\tfts_admin_elements.fts_div = $('#fns');\n\t\tfts_admin_elements.fts_navigation = $('#fts_navigation');\n\t\tfts_admin_elements.fts_platforms = $('#fts_platforms');\n\n\t\tfts_admin_elements.fts_navigation.on('change', fts_admin_settings.saveSettings);\n\t\tfts_admin_elements.fts_platforms.on('change', function () {\n\t\t\tfts_admin_settings.tagSettingsAsNotSaved($(this));\n\t\t\tfts_admin_settings.saveSettings();\n\t\t});\n\t}\n};\n\n\n"
  },
  {
    "path": "js/admin.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: fts_admin_settings */\n/** global: fts_admin_elements */\n\n\n\n$(document).ready(function () {\n\n\n\t/**\n\t * @constructs FullTextSearchAdmin\n\t */\n\tvar FullTextSearchAdmin = function () {\n\t\t$.extend(FullTextSearchAdmin.prototype, fts_admin_elements);\n\t\t$.extend(FullTextSearchAdmin.prototype, fts_admin_settings);\n\n\t\tfts_admin_elements.init();\n\t\tfts_admin_settings.refreshSettingPage();\n\t};\n\n\tOCA.FullTextSearchAdmin = FullTextSearchAdmin;\n\tOCA.FullTextSearchAdmin.settings = new FullTextSearchAdmin();\n\n});\n"
  },
  {
    "path": "js/admin.settings.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OC */\n/** global: fts_admin_elements */\n\n/** @namespace result.platforms_all */\n/** @namespace result.search_platform */\n/** @namespace result.providers_all */\n\n\n\n\nvar fts_admin_settings = {\n\n\tconfig: null,\n\n\trefreshSettingPage: function () {\n\n\t\t$.ajax({\n\t\t\tmethod: 'GET',\n\t\t\turl: OC.generateUrl('/apps/fulltextsearch/admin/settings')\n\t\t}).done(function (res) {\n\t\t\tfts_admin_settings.updateSettingPage(res);\n\t\t});\n\n\t},\n\n\n\tupdateSettingPage: function (result) {\n\n\t\tfts_admin_elements.fts_navigation.prop('checked', (result.app_navigation === true));\n\n\t\tfts_admin_settings.updateSettingPagePlatforms(result);\n\n\t\tfts_admin_settings.updateCurrentPlatform(result);\n\t\tfts_admin_settings.updateEnabledProviders(result);\n\n\t\tfts_admin_settings.tagSettingsAsSaved(fts_admin_elements.fts_div);\n\t},\n\n\n\tupdateSettingPagePlatforms: function (result) {\n\t\tfts_admin_elements.fts_platforms.empty();\n\t\tfts_admin_elements.fts_platforms.append($('<option>', {\n\t\t\tvalue: '',\n\t\t\ttext: ''\n\t\t}));\n\n\t\tvar platforms = result.platforms_all;\n\t\tvar classes = Object.keys(platforms);\n\t\tfor (var i = 0; i < classes.length; i++) {\n\t\t\tvar platformClass = classes[i];\n\t\t\tfts_admin_elements.fts_platforms.append($('<option>', {\n\t\t\t\tvalue: platformClass,\n\t\t\t\tselected: (result.search_platform === platformClass),\n\t\t\t\ttext: platforms[platformClass].name\n\t\t\t}));\n\t\t\t$('#' + platforms[platformClass].id).fadeTo(300, 0, function () {\n\t\t\t\t$(this).hide();\n\t\t\t});\n\t\t}\n\n\t\tfts_admin_elements.fts_platforms.fadeTo(300, 1);\n\t},\n\n\n\tupdateCurrentPlatform: function (result) {\n\t\tif (result.search_platform.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\t$('#' + result.platforms_all[result.search_platform].id).stop().show().fadeTo(300, 1);\n\t},\n\n\n\tupdateEnabledProviders: function (result) {\n\n\t\tvar providers = result.providers_all;\n\t\tvar providerIds = Object.keys(providers);\n\t\tfor (var i = 0; i < providerIds.length; i++) {\n\t\t\t$('#' + providerIds[i]).stop().fadeTo(300, 0);\n\t\t}\n\n\t\t$('.subprovider').stop().fadeTo(300, 0);\n\n\t\t// we only check that a search_platform is valid. we don't manage a list of enabled provider as\n\t\t// of right now\n\t\tif (result.search_platform.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (i = 0; i < providerIds.length; i++) {\n\t\t\t$('#' + providerIds[i]).stop().fadeTo(300, 1);\n\t\t}\n\t},\n\n\n\tupdateEnabledSubProviders: function () {\n\t\t$('body').find('.subprovider').each(function () {\n\t\t\tvar top = $(this).attr('id').split('-', 2);\n\n\t\t\tif (top.length < 2) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar topOption = top[0];\n\t\t\tif ($('#' + topOption).is(':checked')) {\n\t\t\t\t$(this).stop().fadeTo(300, 1).slideDown();\n\t\t\t} else {\n\t\t\t\t$(this).stop().fadeTo(300, 0).slideUp();\n\t\t\t}\n\t\t});\n\t},\n\n\n\ttagSettingsAsNotSaved: function (div) {\n\t\tdiv.animate({\n\t\t\t'backgroundColor': 'rgba(255, 180, 0, 0.18)'\n\t\t}, 300);\n\t},\n\n\n\ttagSettingsAsSaved: function (div) {\n\t\tdiv.find('INPUT').animate({'backgroundColor': 'rgba(255, 255, 255, 0.18)'}, 300);\n\t\tdiv.find('SELECT').animate({'backgroundColor': '#fff'}, 300);\n\n\t\tfts_admin_settings.updateEnabledSubProviders();\n\t},\n\n\n\tsaveSettings: function () {\n\n\t\tvar data = {\n\t\t\tapp_navigation: (fts_admin_elements.fts_navigation.is(':checked')) ? 1 : 0,\n\t\t\tsearch_platform: fts_admin_elements.fts_platforms.val()\n\t\t};\n\n\t\t$.ajax({\n\t\t\tmethod: 'POST',\n\t\t\turl: OC.generateUrl('/apps/fulltextsearch/admin/settings'),\n\t\t\tdata: {\n\t\t\t\tdata: data\n\t\t\t}\n\t\t}).done(function (res) {\n\t\t\tfts_admin_settings.updateSettingPage(res);\n\t\t});\n\n\t}\n\n\n};\n"
  },
  {
    "path": "js/fulltextsearch.v1.api.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n\n/** global: OC */\n/** global: settings */\n/** global: result */\n/** global: search */\n/** global: nav */\n\n\nvar api = {\n\n\n\tsearch: function (request, callback, callbackError) {\n\t\tvar res = {\n\t\t\tstatus: -1,\n\t\t\terror: 'Failed to reach server. Try reloading the page'\n\t\t};\n\n\t\tnav.onSearchRequest(request);\n\n\t\t$.ajax({\n\t\t\tmethod: 'GET',\n\t\t\turl: OC.generateUrl('/apps/fulltextsearch/v1/search'),\n\t\t\tdata: {\n\t\t\t\trequest: JSON.stringify(request)\n\t\t\t}\n\t\t}).done(function (res) {\n\t\t\tif (res.status === -1) {\n\t\t\t\tresult.displayError(res);\n\t\t\t\tnav.onError(res.message);\n\t\t\t\tapi.onCallback(callbackError, res);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresult.displayResult(res);\n\t\t\tnav.onResultDisplayed(res);\n\t\t\tapi.onCallback(callback, res);\n\t\t}).fail(function () {\n\t\t\tif (!api.onCallback(callbackError, res)) {\n\t\t\t\tnav.onError(res.message);\n\t\t\t}\n\t\t});\n\t},\n\n\n\tretrieveOptions: function (providerId, callback) {\n\t\tvar res = {status: -1};\n\n\t\t$.ajax({\n\t\t\tmethod: 'GET',\n\t\t\turl: OC.generateUrl('/apps/fulltextsearch/options/' + providerId)\n\t\t}).done(function (res) {\n\t\t\tsearchbox.onOptionsLoaded(res);\n\t\t\tapi.onCallback(callback, res);\n\t\t}).fail(function () {\n\t\t\tnav.failedToAjax();\n\t\t\tapi.onCallback(callback, res);\n\t\t});\n\t},\n\n\n\tonCallback: function (callback, result) {\n\t\tif (callback && (typeof callback === 'function')) {\n\t\t\tif (typeof result === 'object') {\n\t\t\t\tcallback(result);\n\t\t\t} else {\n\t\t\t\tcallback({status: -1});\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n};\n"
  },
  {
    "path": "js/fulltextsearch.v1.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: settings */\n/** global: searchbar */\n/** global: result */\n/** global: nav */\n/** global: api */\n\n\n(function () {\n\n\t/**\n\t * @constructs FullTextSearch\n\t */\n\tvar FullTextSearch = function () {\n\t\t$.extend(FullTextSearch.prototype, settings);\n\t\t$.extend(FullTextSearch.prototype, result);\n\t\t$.extend(FullTextSearch.prototype, searchbox);\n\t\t$.extend(FullTextSearch.prototype, nav);\n\t\t$.extend(FullTextSearch.prototype, api);\n\n//\t\tsettings.generateNoResultDiv();\n\t};\n\n\tOCA.FullTextSearch = FullTextSearch;\n\tOCA.FullTextSearch.api = new FullTextSearch();\n\n})();\n"
  },
  {
    "path": "js/fulltextsearch.v1.navigation.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: api */\n/** global: search */\n/** global: result */\n/** global: settings */\n\n\n\nvar curr = {\n\tproviderResult: [],\n\tpage: 1,\n\tlastRequest: '',\n\tlastRequestTimer: null,\n\tlastSearchTimer: null,\n\n\tsetProviderResult: function (id, value) {\n\t\tcurr.providerResult[id] = value;\n\t},\n\n\tgetProviderResult: function (id) {\n\t\tvar current = curr.providerResult[id];\n\t\tif (!current) {\n\t\t\tcurrent = [];\n\t\t}\n\n\t\treturn current;\n\t}\n\n};\n\n\nvar nav = {\n\t\tmanageDivProviderNavigation: function (divProviderNavigation, request, meta) {\n\n\t\t\tvar maxPage = Math.ceil(meta.total / request.size);\n\n\t\t\tdivProviderNavigation.attr('data-time', meta.time);\n\t\t\tdivProviderNavigation.attr('data-page', request.page);\n\t\t\tdivProviderNavigation.attr('data-options', JSON.stringify(request.options));\n\t\t\tdivProviderNavigation.attr('data-search', request.search);\n\t\t\tdivProviderNavigation.attr('data-empty-search', request.empty_search);\n\t\t\tdivProviderNavigation.attr('data-max-page', maxPage);\n\t\t\tdivProviderNavigation.attr('data-size', request.size);\n\t\t\tdivProviderNavigation.attr('data-total', meta.total);\n\n\t\t\tvar providerTitle = divProviderNavigation.attr('data-provider-title');\n\t\t\tvar left = '';\n\t\t\tvar data =\n\t\t\t\t{\n\t\t\t\t\ttitle: providerTitle,\n\t\t\t\t\tsearch: request.search,\n\t\t\t\t\ttotal: meta.total,\n\t\t\t\t\ttime: meta.time\n\t\t\t\t};\n\n\t\t\tif (request.search === ':null' || request.search === '') {\n\t\t\t\tleft = t('fulltextsearch', 'the search returned {total} results in {time} ms', data);\n\t\t\t} else {\n\t\t\t\tleft = t('fulltextsearch',\n\t\t\t\t\t\"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\", data);\n\t\t\t}\n\n\t\t\tdivProviderNavigation.find('.provider_navigation_left').text(left);\n\n\t\t\tif (maxPage > 1) {\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_curr').text(request.page + ' / ' +\n\t\t\t\t\tmaxPage).stop().fadeTo(200, 1);\n\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_prev').stop().fadeTo(200,\n\t\t\t\t\t(request.page > 1) ? 1 : 0);\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_next').stop().fadeTo(200,\n\t\t\t\t\t(request.page < maxPage) ? 1 : 0);\n\t\t\t} else {\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_prev').stop().fadeTo(200, 0);\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_curr').stop().fadeTo(200, 0);\n\t\t\t\tdivProviderNavigation.find('.provider_navigation_next').stop().fadeTo(200, 0);\n\t\t\t}\n\t\t},\n\n\n\t\tmanageDivProviderResult: function (divProviderResult, newResult, oldResult) {\n\t\t\t//replaceWith();\n\t\t\tnav.divProviderResultAddItems(divProviderResult, newResult, oldResult);\n\t\t\tif (oldResult) {\n\t\t\t\tnav.divProviderResultRemoveItems(divProviderResult, newResult, oldResult);\n\t\t\t\tnav.divProviderResultMoveItems(divProviderResult, newResult, oldResult);\n\t\t\t}\n\t\t},\n\n\n\t\tdivProviderResultAddItems: function (divProviderResult, newResult, oldResult) {\n\n\t\t\tvar precItem = null;\n\t\t\tfor (var i = 0; i < newResult.length; i++) {\n\t\t\t\tvar entry = newResult[i];\n\t\t\t\tif (result.getResultIndex(entry.id, oldResult) > -1) {\n\t\t\t\t\tprecItem = nav.getDivEntry(entry.id, divProviderResult);\n\t\t\t\t\tnav.fillDivEntry(precItem, entry);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar divResult = nav.generateDivEntry(entry, nav.generateTemplateEntry(entry));\n\t\t\t\tif (precItem === null) {\n\t\t\t\t\tdivProviderResult.prepend(divResult);\n\t\t\t\t} else {\n\t\t\t\t\tprecItem.after(divResult);\n\t\t\t\t}\n\n\t\t\t\tdivResult.slideDown(settings.delay_result, function () {\n\t\t\t\t\t$(this).children('.result_template').fadeTo(settings.delay_result, 1);\n\t\t\t\t});\n\n\t\t\t\tprecItem = divResult;\n\t\t\t}\n\n\t\t},\n\n\n\t\tdivProviderResultRemoveItems: function (divProviderResult, newResult, oldResult) {\n\t\t\tfor (var i = 0; i < oldResult.length; i++) {\n\t\t\t\tvar entry = oldResult[i];\n\t\t\t\tif (result.getResultIndex(entry.id, newResult) === -1) {\n\t\t\t\t\tvar divResult = nav.getDivEntry(entry.id, divProviderResult);\n\t\t\t\t\tdivResult.fadeTo(settings.delay_result, 0, function () {\n\t\t\t\t\t\t$(this).slideUp(settings.delay_result, function () {\n\t\t\t\t\t\t\t$(this).remove();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\n\t\tdivProviderResultMoveItems: function (divProviderResult, newResult, oldResult) {\n\n\t\t\tvar precId = '';\n\n\t\t\toldResult = result.recalibrateResult(oldResult, newResult);\n\t\t\tnewResult = result.recalibrateResult(newResult, oldResult);\n\t\t\tfor (var i = 0; i < newResult.length; i++) {\n\t\t\t\tvar entry = newResult[i];\n\n\t\t\t\tvar pos = result.getResultIndex(entry.id, oldResult);\n\t\t\t\tif (pos > -1 && pos !== i) {\n\t\t\t\t\tnav.animateMoveDivResult(entry.id, divProviderResult, precId);\n\t\t\t\t}\n\n\t\t\t\tprecId = newResult[i].id;\n\t\t\t}\n\t\t},\n\n\n\t\tanimateMoveDivResult: function (entryId, divProviderResult, precId) {\n\n\t\t\tvar divResult = nav.getDivEntry(entryId, divProviderResult);\n\n\t\t\tif (precId === '') {\n\t\t\t\tdivResult.fadeTo(settings.delay_result, 0.35, function () {\n\t\t\t\t\t$(this).prependTo(divProviderResult).fadeTo(100, 1);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tvar precItem = nav.getDivEntry(precId, divProviderResult);\n\t\t\t\tdivResult.fadeTo(settings.delay_result, 0.35, function () {\n\t\t\t\t\t$(this).insertAfter(precItem).fadeTo(100, 1);\n\t\t\t\t});\n\t\t\t}\n\n\t\t},\n\n\n\t\tgetDivProvider: function (providerId, providerName) {\n\t\t\tvar ret = null;\n\t\t\tsettings.resultContainer.children('.provider_header').each(function () {\n\t\t\t\tif ($(this).attr('data-id') === providerId) {\n\t\t\t\t\tret = $(this);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (ret === null) {\n\t\t\t\tret = nav.generateDivProvider(providerId, providerName);\n\t\t\t\tsettings.resultContainer.append(ret);\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t},\n\n\n\t\tgetDivEntry: function (resultId, divProviderResult) {\n\t\t\tvar ret = null;\n\n\t\t\tdivProviderResult.children('.result_entry').each(function () {\n\t\t\t\tif ($(this).attr('data-id') === resultId) {\n\t\t\t\t\tret = $(this);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn ret;\n\t\t},\n\n\n\t\tfillDivEntry: function (divEntry, entry) {\n\t\t\tdivEntry.find('#title').text(entry.title);\n\n\t\t\tdivEntry.find('#info').text('');\n\t\t\tif (settings.options.show_hash === '1') {\n\t\t\t\tdivEntry.find('#info').text(entry.hash);\n\t\t\t}\n\n\t\t\tnav.fillDivResultExcepts(divEntry, entry);\n\n\t\t\tif (entry.link !== '') {\n\t\t\t\tdivEntry.off('click').on('click', function (event) {\n\t\t\t\t\tif (nav.onEntrySelected($(this), event)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\twindow.open(entry.link);\n\t\t\t\t});\n\t\t\t\tdivEntry.find('div').each(function () {\n\t\t\t\t\t$(this).css('cursor', 'pointer');\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tnav.onEntryGenerated(divEntry, entry);\n\t\t},\n\n\n\t\t/**\n\t\t * @namespace entry.excerpts\n\t\t */\n\t\tfillDivResultExcepts: function (divResult, entry) {\n\t\t\tif (entry.excerpts === null) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (entry.excerpts.length > 0) {\n\t\t\t\tdivResult.find('#extract').text(entry.excerpts[0]['excerpt']);\n\t\t\t} else {\n\t\t\t\tdivResult.find('#extract').text('');\n\t\t\t}\n\t\t},\n\n\n\t\tonEntryGenerated: function (divEntry, entry) {\n\t\t\tif (settings.parentHasMethod('onEntryGenerated', window)) {\n\t\t\t\tsettings.parent.onEntryGenerated(divEntry, entry);\n\t\t\t}\n\t\t},\n\n\n\t\tonEntrySelected: function (divEntry, event) {\n\t\t\treturn !!(settings.parentHasMethod('onEntrySelected', window) &&\n\t\t\t\tsettings.parent.onEntrySelected(divEntry, event));\n\t\t},\n\n\n\t\tonSearchRequest: function (data) {\n\t\t\tif (settings.parentHasMethod('onSearchRequest', window)) {\n\t\t\t\tsettings.parent.onSearchRequest(data);\n\t\t\t}\n\t\t},\n\n\n\t\tonSearchReset: function () {\n\t\t\tif (settings.parentHasMethod('onSearchReset', window)) {\n\t\t\t\tsettings.parent.onSearchReset();\n\t\t\t}\n\t\t},\n\n\n\t\tonResultDisplayed: function (data) {\n\t\t\tif (settings.parentHasMethod('onResultDisplayed', window)) {\n\t\t\t\tsettings.parent.onResultDisplayed(data);\n\t\t\t}\n\t\t},\n\n\t\tonResultClose: function () {\n\t\t\tif (settings.parentHasMethod('onResultClose', window)) {\n\t\t\t\tsettings.parent.onResultClose();\n\t\t\t}\n\t\t},\n\n\t\tonError: function (data) {\n\t\t\tif (settings.parentHasMethod('onError', window)) {\n\t\t\t\tsettings.parent.onError(data);\n\t\t\t}\n\t\t},\n\n\t\t// deleteEmptyDiv: function (entry, divId) {\n\t\t// \tvar div = entry.find(divId);\n\t\t// \tif (div.text() === '') {\n\t\t// \t\tdiv.remove();\n\t\t// \t}\n\t\t// },\n\n\n\t\tgenerateTemplateEntry: function () {\n\t\t\tvar divTemplate = settings.entryTemplate;\n\t\t\tif (divTemplate === null) {\n\t\t\t\tdivTemplate = settings.generateDefaultTemplate();\n\t\t\t}\n\n\t\t\tif (!divTemplate.length) {\n\t\t\t\tconsole.log('FullTextSearch Error: entryTemplate is not defined');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar tmpl = divTemplate.html();\n\t\t\t// var divNavToggle = $('<div>', {\n\t\t\t// \tid: 'app-navigation-toggle',\n\t\t\t// \tclass: 'icon-menu'\n\t\t\t// });\n\t\t\t//\n\t\t\tvar div = $('<div>', {class: 'result_template'});\n\t\t\tdiv.html(tmpl).fadeTo(0);\n\n\t\t\treturn div;\n\t\t},\n\n\n\t\tgenerateDivEntry: function (entry, divResultContent) {\n\t\t\tvar divEntry = $('<div>', {class: 'result_entry'});\n\n\t\t\tdivEntry.hide();\n\t\t\tdivEntry.attr('data-id', entry.id);\n\t\t\tdivEntry.attr('data-link', entry.link);\n\t\t\tdivEntry.attr('data-source', entry.source);\n\t\t\tdivEntry.attr('data-info', JSON.stringify(entry.info));\n\t\t\tdivEntry.attr('data-result', JSON.stringify(entry));\n\t\t\tdivEntry.append(divResultContent);\n\n\t\t\tnav.fillDivEntry(divEntry, entry);\n\n\t\t\treturn divEntry;\n\t\t},\n\n\n\t\tgenerateDivProvider: function (providerId, providerName) {\n\n\t\t\tvar divProviderNavigation = $('<div>', {class: 'provider_navigation'});\n\t\t\tdivProviderNavigation.attr('data-provider-id', providerId);\n\t\t\tdivProviderNavigation.attr('data-provider-title', providerName);\n\n\t\t\tvar divProviderLeftNav = $('<div>', {class: 'provider_navigation_left'});\n\t\t\tif (settings.searchProviderId !== '') {\n\t\t\t\tvar divProviderPaginationClose = $('<div>',\n\t\t\t\t\t{class: 'icon-close provider_navigation_close'});\n\t\t\t\tdivProviderPaginationClose.on('click', function () {\n\t\t\t\t\tnav.onResultClose();\n\t\t\t\t});\n\t\t\t\tdivProviderNavigation.append(divProviderPaginationClose);\n\t\t\t}\n\n\t\t\tdivProviderNavigation.append(divProviderLeftNav);\n\n\n\t\t\tvar divProviderPagination = $('<div>', {class: 'provider_navigation_right'});\n\t\t\tvar divProviderPaginationPrev = $('<div>', {class: 'provider_navigation_prev'}).append(\n\t\t\t\t$('<div>', {class: 'provider_navigation_page'}).text('previous page'));\n\n\t\t\tdivProviderPaginationPrev.on('click', function () {\n\t\t\t\tvar prevPage = Number(divProviderNavigation.attr('data-page')) - 1;\n\t\t\t\tif (prevPage < 1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfullTextSearch.search({\n\t\t\t\t\tproviders: providerId,\n\t\t\t\t\toptions: JSON.parse(divProviderNavigation.attr('data-options')),\n\t\t\t\t\tsearch: divProviderNavigation.attr('data-search'),\n\t\t\t\t\tempty_search: divProviderNavigation.attr('data-empty-search'),\n\t\t\t\t\tpage: prevPage,\n\t\t\t\t\tsize: divProviderNavigation.attr('data-size')\n\t\t\t\t});\n\t\t\t});\n\t\t\tdivProviderPagination.append(divProviderPaginationPrev);\n\n\t\t\tdivProviderPagination.append($('<div>', {class: 'provider_navigation_curr'}));\n\n\t\t\tvar divProviderPaginationNext = $('<div>',\n\t\t\t\t{class: 'provider_navigation_next'}).append(\n\t\t\t\t$('<div>', {class: 'provider_navigation_page'}).text('next page'));\n\n\t\t\tdivProviderPaginationNext.on('click', function () {\n\t\t\t\tvar nextPage = Number(divProviderNavigation.attr('data-page')) + 1;\n\t\t\t\tif (nextPage > Number(divProviderNavigation.attr('data-max-page'))) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfullTextSearch.search({\n\t\t\t\t\tproviders: providerId,\n\t\t\t\t\toptions: JSON.parse(divProviderNavigation.attr('data-options')),\n\t\t\t\t\tsearch: divProviderNavigation.attr('data-search'),\n\t\t\t\t\tempty_search: divProviderNavigation.attr('data-empty-search'),\n\t\t\t\t\tpage: nextPage,\n\t\t\t\t\tsize: divProviderNavigation.attr('data-size')\n\t\t\t\t});\n\t\t\t});\n\t\t\tdivProviderPagination.append(divProviderPaginationNext);\n\n\t\t\tdivProviderNavigation.append(divProviderPagination);\n\n\t\t\tvar divProviderResult = $('<div>', {class: 'provider_result'});\n\n\t\t\tvar divProvider = $('<div>', {class: 'provider_header'});\n\t\t\tdivProvider.hide();\n\t\t\tdivProvider.attr('data-id', providerId);\n\t\t\tdivProvider.append(divProviderNavigation);\n\n\t\t\tif (settings.resultHeader !== null) {\n\t\t\t\tdivProvider.append(settings.resultHeader);\n\t\t\t}\n\n\t\t\tdivProvider.append(divProviderResult);\n\n\t\t\tif (settings.resultFooter !== null) {\n\t\t\t\tdivProvider.append(settings.resultFooter);\n\t\t\t}\n\n\t\t\treturn divProvider;\n\t\t}\n\n\t}\n;\n"
  },
  {
    "path": "js/fulltextsearch.v1.result.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: settings */\n/** global: curr */\n/** global: nav */\n\n/** @namespace result.provider */\n/** @namespace result.documents */\n\nvar result = {\n\n\tdisplayResult: function (res) {\n\t\tbox_elements.searchError.hide();\n\t\tif (settings.resultContainer === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar searchResult = res.result;\n\t\tif (searchResult.length === 0) {\n\t\t\t// result.displayNoResult();\n\t\t\treturn;\n\t\t}\n\n\t\tsettings.options = res.request.options;\n\t\tfor (var i = 0; i < searchResult.length; i++) {\n\t\t\tresult.displayProviderResult(res.request, searchResult[i]);\n\t\t}\n\t},\n\n\n\tdisplayError: function (res) {\n\t\tbox_elements.searchError.text(res.message).show();\n\t},\n\n\n\tdisplayProviderResult: function (request, result) {\n\n\t\t// settings.divNoResult.fadeTo(settings.delay_result, 0);\n\n\t\tvar current = curr.getProviderResult(result.provider.id);\n\t\tvar divProvider = nav.getDivProvider(result.provider.id, result.provider.name);\n\t\tnav.manageDivProviderNavigation(divProvider.children('.provider_navigation'), request,\n\t\t\tresult.meta);\n\t\tnav.manageDivProviderResult(divProvider.find('.provider_result'), result.documents,\n\t\t\tcurrent.documents);\n\n\t\tdivProvider.slideDown(settings.delay_provider, function () {\n\t\t\t$(this).fadeTo(settings.delay_provider, 1);\n\t\t});\n\n\t\tcurr.setProviderResult(result.provider.id, result);\n\t},\n\n\n\trecalibrateResult: function (oldResult, newResult) {\n\t\tvar tmpResult = [];\n\t\tfor (var i = 0; i < oldResult.length; i++) {\n\t\t\tif (result.getResultIndex(oldResult[i].id, newResult) > -1) {\n\t\t\t\ttmpResult.push(oldResult[i]);\n\t\t\t}\n\t\t}\n\n\t\treturn tmpResult;\n\t},\n\n\n\tgetResultIndex: function (id, result) {\n\t\tif (!result) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tfor (var i = 0; i < result.length; i++) {\n\t\t\tif (result[i].id === id) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\n};\n"
  },
  {
    "path": "js/fulltextsearch.v1.searchbox.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: OCA */\n/** global: nav */\n/** global: _ */\n/** global: api */\n/** global: search */\n/** global: result */\n/** global: fullTextSearch */\n/** global: settings */\n\n\nvar box_elements = {\n\tsearchInput: null,\n\tsearchOptions: null,\n\tsearchTemplate: null,\n\tsearchError: null,\n\tdivFullTextSearchIcon: null,\n\tdivFullTextSearchPopup: null\n};\n\n\nvar searchbox = {\n\n\tinit: function () {\n\n\t\tvar self = this;\n\n\t\t// we remove old search\n\t\tvar search_form = $('FORM.searchbox');\n\t\tif (search_form.length > 0) {\n\t\t\tsearch_form.remove();\n\t\t}\n\n\t\tvar divHeaderRight = $('DIV.header-right');\n\t\tvar divFullTextSearch = $('<div>', {id: 'fulltextsearch'});\n\t\tdivHeaderRight.prepend(divFullTextSearch);\n\n\t\tbox_elements.divFullTextSearchIcon = searchbox.generateFullTextSearchIcon();\n\t\tbox_elements.divFullTextSearchPopup = searchbox.generateFullTextSearchPopup();\n\t\tdivFullTextSearch.append(box_elements.divFullTextSearchIcon);\n\t\tdivFullTextSearch.append(box_elements.divFullTextSearchPopup);\n\n\t\tOC.registerMenu(box_elements.divFullTextSearchIcon, box_elements.divFullTextSearchPopup,\n\t\t\tsearchbox.displayedSearchPopup);\n\n\t\tapi.retrieveOptions(settings.searchProviderId);\n\n\t\t$(window).bind('keydown', function (event) {\n\t\t\tif (event.ctrlKey || event.metaKey) {\n\t\t\t\tif (String.fromCharCode(event.which).toLowerCase() === 'f') {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tsearchbox.displaySearchPopup(true);\n\t\t\t\t}\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.which === 27) {\n\t\t\t\tsearchbox.displaySearchPopup(false);\n\t\t\t}\n\t\t});\n\n\t},\n\n\n\tgenerateFullTextSearchIcon: function () {\n\t\tvar className = 'icon-fulltextsearch';\n\t\tif (OCA.Theming === undefined || !OCA.Theming.inverted) {\n\t\t\tclassName = 'icon-fulltextsearch-white';\n\t\t}\n\n\t\tvar icon = $('<div>', {\n\t\t\tid: 'fts-icon',\n\t\t\ttabindex: 0,\n\t\t\trole: 'link',\n\t\t\tclass: className + ' menutoggle'\n\t\t});\n\n\t\ticon.fadeTo(0, 0.6);\n\n\t\treturn icon;\n\t},\n\n\n\tgenerateFullTextSearchPopup: function () {\n\t\tvar popup = $('<div>', {\n\t\t\tid: 'fts-popup'\n\t\t});\n\n\t\tbox_elements.searchInput = $('<input>', {\n\t\t\tid: 'fts-input',\n\t\t\tplaceholder: t('fulltextsearch', 'Search') + ' ' + settings.searchProviderName\n\t\t}).on('keyup', searchbox.timedSearch);\n\t\tbox_elements.searchOptions = $('<div>', {id: 'fts-options'});\n\t\tbox_elements.searchTemplate = $('<div>', {id: 'fts-options-template'});\n\t\tbox_elements.searchError = $('<div>', {id: 'fts-error'});\n\n\t\tvar divHeader = $('<div>', {id: 'fts-header'});\n\t\tdivHeader.append($('<div>').append(box_elements.searchInput));\n\n\t\tpopup.append(divHeader);\n\t\tpopup.append(box_elements.searchOptions);\n\t\tpopup.append(box_elements.searchTemplate);\n\t\tpopup.append(box_elements.searchError.hide());\n\n\t\treturn popup;\n\t},\n\n\n\tdisplaySearchPopup: function (display) {\n\t\tif (display) {\n\t\t\tOC.showMenu(box_elements.divFullTextSearchIcon, box_elements.divFullTextSearchPopup,\n\t\t\t\tsearchbox.displayedSearchPopup);\n\t\t} else {\n\t\t\tOC.hideMenus(null);\n\t\t}\n\t},\n\n\n\tdisplayedSearchPopup: function () {\n\t\tbox_elements.searchError.hide();\n\t\tbox_elements.searchInput.focus();\n\t},\n\n\n\tsearching: function () {\n\t\tif (curr.lastRequestTimer !== null) {\n\t\t\twindow.clearTimeout(curr.lastRequestTimer);\n\t\t\tcurr.lastRequestTimer = null;\n\t\t}\n\n\t\tif (curr.lastSearchTimer !== null) {\n\t\t\twindow.clearTimeout(curr.lastSearchTimer);\n\t\t}\n\n\t\tvar search = box_elements.searchInput.val();\n\t\tif (search.length < 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tcurr.lastRequest = search;\n\t\tapi.search({\n\t\t\tproviders: settings.searchProviderId,\n\t\t\tsearch: search,\n\t\t\tpage: curr.page,\n\t\t\toptions: searchbox.getSearchOptions(),\n\t\t\tsize: 20\n\t\t});\n\t},\n\n\n\ttimedSearch: function () {\n\n\t\tif (curr.lastSearchTimer !== null) {\n\t\t\twindow.clearTimeout(curr.lastSearchTimer);\n\t\t}\n\n\t\tcurr.lastSearchTimer = window.setTimeout(function () {\n\t\t\tsearchbox.searching();\n\t\t}, settings.searchEntryTimer);\n\n\t\tif (curr.lastRequestTimer === null) {\n\t\t\tcurr.lastRequestTimer = window.setTimeout(function () {\n\t\t\t\tsearchbox.searching();\n\t\t\t}, settings.searchRequestTimer);\n\t\t}\n\t},\n\n\n\tonOptionsLoaded: function (result) {\n\t\tif (!result[settings.searchProviderId]) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (result[settings.searchProviderId]['options']) {\n\t\t\tsearchbox.generateOptionsHtml(result[settings.searchProviderId]['options']);\n\t\t\tbox_elements.searchOptions.find('INPUT').each(function () {\n\t\t\t\tsearchbox.eventOnOptionsLoadedInput($(this));\n\t\t\t});\n\t\t}\n\n\t\tif (result[settings.searchProviderId]['template']) {\n\t\t\tbox_elements.searchTemplate.html(result[settings.searchProviderId]['template']);\n\t\t\tbox_elements.searchTemplate.find('INPUT').each(function () {\n\t\t\t\tsearchbox.eventOnOptionsLoadedInput($(this))\n\t\t\t});\n\t\t}\n\t},\n\n\n\teventOnOptionsLoadedInput: function (div) {\n\t\tdiv.on('change keyup', function () {\n//\t\t\tconsole.log('eventOnOptionsLoadedInput');\n\n\t\t\tsearchbox.searching();\n\t\t});\n\t},\n\n\n\tgenerateOptionsHtml: function (options) {\n\t\tvar div = $('<div>', {class: 'div-table'});\n\n\t\tfor (var j = 0; j < options.length; j++) {\n\t\t\tvar sub = options[j];\n\t\t\tsearchbox.displayPanelCheckbox(div, sub);\n\t\t\tsearchbox.displayPanelInput(div, sub);\n\t\t}\n\n\t\tbox_elements.searchOptions.append(div);\n\t},\n\n\n\tdisplayPanelOptionTitle: function (sub) {\n\t\tvar subDiv = $('<div>', {\n\t\t\tclass: 'div-table-row'\n\t\t});\n\n\t\tsubDiv.append($('<div>',\n\t\t\t{\n\t\t\t\tclass: 'div-table-col div-table-col-left'\n\t\t\t}).append($('<span>', {\n\t\t\tclass: 'leftcol',\n\t\t\ttext: sub.title\n\t\t})));\n\n\t\tsubDiv.append($('<div>',\n\t\t\t{class: 'div-table-col div-table-col-right'}));\n\n\t\treturn subDiv;\n\t},\n\n\n\tdisplayPanelCheckbox: function (div, sub) {\n\t\tif (sub.type !== 'checkbox') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar subDiv = searchbox.displayPanelOptionTitle(sub);\n\t\tvar subDivInput = $('<input>', {\n\t\t\ttype: 'checkbox',\n\t\t\tid: sub.name,\n\t\t\tvalue: 1\n\t\t});\n\t\tsubDiv.find('.div-table-col-right').append(subDivInput);\n\t\tdiv.append(subDiv);\n\t},\n\n\n\tdisplayPanelInput: function (div, sub) {\n\t\tif (sub.type !== 'input') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar subDiv = searchbox.displayPanelOptionTitle(sub);\n\t\tvar subDivInput = $('<input>', {\n\t\t\tclass: 'fts_options_input fts_options_input_' + sub.size,\n\t\t\ttype: 'text',\n\t\t\tplaceholder: sub.placeholder,\n\t\t\tid: sub.name\n\t\t});\n\t\tsubDiv.find('.div-table-col-right').append(subDivInput);\n\t\tdiv.append(subDiv);\n\t},\n\n\n\tgetSearchOptions: function () {\n\t\tvar options = {};\n\n\t\tif (box_elements.searchTemplate === null) {\n\t\t\treturn options;\n\t\t}\n\n\t\tbox_elements.searchOptions.find('INPUT').each(function () {\n\t\t\tsearchbox.getSearchOptionsFromInput($(this), options);\n\t\t});\n\t\tbox_elements.searchTemplate.find('INPUT').each(function () {\n\t\t\tsearchbox.getSearchOptionsFromInput($(this), options);\n\t\t});\n\n\t\treturn options;\n\t},\n\n\n\tgetSearchOptionsFromInput: function (div, options) {\n\t\tvar value = div.val();\n\n\t\tif (div.attr('type') === 'checkbox' && !div.is(':checked')) {\n\t\t\tvalue = '';\n\t\t}\n\n\t\toptions[div.attr('id')] = value;\n\t}\n\n\n};\n\n\n"
  },
  {
    "path": "js/fulltextsearch.v1.settings.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/** global: searchbar */\n/** global: api */\n\nvar settings = {\n\n\tdelay_provider: 300,\n\tdelay_result: 150,\n\n\tsearchRequestTimer: 4000,\n\tsearchEntryTimer: 1500,\n\tparent: null,\n\tsearchProviderId: '',\n\tsearchProviderName: '',\n\tresultContainer: null,\n\tresultHeader: null,\n\tresultFooter: null,\n\tentryTemplate: null,\n\tentryTemplateDefault: null,\n\t// divNoResult: null,\n\toptions: [],\n\n\t/**\n\t * generate the default template to display search result entries\n\t */\n\tgenerateDefaultTemplate: function () {\n\n\t\tvar resultContent = $('<div>', {class: 'result_content'});\n\t\tresultContent.append($('<div>', {\n\t\t\tid: 'title',\n\t\t\tclass: 'result_title'\n\t\t}));\n\t\tresultContent.append($('<div>', {\n\t\t\tid: 'extract',\n\t\t\tclass: 'result_extract'\n\t\t}));\n\n\t\tvar resultEntry = $('<div>', {class: 'result_entry'});\n\t\tresultEntry.append($('<div>', {class: 'result_div_checkbox'}));\n\n\t\tresultEntry.append($('<div>', {class: 'result_div result_div_content'}).append(resultContent));\n\n\t\tvar resultRight = $('<div>', {class: 'result_div result_div_right'});\n\t\tresultRight.append($('<div>', {id: 'source'}));\n\t\tresultRight.append($('<div>', {id: 'info'}));\n\t\tresultEntry.append(resultRight);\n\n\t\treturn $('<div>').append(resultEntry);\n\t},\n\n\n\t// \tvar divLeft = $('<div>', {class: 'result_entry_left'});\n\t// \tdivLeft.append($('<div>', {id: 'title'}));\n\t// \tdivLeft.append($('<div>', {id: 'line1'}));\n\t// \tdivLeft.append($('<div>', {id: 'line2'}));\n\t//\n\t// \tvar divRight = $('<div>', {class: 'result_entry_right'});\n\t// \t//divRight.append($('<div>', {id: 'score'}));\n\t//\n\t// \tvar div = $('<div>', {class: 'result_entry_default'});\n\t// \tdiv.append(divLeft);\n\t// \tdiv.append(divRight);\n\t//\n\t// \treturn $('<div>').append(div);\n\t// },\n\n\t//\n\t// /**\n\t//  * generate a no result display\n\t//  */\n\t// generateNoResultDiv: function () {\n\t// \tvar div = $('<div>', {id: 'noresult'});\n\t// \tdiv.html('no result');\n\t// \tdiv.hide();\n\t// \tsettings.divNoResult = div;\n\t// },\n\n\n\t/**\n\t * @param template\n\t */\n\tsetResultHeader: function (template) {\n\t\tsettings.resultHeader = template;\n\t},\n\n\t/**\n\t * @param template\n\t */\n\tsetResultFooter: function (template) {\n\t\tsettings.resultFooter = template;\n\t},\n\n\n\t/**\n\t * used to set the template to display search result entries\n\t *\n\t * @param template\n\t */\n\tsetEntryTemplate: function (template) {\n\t\tsettings.entryTemplate = template;\n\t},\n\n\n\t/**\n\t * used to set the container for the search result entries\n\t *\n\t * @param container\n\t */\n\tsetResultContainer: function (container) {\n\t\tsettings.resultContainer = container;\n\t\t// settings.resultContainer.prepend(settings.divNoResult);\n\t},\n\n\n\t/**\n\t *  initialize the full text search and assign a providerId\n\t *\n\t * @param providerId\n\t * @param providerName\n\t * @param parent\n\t */\n\tinitFullTextSearch: function (providerId, providerName, parent) {\n\t\tsettings.searchProviderId = providerId;\n\t\tsettings.searchProviderName = providerName;\n\t\tsettings.parent = parent;\n\t\tsearchbox.init();\n\t},\n\n\n\t/**\n\t * check that the app that call the lib contains a specific method\n\t *\n\t * @param method\n\t * @param context\n\t * @returns {boolean}\n\t */\n\tparentHasMethod: function (method, context) {\n\t\tif (settings.parent === null) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar functionName = 'settings.parent.' + method;\n\t\tvar args = Array.prototype.slice.call(arguments, 2);\n\t\tvar namespaces = functionName.split(\".\");\n\t\tfor (var i = 0; i < namespaces.length; i++) {\n\t\t\tif (context[namespaces[i]] === undefined) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tcontext = context[namespaces[i]];\n\t\t}\n\t\treturn true;\n\t},\n\n\n\texecuteFunction: function (functionName, context) {\n\t\tvar args = Array.prototype.slice.call(arguments, 2);\n\t\tvar namespaces = functionName.split(\".\");\n\t\tvar func = namespaces.pop();\n\t\tfor (var i = 0; i < namespaces.length; i++) {\n\t\t\tif (context[namespaces[i]] === undefined) {\n\t\t\t\t// console.log('Unknown function \\'' + functionName + '\\'');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tcontext = context[namespaces[i]];\n\t\t}\n\n\t\treturn context[func].apply(context, args);\n\t}\n\n\n};\n"
  },
  {
    "path": "js/navigate.js",
    "content": "/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n\n/** global: OCA */\n/** global: _ */\n\nvar fullTextSearch = OCA.FullTextSearch.api;\n\n\nvar elements = {\n\tsearchTimeout: null,\n\tsearch_input: null,\n\tsearch_submit: null,\n\tsearch_result: null,\n\tsearch_json: null\n};\n\nvar Navigate = function () {\n\tthis.init();\n};\n\nNavigate.prototype = {\n\n\tcurrentTagsResult: {},\n\tselectedTags: {},\n\n\tinit: function () {\n\t\tvar self = this;\n\n\t\telements.search_input = $('#search_input');\n\t\telements.search_submit = $('#search_submit');\n\t\telements.search_result = $('#search_result');\n\t\telements.search_panels = $('#search_navigation');\n//\t\telements.search_json = $('#search_json');\n\t\telements.divHeader = $('#search_header');\n\t\tbox_elements.searchError = $('#search_error');\n\n\t\t//\tfullTextSearch.setEntryTemplate($('#template_entry'), self);\n\t\tfullTextSearch.setResultContainer(elements.search_result);\n\n\t\telements.search_input.on('input', this.navigateTimedSearch);\n\t\t// function () {\n\t\t// \tself.resetSearch();\n\t\t// \tif (elements.searchTimeout === null && self.initSearch()) {\n\t\t// \t\telements.searchTimeout = _.delay(function () {\n\t\t// \t\t\tself.initSearch();\n\t\t// \t\t\telements.searchTimeout = null;\n\t\t// \t\t}, 3000);\n\t\t// \t}\n\t\t// });\n\n\t\t//\n\t\t// $(document).keypress(function (e) {\n\t\t// \tif (e.which === 13) {\n\t\t// \t\tself.initSearch(true);\n\t\t// \t}\n\t\t// });\n\n\t\tself.initPanels();\n\t},\n\n\n\tnavigateTimedSearch: function () {\n\n\t\tif (curr.lastSearchTimer !== null) {\n\t\t\twindow.clearTimeout(curr.lastSearchTimer);\n\t\t}\n\n\t\tcurr.lastSearchTimer = window.setTimeout(function () {\n\t\t\tOCA.FullTextSearch.navigate.initSearch();\n\t\t}, settings.searchEntryTimer);\n\n\t\tif (curr.lastRequestTimer === null) {\n\t\t\tcurr.lastRequestTimer = window.setTimeout(function () {\n\t\t\t\tOCA.FullTextSearch.navigate.initSearch();\n\t\t\t}, settings.searchRequestTimer);\n\t\t}\n\t},\n\n\n\tinitPanels: function () {\n\t\tvar self = this;\n\t\t$.ajax({\n\t\t\tmethod: 'GET',\n\t\t\turl: OC.generateUrl('/apps/fulltextsearch/navigation/panels')\n\t\t}).done(function (res) {\n\t\t\tself.displayPanels(res);\n\t\t});\n\t},\n\n\n\tdisplayPanels: function (data) {\n\t\tvar self = this;\n\n\t\tvar ak = Object.keys(data);\n\t\tfor (var i = 0; i < ak.length; i++) {\n\t\t\tvar providerAppId = ak[i];\n\t\t\tvar title = data[ak[i]]['title'];\n\t\t\tvar options = data[ak[i]]['options'];\n\t\t\tvar css = data[ak[i]]['css'];\n\t\t\tvar icon = data[ak[i]]['icon'];\n\t\t\tvar providerId = data[ak[i]]['provider'];\n\n\t\t\tif (css !== '') {\n\t\t\t\tOC.addStyle(providerAppId, css);\n\t\t\t}\n\n\t\t\tvar li = $('<li>', {class: (nav.options !== undefined) ? 'collapsible open' : ''});\n\t\t\tvar aIcon = $('<a>', {\n\t\t\t\thref: '#',\n\t\t\t\tclass: (icon !== undefined) ? icon : 'search_icon'\n\t\t\t});\n\t\t\taIcon.addClass('icon-fts').text(title);\n\n\t\t\tvar ul = $('<ul>');\n\t\t\t// if (nav.options !== undefined) {\n\n\t\t\taIcon.on('click', function () {\n\t\t\t\tvar li = $(this).closest('li');\n\t\t\t\tif (li.hasClass('open')) {\n\t\t\t\t\tli.removeClass('open');\n\t\t\t\t} else {\n\t\t\t\t\tli.addClass('open');\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tfor (var j = 0; j < options.length; j++) {\n\t\t\t\tvar sub = options[j];\n\t\t\t\tself.displayPanelCheckbox(ul, sub);\n\t\t\t\tself.displayPanelInput(ul, sub);\n\t\t\t\tself.displayPanelTags(ul, sub);\n\t\t\t\tself.displayPanelSearch(providerAppId, ul, sub);\n\t\t\t\t//\n\t\t\t\t// <p id=\"tag_filter\" class=\"open\">\n\t\t\t\t// \t\t<input value=\"\" style=\"display: none;\" type=\"text\">\n\t\t\t\t// \t\t<ul class=\"tagit ui-widget ui-widget-content ui-corner-all\">\n\t\t\t\t// \t\t<li class=\"tagit-new\">\n\t\t\t\t// \t\t<input class=\"ui-widget-content ui-autocomplete-input\"\n\t\t\t\t// placeholder=\"Filter by tag\" autocomplete=\"off\" type=\"text\"> <span role=\"status\"\n\t\t\t\t// aria-live=\"polite\" class=\"ui-helper-hidden-accessible\"> 1 result is available,\n\t\t\t\t// use up and down arrow keys to navigate.</span></li> <li class=\"tagit-choice\n\t\t\t\t// ui-widget-content ui-state-default ui-corner-all\"> <span\n\t\t\t\t// class=\"tagit-label\">test</span><a class=\"close\"><span class=\"text-icon\">×</span>\n\t\t\t\t// <span class=\"ui-icon ui-icon-close\"></span></a></li> <li class=\"tagit-choice\n\t\t\t\t// ui-widget-content ui-state-default ui-corner-all\"> <span\n\t\t\t\t// class=\"tagit-label\">perdu</span><a class=\"close\"><span class=\"text-icon\">×</span>\n\t\t\t\t// <span class=\"ui-icon ui-icon-close\"></span></a></li></ul> </p>\n\n\n\t\t\t}\n\t\t\t// }\n\n\t\t\tli.append(aIcon);\n\t\t\tvar aInput = $('<input>', {\n\t\t\t\tclass: 'search_checkbox',\n\t\t\t\ttype: 'checkbox',\n\t\t\t\t'data-provider': ak[i],\n\t\t\t\t'data-provider-id': providerId\n\t\t\t});\n\t\t\taInput.change(function () {\n\t\t\t\tself.initSearch();\n\t\t\t});\n\n\t\t\tli.append(aInput);\n\t\t\tli.append(ul);\n\n\t\t\telements.search_panels.append(li);\n\t\t}\n\n\t},\n\n\n\tdisplayPanelOptionTitle: function (sub) {\n\t\treturn $('<a>', {\n\t\t\thref: '#',\n\t\t\tclass: 'ulsub',\n\t\t\ttext: sub.title\n\t\t});\n\t},\n\n\n\tdisplayPanelCheckbox: function (ul, sub) {\n\n\t\tif (sub.type !== 'checkbox') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar self = this;\n\t\tvar subA = this.displayPanelOptionTitle(sub);\n\t\tvar subAInput = $('<input>', {\n\t\t\tclass: 'search_checkbox_sub',\n\t\t\ttype: 'checkbox',\n\t\t\t'data-option': sub.name\n\t\t});\n\t\tsubAInput.change(function () {\n\t\t\tself.initSearch();\n\t\t});\n\n\t\tul.append($('<li>').append(subA).append(subAInput));\n\t},\n\n\n\tdisplayPanelInput: function (ul, sub) {\n\t\tif (sub.type !== 'input') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar self = this;\n\t\tvar subA = this.displayPanelOptionTitle(sub);\n\t\tvar subAInput = $('<input>', {\n\t\t\tclass: 'search_input_sub search_input_sub_' + sub.size,\n\t\t\ttype: 'text',\n\t\t\tplaceholder: sub.placeholder,\n\t\t\t'data-option': sub.name\n\t\t});\n\t\tsubAInput.on('input', function () {\n\t\t\tself.initSearch();\n\t\t});\n\n\t\tul.append($('<li>').append(subA).append(subAInput));\n\t},\n\n\n\tdisplayPanelTags: function (ul, sub) {\n\t\tif (sub.type !== 'tags') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar self = this;\n\t\tvar subAInput = $('<input>', {\n\t\t\tid: sub.name,\n\t\t\tclass: 'search_tags_sub',\n\t\t\ttype: 'text',\n\t\t\tplaceholder: sub.title,\n\t\t\tlist: sub.name + '_datalist',\n\t\t\t'data-option': sub.name\n\t\t});\n\n\t\tvar subADataList = $('<datalist>', {\n\t\t\tid: sub.name + '_datalist'\n\t\t});\n\n\t\tsub.list.forEach(function (item) {\n\t\t\tsubADataList.append($('<option>', {value: item}));\n\t\t});\n\n\n\t\t/**\n\t\t * <div class=\"systemTagsInfoView\">\n\t\t *       <div class=\"systemTagsInputFieldContainer\">\n\t\t *           <div id=\"s2id_autogen15\" class=\"select2-container select2-container-multi\n\t\t * systemTagsInputField systemtags-select2-container\">\n\t\t *               <ul class=\"select2-choices\">\n\t\t *                   <li class=\"select2-search-choice\">\n\t\t *                       <div>\n\t\t *                           <span class=\"label\">dsfsdfds</span>\n\t\t *                       </div>\n\t\t *                       <a href=\"#\" class=\"select2-search-choice-close\" tabindex=\"-1\"></a>\n\t\t *                   </li>\n\t\t *               </ul>\n\t\t *           </div>\n\t\t *           <input class=\"systemTagsInputField select2-offscreen\" name=\"tags\"\n\t\t * value=\"5,4,3,1,2,6\" tabindex=\"-1\" type=\"hidden\">\n\t\t *       </div>\n\t\t * </div>\n\t\t */\n\t\t// subAInput.on('change', function (e) {\n\t\t// \tvar div = $(this);\n\t\t// \tif (e.which === 13 && div.val() !== '') {\n\t\t// \t\tself.selectPanelTags($(this).attr('id'));\n\t\t// \t}\n\t\t//\n\t\t//\n\t\t// \tvar url = '/apps/' + div.attr('data-provider');\n\t\t// \tvar route = JSON.parse(div.attr('data-route'));\n\t\t//\n\t\t// \troute.url = url + route.url;\n\t\t// \tself.quickSearch(route, div.val(), function (res) {\n\t\t// \t\tself.resultTagsSearch(div, res);\n\t\t// \t});\n\t\t// });\n\n\t\tul.append($('<li>').append(subAInput).append(subADataList));\n\t},\n\n\tdisplayPanelSearch: function (appId, ul, sub) {\n\t\tvar self = this;\n\n\t\tif (sub.type !== 'search') {\n\t\t\treturn;\n\t\t}\n\n\t\tvar subAInput = $('<input>', {\n\t\t\tid: sub.name,\n\t\t\tclass: 'search_tags_sub',\n\t\t\ttype: 'text',\n\t\t\tplaceholder: sub.title,\n\t\t\tlist: sub.name + '_datalist',\n\t\t\t'data-option': sub.name,\n\t\t\t'data-provider': appId,\n\t\t\t'data-route': JSON.stringify(sub.route)\n\t\t});\n\n\t\tvar subADataList = $('<datalist>', {\n\t\t\tid: sub.name + '_datalist'\n\t\t});\n\n\n\t\tsubAInput.on('keypress', function (e) {\n\t\t\tvar div = $(this);\n\t\t\tif (e.which === 13 && div.val() !== '') {\n\t\t\t\tself.selectPanelTags($(this).attr('id'));\n\t\t\t}\n\n\n\t\t\tvar url = '/apps/' + div.attr('data-provider');\n\t\t\tvar route = JSON.parse(div.attr('data-route'));\n\n\t\t\troute.url = url + route.url;\n\t\t\tself.quickSearch(route, div.val(), function (res) {\n\t\t\t\tself.resultTagsSearch(div, res);\n\t\t\t});\n\t\t});\n\n\t\tul.append($('<li>').append(subAInput).append(subADataList));\n\t},\n\n\n\t// selectPanelTags: function (panelId) {\n\t// \tif (this.currentTagsResult === undefined) {\n\t// \t\treturn;\n\t// \t}\n\t//\n\t// \tvar tags = this.currentTagsResult[panelId];\n\t// \tif (tags.length === 0) {\n\t// \t\treturn;\n\t// \t}\n\t//\n\t// \tif (this.selectedTags[panelId] === undefined)\n\t// \t\tthis.selectedTags[panelId] = [];\n\t// \tthis.selectedTags[panelId].push(tags[0]);\n\t//\n\t// \tconsole.log('etntree' + JSON.stringify(this.selectedTags[panelId]));\n\t// },\n\t//\n\t//\n\t// resultTagsSearch: function (div, res) {\n\t// \tthis.currentTagsResult[div.attr('id')] = res;\n\t// \tvar datalistId = div.attr('data-option') + '_datalist';\n\t// \tvar datalist = $('#' + datalistId);\n\t//\n\t// \tdatalist.empty();\n\t// \tres.forEach(function (item) {\n\t// \t\tdatalist.append($('<option>', {value: item}));\n\t// \t});\n\t// },\n\n\n\tgetProviders: function () {\n\t\tvar providers = [];\n\t\telements.search_panels.find('input').each(function () {\n\t\t\tif ($(this).hasClass('search_checkbox') && $(this).is(\":checked\")) {\n\t\t\t\tproviders.push($(this).attr('data-provider-id'));\n\t\t\t}\n\t\t});\n\n\t\tif (providers.length === 0) {\n\t\t\treturn 'all';\n\t\t}\n\n\t\treturn providers;\n\t},\n\n\n\tgetOptions: function () {\n\t\tvar options = {};\n\t\telements.search_panels.find('input').each(function () {\n\t\t\tif ($(this).hasClass('search_checkbox_sub')) {\n\t\t\t\toptions[$(this).attr('data-option')] = (($(this).is(':checked')) ? '1' : '0');\n\t\t\t}\n\n\t\t\tif ($(this).hasClass('search_input_sub')) {\n\t\t\t\toptions[$(this).attr('data-option')] = $(this).val();\n\t\t\t}\n\t\t});\n\n\t\treturn options;\n\t},\n\n\n\tinitSearch: function () {\n\t\tvar search = elements.search_input.val();\n\n\t\tif (search.length < 1) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar providers = this.getProviders();\n\t\tvar options = this.getOptions();\n\n\t\tthis.displayProviderResults(providers);\n\n\t\tvar request = {\n\t\t\tproviders: providers,\n\t\t\toptions: options,\n\t\t\tsearch: search,\n\t\t\tpage: curr.page\n\t\t};\n\n\t\tfullTextSearch.search(request, this.searchResult);\n\t\treturn true;\n\t},\n\n\n\tquickSearch: function (route, search, callback) {\n\t\t$.ajax({\n\t\t\tmethod: route.verb,\n\t\t\turl: OC.generateUrl(route.url),\n\t\t\tdata: {\n\t\t\t\tsearch: search\n\t\t\t}\n\t\t}).done(function (res) {\n\t\t\tif (_.has(res, 'error')) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcallback(res);\n\t\t});\n\t},\n\n\n\tdisplayProviderResults: function (providers) {\n\t\telements.search_result.children('DIV.provider_header').each(function () {\n\t\t\tif (providers === 'all' || providers.indexOf($(this).attr('data-id')) > -1) {\n\t\t\t\t$(this).stop().slideDown(100).fadeTo(settings.delay_provider, 1);\n\t\t\t} else if ($(this).css('display') !== 'none') {\n\t\t\t\t$(this).stop().fadeTo(settings.delay_provider, 0).slideUp(100);\n\t\t\t}\n\t\t});\n\t},\n\n\n\tresetSearch: function () {\n\t\t// if (elements.search_input.val() !== '') {\n\t\t// \treturn;\n\t\t// }\n\t},\n\n\n\tsearchResult: function (result) {\n\n\t\tif (elements.search_json !== null) {\n\t\t\telements.search_json.text(JSON.stringify(result));\n\t\t}\n\n\t\t// console.log(JSON.stringify(result));\n//\t\t\tOCA.notification.onFail('Search returned no result');\n//\t\tOCA.notification.onSuccess('Search returned ' + res.meta.size + ' result(s)');\n\n\t},\n\n\n\tonError: function (message) {\n\t\tconsole.log('error while searching: ' + message);\n\t},\n\n\n\t// onEntryGenerated: function (entry) {\n\t// \tthis.deleteEmptyDiv(entry, '#line1');\n\t// \tthis.deleteEmptyDiv(entry, '#line2');\n\t// },\n\n\n\tdeleteEmptyDiv: function (entry, divId) {\n\t\tvar div = entry.find(divId);\n\t\tif (div.text() === '') {\n\t\t\tdiv.remove();\n\t\t}\n\t}\n\n\n};\n\nOCA.FullTextSearch.Navigate = Navigate;\n\n\n$(document).ready(function () {\n\tOCA.FullTextSearch.navigate = new Navigate();\n});\n\n\n\n"
  },
  {
    "path": "l10n/.gitkeep",
    "content": ""
  },
  {
    "path": "l10n/af.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Soek\",\n    \"Index not found\" : \"Indeks nie gevind nie\",\n    \"Search on %s\" : \"Soek op %s\",\n    \"General\" : \"Algemeen\",\n    \"Navigation Icon\" : \"Navigasie-ikoon\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/af.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Soek\",\n    \"Index not found\" : \"Indeks nie gevind nie\",\n    \"Search on %s\" : \"Soek op %s\",\n    \"General\" : \"Algemeen\",\n    \"Navigation Icon\" : \"Navigasie-ikoon\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/an.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/an.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/ar.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"عاد البحث بـ {total} نتيجة؛ في {time} مللي ثانية\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"البحث في {title} عن \\\"{search}\\\" عاد بـ {total} نتيجة؛ في {time} مللي ثانية\",\n    \"Search\" : \"بحث\",\n    \"Index not found\" : \"الفهرس غير موجود\",\n    \"Process timed out\" : \"إنتهي زمن الإجرائية\",\n    \"Full Text Search\" : \"بحث نصي شامل\",\n    \"Full text search\" : \"بحث نصي شامل\",\n    \"Core of the full-text search framework for Nextcloud\" : \"أساس core إطار عمل نكست كلاود للبحث النصي الشامل \",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"التطبيق الأساسي لإطار عمل نكست كلاود للبحث النصي الش\",\n    \"Search on %s\" : \"البحث في %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"من فضلك، إبحث في الويكي عن وثائق متعلقة بالتنصيب و التهيئة للبحث النصي الشامل داخل خادومك لنكست كلاود\",\n    \"General\" : \"عام\",\n    \"Search Platform\" : \"منصة البحث\",\n    \"Select the app to index content and answer search queries.\" : \"إختر التطبيق لفهرسة المحتويات و الاستجابة لاستعلامات البحث.\",\n    \"Navigation Icon\" : \"أيقونه التصفُّح\",\n    \"Enable global search within all your content.\" : \"فعّل البحث الشامل داخل كل محتوياتك.\"\n},\n\"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\");\n"
  },
  {
    "path": "l10n/ar.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"عاد البحث بـ {total} نتيجة؛ في {time} مللي ثانية\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"البحث في {title} عن \\\"{search}\\\" عاد بـ {total} نتيجة؛ في {time} مللي ثانية\",\n    \"Search\" : \"بحث\",\n    \"Index not found\" : \"الفهرس غير موجود\",\n    \"Process timed out\" : \"إنتهي زمن الإجرائية\",\n    \"Full Text Search\" : \"بحث نصي شامل\",\n    \"Full text search\" : \"بحث نصي شامل\",\n    \"Core of the full-text search framework for Nextcloud\" : \"أساس core إطار عمل نكست كلاود للبحث النصي الشامل \",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"التطبيق الأساسي لإطار عمل نكست كلاود للبحث النصي الش\",\n    \"Search on %s\" : \"البحث في %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"من فضلك، إبحث في الويكي عن وثائق متعلقة بالتنصيب و التهيئة للبحث النصي الشامل داخل خادومك لنكست كلاود\",\n    \"General\" : \"عام\",\n    \"Search Platform\" : \"منصة البحث\",\n    \"Select the app to index content and answer search queries.\" : \"إختر التطبيق لفهرسة المحتويات و الاستجابة لاستعلامات البحث.\",\n    \"Navigation Icon\" : \"أيقونه التصفُّح\",\n    \"Enable global search within all your content.\" : \"فعّل البحث الشامل داخل كل محتوياتك.\"\n},\"pluralForm\" :\"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\"\n}"
  },
  {
    "path": "l10n/ast.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"Xeneral\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ast.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"Xeneral\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/az.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Axtarış\",\n    \"General\" : \"Ümumi\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/az.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Axtarış\",\n    \"General\" : \"Ümumi\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/be.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Пошук\",\n    \"General\" : \"Агульныя\"\n},\n\"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\");\n"
  },
  {
    "path": "l10n/be.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Пошук\",\n    \"General\" : \"Агульныя\"\n},\"pluralForm\" :\"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\"\n}"
  },
  {
    "path": "l10n/bg.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"търсенето докладва {total} резултата за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"търсенето в {title} за „{search}“ докладва {total} резултата за {time} мс\",\n    \"Search\" : \"Търси\",\n    \"Index not found\" : \"Няма открито съдържание\",\n    \"Process timed out\" : \"Времето за изчакване на процеса изтече\",\n    \"Full Text Search\" : \"Пълно текстово търсене\",\n    \"Full text search\" : \"Пълно текстово търсене\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Основа на рамката за пълнотекстово търсене за Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основно приложение на рамката за пълнотекстово търсене за вашия Nextcloud.\",\n    \"Search on %s\" : \"Търси в %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Моля, проверете wiki за документация, свързана с инсталацията и конфигурацията на пълнотекстово търсене във вашият Nextcloud\",\n    \"General\" : \"Общи\",\n    \"Search Platform\" : \"Платформа за търсене\",\n    \"Select the app to index content and answer search queries.\" : \"Изберете приложението, за да индексирате съдържание и да отговорите на заявки за търсене.\",\n    \"Navigation Icon\" : \"Икона за навигация\",\n    \"Enable global search within all your content.\" : \"Активиране на глобално търсене в цялото ви съдържание.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/bg.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"търсенето докладва {total} резултата за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"търсенето в {title} за „{search}“ докладва {total} резултата за {time} мс\",\n    \"Search\" : \"Търси\",\n    \"Index not found\" : \"Няма открито съдържание\",\n    \"Process timed out\" : \"Времето за изчакване на процеса изтече\",\n    \"Full Text Search\" : \"Пълно текстово търсене\",\n    \"Full text search\" : \"Пълно текстово търсене\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Основа на рамката за пълнотекстово търсене за Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основно приложение на рамката за пълнотекстово търсене за вашия Nextcloud.\",\n    \"Search on %s\" : \"Търси в %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Моля, проверете wiki за документация, свързана с инсталацията и конфигурацията на пълнотекстово търсене във вашият Nextcloud\",\n    \"General\" : \"Общи\",\n    \"Search Platform\" : \"Платформа за търсене\",\n    \"Select the app to index content and answer search queries.\" : \"Изберете приложението, за да индексирате съдържание и да отговорите на заявки за търсене.\",\n    \"Navigation Icon\" : \"Икона за навигация\",\n    \"Enable global search within all your content.\" : \"Активиране на глобално търсене в цялото ви съдържание.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/bn_BD.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"সাধারণ\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/bn_BD.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"সাধারণ\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/br.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"an enklask en deus roet {total} disoc'h e {time} ms\",\n    \"Search\" : \"Klask\",\n    \"Index not found\" : \"Roll-gerioù kavet ebet\",\n    \"Process timed out\" : \"Poent termenn an argerzh\",\n    \"Full text search\" : \"Klask e tout an destenn\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kalon an enklask e tout an destenn er framm labour Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Meziant Kalon an enklask e tout un destenn er fram labour evit ho Nextcloud\",\n    \"Search on %s\" : \"Klask war %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Gwiriit er wiki evit an dielvadur liammet d'ar staliadur  hag an arventennañ eit ar c'hlasker e tout an destenn e barzh ho Nextcloud\",\n    \"General\" : \"Hollek\",\n    \"Search Platform\" : \"Klask pladenn\",\n    \"Select the app to index content and answer search queries.\" : \"Choazit ur meziant da lakaat er roll-gerioù ha respontit d'ar goulennoù enklask\",\n    \"Navigation Icon\" : \"Skeudennig Merdeiñ\",\n    \"Enable global search within all your content.\" : \"Aotreañ an enklask hollek e-barzh pep tra.\"\n},\n\"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);\");\n"
  },
  {
    "path": "l10n/br.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"an enklask en deus roet {total} disoc'h e {time} ms\",\n    \"Search\" : \"Klask\",\n    \"Index not found\" : \"Roll-gerioù kavet ebet\",\n    \"Process timed out\" : \"Poent termenn an argerzh\",\n    \"Full text search\" : \"Klask e tout an destenn\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kalon an enklask e tout an destenn er framm labour Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Meziant Kalon an enklask e tout un destenn er fram labour evit ho Nextcloud\",\n    \"Search on %s\" : \"Klask war %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Gwiriit er wiki evit an dielvadur liammet d'ar staliadur  hag an arventennañ eit ar c'hlasker e tout an destenn e barzh ho Nextcloud\",\n    \"General\" : \"Hollek\",\n    \"Search Platform\" : \"Klask pladenn\",\n    \"Select the app to index content and answer search queries.\" : \"Choazit ur meziant da lakaat er roll-gerioù ha respontit d'ar goulennoù enklask\",\n    \"Navigation Icon\" : \"Skeudennig Merdeiñ\",\n    \"Enable global search within all your content.\" : \"Aotreañ an enklask hollek e-barzh pep tra.\"\n},\"pluralForm\" :\"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);\"\n}"
  },
  {
    "path": "l10n/bs.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\"\n},\n\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\");\n"
  },
  {
    "path": "l10n/bs.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\"\n},\"pluralForm\" :\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\"\n}"
  },
  {
    "path": "l10n/ca.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la cerca ha retornat {total} resultats en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"La cerca de {title} de \\\"{search}\\\" ha donat {total} resultats en {time} ms\",\n    \"Search\" : \"Cerca\",\n    \"Index not found\" : \"Índex no trobat\",\n    \"Process timed out\" : \"Temps d'espera exhaurit\",\n    \"Full Text Search\" : \"Cerca de text complet\",\n    \"Full text search\" : \"Cerca de text complet\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nucli de l'estructura de cerca de text complet per a Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicació nucli de l'estructura de recerca de text complet per al vostre Nextcloud.\",\n    \"Search on %s\" : \"Cerca a %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Si us plau reviseu el wiki de documentació relacionada amb la instal·lació i configuració de la cerca de text complet dins del vostre Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de cerca\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona l'aplicació per indexar el contingut i respondre consultes de cerca.\",\n    \"Navigation Icon\" : \"Icona de navegació\",\n    \"Enable global search within all your content.\" : \"Habiliteu la cerca global dins de tots els vostres continguts.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ca.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la cerca ha retornat {total} resultats en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"La cerca de {title} de \\\"{search}\\\" ha donat {total} resultats en {time} ms\",\n    \"Search\" : \"Cerca\",\n    \"Index not found\" : \"Índex no trobat\",\n    \"Process timed out\" : \"Temps d'espera exhaurit\",\n    \"Full Text Search\" : \"Cerca de text complet\",\n    \"Full text search\" : \"Cerca de text complet\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nucli de l'estructura de cerca de text complet per a Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicació nucli de l'estructura de recerca de text complet per al vostre Nextcloud.\",\n    \"Search on %s\" : \"Cerca a %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Si us plau reviseu el wiki de documentació relacionada amb la instal·lació i configuració de la cerca de text complet dins del vostre Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de cerca\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona l'aplicació per indexar el contingut i respondre consultes de cerca.\",\n    \"Navigation Icon\" : \"Icona de navegació\",\n    \"Enable global search within all your content.\" : \"Habiliteu la cerca global dins de tots els vostres continguts.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/cs.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"vyhledávání nalezlo {total} výsledků v čase {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"vyhledávání v {title} pro výraz „{search}“ nalezlo {total} výsledků v čase {time} ms\",\n    \"Search\" : \"Hledat\",\n    \"Index not found\" : \"Nenalezen rejstřík\",\n    \"Process timed out\" : \"Překročen časový limit pro zpracování\",\n    \"Full Text Search\" : \"Plnotextové vyhledávání\",\n    \"Full text search\" : \"Plnotextové vyhledávání\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jádro aplikačního rámce pro plnotextové vyhledávání v Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Hlavní aplikace aplikačního rámce pro plnotextové hledání pro váš Nextcloud.\",\n    \"Search on %s\" : \"Hledat v %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ohledně instalace a nastavení plnotextového vyhledávání v Nextcloud nahlédněte do dokumentace (na wiki)\",\n    \"General\" : \"Obecné\",\n    \"Search Platform\" : \"Vyhledávací platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Vyberte aplikaci kterou indexovat obsah a zodpovídat dotazy vyhledávání.\",\n    \"Navigation Icon\" : \"Ikona navigace\",\n    \"Enable global search within all your content.\" : \"Zapnout globální vyhledávání ve veškerém vašem obsahu.\"\n},\n\"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\");\n"
  },
  {
    "path": "l10n/cs.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"vyhledávání nalezlo {total} výsledků v čase {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"vyhledávání v {title} pro výraz „{search}“ nalezlo {total} výsledků v čase {time} ms\",\n    \"Search\" : \"Hledat\",\n    \"Index not found\" : \"Nenalezen rejstřík\",\n    \"Process timed out\" : \"Překročen časový limit pro zpracování\",\n    \"Full Text Search\" : \"Plnotextové vyhledávání\",\n    \"Full text search\" : \"Plnotextové vyhledávání\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jádro aplikačního rámce pro plnotextové vyhledávání v Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Hlavní aplikace aplikačního rámce pro plnotextové hledání pro váš Nextcloud.\",\n    \"Search on %s\" : \"Hledat v %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ohledně instalace a nastavení plnotextového vyhledávání v Nextcloud nahlédněte do dokumentace (na wiki)\",\n    \"General\" : \"Obecné\",\n    \"Search Platform\" : \"Vyhledávací platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Vyberte aplikaci kterou indexovat obsah a zodpovídat dotazy vyhledávání.\",\n    \"Navigation Icon\" : \"Ikona navigace\",\n    \"Enable global search within all your content.\" : \"Zapnout globální vyhledávání ve veškerém vašem obsahu.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\"\n}"
  },
  {
    "path": "l10n/cy_GB.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Chwilio\",\n    \"General\" : \"Cyffredinol\"\n},\n\"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\");\n"
  },
  {
    "path": "l10n/cy_GB.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Chwilio\",\n    \"General\" : \"Cyffredinol\"\n},\"pluralForm\" :\"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\"\n}"
  },
  {
    "path": "l10n/da.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"søgningen retunerede {total} resultater og det tog {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"søgningen i {title} efter \\\"{search}\\\" gav {total} resultater på {time} ms\",\n    \"Search\" : \"Søg\",\n    \"Index not found\" : \"Index ikke fundet\",\n    \"Process timed out\" : \"Processen tog for lang tid\",\n    \"Full Text Search\" : \"Fuld Tekstsøgning\",\n    \"Full text search\" : \"Fuld tekst søgning\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kernen af det fulde tekstsøgningsframework til Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kerne App i fuldtekstsøgningsframeworket til din Nextcloud.\",\n    \"Search on %s\" : \"Søg på %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Kontroller venligst Wikien for dokumentation relateret til installationen og konfigurationen af Fuld tekstsøgning i din Nextcloud\",\n    \"General\" : \"Generelt\",\n    \"Search Platform\" : \"Søgeplatform\",\n    \"Select the app to index content and answer search queries.\" : \"Vælg app for at indeksere indhold og besvare søgeforespørgsler.\",\n    \"Navigation Icon\" : \"Navigationsikon\",\n    \"Enable global search within all your content.\" : \"Aktiver globale søgninger i alt dit indhold.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/da.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"søgningen retunerede {total} resultater og det tog {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"søgningen i {title} efter \\\"{search}\\\" gav {total} resultater på {time} ms\",\n    \"Search\" : \"Søg\",\n    \"Index not found\" : \"Index ikke fundet\",\n    \"Process timed out\" : \"Processen tog for lang tid\",\n    \"Full Text Search\" : \"Fuld Tekstsøgning\",\n    \"Full text search\" : \"Fuld tekst søgning\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kernen af det fulde tekstsøgningsframework til Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kerne App i fuldtekstsøgningsframeworket til din Nextcloud.\",\n    \"Search on %s\" : \"Søg på %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Kontroller venligst Wikien for dokumentation relateret til installationen og konfigurationen af Fuld tekstsøgning i din Nextcloud\",\n    \"General\" : \"Generelt\",\n    \"Search Platform\" : \"Søgeplatform\",\n    \"Select the app to index content and answer search queries.\" : \"Vælg app for at indeksere indhold og besvare søgeforespørgsler.\",\n    \"Navigation Icon\" : \"Navigationsikon\",\n    \"Enable global search within all your content.\" : \"Aktiver globale søgninger i alt dit indhold.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/de.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Die Suche ergab {total} Treffer in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Die Suche in {title} nach \\\"{search}\\\" ergab {total} Treffer in {time} ms\",\n    \"Search\" : \"Suchen\",\n    \"Index not found\" : \"Index nicht gefunden\",\n    \"Process timed out\" : \"Zeitüberschreitung bei der Verarbeitung\",\n    \"Full Text Search\" : \"Volltextsuche\",\n    \"Full text search\" : \"Volltextsuche\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kernstück des Volltextsuche-Frameworks für Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Haupt-App des Volltextsuche-Frameworks für Nextcloud.\",\n    \"Search on %s\" : \"Suche in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Informationen zur Installation und Konfiguration der Volltextsuche unter Nextcloud findest du im Wiki\",\n    \"General\" : \"Allgemein\",\n    \"Search Platform\" : \"Suchplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Wähle die App, die Inhalte indizieren und Such-Anfragen beantworten soll.\",\n    \"Navigation Icon\" : \"Navigations-Symbol\",\n    \"Enable global search within all your content.\" : \"Globale Suche über alle Inhalte aktivieren.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/de.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Die Suche ergab {total} Treffer in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Die Suche in {title} nach \\\"{search}\\\" ergab {total} Treffer in {time} ms\",\n    \"Search\" : \"Suchen\",\n    \"Index not found\" : \"Index nicht gefunden\",\n    \"Process timed out\" : \"Zeitüberschreitung bei der Verarbeitung\",\n    \"Full Text Search\" : \"Volltextsuche\",\n    \"Full text search\" : \"Volltextsuche\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kernstück des Volltextsuche-Frameworks für Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Haupt-App des Volltextsuche-Frameworks für Nextcloud.\",\n    \"Search on %s\" : \"Suche in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Informationen zur Installation und Konfiguration der Volltextsuche unter Nextcloud findest du im Wiki\",\n    \"General\" : \"Allgemein\",\n    \"Search Platform\" : \"Suchplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Wähle die App, die Inhalte indizieren und Such-Anfragen beantworten soll.\",\n    \"Navigation Icon\" : \"Navigations-Symbol\",\n    \"Enable global search within all your content.\" : \"Globale Suche über alle Inhalte aktivieren.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/de_DE.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Die Suche ergab {total} Treffer in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Die Suche in {title} nach \\\"{search}\\\" ergab {total} Treffer in {time} ms\",\n    \"Search\" : \"Suchen\",\n    \"Index not found\" : \"Index nicht gefunden\",\n    \"Process timed out\" : \"Zeitüberschreitung bei der Verarbeitung\",\n    \"Full Text Search\" : \"Volltextsuche\",\n    \"Full text search\" : \"Volltextsuche\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kern des Sytems für die Volltextsuche in Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Haupt-App für die Volltextsuche in Nextcloud\",\n    \"Search on %s\" : \"Suche in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Informationen zur Installation und Konfiguration der Volltextsuche unter Nextcloud finden Sie im Wiki\",\n    \"General\" : \"Allgemein\",\n    \"Search Platform\" : \"Suchplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Wählen Sie die App, die Inhalte indizieren und Suchanfragen beantworten soll.\",\n    \"Navigation Icon\" : \"Navigations-Symbol\",\n    \"Enable global search within all your content.\" : \"Globale Suche für alle Inhalte aktivieren.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/de_DE.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Die Suche ergab {total} Treffer in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Die Suche in {title} nach \\\"{search}\\\" ergab {total} Treffer in {time} ms\",\n    \"Search\" : \"Suchen\",\n    \"Index not found\" : \"Index nicht gefunden\",\n    \"Process timed out\" : \"Zeitüberschreitung bei der Verarbeitung\",\n    \"Full Text Search\" : \"Volltextsuche\",\n    \"Full text search\" : \"Volltextsuche\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kern des Sytems für die Volltextsuche in Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Haupt-App für die Volltextsuche in Nextcloud\",\n    \"Search on %s\" : \"Suche in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Informationen zur Installation und Konfiguration der Volltextsuche unter Nextcloud finden Sie im Wiki\",\n    \"General\" : \"Allgemein\",\n    \"Search Platform\" : \"Suchplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Wählen Sie die App, die Inhalte indizieren und Suchanfragen beantworten soll.\",\n    \"Navigation Icon\" : \"Navigations-Symbol\",\n    \"Enable global search within all your content.\" : \"Globale Suche für alle Inhalte aktivieren.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/el.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"η αναζήτηση επέστρεψε {total} αποτελέσματα σε {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"η αναζήτηση στο {title} για \\\"{search}\\\" επέστρεψε {total} αποτελέσματα σε {time} ms\",\n    \"Search\" : \"Αναζήτηση\",\n    \"Index not found\" : \"Δεν βρέθηκε ευρετήριο\",\n    \"Process timed out\" : \"Η επεξεργασία έληξε\",\n    \"Full Text Search\" : \"Αναζήτηση πλήρους κειμένου\",\n    \"Full text search\" : \"Πλήρης αναζήτηση κειμένου\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Βάση του πλαισίου αναζήτησης πλήρους κειμένου για το Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Βασική Εφαρμογή του πλαισίου αναζήτησης πλήρους κειμένου για το Nextcloud σας.\",\n    \"Search on %s\" : \"Αναζήτηση στο %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ελέγξτε το wiki για την τεκμηρίωση σχετικά με την εγκατάσταση και τη διαμόρφωση της αναζήτησης πλήρους κειμένου στο Nextcloud\",\n    \"General\" : \"Γενικά\",\n    \"Search Platform\" : \"Πλατφόρμα αναζήτησης\",\n    \"Select the app to index content and answer search queries.\" : \"Επιλέξτε την εφαρμογή για την ευρετηρίοποίηση περιεχομένου και απαντήσεων σε ερωτήματα αναζήτησης.\",\n    \"Navigation Icon\" : \"Εικονίδιο Πλοήγησης\",\n    \"Enable global search within all your content.\" : \"Ενεργοποιήστε την γενική αναζήτηση σε όλο το περιεχόμενό σας.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/el.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"η αναζήτηση επέστρεψε {total} αποτελέσματα σε {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"η αναζήτηση στο {title} για \\\"{search}\\\" επέστρεψε {total} αποτελέσματα σε {time} ms\",\n    \"Search\" : \"Αναζήτηση\",\n    \"Index not found\" : \"Δεν βρέθηκε ευρετήριο\",\n    \"Process timed out\" : \"Η επεξεργασία έληξε\",\n    \"Full Text Search\" : \"Αναζήτηση πλήρους κειμένου\",\n    \"Full text search\" : \"Πλήρης αναζήτηση κειμένου\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Βάση του πλαισίου αναζήτησης πλήρους κειμένου για το Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Βασική Εφαρμογή του πλαισίου αναζήτησης πλήρους κειμένου για το Nextcloud σας.\",\n    \"Search on %s\" : \"Αναζήτηση στο %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ελέγξτε το wiki για την τεκμηρίωση σχετικά με την εγκατάσταση και τη διαμόρφωση της αναζήτησης πλήρους κειμένου στο Nextcloud\",\n    \"General\" : \"Γενικά\",\n    \"Search Platform\" : \"Πλατφόρμα αναζήτησης\",\n    \"Select the app to index content and answer search queries.\" : \"Επιλέξτε την εφαρμογή για την ευρετηρίοποίηση περιεχομένου και απαντήσεων σε ερωτήματα αναζήτησης.\",\n    \"Navigation Icon\" : \"Εικονίδιο Πλοήγησης\",\n    \"Enable global search within all your content.\" : \"Ενεργοποιήστε την γενική αναζήτηση σε όλο το περιεχόμενό σας.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/en_GB.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"the search returned {total} results in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"Search\",\n    \"Index not found\" : \"Index not found\",\n    \"Process timed out\" : \"Process timed out\",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"Full text search\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Core of the full-text search framework for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Core App of the full-text search framework for your Nextcloud.\",\n    \"Search on %s\" : \"Search on %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Search Platform\",\n    \"Select the app to index content and answer search queries.\" : \"Select the app to index content and answer search queries.\",\n    \"Navigation Icon\" : \"Navigation Icon\",\n    \"Enable global search within all your content.\" : \"Enable global search within all your content.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/en_GB.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"the search returned {total} results in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"Search\",\n    \"Index not found\" : \"Index not found\",\n    \"Process timed out\" : \"Process timed out\",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"Full text search\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Core of the full-text search framework for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Core App of the full-text search framework for your Nextcloud.\",\n    \"Search on %s\" : \"Search on %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Search Platform\",\n    \"Select the app to index content and answer search queries.\" : \"Select the app to index content and answer search queries.\",\n    \"Navigation Icon\" : \"Navigation Icon\",\n    \"Enable global search within all your content.\" : \"Enable global search within all your content.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/eo.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la serĉo liveris {total} rezultojn en {time} ms\",\n    \"Search\" : \"Serĉi\",\n    \"Index not found\" : \"Indekso ne trovita\",\n    \"Process timed out\" : \"Procezo eltempiĝis\",\n    \"Full text search\" : \"Plenteksta serĉo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Baza framo por plenteksta serĉo en Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Baza aplikaĵo pri framo por plenteksta serĉo en Nextcloud\",\n    \"Search on %s\" : \"Serĉi ĉe %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Bv. vidi la vikion pri dokumentaro pri instalado kaj agordo de la plenteksta serĉo ene de via Nextcloud.\",\n    \"General\" : \"Ĝenerala\",\n    \"Search Platform\" : \"Serĉa platformo\",\n    \"Select the app to index content and answer search queries.\" : \"Elekti la aplikaĵon, kiu indeksos la enhavon kaj respondos serĉopetojn.\",\n    \"Navigation Icon\" : \"Navigada piktogramo\",\n    \"Enable global search within all your content.\" : \"Ebligi serĉon ene de ĉiuj viaj enhavoj.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/eo.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la serĉo liveris {total} rezultojn en {time} ms\",\n    \"Search\" : \"Serĉi\",\n    \"Index not found\" : \"Indekso ne trovita\",\n    \"Process timed out\" : \"Procezo eltempiĝis\",\n    \"Full text search\" : \"Plenteksta serĉo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Baza framo por plenteksta serĉo en Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Baza aplikaĵo pri framo por plenteksta serĉo en Nextcloud\",\n    \"Search on %s\" : \"Serĉi ĉe %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Bv. vidi la vikion pri dokumentaro pri instalado kaj agordo de la plenteksta serĉo ene de via Nextcloud.\",\n    \"General\" : \"Ĝenerala\",\n    \"Search Platform\" : \"Serĉa platformo\",\n    \"Select the app to index content and answer search queries.\" : \"Elekti la aplikaĵon, kiu indeksos la enhavon kaj respondos serĉopetojn.\",\n    \"Navigation Icon\" : \"Navigada piktogramo\",\n    \"Enable global search within all your content.\" : \"Ebligi serĉon ene de ĉiuj viaj enhavoj.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/es.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la búsqueda encontró {total} resultados en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la búsqueda en {title} para \\\"{search}\\\" devolvió {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Índice no encontrado\",\n    \"Process timed out\" : \"Tiempo de proceso agotado\",\n    \"Full Text Search\" : \"Búsqueda de texto completo\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo de la infraestructura de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"App núcleo de la infraestructura de búsqueda de texto completo para Nextcloud\",\n    \"Search on %s\" : \"Buscar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Por favor, comprueba la wiki para encontrar documentación relacionada con al instalación y la configuración de la búsqueda de texto completo en tu Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de búsqueda\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona la app para indexar contenido y responder a peticiones de búsqueda\",\n    \"Navigation Icon\" : \"Icono de navegación\",\n    \"Enable global search within all your content.\" : \"Activa la búsqueda global en todo tu contenido.\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la búsqueda encontró {total} resultados en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la búsqueda en {title} para \\\"{search}\\\" devolvió {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Índice no encontrado\",\n    \"Process timed out\" : \"Tiempo de proceso agotado\",\n    \"Full Text Search\" : \"Búsqueda de texto completo\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo de la infraestructura de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"App núcleo de la infraestructura de búsqueda de texto completo para Nextcloud\",\n    \"Search on %s\" : \"Buscar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Por favor, comprueba la wiki para encontrar documentación relacionada con al instalación y la configuración de la búsqueda de texto completo en tu Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de búsqueda\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona la app para indexar contenido y responder a peticiones de búsqueda\",\n    \"Navigation Icon\" : \"Icono de navegación\",\n    \"Enable global search within all your content.\" : \"Activa la búsqueda global en todo tu contenido.\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_419.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_419.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_AR.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Índice no encontrado\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación principal del marco de búsqueda de texto completo para su Nextcloud.\",\n    \"Search on %s\" : \"Busca en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Consulte la wiki para obtener documentación relacionada con la instalación y la configuración de la búsqueda de texto completo en su Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Buscar Plataforma\",\n    \"Select the app to index content and answer search queries.\" : \"Seleccione la aplicación para indexar contenido y responder consultas de búsqueda.\",\n    \"Navigation Icon\" : \"Icono de navegación\",\n    \"Enable global search within all your content.\" : \"Habilita la búsqueda global dentro de todo tu contenido.\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_AR.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Índice no encontrado\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación principal del marco de búsqueda de texto completo para su Nextcloud.\",\n    \"Search on %s\" : \"Busca en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Consulte la wiki para obtener documentación relacionada con la instalación y la configuración de la búsqueda de texto completo en su Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Buscar Plataforma\",\n    \"Select the app to index content and answer search queries.\" : \"Seleccione la aplicación para indexar contenido y responder consultas de búsqueda.\",\n    \"Navigation Icon\" : \"Icono de navegación\",\n    \"Enable global search within all your content.\" : \"Habilita la búsqueda global dentro de todo tu contenido.\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_CL.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_CL.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_CO.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_CO.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_CR.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_CR.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_DO.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_DO.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_EC.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la búsqueda arrojó {total} resultados en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la búsqueda en {title} para \\\"{search}\\\" arrojó {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Process timed out\" : \"Proceso agotado\",\n    \"Full Text Search\" : \"Búsqueda de Texto Completo\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación principal del marco de búsqueda de texto completo para tu Nextcloud.\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Por favor, consulta la wiki para obtener documentación relacionada con la instalación y la configuración de la búsqueda de texto completo en tu Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de Búsqueda\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona la aplicación para indexar el contenido y responder a las consultas de búsqueda.\",\n    \"Navigation Icon\" : \"Icono de Navegación\",\n    \"Enable global search within all your content.\" : \"Habilita la búsqueda global dentro de todo tu contenido.\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_EC.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la búsqueda arrojó {total} resultados en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la búsqueda en {title} para \\\"{search}\\\" arrojó {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Process timed out\" : \"Proceso agotado\",\n    \"Full Text Search\" : \"Búsqueda de Texto Completo\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de búsqueda de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación principal del marco de búsqueda de texto completo para tu Nextcloud.\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Por favor, consulta la wiki para obtener documentación relacionada con la instalación y la configuración de la búsqueda de texto completo en tu Nextcloud\",\n    \"General\" : \"General\",\n    \"Search Platform\" : \"Plataforma de Búsqueda\",\n    \"Select the app to index content and answer search queries.\" : \"Selecciona la aplicación para indexar el contenido y responder a las consultas de búsqueda.\",\n    \"Navigation Icon\" : \"Icono de Navegación\",\n    \"Enable global search within all your content.\" : \"Habilita la búsqueda global dentro de todo tu contenido.\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_GT.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_GT.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_HN.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_HN.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_MX.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de trabajo de la búsqueda por texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación núcleo del marco de trabajo de la búsqueda por texto completo para Nextcloud\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_MX.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo del marco de trabajo de la búsqueda por texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicación núcleo del marco de trabajo de la búsqueda por texto completo para Nextcloud\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_NI.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_NI.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_PA.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_PA.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_PE.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_PE.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_PR.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_PR.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_PY.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_PY.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_SV.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_SV.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"No se encontró el índice\",\n    \"Full text search\" : \"Búsqueda de texto completo\",\n    \"Search on %s\" : \"Busar en %s\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/es_UY.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/es_UY.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Buscar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/et_EE.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Otsing kestusega {time} ms andis {total} vastust\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"„{search}“ märksõna otsing asukohas „{title}“ andis {total} vastust. Otsingu kestus {time}\",\n    \"Search\" : \"Otsi\",\n    \"Index not found\" : \"Indeksit ei leidu\",\n    \"Process timed out\" : \"Protsess aegus\",\n    \"Full Text Search\" : \"Täistekstotsing\",\n    \"Full text search\" : \"Täistekstotsing\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloudi täistekstotsingu raamistiku tuum\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Sinu Nextcloudi serveri täistekstotsingu raamistiku tuumikrakendus.\",\n    \"Search on %s\" : \"Otsi siin: %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Täistekstotsingu paigaldamise ja seadistamise kohta oma Nextcloudi serveris leiad lisateavet vikist\",\n    \"General\" : \"Üldine\",\n    \"Search Platform\" : \"Otsinguplatvorm\",\n    \"Select the app to index content and answer search queries.\" : \"Vali rakendus sisu indekseerimiseks ja vastamiseks otsingupäringutele.\",\n    \"Navigation Icon\" : \"Menüüikoon\",\n    \"Enable global search within all your content.\" : \"Kasuta üldist otsingut kogu oma sisust.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/et_EE.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Otsing kestusega {time} ms andis {total} vastust\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"„{search}“ märksõna otsing asukohas „{title}“ andis {total} vastust. Otsingu kestus {time}\",\n    \"Search\" : \"Otsi\",\n    \"Index not found\" : \"Indeksit ei leidu\",\n    \"Process timed out\" : \"Protsess aegus\",\n    \"Full Text Search\" : \"Täistekstotsing\",\n    \"Full text search\" : \"Täistekstotsing\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloudi täistekstotsingu raamistiku tuum\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Sinu Nextcloudi serveri täistekstotsingu raamistiku tuumikrakendus.\",\n    \"Search on %s\" : \"Otsi siin: %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Täistekstotsingu paigaldamise ja seadistamise kohta oma Nextcloudi serveris leiad lisateavet vikist\",\n    \"General\" : \"Üldine\",\n    \"Search Platform\" : \"Otsinguplatvorm\",\n    \"Select the app to index content and answer search queries.\" : \"Vali rakendus sisu indekseerimiseks ja vastamiseks otsingupäringutele.\",\n    \"Navigation Icon\" : \"Menüüikoon\",\n    \"Enable global search within all your content.\" : \"Kasuta üldist otsingut kogu oma sisust.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/eu.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"bilaketak {total} emaitza itzuli ditu {time} ms-tan\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title}-n bilaketak \\\"{search}\\\" bilaketarako {total} emaitza eman ditu {time} ms-tan\",\n    \"Search\" : \"Bilatu\",\n    \"Index not found\" : \"Indizea ez da aurkitu\",\n    \"Process timed out\" : \"Prozesua denboraz kanpo\",\n    \"Full Text Search\" : \"Testu osoko bilaketa\",\n    \"Full text search\" : \"Testu osoko bilaketa\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloudentzat testu bilaketa ingurunearen bihotza\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Zure Nextcloudentzat testu bilaketa ingurunearen aplikazioa\",\n    \"Search on %s\" : \"Bilatu %s-(e)an\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Irakurri wikia zure Nextclouden testu bilaketa instalatu eta konfiguratzeko.\",\n    \"General\" : \"Orokorra\",\n    \"Search Platform\" : \"Bilaketa plataforma\",\n    \"Select the app to index content and answer search queries.\" : \"Aukeratu zein aplikaziok indexatu eta erantzungo dituen duen zure edukiaren inguruko bilaketak.\",\n    \"Navigation Icon\" : \"Nabigazioaren ikonoa\",\n    \"Enable global search within all your content.\" : \"Aktibatu zure eduki guztian bilatzea.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/eu.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"bilaketak {total} emaitza itzuli ditu {time} ms-tan\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title}-n bilaketak \\\"{search}\\\" bilaketarako {total} emaitza eman ditu {time} ms-tan\",\n    \"Search\" : \"Bilatu\",\n    \"Index not found\" : \"Indizea ez da aurkitu\",\n    \"Process timed out\" : \"Prozesua denboraz kanpo\",\n    \"Full Text Search\" : \"Testu osoko bilaketa\",\n    \"Full text search\" : \"Testu osoko bilaketa\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloudentzat testu bilaketa ingurunearen bihotza\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Zure Nextcloudentzat testu bilaketa ingurunearen aplikazioa\",\n    \"Search on %s\" : \"Bilatu %s-(e)an\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Irakurri wikia zure Nextclouden testu bilaketa instalatu eta konfiguratzeko.\",\n    \"General\" : \"Orokorra\",\n    \"Search Platform\" : \"Bilaketa plataforma\",\n    \"Select the app to index content and answer search queries.\" : \"Aukeratu zein aplikaziok indexatu eta erantzungo dituen duen zure edukiaren inguruko bilaketak.\",\n    \"Navigation Icon\" : \"Nabigazioaren ikonoa\",\n    \"Enable global search within all your content.\" : \"Aktibatu zure eduki guztian bilatzea.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/fa.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"بازگشت جست‌‌‌وجو‌ {کل} نتایج در {زمان}\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"جستجو\",\n    \"Index not found\" : \"نمایه یافت نشد\",\n    \"Process timed out\" : \"زمان فرآیند به پایان رسیده \",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"جستجوی کامل متن\",\n    \"Core of the full-text search framework for Nextcloud\" : \"هسته‌ی چهارچوب جست‌وجوی متن کامل برای نکست کلود\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"برنامه‌ی اصلی چارچوبِ جستجوی متن کامل برای نکست کلود شما\",\n    \"Search on %s\" : \"%s جستجو در ‍\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"لطفاً در ویکی پدیا برای مستندات مربوط به نصب و پیکربندی جستجوی متن کامل در نکست کلود خود بررسی کنید\",\n    \"General\" : \"عمومی\",\n    \"Search Platform\" : \"پلتفرم جستجو\",\n    \"Select the app to index content and answer search queries.\" : \"برنامه را برای فهرست بندی محتوا و پاسخ به سؤالات جستجو انتخاب کنید\",\n    \"Navigation Icon\" : \"نماد ناوبری\",\n    \"Enable global search within all your content.\" : \"جستجوی جهانی را در تمام محتوای خود فعال کن\"\n},\n\"nplurals=2; plural=(n > 1);\");\n"
  },
  {
    "path": "l10n/fa.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"بازگشت جست‌‌‌وجو‌ {کل} نتایج در {زمان}\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"جستجو\",\n    \"Index not found\" : \"نمایه یافت نشد\",\n    \"Process timed out\" : \"زمان فرآیند به پایان رسیده \",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"جستجوی کامل متن\",\n    \"Core of the full-text search framework for Nextcloud\" : \"هسته‌ی چهارچوب جست‌وجوی متن کامل برای نکست کلود\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"برنامه‌ی اصلی چارچوبِ جستجوی متن کامل برای نکست کلود شما\",\n    \"Search on %s\" : \"%s جستجو در ‍\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"لطفاً در ویکی پدیا برای مستندات مربوط به نصب و پیکربندی جستجوی متن کامل در نکست کلود خود بررسی کنید\",\n    \"General\" : \"عمومی\",\n    \"Search Platform\" : \"پلتفرم جستجو\",\n    \"Select the app to index content and answer search queries.\" : \"برنامه را برای فهرست بندی محتوا و پاسخ به سؤالات جستجو انتخاب کنید\",\n    \"Navigation Icon\" : \"نماد ناوبری\",\n    \"Enable global search within all your content.\" : \"جستجوی جهانی را در تمام محتوای خود فعال کن\"\n},\"pluralForm\" :\"nplurals=2; plural=(n > 1);\"\n}"
  },
  {
    "path": "l10n/fi.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"haku palautti {total} tulosta ajassa {time} ms\",\n    \"Search\" : \"Haku\",\n    \"Index not found\" : \"Indeksiä ei löydy\",\n    \"Process timed out\" : \"Toiminto aikakatkaistiin\",\n    \"Full Text Search\" : \"Kokotekstihaku\",\n    \"Full text search\" : \"Kokotekstihaku\",\n    \"Search on %s\" : \"Etsi kohteesta %s\",\n    \"General\" : \"Yleiset\",\n    \"Search Platform\" : \"Hakualusta\",\n    \"Select the app to index content and answer search queries.\" : \"Valitse sovellus, jota käytetään sisällön indeksointiin ja hakukyselyihin vastaamiseen.\",\n    \"Navigation Icon\" : \"Valikon kuvake\",\n    \"Enable global search within all your content.\" : \"Ota käyttöön globaali haku sisällöstäsi.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/fi.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"haku palautti {total} tulosta ajassa {time} ms\",\n    \"Search\" : \"Haku\",\n    \"Index not found\" : \"Indeksiä ei löydy\",\n    \"Process timed out\" : \"Toiminto aikakatkaistiin\",\n    \"Full Text Search\" : \"Kokotekstihaku\",\n    \"Full text search\" : \"Kokotekstihaku\",\n    \"Search on %s\" : \"Etsi kohteesta %s\",\n    \"General\" : \"Yleiset\",\n    \"Search Platform\" : \"Hakualusta\",\n    \"Select the app to index content and answer search queries.\" : \"Valitse sovellus, jota käytetään sisällön indeksointiin ja hakukyselyihin vastaamiseen.\",\n    \"Navigation Icon\" : \"Valikon kuvake\",\n    \"Enable global search within all your content.\" : \"Ota käyttöön globaali haku sisällöstäsi.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/fr.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la recherche a retourné {total} résultat(s) en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la recherche dans {title} pour les mots clés \\\"{search}\\\" a retourné {total} résultat(s) en {time} ms\",\n    \"Search\" : \"Recherche\",\n    \"Index not found\" : \"Index non trouvé\",\n    \"Process timed out\" : \"Processus expiré\",\n    \"Full Text Search\" : \"Recherche plein texte\",\n    \"Full text search\" : \"Recherche de texte intégral\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Noyau du module de recherche de texte intégral pour Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Noyau de l'application du module de recherche de texte intégral pour votre Nextcloud\",\n    \"Search on %s\" : \"Recherche sur %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Merci de consulter le wiki pour la documentation sur l'installation et la configuration de la recherche en texte intégral dans votre instance Nextcloud\",\n    \"General\" : \"Général\",\n    \"Search Platform\" : \"Plateforme de recherche\",\n    \"Select the app to index content and answer search queries.\" : \"Sélectionner l'application pour indexer le contenu et répondre à des recherches.\",\n    \"Navigation Icon\" : \"Icône de navigation\",\n    \"Enable global search within all your content.\" : \"Activer la recherche dans tout votre contenu.\"\n},\n\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/fr.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la recherche a retourné {total} résultat(s) en {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la recherche dans {title} pour les mots clés \\\"{search}\\\" a retourné {total} résultat(s) en {time} ms\",\n    \"Search\" : \"Recherche\",\n    \"Index not found\" : \"Index non trouvé\",\n    \"Process timed out\" : \"Processus expiré\",\n    \"Full Text Search\" : \"Recherche plein texte\",\n    \"Full text search\" : \"Recherche de texte intégral\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Noyau du module de recherche de texte intégral pour Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Noyau de l'application du module de recherche de texte intégral pour votre Nextcloud\",\n    \"Search on %s\" : \"Recherche sur %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Merci de consulter le wiki pour la documentation sur l'installation et la configuration de la recherche en texte intégral dans votre instance Nextcloud\",\n    \"General\" : \"Général\",\n    \"Search Platform\" : \"Plateforme de recherche\",\n    \"Select the app to index content and answer search queries.\" : \"Sélectionner l'application pour indexer le contenu et répondre à des recherches.\",\n    \"Navigation Icon\" : \"Icône de navigation\",\n    \"Enable global search within all your content.\" : \"Activer la recherche dans tout votre contenu.\"\n},\"pluralForm\" :\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/ga.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"fuair an cuardach {total} torthaí in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"thug an cuardach i {title} le haghaidh \\\"{search}\\\" torthaí {total} ar ais in {time} ms\",\n    \"Search\" : \"Cuardach\",\n    \"Index not found\" : \"Innéacs gan aimsiú\",\n    \"Process timed out\" : \"Chuaigh an próiseas thar am\",\n    \"Full Text Search\" : \"Cuardach Téacs Iomlán\",\n    \"Full text search\" : \"Cuardach téacs iomlán\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Croílár an chreata cuardaigh téacs iomlán do Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aip lárnach den chreat cuardaigh téacs iomlán do do Nextcloud.\",\n    \"Search on %s\" : \"Cuardaigh ar %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Seiceáil an vicí le haghaidh doiciméadú a bhaineann le suiteáil agus cumraíocht an chuardaigh téacs iomlán laistigh de do Nextcloud le do thoil\",\n    \"General\" : \"Ginearálta\",\n    \"Search Platform\" : \"Ardán Cuardaigh\",\n    \"Select the app to index content and answer search queries.\" : \"Roghnaigh an aip chun ábhar a innéacsú agus chun ceisteanna cuardaigh a fhreagairt.\",\n    \"Navigation Icon\" : \"Deilbhín Nascleanúna\",\n    \"Enable global search within all your content.\" : \"Cumasaigh cuardach domhanda laistigh d'ábhar go léir.\"\n},\n\"nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\");\n"
  },
  {
    "path": "l10n/ga.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"fuair an cuardach {total} torthaí in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"thug an cuardach i {title} le haghaidh \\\"{search}\\\" torthaí {total} ar ais in {time} ms\",\n    \"Search\" : \"Cuardach\",\n    \"Index not found\" : \"Innéacs gan aimsiú\",\n    \"Process timed out\" : \"Chuaigh an próiseas thar am\",\n    \"Full Text Search\" : \"Cuardach Téacs Iomlán\",\n    \"Full text search\" : \"Cuardach téacs iomlán\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Croílár an chreata cuardaigh téacs iomlán do Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aip lárnach den chreat cuardaigh téacs iomlán do do Nextcloud.\",\n    \"Search on %s\" : \"Cuardaigh ar %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Seiceáil an vicí le haghaidh doiciméadú a bhaineann le suiteáil agus cumraíocht an chuardaigh téacs iomlán laistigh de do Nextcloud le do thoil\",\n    \"General\" : \"Ginearálta\",\n    \"Search Platform\" : \"Ardán Cuardaigh\",\n    \"Select the app to index content and answer search queries.\" : \"Roghnaigh an aip chun ábhar a innéacsú agus chun ceisteanna cuardaigh a fhreagairt.\",\n    \"Navigation Icon\" : \"Deilbhín Nascleanúna\",\n    \"Enable global search within all your content.\" : \"Cumasaigh cuardach domhanda laistigh d'ábhar go léir.\"\n},\"pluralForm\" :\"nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\"\n}"
  },
  {
    "path": "l10n/gd.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Lorg\",\n    \"General\" : \"Coitcheann\"\n},\n\"nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;\");\n"
  },
  {
    "path": "l10n/gd.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Lorg\",\n    \"General\" : \"Coitcheann\"\n},\"pluralForm\" :\"nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;\"\n}"
  },
  {
    "path": "l10n/gl.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"a busca devolveu {total} resultados en {time} ms.\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a busca en {title} de «{search}»  devolveu {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Non se atopou o índice\",\n    \"Process timed out\" : \"Esgotouse o tempo do proceso\",\n    \"Full Text Search\" : \"Busca de texto completo\",\n    \"Full text search\" : \"Busca de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo do marco de traballo de busca de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Núcleo da aplicación do marco de traballo de busca de texto completo para o seu Nextcloud\",\n    \"Search on %s\" : \"Buscar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Consulte a wiki para ver documentación relacionada coa instalación e configuración da busca de texto completo no seu Nextcloud\",\n    \"General\" : \"Xeral\",\n    \"Search Platform\" : \"Plataforma de buscas\",\n    \"Select the app to index content and answer search queries.\" : \"Seleccionar a aplicación para indexar o contido e responder a peticións de buscas\",\n    \"Navigation Icon\" : \"Icona de navegación\",\n    \"Enable global search within all your content.\" : \"Activa a busca global en todo o seu contido.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/gl.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"a busca devolveu {total} resultados en {time} ms.\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a busca en {title} de «{search}»  devolveu {total} resultados en {time} ms\",\n    \"Search\" : \"Buscar\",\n    \"Index not found\" : \"Non se atopou o índice\",\n    \"Process timed out\" : \"Esgotouse o tempo do proceso\",\n    \"Full Text Search\" : \"Busca de texto completo\",\n    \"Full text search\" : \"Busca de texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo do marco de traballo de busca de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Núcleo da aplicación do marco de traballo de busca de texto completo para o seu Nextcloud\",\n    \"Search on %s\" : \"Buscar en %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Consulte a wiki para ver documentación relacionada coa instalación e configuración da busca de texto completo no seu Nextcloud\",\n    \"General\" : \"Xeral\",\n    \"Search Platform\" : \"Plataforma de buscas\",\n    \"Select the app to index content and answer search queries.\" : \"Seleccionar a aplicación para indexar o contido e responder a peticións de buscas\",\n    \"Navigation Icon\" : \"Icona de navegación\",\n    \"Enable global search within all your content.\" : \"Activa a busca global en todo o seu contido.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/he.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"החיפוש החזיר {total} תוצאות תוך {time} מילישניות\",\n    \"Search\" : \"חיפוש\",\n    \"Index not found\" : \"המפתח לא נמצא\",\n    \"Process timed out\" : \"עבר הזמן שהוקצב לתהליך\",\n    \"Full Text Search\" : \"חיפוש טקסט מלא\",\n    \"Full text search\" : \"חיפוש טקסט מלא\",\n    \"Core of the full-text search framework for Nextcloud\" : \"ליבת סביבת עבודת חיפוש טקסט מלא עבור Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"יישומון ליבה של סביבת עבודה לחיפוש טקסט מלא עבור ה־Nextcloud שלך.\",\n    \"Search on %s\" : \"חיפוש תחת %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"נא לנסות לאתר בוויקי את התיעוד שקשור להתקנה ולהגדרה של חיפוש טקסט מלא בתוך ה־Nextcloud שלך.\",\n    \"General\" : \"כללי\",\n    \"Search Platform\" : \"פלטפורמת חיפוש\",\n    \"Select the app to index content and answer search queries.\" : \"נא לבחור את היישומון לסידור תוכן באינדקס ולענות על שאילתות חיפוש.\",\n    \"Navigation Icon\" : \"סמל ניווט\",\n    \"Enable global search within all your content.\" : \"הפעלת חיפוש גלובלי על כל התוכן שלך.\"\n},\n\"nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\");\n"
  },
  {
    "path": "l10n/he.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"החיפוש החזיר {total} תוצאות תוך {time} מילישניות\",\n    \"Search\" : \"חיפוש\",\n    \"Index not found\" : \"המפתח לא נמצא\",\n    \"Process timed out\" : \"עבר הזמן שהוקצב לתהליך\",\n    \"Full Text Search\" : \"חיפוש טקסט מלא\",\n    \"Full text search\" : \"חיפוש טקסט מלא\",\n    \"Core of the full-text search framework for Nextcloud\" : \"ליבת סביבת עבודת חיפוש טקסט מלא עבור Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"יישומון ליבה של סביבת עבודה לחיפוש טקסט מלא עבור ה־Nextcloud שלך.\",\n    \"Search on %s\" : \"חיפוש תחת %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"נא לנסות לאתר בוויקי את התיעוד שקשור להתקנה ולהגדרה של חיפוש טקסט מלא בתוך ה־Nextcloud שלך.\",\n    \"General\" : \"כללי\",\n    \"Search Platform\" : \"פלטפורמת חיפוש\",\n    \"Select the app to index content and answer search queries.\" : \"נא לבחור את היישומון לסידור תוכן באינדקס ולענות על שאילתות חיפוש.\",\n    \"Navigation Icon\" : \"סמל ניווט\",\n    \"Enable global search within all your content.\" : \"הפעלת חיפוש גלובלי על כל התוכן שלך.\"\n},\"pluralForm\" :\"nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\"\n}"
  },
  {
    "path": "l10n/hr.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"pretraživanjem je pronađeno {total} rezultata za {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"pretraživanjem u {title} za „{search}“ pronađeno je {total} rezultata za {time} ms\",\n    \"Search\" : \"Traži\",\n    \"Index not found\" : \"Indeks nije pronađen\",\n    \"Process timed out\" : \"Isteklo vrijeme postupka\",\n    \"Full Text Search\" : \"Pretraživanje cijelog teksta\",\n    \"Full text search\" : \"Pretraživanje cijelog teksta\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jezgra okvira za pretraživanje cijelog teksta za Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Osnovna aplikacija za pretraživanje cijelog teksta za Nextcloud.\",\n    \"Search on %s\" : \"Pretraži na %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Provjerite wiki za dokumentaciju koja se odnosi na instalaciju i konfiguraciju pretraživanja cijelog teksta unutar Nextclouda\",\n    \"General\" : \"Općenito\",\n    \"Search Platform\" : \"Platforma za pretraživanje\",\n    \"Select the app to index content and answer search queries.\" : \"Odaberite aplikaciju za indeksiranje sadržaja i odgovorite na upite pretraživanja.\",\n    \"Navigation Icon\" : \"Navigacijska ikona\",\n    \"Enable global search within all your content.\" : \"Omogućite globalno pretraživanje cijelog sadržaja.\"\n},\n\"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/hr.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"pretraživanjem je pronađeno {total} rezultata za {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"pretraživanjem u {title} za „{search}“ pronađeno je {total} rezultata za {time} ms\",\n    \"Search\" : \"Traži\",\n    \"Index not found\" : \"Indeks nije pronađen\",\n    \"Process timed out\" : \"Isteklo vrijeme postupka\",\n    \"Full Text Search\" : \"Pretraživanje cijelog teksta\",\n    \"Full text search\" : \"Pretraživanje cijelog teksta\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jezgra okvira za pretraživanje cijelog teksta za Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Osnovna aplikacija za pretraživanje cijelog teksta za Nextcloud.\",\n    \"Search on %s\" : \"Pretraži na %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Provjerite wiki za dokumentaciju koja se odnosi na instalaciju i konfiguraciju pretraživanja cijelog teksta unutar Nextclouda\",\n    \"General\" : \"Općenito\",\n    \"Search Platform\" : \"Platforma za pretraživanje\",\n    \"Select the app to index content and answer search queries.\" : \"Odaberite aplikaciju za indeksiranje sadržaja i odgovorite na upite pretraživanja.\",\n    \"Navigation Icon\" : \"Navigacijska ikona\",\n    \"Enable global search within all your content.\" : \"Omogućite globalno pretraživanje cijelog sadržaja.\"\n},\"pluralForm\" :\"nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/hu.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"a keresés {total} találatot adott vissza {time} ms alatt\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a(z) {title} elemben, a(z) „{search}” keresés {total} találatot adott vissza {time} ms alatt\",\n    \"Search\" : \"Keresés\",\n    \"Index not found\" : \"Index nem található\",\n    \"Process timed out\" : \"A folyamatban időtúllépés történt\",\n    \"Full Text Search\" : \"Teljes szöveges keresés\",\n    \"Full text search\" : \"Teljes szöveges keresés\",\n    \"Core of the full-text search framework for Nextcloud\" : \"A Nextcloud teljes szövegű keresési keretrendszerének magja\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"A Nextcloud teljes szöveges keresési keretrendszerének alapalkalmazása.\",\n    \"Search on %s\" : \"Keresés itt: %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ellenőrizze a wikiben a telepítéssel kapcsolatos dokumentációt, és a Nextcloud teljes szöveges keresésének konfigurációját\",\n    \"General\" : \"Általános\",\n    \"Search Platform\" : \"Keresési platform\",\n    \"Select the app to index content and answer search queries.\" : \"Válassza ki az alkalmazást, amely indexeli a tartalmat és válaszol a keresési kérésekre.\",\n    \"Navigation Icon\" : \"Navigációs ikon\",\n    \"Enable global search within all your content.\" : \"Globális keresés engedélyezése az összes tartalomban.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/hu.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"a keresés {total} találatot adott vissza {time} ms alatt\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a(z) {title} elemben, a(z) „{search}” keresés {total} találatot adott vissza {time} ms alatt\",\n    \"Search\" : \"Keresés\",\n    \"Index not found\" : \"Index nem található\",\n    \"Process timed out\" : \"A folyamatban időtúllépés történt\",\n    \"Full Text Search\" : \"Teljes szöveges keresés\",\n    \"Full text search\" : \"Teljes szöveges keresés\",\n    \"Core of the full-text search framework for Nextcloud\" : \"A Nextcloud teljes szövegű keresési keretrendszerének magja\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"A Nextcloud teljes szöveges keresési keretrendszerének alapalkalmazása.\",\n    \"Search on %s\" : \"Keresés itt: %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Ellenőrizze a wikiben a telepítéssel kapcsolatos dokumentációt, és a Nextcloud teljes szöveges keresésének konfigurációját\",\n    \"General\" : \"Általános\",\n    \"Search Platform\" : \"Keresési platform\",\n    \"Select the app to index content and answer search queries.\" : \"Válassza ki az alkalmazást, amely indexeli a tartalmat és válaszol a keresési kérésekre.\",\n    \"Navigation Icon\" : \"Navigációs ikon\",\n    \"Enable global search within all your content.\" : \"Globális keresés engedélyezése az összes tartalomban.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/hy.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"Ընդհանուր\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/hy.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"Ընդհանուր\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/ia.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Cercar\",\n    \"General\" : \"General\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ia.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Cercar\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/id.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"pencarian mengembalikan {total} hasil dalam {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"pencarian di {title} untuk \\\"{search}\\\" mengembalikan {total} hasil dalam {time} ms\",\n    \"Search\" : \"Cari\",\n    \"Index not found\" : \"Indeks tidak ditemukan\",\n    \"Process timed out\" : \"Proses kehabisan waktu\",\n    \"Full Text Search\" : \"Pencarian Teks Penuh\",\n    \"Full text search\" : \"pencarian teks penuh\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Inti dari kerangka kerja pencarian teks penuh untuk Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplikasi inti dari kerangka kerja pencarian teks penuh untuk Nextcloud Anda.\",\n    \"Search on %s\" : \"Cari di %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Silakan periksa wiki untuk dokumentasi terkait instalasi dan konfigurasi pencarian teks penuh di Nextcloud Anda\",\n    \"General\" : \"Umum\",\n    \"Search Platform\" : \"Platform Pencarian\",\n    \"Select the app to index content and answer search queries.\" : \"Pilih aplikasi untuk mengindeks konten dan menjawab kueri pencarian.\",\n    \"Navigation Icon\" : \"Ikon Navigasi\",\n    \"Enable global search within all your content.\" : \"Aktifkan pencarian global di seluruh konten Anda.\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/id.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"pencarian mengembalikan {total} hasil dalam {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"pencarian di {title} untuk \\\"{search}\\\" mengembalikan {total} hasil dalam {time} ms\",\n    \"Search\" : \"Cari\",\n    \"Index not found\" : \"Indeks tidak ditemukan\",\n    \"Process timed out\" : \"Proses kehabisan waktu\",\n    \"Full Text Search\" : \"Pencarian Teks Penuh\",\n    \"Full text search\" : \"pencarian teks penuh\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Inti dari kerangka kerja pencarian teks penuh untuk Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplikasi inti dari kerangka kerja pencarian teks penuh untuk Nextcloud Anda.\",\n    \"Search on %s\" : \"Cari di %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Silakan periksa wiki untuk dokumentasi terkait instalasi dan konfigurasi pencarian teks penuh di Nextcloud Anda\",\n    \"General\" : \"Umum\",\n    \"Search Platform\" : \"Platform Pencarian\",\n    \"Select the app to index content and answer search queries.\" : \"Pilih aplikasi untuk mengindeks konten dan menjawab kueri pencarian.\",\n    \"Navigation Icon\" : \"Ikon Navigasi\",\n    \"Enable global search within all your content.\" : \"Aktifkan pencarian global di seluruh konten Anda.\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/is.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"leitin skilaði {total} niðurstöðum á {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"leitin í {title} eftir \\\"{search}\\\" skilaði {total} niðurstöðum á {time} ms\",\n    \"Search\" : \"Leita\",\n    \"Index not found\" : \"Atriðavísir fannst ekki\",\n    \"Process timed out\" : \"Ferlið féll á tíma\",\n    \"Full Text Search\" : \"Leit í öllum textanum\",\n    \"Full text search\" : \"Leit í öllum textanum\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kjarni kerfisins sem styður leit í öllum textanum í Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kjarnaforrit kerfisins sem styður leit í öllum textanum í Nextcloud.\",\n    \"Search on %s\" : \"Leita í %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Skoðaðu vel wiki-vefinn eftir hjálparskjölum varðandi uppsetningu og stillingar á leit í heildartexta innan Nextcloud skýsins þíns\",\n    \"General\" : \"Almennt\",\n    \"Search Platform\" : \"Leita á kerfi\",\n    \"Select the app to index content and answer search queries.\" : \"Veldu forrit til að atriðaskrá efni og svara leitarbeiðnum.\",\n    \"Navigation Icon\" : \"Táknmynd flakks\",\n    \"Enable global search within all your content.\" : \"Virkja víðværa leit inna alls efnisins þíns.\"\n},\n\"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);\");\n"
  },
  {
    "path": "l10n/is.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"leitin skilaði {total} niðurstöðum á {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"leitin í {title} eftir \\\"{search}\\\" skilaði {total} niðurstöðum á {time} ms\",\n    \"Search\" : \"Leita\",\n    \"Index not found\" : \"Atriðavísir fannst ekki\",\n    \"Process timed out\" : \"Ferlið féll á tíma\",\n    \"Full Text Search\" : \"Leit í öllum textanum\",\n    \"Full text search\" : \"Leit í öllum textanum\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kjarni kerfisins sem styður leit í öllum textanum í Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kjarnaforrit kerfisins sem styður leit í öllum textanum í Nextcloud.\",\n    \"Search on %s\" : \"Leita í %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Skoðaðu vel wiki-vefinn eftir hjálparskjölum varðandi uppsetningu og stillingar á leit í heildartexta innan Nextcloud skýsins þíns\",\n    \"General\" : \"Almennt\",\n    \"Search Platform\" : \"Leita á kerfi\",\n    \"Select the app to index content and answer search queries.\" : \"Veldu forrit til að atriðaskrá efni og svara leitarbeiðnum.\",\n    \"Navigation Icon\" : \"Táknmynd flakks\",\n    \"Enable global search within all your content.\" : \"Virkja víðværa leit inna alls efnisins þíns.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);\"\n}"
  },
  {
    "path": "l10n/it.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"la ricerca ha restituito {total} risultati in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la ricerca in {title} per '{search}' ha restituito {total} risultati in {time} ms\",\n    \"Search\" : \"Cerca\",\n    \"Index not found\" : \"Indice non trovato\",\n    \"Process timed out\" : \"Processo scaduto\",\n    \"Full Text Search\" : \"Ricerca del testo integrale\",\n    \"Full text search\" : \"Ricerca del testo integrale\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nucleo dell'infrastruttura di ricerca del testo integrale per Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Applicazione principale dell'infrastruttura di ricerca del testo integrale per il tuo Nextcloud.\",\n    \"Search on %s\" : \"Cerca in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Verifica la presenza nel wiki di documentazione relativa all'installazione e alla configurazione della ricerca di testo integrale nel tuo Nextcloud\",\n    \"General\" : \"Generale\",\n    \"Search Platform\" : \"Piattaforma di ricerca\",\n    \"Select the app to index content and answer search queries.\" : \"Seleziona l'applicazione per indicizzare i contenuti e rispondere alle ricerche.\",\n    \"Navigation Icon\" : \"Icona di navigazione\",\n    \"Enable global search within all your content.\" : \"Abilita la ricerca globale in tutti i tuoi contenuti.\"\n},\n\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/it.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"la ricerca ha restituito {total} risultati in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"la ricerca in {title} per '{search}' ha restituito {total} risultati in {time} ms\",\n    \"Search\" : \"Cerca\",\n    \"Index not found\" : \"Indice non trovato\",\n    \"Process timed out\" : \"Processo scaduto\",\n    \"Full Text Search\" : \"Ricerca del testo integrale\",\n    \"Full text search\" : \"Ricerca del testo integrale\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nucleo dell'infrastruttura di ricerca del testo integrale per Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Applicazione principale dell'infrastruttura di ricerca del testo integrale per il tuo Nextcloud.\",\n    \"Search on %s\" : \"Cerca in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Verifica la presenza nel wiki di documentazione relativa all'installazione e alla configurazione della ricerca di testo integrale nel tuo Nextcloud\",\n    \"General\" : \"Generale\",\n    \"Search Platform\" : \"Piattaforma di ricerca\",\n    \"Select the app to index content and answer search queries.\" : \"Seleziona l'applicazione per indicizzare i contenuti e rispondere alle ricerche.\",\n    \"Navigation Icon\" : \"Icona di navigazione\",\n    \"Enable global search within all your content.\" : \"Abilita la ricerca globale in tutti i tuoi contenuti.\"\n},\"pluralForm\" :\"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/ja.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"検索結果{total}件{time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"\\\"{search}\\\" を 「{title}」 で検索すると、{time} ms で {total} 件返却されます\",\n    \"Search\" : \"検索\",\n    \"Index not found\" : \"インデックスが見つかりません\",\n    \"Process timed out\" : \"プロセスがタイムアウトしました\",\n    \"Full Text Search\" : \"全文検索\",\n    \"Full text search\" : \"全文検索\",\n    \"Core of the full-text search framework for Nextcloud\" : \"NextCloudの全文検索フレームワークのコア\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"NextCloudの全文検索フレームワークのコアアプリケーション\",\n    \"Search on %s\" : \"%s で検索\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"あなたのNextcloud内での全文検索のインストールと設定に関連するドキュメントはwikiで確認してください\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"検索プラットフォーム\",\n    \"Select the app to index content and answer search queries.\" : \"コンテンツをインデックス化して検索クエリに答えるアプリを選択します。\",\n    \"Navigation Icon\" : \"ナビゲーションアイコン\",\n    \"Enable global search within all your content.\" : \"すべてのコンテンツ内でグローバル検索を有効にする。\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/ja.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"検索結果{total}件{time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"\\\"{search}\\\" を 「{title}」 で検索すると、{time} ms で {total} 件返却されます\",\n    \"Search\" : \"検索\",\n    \"Index not found\" : \"インデックスが見つかりません\",\n    \"Process timed out\" : \"プロセスがタイムアウトしました\",\n    \"Full Text Search\" : \"全文検索\",\n    \"Full text search\" : \"全文検索\",\n    \"Core of the full-text search framework for Nextcloud\" : \"NextCloudの全文検索フレームワークのコア\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"NextCloudの全文検索フレームワークのコアアプリケーション\",\n    \"Search on %s\" : \"%s で検索\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"あなたのNextcloud内での全文検索のインストールと設定に関連するドキュメントはwikiで確認してください\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"検索プラットフォーム\",\n    \"Select the app to index content and answer search queries.\" : \"コンテンツをインデックス化して検索クエリに答えるアプリを選択します。\",\n    \"Navigation Icon\" : \"ナビゲーションアイコン\",\n    \"Enable global search within all your content.\" : \"すべてのコンテンツ内でグローバル検索を有効にする。\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/ka.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"General\"\n},\n\"nplurals=2; plural=(n!=1);\");\n"
  },
  {
    "path": "l10n/ka.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=2; plural=(n!=1);\"\n}"
  },
  {
    "path": "l10n/ka_GE.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"ძიება\",\n    \"Index not found\" : \"ინდექსი ვერ იქნა ნაპოვნი\",\n    \"Full text search\" : \"სრული ტექსტის ძიება\",\n    \"Search on %s\" : \"ძიება %s-ზე\",\n    \"General\" : \"ზოგადი\"\n},\n\"nplurals=2; plural=(n!=1);\");\n"
  },
  {
    "path": "l10n/ka_GE.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"ძიება\",\n    \"Index not found\" : \"ინდექსი ვერ იქნა ნაპოვნი\",\n    \"Full text search\" : \"სრული ტექსტის ძიება\",\n    \"Search on %s\" : \"ძიება %s-ზე\",\n    \"General\" : \"ზოგადი\"\n},\"pluralForm\" :\"nplurals=2; plural=(n!=1);\"\n}"
  },
  {
    "path": "l10n/kab.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Nadi\",\n    \"General\" : \"Amatu\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/kab.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Nadi\",\n    \"General\" : \"Amatu\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/km.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"ទូទៅ\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/km.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"ទូទៅ\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/kn.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\"\n},\n\"nplurals=2; plural=(n > 1);\");\n"
  },
  {
    "path": "l10n/kn.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\"\n},\"pluralForm\" :\"nplurals=2; plural=(n > 1);\"\n}"
  },
  {
    "path": "l10n/ko.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"검색\",\n    \"Index not found\" : \"색인을 찾을 수 없음\",\n    \"Process timed out\" : \"처리 시간 초과됨\",\n    \"Full text search\" : \"전문 검색\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 전문 검색 프레임워크 코어\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud 전문 검색 프레임워크 코어 앱입니다.\",\n    \"Search on %s\" : \"%s에서 검색\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud 전문 검색을 사용하려면 설치 및 설정 정보를 위키에서 참조하세요.\",\n    \"General\" : \"일반\",\n    \"Search Platform\" : \"검색 플랫폼\",\n    \"Select the app to index content and answer search queries.\" : \"내용을 색인에 추가하고 질의를 처리할 앱을 선택하십시오.\",\n    \"Navigation Icon\" : \"탐색 아이콘\",\n    \"Enable global search within all your content.\" : \"모든 내용을 포함하는 전역 검색을 활성화합니다.\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/ko.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"검색\",\n    \"Index not found\" : \"색인을 찾을 수 없음\",\n    \"Process timed out\" : \"처리 시간 초과됨\",\n    \"Full text search\" : \"전문 검색\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 전문 검색 프레임워크 코어\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud 전문 검색 프레임워크 코어 앱입니다.\",\n    \"Search on %s\" : \"%s에서 검색\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud 전문 검색을 사용하려면 설치 및 설정 정보를 위키에서 참조하세요.\",\n    \"General\" : \"일반\",\n    \"Search Platform\" : \"검색 플랫폼\",\n    \"Select the app to index content and answer search queries.\" : \"내용을 색인에 추가하고 질의를 처리할 앱을 선택하십시오.\",\n    \"Navigation Icon\" : \"탐색 아이콘\",\n    \"Enable global search within all your content.\" : \"모든 내용을 포함하는 전역 검색을 활성화합니다.\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/lb.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"Allgemeng\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/lb.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"Allgemeng\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/lo.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"the search returned {total} results in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"ຄົ້ນຫາ\",\n    \"Index not found\" : \"Index not found\",\n    \"Process timed out\" : \"Process timed out\",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"Full text search\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Core of the full-text search framework for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Core App of the full-text search framework for your Nextcloud.\",\n    \"Search on %s\" : \"Search on %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\",\n    \"General\" : \"ທົ່ວໄປ\",\n    \"Search Platform\" : \"Search Platform\",\n    \"Select the app to index content and answer search queries.\" : \"Select the app to index content and answer search queries.\",\n    \"Navigation Icon\" : \"Navigation Icon\",\n    \"Enable global search within all your content.\" : \"Enable global search within all your content.\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/lo.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"the search returned {total} results in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\",\n    \"Search\" : \"ຄົ້ນຫາ\",\n    \"Index not found\" : \"Index not found\",\n    \"Process timed out\" : \"Process timed out\",\n    \"Full Text Search\" : \"Full Text Search\",\n    \"Full text search\" : \"Full text search\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Core of the full-text search framework for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Core App of the full-text search framework for your Nextcloud.\",\n    \"Search on %s\" : \"Search on %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\",\n    \"General\" : \"ທົ່ວໄປ\",\n    \"Search Platform\" : \"Search Platform\",\n    \"Select the app to index content and answer search queries.\" : \"Select the app to index content and answer search queries.\",\n    \"Navigation Icon\" : \"Navigation Icon\",\n    \"Enable global search within all your content.\" : \"Enable global search within all your content.\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/lt_LT.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"paieška pateikė {total} rezultatus per {time} ms\",\n    \"Search\" : \"Ieškoti\",\n    \"Index not found\" : \"Indeksas nerastas\",\n    \"Process timed out\" : \"Pasibaigė procesui skirtas laikas\",\n    \"Full text search\" : \"Absoliučioji paieška\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud absoliučiosios paieškos karkaso branduolys\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Jūsų Nextcloud absoliučiosios paieškos karkaso pagrindinė programėlė.\",\n    \"Search on %s\" : \"Ieškoti ties %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Patikrinkite wiki dokumentus, susijusius su diegimu ir  teksto paieškos konfigūracija „Nextcloud“.\",\n    \"General\" : \"Bendra\",\n    \"Search Platform\" : \"Paieškos platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Pasirinkite programą, norėdami indeksuoti turinį ir atsakyti į paieškos užklausas.\",\n    \"Navigation Icon\" : \"Naršymo piktograma\",\n    \"Enable global search within all your content.\" : \"Įjunkite visuotinę paiešką visame savo turinyje.\"\n},\n\"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\");\n"
  },
  {
    "path": "l10n/lt_LT.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"paieška pateikė {total} rezultatus per {time} ms\",\n    \"Search\" : \"Ieškoti\",\n    \"Index not found\" : \"Indeksas nerastas\",\n    \"Process timed out\" : \"Pasibaigė procesui skirtas laikas\",\n    \"Full text search\" : \"Absoliučioji paieška\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud absoliučiosios paieškos karkaso branduolys\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Jūsų Nextcloud absoliučiosios paieškos karkaso pagrindinė programėlė.\",\n    \"Search on %s\" : \"Ieškoti ties %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Patikrinkite wiki dokumentus, susijusius su diegimu ir  teksto paieškos konfigūracija „Nextcloud“.\",\n    \"General\" : \"Bendra\",\n    \"Search Platform\" : \"Paieškos platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Pasirinkite programą, norėdami indeksuoti turinį ir atsakyti į paieškos užklausas.\",\n    \"Navigation Icon\" : \"Naršymo piktograma\",\n    \"Enable global search within all your content.\" : \"Įjunkite visuotinę paiešką visame savo turinyje.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\"\n}"
  },
  {
    "path": "l10n/lv.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Meklēt\",\n    \"Index not found\" : \"Indekss netika atrasts\",\n    \"Full text search\" : \"Pilna teksta meklēšana\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud pilna teksta meklēšanas satvara kodols\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Pilna teksta meklēšanas satvara kodola lietotne Tavam Nextcloud.\",\n    \"Search on %s\" : \"Meklēt %s\",\n    \"General\" : \"Vispārīgs\"\n},\n\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\");\n"
  },
  {
    "path": "l10n/lv.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Meklēt\",\n    \"Index not found\" : \"Indekss netika atrasts\",\n    \"Full text search\" : \"Pilna teksta meklēšana\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud pilna teksta meklēšanas satvara kodols\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Pilna teksta meklēšanas satvara kodola lietotne Tavam Nextcloud.\",\n    \"Search on %s\" : \"Meklēt %s\",\n    \"General\" : \"Vispārīgs\"\n},\"pluralForm\" :\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\"\n}"
  },
  {
    "path": "l10n/mk.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Пронајдени се {total} резултати за време од {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"пребарувањето во {title} за \\\"{search}\\\" пронајде {total} резултати за време од {time} ms\",\n    \"Search\" : \"Барај\",\n    \"Full Text Search\" : \"Пребарување на целосен текст\",\n    \"Full text search\" : \"Пребарување на целосен текст\",\n    \"Search on %s\" : \"Барај во %s\",\n    \"General\" : \"Општо\"\n},\n\"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\");\n"
  },
  {
    "path": "l10n/mk.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Пронајдени се {total} резултати за време од {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"пребарувањето во {title} за \\\"{search}\\\" пронајде {total} резултати за време од {time} ms\",\n    \"Search\" : \"Барај\",\n    \"Full Text Search\" : \"Пребарување на целосен текст\",\n    \"Full text search\" : \"Пребарување на целосен текст\",\n    \"Search on %s\" : \"Барај во %s\",\n    \"General\" : \"Општо\"\n},\"pluralForm\" :\"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\"\n}"
  },
  {
    "path": "l10n/mn.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Хайх\",\n    \"General\" : \"Ерөнхий\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/mn.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Хайх\",\n    \"General\" : \"Ерөнхий\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/ms_MY.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"Umum\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/ms_MY.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"Umum\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/nb.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"søket ga {total} resultater på {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"søket i {title} for \\\"{search}\\\" ga {total} resultater på {time} ms\",\n    \"Search\" : \"Søk\",\n    \"Index not found\" : \"Indeks ble ikke funnet\",\n    \"Process timed out\" : \"Prosessen ble tidsavbrutt\",\n    \"Full Text Search\" : \"Fulltekst søk\",\n    \"Full text search\" : \"Fulltekst søk\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kjernen i rammeverket for fulltekstsøk for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kjerne-App av fulltekstsøkerammeverket for din Nextcloud.\",\n    \"Search on %s\" : \"Søk etter %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Vennligst sjekk wikien for dokumentasjon relatert til installasjonen og konfigurasjonen av fulltekstsøket i din Nextcloud\",\n    \"General\" : \"Generelt\",\n    \"Search Platform\" : \"Søk plattform\",\n    \"Select the app to index content and answer search queries.\" : \"Velg appen for å indeksere innhold og svare på søk.\",\n    \"Navigation Icon\" : \"Navigasjonsikon\",\n    \"Enable global search within all your content.\" : \"Aktiver globalt søk i alt innholdet ditt.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/nb.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"søket ga {total} resultater på {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"søket i {title} for \\\"{search}\\\" ga {total} resultater på {time} ms\",\n    \"Search\" : \"Søk\",\n    \"Index not found\" : \"Indeks ble ikke funnet\",\n    \"Process timed out\" : \"Prosessen ble tidsavbrutt\",\n    \"Full Text Search\" : \"Fulltekst søk\",\n    \"Full text search\" : \"Fulltekst søk\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kjernen i rammeverket for fulltekstsøk for Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kjerne-App av fulltekstsøkerammeverket for din Nextcloud.\",\n    \"Search on %s\" : \"Søk etter %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Vennligst sjekk wikien for dokumentasjon relatert til installasjonen og konfigurasjonen av fulltekstsøket i din Nextcloud\",\n    \"General\" : \"Generelt\",\n    \"Search Platform\" : \"Søk plattform\",\n    \"Select the app to index content and answer search queries.\" : \"Velg appen for å indeksere innhold og svare på søk.\",\n    \"Navigation Icon\" : \"Navigasjonsikon\",\n    \"Enable global search within all your content.\" : \"Aktiver globalt søk i alt innholdet ditt.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/nl.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"de zoekopdracht leverde {total} resultaten op in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"de zoekopdracht in {title} naar \\\"{search}\\\" leverde {total} resultaten op in {time} ms\",\n    \"Search\" : \"Zoeken\",\n    \"Index not found\" : \"Index niet gevonden\",\n    \"Process timed out\" : \"Proces time-out\",\n    \"Full Text Search\" : \"Volledige tekstzoekfuncties\",\n    \"Full text search\" : \"Volledige tekst doorzoeken\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kern van het volledige-text zoekfunctie framework voor Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kernapp van de volledige-tekst zoekfunctie framework voor je Nextcloud.\",\n    \"Search on %s\" : \"Zoek op %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Lees de wiki voor documentatie over installatie en configuratie van de volledige-tekst zoekfunctie van jouw Nextcloud\",\n    \"General\" : \"Algemeen\",\n    \"Search Platform\" : \"Zoekplatform\",\n    \"Select the app to index content and answer search queries.\" : \"Selecteer de app om inhoud te indexeren en zoekvragen te beantwoorden.\",\n    \"Navigation Icon\" : \"Navigatie-pictogram\",\n    \"Enable global search within all your content.\" : \"Inschakelen globaal al je inhoud indexeren.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/nl.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"de zoekopdracht leverde {total} resultaten op in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"de zoekopdracht in {title} naar \\\"{search}\\\" leverde {total} resultaten op in {time} ms\",\n    \"Search\" : \"Zoeken\",\n    \"Index not found\" : \"Index niet gevonden\",\n    \"Process timed out\" : \"Proces time-out\",\n    \"Full Text Search\" : \"Volledige tekstzoekfuncties\",\n    \"Full text search\" : \"Volledige tekst doorzoeken\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Kern van het volledige-text zoekfunctie framework voor Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Kernapp van de volledige-tekst zoekfunctie framework voor je Nextcloud.\",\n    \"Search on %s\" : \"Zoek op %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Lees de wiki voor documentatie over installatie en configuratie van de volledige-tekst zoekfunctie van jouw Nextcloud\",\n    \"General\" : \"Algemeen\",\n    \"Search Platform\" : \"Zoekplatform\",\n    \"Select the app to index content and answer search queries.\" : \"Selecteer de app om inhoud te indexeren en zoekvragen te beantwoorden.\",\n    \"Navigation Icon\" : \"Navigatie-pictogram\",\n    \"Enable global search within all your content.\" : \"Inschakelen globaal al je inhoud indexeren.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/nn_NO.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"Generelt\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/nn_NO.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"Generelt\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/oc.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Recercar\",\n    \"Full text search\" : \"Recèrca tèxt brut\",\n    \"General\" : \"Generals\"\n},\n\"nplurals=2; plural=(n > 1);\");\n"
  },
  {
    "path": "l10n/oc.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Recercar\",\n    \"Full text search\" : \"Recèrca tèxt brut\",\n    \"General\" : \"Generals\"\n},\"pluralForm\" :\"nplurals=2; plural=(n > 1);\"\n}"
  },
  {
    "path": "l10n/pl.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"wyszukiwanie zwróciło {total} wyników w {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"wyszukiwanie dla \\\"{search}\\\" w {title} zwróciło {total} wyników w {time} ms\",\n    \"Search\" : \"Wyszukaj\",\n    \"Index not found\" : \"Nie znaleziono indeksu\",\n    \"Process timed out\" : \"Przekroczono czas procesu\",\n    \"Full Text Search\" : \"Wyszukiwanie pełnotekstowe\",\n    \"Full text search\" : \"Wyszukiwanie pełnotekstowe\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Podstawa systemu wyszukiwania pełnotekstowego dla Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Podstawowa aplikacja systemu wyszukiwania pełnotekstowego dla Nextcloud\",\n    \"Search on %s\" : \"Szukaj w %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Sprawdź wiki na temat dokumentacji związanej z instalacją i konfiguracją wyszukiwania pełnotekstowego w swoim Nextcloud\",\n    \"General\" : \"Ogólne\",\n    \"Search Platform\" : \"Platforma wyszukiwania\",\n    \"Select the app to index content and answer search queries.\" : \"Wybierz aplikację, aby indeksować treść i odpowiadać na wyszukiwane hasła.\",\n    \"Navigation Icon\" : \"Ikona nawigacji\",\n    \"Enable global search within all your content.\" : \"Włącz globalne wyszukiwanie we wszystkich swoich treściach.\"\n},\n\"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\");\n"
  },
  {
    "path": "l10n/pl.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"wyszukiwanie zwróciło {total} wyników w {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"wyszukiwanie dla \\\"{search}\\\" w {title} zwróciło {total} wyników w {time} ms\",\n    \"Search\" : \"Wyszukaj\",\n    \"Index not found\" : \"Nie znaleziono indeksu\",\n    \"Process timed out\" : \"Przekroczono czas procesu\",\n    \"Full Text Search\" : \"Wyszukiwanie pełnotekstowe\",\n    \"Full text search\" : \"Wyszukiwanie pełnotekstowe\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Podstawa systemu wyszukiwania pełnotekstowego dla Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Podstawowa aplikacja systemu wyszukiwania pełnotekstowego dla Nextcloud\",\n    \"Search on %s\" : \"Szukaj w %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Sprawdź wiki na temat dokumentacji związanej z instalacją i konfiguracją wyszukiwania pełnotekstowego w swoim Nextcloud\",\n    \"General\" : \"Ogólne\",\n    \"Search Platform\" : \"Platforma wyszukiwania\",\n    \"Select the app to index content and answer search queries.\" : \"Wybierz aplikację, aby indeksować treść i odpowiadać na wyszukiwane hasła.\",\n    \"Navigation Icon\" : \"Ikona nawigacji\",\n    \"Enable global search within all your content.\" : \"Włącz globalne wyszukiwanie we wszystkich swoich treściach.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\"\n}"
  },
  {
    "path": "l10n/pt_BR.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"a procura encontrou {total} resultados em {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a pesquisa em {title} por \\\"{search}\\\" retornou {total} resultados em {time} ms\",\n    \"Search\" : \"Pesquisar\",\n    \"Index not found\" : \"Índice não encontrado\",\n    \"Process timed out\" : \"Tempo esgotado\",\n    \"Full Text Search\" : \"Pesquisa por Texto Completo\",\n    \"Full text search\" : \"Pesquisa por texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo da estrutura de pesquisa de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicativo principal da estrutura de pesquisa de texto completo para Nextcloud.\",\n    \"Search on %s\" : \"Pesquisar em %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Verifique o wiki para documentação relacionada com a instalação e configuração da pesquisa de texto completo dentro do seu Nextcloud.\",\n    \"General\" : \"Geral\",\n    \"Search Platform\" : \"Plataforma de Pesquisa\",\n    \"Select the app to index content and answer search queries.\" : \"Selecione o aplicativo para indexar conteúdo e responder a consultas de pesquisa.\",\n    \"Navigation Icon\" : \"Ícone de Navegação\",\n    \"Enable global search within all your content.\" : \"Ativar a pesquisa global em todo o seu conteúdo.\"\n},\n\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/pt_BR.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"a procura encontrou {total} resultados em {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"a pesquisa em {title} por \\\"{search}\\\" retornou {total} resultados em {time} ms\",\n    \"Search\" : \"Pesquisar\",\n    \"Index not found\" : \"Índice não encontrado\",\n    \"Process timed out\" : \"Tempo esgotado\",\n    \"Full Text Search\" : \"Pesquisa por Texto Completo\",\n    \"Full text search\" : \"Pesquisa por texto completo\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Núcleo da estrutura de pesquisa de texto completo para Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicativo principal da estrutura de pesquisa de texto completo para Nextcloud.\",\n    \"Search on %s\" : \"Pesquisar em %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Verifique o wiki para documentação relacionada com a instalação e configuração da pesquisa de texto completo dentro do seu Nextcloud.\",\n    \"General\" : \"Geral\",\n    \"Search Platform\" : \"Plataforma de Pesquisa\",\n    \"Select the app to index content and answer search queries.\" : \"Selecione o aplicativo para indexar conteúdo e responder a consultas de pesquisa.\",\n    \"Navigation Icon\" : \"Ícone de Navegação\",\n    \"Enable global search within all your content.\" : \"Ativar a pesquisa global em todo o seu conteúdo.\"\n},\"pluralForm\" :\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/pt_PT.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Pesquisar\",\n    \"Index not found\" : \"Índice não encontrado\",\n    \"Full text search\" : \"Pesquisa de texto completo \",\n    \"Search on %s\" : \"Pesquisar em %s\",\n    \"General\" : \"Geral\"\n},\n\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\");\n"
  },
  {
    "path": "l10n/pt_PT.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Pesquisar\",\n    \"Index not found\" : \"Índice não encontrado\",\n    \"Full text search\" : \"Pesquisa de texto completo \",\n    \"Search on %s\" : \"Pesquisar em %s\",\n    \"General\" : \"Geral\"\n},\"pluralForm\" :\"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\"\n}"
  },
  {
    "path": "l10n/ro.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Caută\",\n    \"General\" : \"General\"\n},\n\"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\");\n"
  },
  {
    "path": "l10n/ro.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Caută\",\n    \"General\" : \"General\"\n},\"pluralForm\" :\"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\"\n}"
  },
  {
    "path": "l10n/ru.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"поиск вернул {total} результатов за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"поиск в {title} по запросу «{search}» вернул {total} результатов за {time} мс\",\n    \"Search\" : \"Поиск\",\n    \"Index not found\" : \"Индекс не найден\",\n    \"Process timed out\" : \"Превышено время ожидания запроса\",\n    \"Full Text Search\" : \"Полнотекстовый поиск\",\n    \"Full text search\" : \"Полнотекстовый поиск\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Ядро механизма полнотекстового поиска для Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основное приложение полнотекстового поиска для Nextcloud.\",\n    \"Search on %s\" : \"Поиск в %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Обратитесь к вики для получения документации об установке и настройке полнотекстового поиска для Nextcloud\",\n    \"General\" : \"Основные\",\n    \"Search Platform\" : \"Платформа поиска\",\n    \"Select the app to index content and answer search queries.\" : \"Выберите приложение для индексирования содержимого файлов и обработки поисковых запросов.\",\n    \"Navigation Icon\" : \"Значок навигации\",\n    \"Enable global search within all your content.\" : \"Включить глобальный поиск по всему содержимому.\"\n},\n\"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\");\n"
  },
  {
    "path": "l10n/ru.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"поиск вернул {total} результатов за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"поиск в {title} по запросу «{search}» вернул {total} результатов за {time} мс\",\n    \"Search\" : \"Поиск\",\n    \"Index not found\" : \"Индекс не найден\",\n    \"Process timed out\" : \"Превышено время ожидания запроса\",\n    \"Full Text Search\" : \"Полнотекстовый поиск\",\n    \"Full text search\" : \"Полнотекстовый поиск\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Ядро механизма полнотекстового поиска для Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основное приложение полнотекстового поиска для Nextcloud.\",\n    \"Search on %s\" : \"Поиск в %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Обратитесь к вики для получения документации об установке и настройке полнотекстового поиска для Nextcloud\",\n    \"General\" : \"Основные\",\n    \"Search Platform\" : \"Платформа поиска\",\n    \"Select the app to index content and answer search queries.\" : \"Выберите приложение для индексирования содержимого файлов и обработки поисковых запросов.\",\n    \"Navigation Icon\" : \"Значок навигации\",\n    \"Enable global search within all your content.\" : \"Включить глобальный поиск по всему содержимому.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\"\n}"
  },
  {
    "path": "l10n/sc.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"sa chirca at torradu {total} resurtados in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"sa chirca in {title} pro \\\"{search}\\\" at torradu {total} resurtados in {time} ms\",\n    \"Search\" : \"Chirca\",\n    \"Index not found\" : \"Ìnditze no agatadu\",\n    \"Process timed out\" : \"Protzessu iscadidu\",\n    \"Full Text Search\" : \"Chirca de testu cumpletu\",\n    \"Full text search\" : \"Chirca de testu cumpletu\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nùcleu de s'infraistrutura de chirca de su testu integrale pro Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicatzione printzipale de s'infraistrutura de chirca de su testu integrale pro su Nextcloud tuo.\",\n    \"Search on %s\" : \"Chirca in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Controlla sa documentatzione de sa wiki pro cantu tocat a s'installatzione e sa cunfiguratzione de sa chirca de testu cumpletu a intro de su Nextcloud tuo.\",\n    \"General\" : \"Generale\",\n    \"Search Platform\" : \"Prataforma de chirca\",\n    \"Select the app to index content and answer search queries.\" : \"Sèbera s'aplicatzione pro inditzizare is cuntenutos e rispòndere a is chircas.\",\n    \"Navigation Icon\" : \"Icona de navigatzione\",\n    \"Enable global search within all your content.\" : \"Ativa sa chirca globale in totu su cuntenutu tuo.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/sc.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"sa chirca at torradu {total} resurtados in {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"sa chirca in {title} pro \\\"{search}\\\" at torradu {total} resurtados in {time} ms\",\n    \"Search\" : \"Chirca\",\n    \"Index not found\" : \"Ìnditze no agatadu\",\n    \"Process timed out\" : \"Protzessu iscadidu\",\n    \"Full Text Search\" : \"Chirca de testu cumpletu\",\n    \"Full text search\" : \"Chirca de testu cumpletu\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nùcleu de s'infraistrutura de chirca de su testu integrale pro Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Aplicatzione printzipale de s'infraistrutura de chirca de su testu integrale pro su Nextcloud tuo.\",\n    \"Search on %s\" : \"Chirca in %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Controlla sa documentatzione de sa wiki pro cantu tocat a s'installatzione e sa cunfiguratzione de sa chirca de testu cumpletu a intro de su Nextcloud tuo.\",\n    \"General\" : \"Generale\",\n    \"Search Platform\" : \"Prataforma de chirca\",\n    \"Select the app to index content and answer search queries.\" : \"Sèbera s'aplicatzione pro inditzizare is cuntenutos e rispòndere a is chircas.\",\n    \"Navigation Icon\" : \"Icona de navigatzione\",\n    \"Enable global search within all your content.\" : \"Ativa sa chirca globale in totu su cuntenutu tuo.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/si.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"සොයන්න\",\n    \"General\" : \"සමාන්‍යය\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/si.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"සොයන්න\",\n    \"General\" : \"සමාන්‍යය\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/sk.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Vyhľadávanie našlo {total} výsledkov za {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Vyhľadávanie \\\"{search}\\\" v {title} našlo {total} výsledkov za {time} ms\",\n    \"Search\" : \"Hľadať\",\n    \"Index not found\" : \"Index nenájdený\",\n    \"Process timed out\" : \"Spracovanie expirovalo\",\n    \"Full Text Search\" : \"Fulltextové vyhľadávanie\",\n    \"Full text search\" : \"Fulltextové vyhľadávanie\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Základ pre fulltextové vyhľadávanie Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Základná aplikácia pre fulltextové vyhľadávanie Nextcloud\",\n    \"Search on %s\" : \"Hľadať v %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Skontroluj dokumentáciu vo wiki súvisiacu s inštaláciou a nastavením fulltextového hľadania v Nextcloud\",\n    \"General\" : \"Všeobecné\",\n    \"Search Platform\" : \"Vyhľadávacia platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Vyber aplikáciu pre indexovanie obsahu a odpoveď na dotazy.\",\n    \"Navigation Icon\" : \"Ikona navigácie\",\n    \"Enable global search within all your content.\" : \"Povoliť globálne vyhľadávanie v celom obsahu.\"\n},\n\"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\");\n"
  },
  {
    "path": "l10n/sk.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Vyhľadávanie našlo {total} výsledkov za {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"Vyhľadávanie \\\"{search}\\\" v {title} našlo {total} výsledkov za {time} ms\",\n    \"Search\" : \"Hľadať\",\n    \"Index not found\" : \"Index nenájdený\",\n    \"Process timed out\" : \"Spracovanie expirovalo\",\n    \"Full Text Search\" : \"Fulltextové vyhľadávanie\",\n    \"Full text search\" : \"Fulltextové vyhľadávanie\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Základ pre fulltextové vyhľadávanie Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Základná aplikácia pre fulltextové vyhľadávanie Nextcloud\",\n    \"Search on %s\" : \"Hľadať v %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Skontroluj dokumentáciu vo wiki súvisiacu s inštaláciou a nastavením fulltextového hľadania v Nextcloud\",\n    \"General\" : \"Všeobecné\",\n    \"Search Platform\" : \"Vyhľadávacia platforma\",\n    \"Select the app to index content and answer search queries.\" : \"Vyber aplikáciu pre indexovanie obsahu a odpoveď na dotazy.\",\n    \"Navigation Icon\" : \"Ikona navigácie\",\n    \"Enable global search within all your content.\" : \"Povoliť globálne vyhľadávanie v celom obsahu.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\"\n}"
  },
  {
    "path": "l10n/sl.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"iskanje je vrnilo skupno {total} zadetkov v {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"iskanje po predmetu {title} za »{search}« je vrnilo skupno {total} zadetkov v {time} ms\",\n    \"Search\" : \"Poišči\",\n    \"Index not found\" : \"Kazala ni mogoče najti\",\n    \"Process timed out\" : \"Opravilo je časovno poteklo\",\n    \"Full Text Search\" : \"Iskanje po polnem besedilu\",\n    \"Full text search\" : \"Iskanje po polnem besedilu\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jedro iskalnika po polnem besedilu za Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Program za iskanje po polnem besedilu za Nextcloud.\",\n    \"Search on %s\" : \"Išči na %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Več podrobnosti o namestitvi in nastavitvi iskalnika za Nextcloud je na voljo v dokumentaciji.\",\n    \"General\" : \"Splošno\",\n    \"Search Platform\" : \"Iskalno orodje\",\n    \"Select the app to index content and answer search queries.\" : \"Izbor programa za indeksiranje vsebine in izvajanje iskalnih poizvedb.\",\n    \"Navigation Icon\" : \"Navigacijska ikona\",\n    \"Enable global search within all your content.\" : \"Omogoči splošno iskanje med vso vsebino oblaka.\"\n},\n\"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\");\n"
  },
  {
    "path": "l10n/sl.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"iskanje je vrnilo skupno {total} zadetkov v {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"iskanje po predmetu {title} za »{search}« je vrnilo skupno {total} zadetkov v {time} ms\",\n    \"Search\" : \"Poišči\",\n    \"Index not found\" : \"Kazala ni mogoče najti\",\n    \"Process timed out\" : \"Opravilo je časovno poteklo\",\n    \"Full Text Search\" : \"Iskanje po polnem besedilu\",\n    \"Full text search\" : \"Iskanje po polnem besedilu\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Jedro iskalnika po polnem besedilu za Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Program za iskanje po polnem besedilu za Nextcloud.\",\n    \"Search on %s\" : \"Išči na %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Več podrobnosti o namestitvi in nastavitvi iskalnika za Nextcloud je na voljo v dokumentaciji.\",\n    \"General\" : \"Splošno\",\n    \"Search Platform\" : \"Iskalno orodje\",\n    \"Select the app to index content and answer search queries.\" : \"Izbor programa za indeksiranje vsebine in izvajanje iskalnih poizvedb.\",\n    \"Navigation Icon\" : \"Navigacijska ikona\",\n    \"Enable global search within all your content.\" : \"Omogoči splošno iskanje med vso vsebino oblaka.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\"\n}"
  },
  {
    "path": "l10n/sq.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Kërko\",\n    \"Search on %s\" : \"Kërkoni në %s\",\n    \"General\" : \"Të përgjithshme\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/sq.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Kërko\",\n    \"Search on %s\" : \"Kërkoni në %s\",\n    \"General\" : \"Të përgjithshme\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/sr.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"претрага је вратила укупно {total} резултата за {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"претрага {title} за „{search}” је вратила {total} резултата за {time} ms\",\n    \"Search\" : \"Претрага\",\n    \"Index not found\" : \"Индекс није нађен\",\n    \"Process timed out\" : \"Истекло време извршавања процеса\",\n    \"Full Text Search\" : \"Претрага свих текстова\",\n    \"Full text search\" : \"Претраге свих текстова\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Језгро радног оквира претраге свих текстова за Некстклауд\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основна апликација радног оквира претраге свих текстова за Некстклауд.\",\n    \"Search on %s\" : \"Претражи %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Погледајте вики за документацију везану за инсталацију и подешавање претраге целог текста из Вашег Некстклауда\",\n    \"General\" : \"Опште\",\n    \"Search Platform\" : \"Платформа претраге\",\n    \"Select the app to index content and answer search queries.\" : \"Одаберите апликацију за индексирање садржаја и за одговарање на упите.\",\n    \"Navigation Icon\" : \"Икона навигације\",\n    \"Enable global search within all your content.\" : \"Укључите глобалну претраге кроз цео Ваш садржај.\"\n},\n\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\");\n"
  },
  {
    "path": "l10n/sr.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"претрага је вратила укупно {total} резултата за {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"претрага {title} за „{search}” је вратила {total} резултата за {time} ms\",\n    \"Search\" : \"Претрага\",\n    \"Index not found\" : \"Индекс није нађен\",\n    \"Process timed out\" : \"Истекло време извршавања процеса\",\n    \"Full Text Search\" : \"Претрага свих текстова\",\n    \"Full text search\" : \"Претраге свих текстова\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Језгро радног оквира претраге свих текстова за Некстклауд\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основна апликација радног оквира претраге свих текстова за Некстклауд.\",\n    \"Search on %s\" : \"Претражи %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Погледајте вики за документацију везану за инсталацију и подешавање претраге целог текста из Вашег Некстклауда\",\n    \"General\" : \"Опште\",\n    \"Search Platform\" : \"Платформа претраге\",\n    \"Select the app to index content and answer search queries.\" : \"Одаберите апликацију за индексирање садржаја и за одговарање на упите.\",\n    \"Navigation Icon\" : \"Икона навигације\",\n    \"Enable global search within all your content.\" : \"Укључите глобалну претраге кроз цео Ваш садржај.\"\n},\"pluralForm\" :\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\"\n}"
  },
  {
    "path": "l10n/sr@latin.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"Opšte\"\n},\n\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\");\n"
  },
  {
    "path": "l10n/sr@latin.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"Opšte\"\n},\"pluralForm\" :\"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\"\n}"
  },
  {
    "path": "l10n/sv.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"sökningen gav {total} resultat på {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"sökningen på {title} för \\\"{search}\\\" gav {total} resultat på {time} ms\",\n    \"Search\" : \"Sök\",\n    \"Index not found\" : \"Index hittades inte\",\n    \"Process timed out\" : \"Processen tog för lång tid\",\n    \"Full Text Search\" : \"Fulltextsökning\",\n    \"Full text search\" : \"Fulltextsökning\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Central del i ramverket fulltext-sökning för Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Central app i ramverket fulltext-sökning för Nextcloud\",\n    \"Search on %s\" : \"Sök på %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Kontrollera wiki för dokumentation i samband med installationen och konfigurationen av fulltext-sökning i ditt Nextcloud\",\n    \"General\" : \"Allmänt\",\n    \"Search Platform\" : \"Sökplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Välj app för att indexera innehåll och svara på sökfrågor.\",\n    \"Navigation Icon\" : \"Navigations-ikon\",\n    \"Enable global search within all your content.\" : \"Aktivera global sökning i allt ditt innehåll.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/sv.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"sökningen gav {total} resultat på {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"sökningen på {title} för \\\"{search}\\\" gav {total} resultat på {time} ms\",\n    \"Search\" : \"Sök\",\n    \"Index not found\" : \"Index hittades inte\",\n    \"Process timed out\" : \"Processen tog för lång tid\",\n    \"Full Text Search\" : \"Fulltextsökning\",\n    \"Full text search\" : \"Fulltextsökning\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Central del i ramverket fulltext-sökning för Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Central app i ramverket fulltext-sökning för Nextcloud\",\n    \"Search on %s\" : \"Sök på %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Kontrollera wiki för dokumentation i samband med installationen och konfigurationen av fulltext-sökning i ditt Nextcloud\",\n    \"General\" : \"Allmänt\",\n    \"Search Platform\" : \"Sökplattform\",\n    \"Select the app to index content and answer search queries.\" : \"Välj app för att indexera innehåll och svara på sökfrågor.\",\n    \"Navigation Icon\" : \"Navigations-ikon\",\n    \"Enable global search within all your content.\" : \"Aktivera global sökning i allt ditt innehåll.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/sw.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \" utafutaji umerejesha matokeo{total} baada ya {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \" utafutaji katika {title} kwa \\\"{search}\\\" umerejesha matokeo {total} katika {time} ms\",\n    \"Search\" : \"Tafuta\",\n    \"Index not found\" : \" Faharasa haipatikani\",\n    \"Process timed out\" : \" Mchakato umekwisha\",\n    \"Full Text Search\" : \"Utafutaji wa maandishi kamili\",\n    \"Full text search\" : \"Utafutaji wa maandishi kamili\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Msingi wa mfumo wa utafutaji wa maandishi kamili wa Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Programu kuu ya mfumo wa utafutaji wa maandishi kamili kwa Nextcloud yako.\",\n    \"Search on %s\" : \"Tafuta juu ya %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Tafadhali angalia wiki kwa nyaraka zinazohusiana na usakinishaji na usanidi wa utafutaji kamili wa maandishi ndani ya Nextcloud yako\",\n    \"General\" : \"Kuu\",\n    \"Search Platform\" : \"Tafuta jukwaa\",\n    \"Select the app to index content and answer search queries.\" : \"Chagua programu ili kuorodhesha maudhui na ujibu maswali ya utafutaji.\",\n    \"Navigation Icon\" : \"Aikoni ya Urambazaji\",\n    \"Enable global search within all your content.\" : \"Wezesha utafutaji wa kimataifa ndani ya maudhui yako yote.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/sw.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \" utafutaji umerejesha matokeo{total} baada ya {time} ms\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \" utafutaji katika {title} kwa \\\"{search}\\\" umerejesha matokeo {total} katika {time} ms\",\n    \"Search\" : \"Tafuta\",\n    \"Index not found\" : \" Faharasa haipatikani\",\n    \"Process timed out\" : \" Mchakato umekwisha\",\n    \"Full Text Search\" : \"Utafutaji wa maandishi kamili\",\n    \"Full text search\" : \"Utafutaji wa maandishi kamili\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Msingi wa mfumo wa utafutaji wa maandishi kamili wa Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Programu kuu ya mfumo wa utafutaji wa maandishi kamili kwa Nextcloud yako.\",\n    \"Search on %s\" : \"Tafuta juu ya %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Tafadhali angalia wiki kwa nyaraka zinazohusiana na usakinishaji na usanidi wa utafutaji kamili wa maandishi ndani ya Nextcloud yako\",\n    \"General\" : \"Kuu\",\n    \"Search Platform\" : \"Tafuta jukwaa\",\n    \"Select the app to index content and answer search queries.\" : \"Chagua programu ili kuorodhesha maudhui na ujibu maswali ya utafutaji.\",\n    \"Navigation Icon\" : \"Aikoni ya Urambazaji\",\n    \"Enable global search within all your content.\" : \"Wezesha utafutaji wa kimataifa ndani ya maudhui yako yote.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/ta.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\",\n    \"General\" : \"பொதுவான\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ta.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\",\n    \"General\" : \"பொதுவான\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/th.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"ค้นหา\",\n    \"General\" : \"ทั่วไป\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/th.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"ค้นหา\",\n    \"General\" : \"ทั่วไป\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/tk.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Gözlemek\",\n    \"General\" : \"Esasy\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/tk.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Gözlemek\",\n    \"General\" : \"Esasy\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/tr.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"aramada {time} ms sürede {total} sonuç bulundu\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title} başlığındaki \\\"{search}\\\" aramasında {time} ms sürede {total} sonuç bulundu\",\n    \"Search\" : \"Arama\",\n    \"Index not found\" : \"Dizin bulunamadı\",\n    \"Process timed out\" : \"İşlem zaman aşımına uğradı\",\n    \"Full Text Search\" : \"Tam yazı arama\",\n    \"Full text search\" : \"Tam yazı arama\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud tam yazı arama çatısının çekirdek uygulaması\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud tam yazı arama çatısının çekirdek uygulaması.\",\n    \"Search on %s\" : \"%s üzerine arama\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud tam yazı arama özelliğinin kurulması ve yapılandırılması için wiki belgelerine bakabilirsiniz\",\n    \"General\" : \"Genel\",\n    \"Search Platform\" : \"Arama platformu\",\n    \"Select the app to index content and answer search queries.\" : \"İçeriği dizine ekleyecek ve arama sorgularını yanıtlayacak uygulamayı seçin\",\n    \"Navigation Icon\" : \"Gezinme simgesi\",\n    \"Enable global search within all your content.\" : \"Tüm içeriğiniz üzerinde genel aramayı açın.\"\n},\n\"nplurals=2; plural=(n > 1);\");\n"
  },
  {
    "path": "l10n/tr.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"aramada {time} ms sürede {total} sonuç bulundu\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title} başlığındaki \\\"{search}\\\" aramasında {time} ms sürede {total} sonuç bulundu\",\n    \"Search\" : \"Arama\",\n    \"Index not found\" : \"Dizin bulunamadı\",\n    \"Process timed out\" : \"İşlem zaman aşımına uğradı\",\n    \"Full Text Search\" : \"Tam yazı arama\",\n    \"Full text search\" : \"Tam yazı arama\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud tam yazı arama çatısının çekirdek uygulaması\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud tam yazı arama çatısının çekirdek uygulaması.\",\n    \"Search on %s\" : \"%s üzerine arama\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud tam yazı arama özelliğinin kurulması ve yapılandırılması için wiki belgelerine bakabilirsiniz\",\n    \"General\" : \"Genel\",\n    \"Search Platform\" : \"Arama platformu\",\n    \"Select the app to index content and answer search queries.\" : \"İçeriği dizine ekleyecek ve arama sorgularını yanıtlayacak uygulamayı seçin\",\n    \"Navigation Icon\" : \"Gezinme simgesi\",\n    \"Enable global search within all your content.\" : \"Tüm içeriğiniz üzerinde genel aramayı açın.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n > 1);\"\n}"
  },
  {
    "path": "l10n/ug.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"ئىزدەش {total} ms دىكى {time} نەتىجىنى قايتۇردى\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title} دىكى ئىزدەش {search} \\\"{total} ms دىكى {time} نەتىجىگە قايتتى\",\n    \"Search\" : \"Search\",\n    \"Index not found\" : \"كۆرسەتكۈچ تېپىلمىدى\",\n    \"Process timed out\" : \"جەريان ۋاقتى ئۆتتى\",\n    \"Full Text Search\" : \"تولۇق تېكىست ئىزدەش\",\n    \"Full text search\" : \"تولۇق تېكىست ئىزدەش\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud نىڭ تولۇق تېكىست ئىزدەش رامكىسىنىڭ يادروسى\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud ئۈچۈن تولۇق تېكىست ئىزدەش رامكىسىنىڭ يادرولۇق دېتالى.\",\n    \"Search on %s\" : \"%s دىن ئىزدەڭ\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud دىكى قاچىلاش ۋە تولۇق تېكىست ئىزدەشنىڭ سەپلىمىسىگە مۇناسىۋەتلىك ھۆججەتلەرنى ۋىكىدىن تەكشۈرۈپ بېقىڭ\",\n    \"General\" : \"ئادەتتىكى\",\n    \"Search Platform\" : \"ئىزدەش سۇپىسى\",\n    \"Select the app to index content and answer search queries.\" : \"مەزمۇننى كۆرسەتكۈچ ۋە ئىزدەش سوئاللىرىغا جاۋاب بېرىدىغان ئەپنى تاللاڭ.\",\n    \"Navigation Icon\" : \"يولباشچى سىنبەلگىسى\",\n    \"Enable global search within all your content.\" : \"بارلىق مەزمۇنلىرىڭىزدا دۇنياۋى ئىزدەشنى قوزغىتىڭ.\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ug.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"ئىزدەش {total} ms دىكى {time} نەتىجىنى قايتۇردى\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"{title} دىكى ئىزدەش {search} \\\"{total} ms دىكى {time} نەتىجىگە قايتتى\",\n    \"Search\" : \"Search\",\n    \"Index not found\" : \"كۆرسەتكۈچ تېپىلمىدى\",\n    \"Process timed out\" : \"جەريان ۋاقتى ئۆتتى\",\n    \"Full Text Search\" : \"تولۇق تېكىست ئىزدەش\",\n    \"Full text search\" : \"تولۇق تېكىست ئىزدەش\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud نىڭ تولۇق تېكىست ئىزدەش رامكىسىنىڭ يادروسى\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud ئۈچۈن تولۇق تېكىست ئىزدەش رامكىسىنىڭ يادرولۇق دېتالى.\",\n    \"Search on %s\" : \"%s دىن ئىزدەڭ\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Nextcloud دىكى قاچىلاش ۋە تولۇق تېكىست ئىزدەشنىڭ سەپلىمىسىگە مۇناسىۋەتلىك ھۆججەتلەرنى ۋىكىدىن تەكشۈرۈپ بېقىڭ\",\n    \"General\" : \"ئادەتتىكى\",\n    \"Search Platform\" : \"ئىزدەش سۇپىسى\",\n    \"Select the app to index content and answer search queries.\" : \"مەزمۇننى كۆرسەتكۈچ ۋە ئىزدەش سوئاللىرىغا جاۋاب بېرىدىغان ئەپنى تاللاڭ.\",\n    \"Navigation Icon\" : \"يولباشچى سىنبەلگىسى\",\n    \"Enable global search within all your content.\" : \"بارلىق مەزمۇنلىرىڭىزدا دۇنياۋى ئىزدەشنى قوزغىتىڭ.\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/uk.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"Знайдено {total} за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"В результаті пошуку у {title} за \\\"{search}\\\" знайдено {total} за {time} мс\",\n    \"Search\" : \"Пошук\",\n    \"Index not found\" : \"Індекс не знайдено\",\n    \"Process timed out\" : \"Час на обробку вичерпано\",\n    \"Full Text Search\" : \"Контекстний пошук\",\n    \"Full text search\" : \"Контекстний пошук\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Ядро рушія контекстного пошуку для Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основний застосунок рушія контекстного пошуку для Nextcloud.\",\n    \"Search on %s\" : \"Шукати у %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Будь ласка, ознайомтеся з документацією щодо встановлення та налаштування контекстного пошуку у Nextcloud.\",\n    \"General\" : \"Загальне\",\n    \"Search Platform\" : \"Пошукова платформа\",\n    \"Select the app to index content and answer search queries.\" : \"Виберіть застосунок для індексування вмісту та відповіді на пошукові запити\",\n    \"Navigation Icon\" : \"Значок на панелі інструментів\",\n    \"Enable global search within all your content.\" : \"Увімкнути глобальний пошук по всьому вашому вмісту.\"\n},\n\"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\");\n"
  },
  {
    "path": "l10n/uk.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"Знайдено {total} за {time} мс\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"В результаті пошуку у {title} за \\\"{search}\\\" знайдено {total} за {time} мс\",\n    \"Search\" : \"Пошук\",\n    \"Index not found\" : \"Індекс не знайдено\",\n    \"Process timed out\" : \"Час на обробку вичерпано\",\n    \"Full Text Search\" : \"Контекстний пошук\",\n    \"Full text search\" : \"Контекстний пошук\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Ядро рушія контекстного пошуку для Nextcloud\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Основний застосунок рушія контекстного пошуку для Nextcloud.\",\n    \"Search on %s\" : \"Шукати у %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"Будь ласка, ознайомтеся з документацією щодо встановлення та налаштування контекстного пошуку у Nextcloud.\",\n    \"General\" : \"Загальне\",\n    \"Search Platform\" : \"Пошукова платформа\",\n    \"Select the app to index content and answer search queries.\" : \"Виберіть застосунок для індексування вмісту та відповіді на пошукові запити\",\n    \"Navigation Icon\" : \"Значок на панелі інструментів\",\n    \"Enable global search within all your content.\" : \"Увімкнути глобальний пошук по всьому вашому вмісту.\"\n},\"pluralForm\" :\"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\"\n}"
  },
  {
    "path": "l10n/ur_PK.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Search\"\n},\n\"nplurals=2; plural=(n != 1);\");\n"
  },
  {
    "path": "l10n/ur_PK.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Search\"\n},\"pluralForm\" :\"nplurals=2; plural=(n != 1);\"\n}"
  },
  {
    "path": "l10n/uz.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Qidirish\",\n    \"General\" : \"Umumiy\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/uz.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Qidirish\",\n    \"General\" : \"Umumiy\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/vi.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"Search\" : \"Tìm\",\n    \"General\" : \"Tổng hợp\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/vi.json",
    "content": "{ \"translations\": {\n    \"Search\" : \"Tìm\",\n    \"General\" : \"Tổng hợp\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/zh_CN.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"搜索在 {time} ms内返回 {total} 个结果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \" {title} 中搜索 \\\"{search}\\\" 在{time} ms内返回了 {total} 个结果\",\n    \"Search\" : \"搜索\",\n    \"Index not found\" : \"未找到索引\",\n    \"Process timed out\" : \"处理超时\",\n    \"Full Text Search\" : \"全文搜索\",\n    \"Full text search\" : \"全文搜索\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜索框架内核\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud 全文搜索框架核心应用\",\n    \"Search on %s\" : \"搜索 %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"请查看 wiki 中与安装和配置 NextCloud 全文搜索有关的文档。\",\n    \"General\" : \"常规\",\n    \"Search Platform\" : \"搜索平台\",\n    \"Select the app to index content and answer search queries.\" : \"选择用于索引内容和回答搜索查询的应用程序。\",\n    \"Navigation Icon\" : \"导航图标\",\n    \"Enable global search within all your content.\" : \"为您所有的内容启用全局搜索。\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/zh_CN.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"搜索在 {time} ms内返回 {total} 个结果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \" {title} 中搜索 \\\"{search}\\\" 在{time} ms内返回了 {total} 个结果\",\n    \"Search\" : \"搜索\",\n    \"Index not found\" : \"未找到索引\",\n    \"Process timed out\" : \"处理超时\",\n    \"Full Text Search\" : \"全文搜索\",\n    \"Full text search\" : \"全文搜索\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜索框架内核\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"Nextcloud 全文搜索框架核心应用\",\n    \"Search on %s\" : \"搜索 %s\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"请查看 wiki 中与安装和配置 NextCloud 全文搜索有关的文档。\",\n    \"General\" : \"常规\",\n    \"Search Platform\" : \"搜索平台\",\n    \"Select the app to index content and answer search queries.\" : \"选择用于索引内容和回答搜索查询的应用程序。\",\n    \"Navigation Icon\" : \"导航图标\",\n    \"Enable global search within all your content.\" : \"为您所有的内容启用全局搜索。\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/zh_HK.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"搜尋在 {time} ms 內回傳了 {total} 個結果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"對 \\\"{search}\\\" 中的 {title} 搜尋於 {time} ms 內回傳了 {total} 個結果\",\n    \"Search\" : \"搜尋\",\n    \"Index not found\" : \"找不到索引\",\n    \"Process timed out\" : \"處理程序逾時\",\n    \"Full Text Search\" : \"全文搜尋\",\n    \"Full text search\" : \"全文搜尋\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜尋框架的核心\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"您 Nextcloud 全文搜尋框架的核心應用程式。\",\n    \"Search on %s\" : \"在 %s 搜尋\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"請檢視關於在您 Nextcloud 中安裝與設定全文搜尋的 wiki 文件\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"搜尋平台\",\n    \"Select the app to index content and answer search queries.\" : \"選擇應用以索引內容並回應搜尋查詢。\",\n    \"Navigation Icon\" : \"導覽圖示\",\n    \"Enable global search within all your content.\" : \"在您所有的內容中啟用全域搜尋。\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/zh_HK.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"搜尋在 {time} ms 內回傳了 {total} 個結果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"對 \\\"{search}\\\" 中的 {title} 搜尋於 {time} ms 內回傳了 {total} 個結果\",\n    \"Search\" : \"搜尋\",\n    \"Index not found\" : \"找不到索引\",\n    \"Process timed out\" : \"處理程序逾時\",\n    \"Full Text Search\" : \"全文搜尋\",\n    \"Full text search\" : \"全文搜尋\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜尋框架的核心\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"您 Nextcloud 全文搜尋框架的核心應用程式。\",\n    \"Search on %s\" : \"在 %s 搜尋\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"請檢視關於在您 Nextcloud 中安裝與設定全文搜尋的 wiki 文件\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"搜尋平台\",\n    \"Select the app to index content and answer search queries.\" : \"選擇應用以索引內容並回應搜尋查詢。\",\n    \"Navigation Icon\" : \"導覽圖示\",\n    \"Enable global search within all your content.\" : \"在您所有的內容中啟用全域搜尋。\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "l10n/zh_TW.js",
    "content": "OC.L10N.register(\n    \"fulltextsearch\",\n    {\n    \"the search returned {total} results in {time} ms\" : \"搜尋在 {time} 毫秒內回傳了 {total} 個結果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"對「{search}」中的 {title} 搜尋於 {time} 毫秒內回傳了 {total} 個結果\",\n    \"Search\" : \"搜尋\",\n    \"Index not found\" : \"找不到索引\",\n    \"Process timed out\" : \"處理程序逾時\",\n    \"Full Text Search\" : \"全文搜尋\",\n    \"Full text search\" : \"全文搜尋\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜尋框架的核心\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"您 Nextcloud 全文搜尋框架的核心應用程式。\",\n    \"Search on %s\" : \"在 %s 搜尋\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"請檢視關於在您 Nextcloud 中安裝與設定全文搜尋的 wiki 文件\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"搜尋平台\",\n    \"Select the app to index content and answer search queries.\" : \"選取應用以索引內容並回應搜尋查詢。\",\n    \"Navigation Icon\" : \"導覽圖示\",\n    \"Enable global search within all your content.\" : \"在您所有的內容中啟用全域搜尋。\"\n},\n\"nplurals=1; plural=0;\");\n"
  },
  {
    "path": "l10n/zh_TW.json",
    "content": "{ \"translations\": {\n    \"the search returned {total} results in {time} ms\" : \"搜尋在 {time} 毫秒內回傳了 {total} 個結果\",\n    \"the search in {title} for \\\"{search}\\\" returned {total} results in {time} ms\" : \"對「{search}」中的 {title} 搜尋於 {time} 毫秒內回傳了 {total} 個結果\",\n    \"Search\" : \"搜尋\",\n    \"Index not found\" : \"找不到索引\",\n    \"Process timed out\" : \"處理程序逾時\",\n    \"Full Text Search\" : \"全文搜尋\",\n    \"Full text search\" : \"全文搜尋\",\n    \"Core of the full-text search framework for Nextcloud\" : \"Nextcloud 全文搜尋框架的核心\",\n    \"Core App of the full-text search framework for your Nextcloud.\" : \"您 Nextcloud 全文搜尋框架的核心應用程式。\",\n    \"Search on %s\" : \"在 %s 搜尋\",\n    \"Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud\" : \"請檢視關於在您 Nextcloud 中安裝與設定全文搜尋的 wiki 文件\",\n    \"General\" : \"一般\",\n    \"Search Platform\" : \"搜尋平台\",\n    \"Select the app to index content and answer search queries.\" : \"選取應用以索引內容並回應搜尋查詢。\",\n    \"Navigation Icon\" : \"導覽圖示\",\n    \"Enable global search within all your content.\" : \"在您所有的內容中啟用全域搜尋。\"\n},\"pluralForm\" :\"nplurals=1; plural=0;\"\n}"
  },
  {
    "path": "lib/ACommandBase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch;\n\n\nuse OC\\Core\\Command\\Base;\n\n\n/**\n * Abstract class ICommandBase\n */\nabstract class ACommandBase extends Base {\n\n\tabstract public function abort();\n\n}\n\n"
  },
  {
    "path": "lib/AppInfo/Application.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\AppInfo;\n\n\nuse Closure;\nuse OC;\nuse OCA\\FullTextSearch\\Capabilities;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Search\\UnifiedSearchProvider;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\SearchService;\nuse OCP\\AppFramework\\App;\nuse OCP\\AppFramework\\Bootstrap\\IBootContext;\nuse OCP\\AppFramework\\Bootstrap\\IBootstrap;\nuse OCP\\AppFramework\\Bootstrap\\IRegistrationContext;\nuse OCP\\FullTextSearch\\IFullTextSearchManager;\nuse OCP\\IAppConfig;\nuse OCP\\INavigationManager;\nuse OCP\\IServerContainer;\nuse OCP\\IURLGenerator;\nuse Symfony\\Component\\Routing\\Exception\\RouteNotFoundException;\nuse Psr\\Container\\ContainerInterface;\nuse Throwable;\n\nif (file_exists($autoLoad = __DIR__ . '/../../vendor/autoload.php')) {\n\tinclude_once $autoLoad;\n}\n\nclass Application extends App implements IBootstrap {\n\tconst APP_ID = 'fulltextsearch';\n\tconst APP_NAME = 'FullTextSearch';\n\n\n\t/**\n\t * Application constructor.\n\t *\n\t * @param array $params\n\t */\n\tpublic function __construct(array $params = []) {\n\t\tparent::__construct(self::APP_ID, $params);\n\t}\n\n\n\t/**\n\t * @param IRegistrationContext $context\n\t */\n\tpublic function register(IRegistrationContext $context): void {\n\t\t$context->registerCapability(Capabilities::class);\n\t\t$context->registerSearchProvider(UnifiedSearchProvider::class);\n\t\t$context->registerConfigLexicon(ConfigLexicon::class);\n\t\t$this->registerServices($this->getContainer());\n\t}\n\n\t/**\n\t * @param IBootContext $context\n\t *\n\t * @throws Throwable\n\t */\n\tpublic function boot(IBootContext $context): void {\n\t\t$context->injectFn(Closure::fromCallable([$this, 'registerNavigation']));\n\t}\n\n\n\t/**\n\t * Register Navigation Tab\n\t *\n\t * @param ContainerInterface $container\n\t */\n\tprotected function registerServices(ContainerInterface $container) {\n\t\t/** @var IFullTextSearchManager $fullTextSearchManager */\n\t\t$fullTextSearchManager = $container->get(IFullTextSearchManager::class);\n\n\t\t$providerService = $container->get(ProviderService::class);\n\t\t$indexService = $container->get(IndexService::class);\n\t\t$searchService = $container->get(SearchService::class);\n\n\t\t$fullTextSearchManager->registerProviderService($providerService);\n\t\t$fullTextSearchManager->registerIndexService($indexService);\n\t\t$fullTextSearchManager->registerSearchService($searchService);\n\t}\n\n\n\t/**\n\t * Register Navigation Tab\n\t *\n\t * @param IServerContainer $container\n\t */\n\tprotected function registerNavigation(IServerContainer $container) {\n\t\t/** @var IAppConfig $appConfig */\n\t\t$appConfig = $container->get(IAppConfig::class);\n\t\tif (!$appConfig->getValueBool(self::APP_ID, ConfigLexicon::APP_NAVIGATION)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$container->get(INavigationManager::class)\n\t\t\t\t\t  ->add(fn () => $this->fullTextSearchNavigation());\n\t\t} catch (RouteNotFoundException $e) {\n\t\t}\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tprivate function fullTextSearchNavigation(): array {\n\t\t/** @var IURLGenerator $urlGen */\n\t\t$urlGen = OC::$server->get(IURLGenerator::class);\n\n\t\treturn [\n\t\t\t'id'    => self::APP_ID,\n\t\t\t'order' => 5,\n\t\t\t'href'  => $urlGen->linkToRoute(self::APP_ID . '.Navigation.navigate'),\n\t\t\t'icon'  => $urlGen->imagePath(self::APP_ID, 'fulltextsearch.svg'),\n\t\t\t'name'  => \\OC::$server->getL10NFactory()->get('fulltextsearch')->t('Search')\n\t\t];\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Capabilities.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch;\n\n\nuse Exception;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCP\\Capabilities\\ICapability;\n\n\n/**\n * Class Capabilities\n *\n * @package OCA\\FullTextSearch\n */\nclass Capabilities implements ICapability {\n\n\n\t/** @var ProviderService */\n\tprivate $providerService;\n\n\n\t/**\n\t * Capabilities constructor.\n\t *\n\t * @param ProviderService $providerService\n\t */\n\tpublic function __construct(ProviderService $providerService) {\n\t\t$this->providerService = $providerService;\n\t}\n\n\n\t/**\n\t * Return this classes capabilities\n\t *\n\t * Result to be expected:\n\t *    {\"fulltextsearch\":{\"remote\":true,\"providers\":[{\"id\":\"files\",\"name\":\"Files\"}]}}\n\t *\n\t * if 'remote' is false, it means administrator does not allow search request with no CSRF\n\t * check.\n\t *\n\t * 'providers' will returns the list of provider configured for this user.\n\t * If a provider is not listed, no search will be available on it; so replace the 'files'\n\t * search\n\t * only if the 'files' provider is in the list\n\t *\n\t * @return array<string,array<string,boolean|array>>\n\t * @throws Exception\n\t */\n\tpublic function getCapabilities(): array {\n\n\t\t$providers = $this->providerService->getConfiguredProviders();\n\n\t\treturn [\n\t\t\t'fulltextsearch' => [\n\t\t\t\t'remote'    => true,\n\t\t\t\t'providers' => $this->providerService->serialize($providers)\n\t\t\t]\n\t\t];\n\t}\n\n\n}\n\n\n\n\n"
  },
  {
    "path": "lib/Command/Check.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass Check extends Base {\n\tpublic function __construct(\n\t\tprivate ConfigService $configService,\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t\tprivate readonly IAppConfig $appConfig,\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:check')\n\t\t\t ->addOption('json', 'j', InputOption::VALUE_NONE, 'return result as JSON')\n\t\t\t ->setDescription('Check the installation');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output): int {\n\t\tif ($input->getOption('json') === true) {\n\t\t\t$output->writeln(json_encode($this->displayAsJson(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));\n\n\t\t\treturn 0;\n\t\t}\n\n\t\t$output->writeln('Full text search ' . $this->appConfig->getAppValueString('installed_version'));\n\t\t$output->writeln(json_encode($this->configService->getConfig(), JSON_PRETTY_PRINT));\n\t\t$output->writeln('');\n\n\t\t$this->displayPlatform($output);\n\t\t$this->displayProviders($output);\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tprivate function displayAsJson(): array {\n\n\t\t$resultPlatform = [];\n\n\t\ttry {\n\t\t\t$platforms = $this->platformService->getPlatforms();\n\t\t\tforeach ($platforms as $platformWrapper) {\n\t\t\t\t$platform = $platformWrapper->getPlatform();\n\t\t\t\t$platform->loadPlatform();\n\t\t\t\t$resultPlatform[] = [\n\t\t\t\t\t'class'   => $platformWrapper->getClass(),\n\t\t\t\t\t'version' => $platformWrapper->getVersion(),\n\t\t\t\t\t'config'  => $platform->getConfiguration()\n\t\t\t\t];\n\t\t\t}\n\n\t\t} catch (Exception $e) {\n\t\t\t$resultPlatform = ['error' => $e->getMessage()];\n\t\t}\n\n\t\t$resultProviders = [];\n\t\ttry {\n\t\t\t$providers = $this->providerService->getProviders();\n\t\t\tforeach ($providers as $providerWrapper) {\n\t\t\t\t$provider = $providerWrapper->getProvider();\n\t\t\t\t$resultProviders[$provider->getId()] = [\n\t\t\t\t\t'version' => $providerWrapper->getVersion(),\n\t\t\t\t\t'config'  => $provider->getConfiguration()\n\t\t\t\t];\n\t\t\t}\n\t\t} catch (Exception $e) {\n\t\t\t$resultProviders[] = ['error' => $e->getMessage()];\n\t\t}\n\n\t\treturn [\n\t\t\t'fulltextsearch' => [\n\t\t\t\t'version' => $this->appConfig->getAppValueString('installed_version'),\n\t\t\t\t'config'  => $this->configService->getConfig()\n\t\t\t],\n\n\t\t\t'platform'  => $resultPlatform,\n\t\t\t'providers' => $resultProviders\n\t\t];\n\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprivate function displayPlatform(OutputInterface $output) {\n\t\t$platforms = $this->platformService->getPlatforms();\n\n\t\tif (empty($platforms)) {\n\t\t\t$output->writeln('No Search Platform available');\n\n\t\t\treturn;\n\t\t}\n\n\t\t$select = $this->appConfig->getAppValueString(ConfigLexicon::SEARCH_PLATFORM);\n\t\t$output->writeln('- Search Platform:' . (($select === '') ? ' (none selected)' : ''));\n\n\t\tforeach ($platforms as $platformWrapper) {\n\t\t\t$platform = $platformWrapper->getPlatform();\n\t\t\t$selected = ($platformWrapper->getClass() === $select) ? '(Selected)' : '';\n\t\t\t$output->writeln($platform->getName() . ' ' . $platformWrapper->getVersion() . ' ' . $selected);\n\t\t\ttry {\n\t\t\t\techo json_encode($platform->getConfiguration(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n\t\t\t} catch (Exception $e) {\n\t\t\t\techo '(not configured)';\n\t\t\t}\n\t\t\t$output->writeln(' ');\n\t\t}\n\n\t\t$output->writeln('');\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprivate function displayProviders(OutputInterface $output) {\n\t\t$providers = $this->providerService->getProviders();\n\n\t\tif (sizeof($providers) === 0) {\n\t\t\t$output->writeln('No Content Provider available');\n\n\t\t\treturn;\n\t\t}\n\n\t\t$output->writeln('- Content Providers:');\n\n\t\tforeach ($providers as $providerWrapper) {\n\t\t\t$provider = $providerWrapper->getProvider();\n\t\t\t$output->writeln($provider->getName() . ' ' . $providerWrapper->getVersion());\n\t\t\techo json_encode($provider->getConfiguration(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);\n\t\t\t$output->writeln('');\n\t\t}\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Command/CollectionDelete.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\n\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Exceptions\\CollectionArgumentException;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n\nclass CollectionDelete extends Base {\n\n\n\t/** @var CollectionService */\n\tprivate $collectionService;\n\n\n\t/**\n\t * @param CollectionService $collectionService\n\t */\n\tpublic function __construct(CollectionService $collectionService) {\n\t\tparent::__construct();\n\n\t\t$this->collectionService = $collectionService;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:collection:delete')\n\t\t\t ->setDescription('Delete collection')\n\t\t\t ->addArgument('name', InputArgument::REQUIRED, 'name of the collection to delete');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$collection = $input->getArgument('name');\n\t\tif (!$this->collectionService->hasCollection($collection)) {\n\t\t\tthrow new CollectionArgumentException('unknown collection');\n\t\t}\n\n\t\t$this->collectionService->deleteCollection($collection);\n\n\t\treturn 0;\n\t}\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/CollectionInit.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\CliService;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\IUserManager;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n\nclass CollectionInit extends Base {\n\n\n\t/** @var ProviderService */\n\tprivate $providerService;\n\n\t/** @var CollectionService */\n\tprivate $collectionService;\n\n\t/** @var IUserManager */\n\tprivate $userManager;\n\n\t/** @var RunningService */\n\tprivate $runningService;\n\n\t/** @var CliService */\n\tprivate $cliService;\n\n\n\t/**\n\t * @param IUserManager $userManager\n\t * @param CollectionService $collectionService\n\t * @param ProviderService $providerService\n\t * @param RunningService $runningService\n\t * @param CliService $cliService\n\t */\n\tpublic function __construct(\n\t\tIUserManager $userManager,\n\t\tCollectionService $collectionService,\n\t\tProviderService $providerService,\n\t\tRunningService $runningService,\n\t\tCliService $cliService\n\t) {\n\t\tparent::__construct();\n\n\t\t$this->userManager = $userManager;\n\t\t$this->collectionService = $collectionService;\n\t\t$this->providerService = $providerService;\n\t\t$this->runningService = $runningService;\n\t\t$this->cliService = $cliService;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:collection:init')\n\t\t\t ->setDescription('Initiate a collection')\n\t\t\t ->addArgument('name', InputArgument::REQUIRED, 'name of the collection');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$collection = $input->getArgument('name');\n\t\t$this->collectionService->confirmCollectionString($collection);\n\n\t\t$runner = new Runner($this->runningService, 'commandIndex', ['nextStep' => 'n']);\n//\t\t$runner->sourceIsCommandLine($this, $output);\n\t\t$this->collectionService->setRunner($runner);\n\t\t$this->cliService->setRunner($runner);\n\n\t\t$this->cliService->createPanel(\n\t\t\t'collection', [\n\t\t\t\t\t\t\t'┌─ Collection ' . $collection . ' ────',\n\t\t\t\t\t\t\t'│ ProviderId, UserId: <info>%providerId%</info> / <info>%userId%</info>',\n\t\t\t\t\t\t\t'│ Chunk: <info>%chunkCurr:3s%</info>/<info>%chunkTotal%</info>',\n\t\t\t\t\t\t\t'│ Document: <info>%documentCurr:6s%</info>/<info>%documentChunk%</info>',\n\t\t\t\t\t\t\t'│',\n\t\t\t\t\t\t\t'│ Total Document: <info>%documentTotal%</info>',\n\t\t\t\t\t\t\t'│ Index initiated: <info>%indexCount%</info>',\n\t\t\t\t\t\t\t'└──'\n\t\t\t\t\t\t]\n\t\t);\n\n\t\t$runner->setInfoArray([\n\t\t\t\t\t\t\t\t  'providerId' => '',\n\t\t\t\t\t\t\t\t  'userId' => '',\n\t\t\t\t\t\t\t\t  'chunkCurr' => '',\n\t\t\t\t\t\t\t\t  'chunkTotal' => '',\n\t\t\t\t\t\t\t\t  'documentCurr' => '',\n\t\t\t\t\t\t\t\t  'documentChunk' => '',\n\t\t\t\t\t\t\t\t  'documentTotal' => '',\n\t\t\t\t\t\t\t\t  'indexCount' => 0\n\t\t\t\t\t\t\t  ]);\n\n\t\t$this->cliService->initDisplay();\n\t\t$this->cliService->displayPanel('run', 'collection');\n\t\t$this->cliService->runDisplay($output);\n\n\t\t$providers = $this->providerService->getProviders();\n\t\tforeach ($providers as $providerWrapper) {\n\t\t\t$this->indexProvider($runner, $collection, $providerWrapper->getProvider());\n\t\t}\n\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function indexProvider(\n\t\tRunner $runner,\n\t\tstring $collection,\n\t\tIFullTextSearchProvider $provider,\n\t\tstring $userId = ''\n\t) {\n\t\t$runner->setInfo('providerId', $provider->getId());\n\t\t$options = new IndexOptions();\n\t\t$provider->setIndexOptions($options);\n\n\t\tif ($userId === '') {\n\t\t\t$users = $this->userManager->search('');\n\t\t} else {\n\t\t\t$users = [$userId];\n\t\t}\n\n\t\tforeach ($users as $user) {\n\t\t\tif ($user === null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$runner->setInfo('userId', $user->getUID());\n\n\t\t\ttry {\n\t\t\t\t$this->collectionService->initCollectionIndexes(\n\t\t\t\t\t$provider,\n\t\t\t\t\t$collection,\n\t\t\t\t\t$user->getUID(),\n\t\t\t\t\t$options\n\t\t\t\t);\n\t\t\t} catch (Exception $e) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "lib/Command/CollectionLink.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Exceptions\\CollectionArgumentException;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCP\\IAppConfig;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass CollectionLink extends Base {\n\tpublic function __construct(\n\t\tprivate CollectionService $collectionService,\n\t) {\n\t\tparent::__construct();\n\t}\n\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:collection:link')\n\t\t\t->setDescription('Link collection to a user')\n\t\t    ->addArgument('collection', InputArgument::OPTIONAL, 'collection' , '')\n\t\t    ->addArgument('userId', InputArgument::OPTIONAL, 'user to link a collection to', '')\n\t\t\t->addOption('unlink', '', InputOption::VALUE_NONE, 'unlink collection');\n\t}\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws CollectionArgumentException\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output): int {\n\t\t$links = $this->collectionService->getLinks();\n\t\t$collection = $input->getArgument('collection');\n\n\t\tif ($collection === '') {\n\t\t\tif (empty($links)) {\n\t\t\t\t$output->writeln('no collection linked to any user');\n\t\t\t}\n\n\t\t\tforeach($links as $name => $userId) {\n\t\t\t\t$output->writeln('- Collection <info>' . $name . '</info> linked to user <info>' . $userId . '</info>');\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (!$this->collectionService->hasCollection($collection)) {\n\t\t\tthrow new CollectionArgumentException('unknown collection');\n\t\t}\n\n\t\tif ($input->getOption('unlink')) {\n\t\t\t$this->collectionService->removeLink($collection);\n\t\t\t$output->writeln('unlinked collection');\n\t\t\treturn 0;\n\t\t}\n\n\t\t$userId = $input->getArgument('userId');\n\t\tif ($userId === '') {\n\t\t\tthrow new CollectionArgumentException('missing userId');\n\t\t}\n\n\t\t$this->collectionService->addLink($collection, $userId);\n\t\t$output->writeln('linked collection');\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "lib/Command/CollectionList.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass CollectionList extends Base {\n\tpublic function __construct(\n\t\tprivate CollectionService $collectionService,\n\t\tprivate ConfigService $configService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:collection:list')\n\t\t\t->setDescription('List collections');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$collections = $this->collectionService->getCollections();\n\t\t$output->writeln('found ' . sizeof($collections) . ' collection(s)');\n\n\t\tforeach ($this->collectionService->getCollections() as $collection) {\n\t\t\t$output->writeln('- ' . (($collection === $this->configService->getInternalCollection()) ? '*' : '') . $collection);\n\t\t}\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "lib/Command/Configure.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass Configure extends Base {\n\tpublic function __construct(\n\t\tprivate ConfigService $configService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:configure')\n\t\t\t ->addArgument('json', InputArgument::OPTIONAL, 'set config')\n\t\t\t ->setDescription('Configure the installation');\n\t}\n\n\tprotected function execute(InputInterface $input, OutputInterface $output): int {\n\t\tif ($input->getArgument('json')) {\n\t\t\t$this->configService->setConfig(json_decode($input->getArgument('json') ?? '', true) ?? []);\n\t\t}\n\n\t\t$output->writeln(json_encode($this->configService->getConfig(), JSON_PRETTY_PRINT));\n\t\treturn self::SUCCESS;\n\t}\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/DocumentIndex.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass DocumentIndex extends Base {\n\tpublic function __construct(\n\t\tprivate ProviderService $providerService,\n\t\tprivate PlatformService $platformService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:document:index')\n\t\t\t ->setDescription('index one specific document')\n\t\t\t ->addArgument('userId', InputArgument::REQUIRED, 'userId')\n\t\t\t ->addArgument('providerId', InputArgument::REQUIRED, 'providerId')\n\t\t\t ->addArgument('documentId', InputArgument::REQUIRED, 'documentId');\n\t}\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output): int {\n\t\t$providerId = $input->getArgument('providerId');\n\t\t$documentId = $input->getArgument('documentId');\n\t\t$userId = $input->getArgument('userId');\n\n\t\t$providerWrapper = $this->providerService->getProvider($providerId);\n\t\t$provider = $providerWrapper->getProvider();\n\n\t\t$index = new Index($providerId, $documentId);\n\t\t$index->setOwnerId($userId);\n\t\t$index->setStatus(Index::INDEX_FULL);\n\t\t$indexDocument = $provider->updateDocument($index);\n\t\tif (!$indexDocument->hasIndex()) {\n\t\t\t$indexDocument->setIndex($index);\n\t\t}\n\n\t\tif ($indexDocument->getIndex()\n\t\t\t\t\t\t  ->isStatus(Index::INDEX_REMOVE)) {\n\t\t\tthrow new Exception('Unknown document');\n\t\t}\n\n\t\t$platformWrapper = $this->platformService->getPlatform();\n\t\t$platform = $platformWrapper->getPlatform();\n\n\t\t$indexDocument->getIndex()\n\t\t\t\t\t  ->setStatus(Index::INDEX_FULL);\n\t\t$platform->indexDocument($indexDocument);\n\n\t\treturn 0;\n\t}\n\n\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/DocumentPlatform.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass DocumentPlatform extends Base {\n\tpublic function __construct(\n\t\tprivate PlatformService $platformService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:document:platform')\n\t\t\t ->setDescription('Get document from index')\n\t\t\t ->addArgument('providerId', InputArgument::REQUIRED, 'providerId')\n\t\t\t ->addArgument('documentId', InputArgument::REQUIRED, 'documentId')\n\t\t\t ->addOption('content', 'c', InputOption::VALUE_NONE, 'return some content');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$providerId = $input->getArgument('providerId');\n\t\t$documentId = $input->getArgument('documentId');\n\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\n\t\t$indexDocument = $platform->getDocument($providerId, $documentId);\n\t\t$result = [\n\t\t\t'document' => $indexDocument\n\t\t];\n\t\tif ($input->getOption('content') === true) {\n\t\t\t$result['content'] = substr($indexDocument->getContent(), 0, 200);\n\t\t}\n\n\t\t$output->writeln(json_encode($result, JSON_PRETTY_PRINT));\n\n\t\treturn 0;\n\t}\n\n\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/DocumentProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass DocumentProvider extends Base {\n\tpublic function __construct(\n\t\tprivate ProviderService $providerService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:document:provider')\n\t\t\t ->setDescription('Get document from index')\n\t\t\t ->addArgument('userId', InputArgument::REQUIRED, 'userId')\n\t\t\t ->addArgument('providerId', InputArgument::REQUIRED, 'providerId')\n\t\t\t ->addArgument('documentId', InputArgument::REQUIRED, 'documentId')\n\t\t\t ->addOption('content', 'c', InputOption::VALUE_NONE, 'return some content');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$providerId = $input->getArgument('providerId');\n\t\t$documentId = $input->getArgument('documentId');\n\t\t$userId = $input->getArgument('userId');\n\n\t\t$providerWrapper = $this->providerService->getProvider($providerId);\n\t\t$provider = $providerWrapper->getProvider();\n\n\t\t$index = new Index($providerId, $documentId);\n\t\t$index->setOwnerId($userId);\n\t\t$index->setStatus(Index::INDEX_FULL);\n\t\t$indexDocument = $provider->updateDocument($index);\n\n\t\t$index->setOwnerId($indexDocument->getAccess()->getOwnerId());\n\n\t\tif (!$indexDocument->hasIndex()) {\n\t\t\t$indexDocument->setIndex($index);\n\t\t}\n\n\t\tif ($indexDocument->getIndex()\n\t\t\t\t\t\t  ->isStatus(Index::INDEX_REMOVE)) {\n\t\t\tthrow new Exception('Unknown document');\n\t\t}\n\n\t\t$output->writeln('Document: ');\n\t\t$output->writeln(json_encode($indexDocument, JSON_PRETTY_PRINT));\n\n\t\tif ($input->getOption('content') !== true) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t$output->writeln('Content: ');\n\t\t$content = $indexDocument->getContent();\n\t\tif ($indexDocument->isContentEncoded() === IIndexDocument::ENCODED_BASE64) {\n\t\t\t$content = base64_decode($content, true);\n\t\t}\n\n\t\t$output->writeln(substr($content, 0, 80));\n\n\t\t$parts = $indexDocument->getParts();\n\t\t$output->writeln(sizeof($parts) . ' Part(s)');\n\t\tforeach (array_keys($parts) as $part) {\n\t\t\t$output->writeln(\n\t\t\t\t\"'\" . $part . \"' \" . substr($parts[$part], 0, 80) . '   (size: ' . strlen(\n\t\t\t\t\t$parts[$part]\n\t\t\t\t) . ')'\n\t\t\t);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/DocumentStatus.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Exceptions\\IndexDoesNotExistException;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass DocumentStatus extends Base {\n\tprivate array $statusAvailable = [\n\t\t'IGNORE' => 'document will never be indexed',\n\t\t'INDEX'  => 'document will be indexed',\n\t\t'DONE'   => 'document is well indexed',\n\t\t'REMOVE' => 'document will be removed',\n\t\t'FAILED' => 'index had fail'\n\t];\n\n\tpublic function __construct(\n\t\tprivate IndexService $indexService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:document:status')\n\t\t\t ->setDescription('change the status on one specific document')\n\t\t\t ->addArgument('provider', InputArgument::REQUIRED, 'Id of the provider')\n\t\t\t ->addArgument('document', InputArgument::REQUIRED, 'If of the document')\n\t\t\t ->addOption('value', '', InputOption::VALUE_REQUIRED, 'new status', '')\n\t\t\t ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'specify the owner of the document', '')\n\t\t\t ->addOption('json', 'j', InputOption::VALUE_NONE, 'return status in JSON');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$providerId = $input->getArgument('provider');\n\t\t$documentId = $input->getArgument('document');\n\t\t$value = $input->getOption('value');\n\t\t$userId = $input->getOption('user');\n\t\t$json = $input->getOption('json');\n\n\t\ttry {\n\t\t\t$index = $this->indexService->getIndex($providerId, $documentId);\n\t\t\tif ($value !== '') {\n\t\t\t\t$status = $this->statusConvertFromString($value);\n\t\t\t\t$index->setStatus($status, true);\n\t\t\t\t$this->indexService->updateIndex($index);\n\t\t\t}\n\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\tif ($userId === '') {\n\t\t\t\tthrow new Exception(\n\t\t\t\t\t\"Index is not known.\\nIf you want to generate the entry, please specify the owner of the document using --user <userId>\"\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t$status = $this->statusConvertFromString($value);\n\t\t\t$index = $this->indexService->createIndex($providerId, $documentId, $userId, $status);\n\t\t}\n\n\n\t\tif ($json) {\n\t\t\techo json_encode($index, JSON_PRETTY_PRINT) . \"\\n\";\n\n\t\t\treturn 0;\n\t\t}\n\n\t\t$status = $this->statusConvertToString($index->getStatus());\n\t\t$desc = $this->statusAvailable[$status];\n\t\t$output->writeln('current status: <info>' . $status . '</info> (' . $desc . ')');\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @param int $status\n\t *\n\t * @return string\n\t */\n\tprivate function statusConvertToString(int $status): string {\n\t\tswitch ($status) {\n\t\t\tcase IIndex::INDEX_OK:\n\t\t\tcase IIndex::INDEX_DONE:\n\t\t\t\treturn 'DONE';\n\n\t\t\tcase IIndex::INDEX_IGNORE:\n\t\t\t\treturn 'IGNORE';\n\n\t\t\tcase IIndex::INDEX_META:\n\t\t\tcase IIndex::INDEX_CONTENT:\n\t\t\tcase IIndex:: INDEX_PARTS:\n\t\t\tcase IIndex:: INDEX_FULL:\n\t\t\t\treturn 'INDEX';\n\n\t\t\tcase IIndex:: INDEX_REMOVE:\n\t\t\t\treturn 'REMOVE';\n\n\t\t\tcase IIndex::INDEX_FAILED:\n\t\t\t\treturn 'FAILED';\n\t\t}\n\n\t\treturn 'unknown';\n\t}\n\n\n\t/**\n\t * @param string $status\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprivate function statusConvertFromString(string $status): int {\n\t\tswitch ($status) {\n\t\t\tcase 'DONE':\n\t\t\t\treturn IIndex::INDEX_OK;\n\n\t\t\tcase 'IGNORE':\n\t\t\t\treturn IIndex::INDEX_IGNORE;\n\n\t\t\tcase 'INDEX':\n\t\t\t\treturn IIndex:: INDEX_FULL;\n\n\t\t\tcase 'REMOVE':\n\t\t\t\treturn IIndex:: INDEX_REMOVE;\n\n\t\t\tcase 'FAILED':\n\t\t\t\treturn IIndex::INDEX_FAILED;\n\t\t}\n\n\t\tthrow new Exception(\"Specify a valid status: \" . implode(', ', array_keys($this->statusAvailable)));\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Command/Index.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\InterruptedException;\nuse OCA\\FullTextSearch\\ACommandBase;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformTemporaryException;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Index as ModelIndex;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\CliService;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\IUserManager;\nuse OutOfBoundsException;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Terminal;\nuse Throwable;\n\nclass Index extends ACommandBase {\n\tuse TArrayTools;\n\n\tconst INDEX_OPTION_NO_READLINE = '_no-readline';\n\n//\t\t\t'%job:1s%%message:-40s%%current:6s%/%max:6s% [%bar%] %percent:3s%% \\n %duration% %infos:-12s% %jvm:-30s%      '\n\tconst PANEL_RUN = 'run';\n\tconst PANEL_RUN_LINE_OPTIONS = 'Options: %options%';\n\tconst PANEL_RUN_LINE_MEMORY = 'Memory: %_memory%';\n\n\tconst PANEL_INDEX = 'indexing';\n\tconst PANEL_INDEX_LINE_HEADER = '┌─ Indexing %_paused% ────';\n\tconst PANEL_INDEX_LINE_ACCOUNT = '│ Provider: <info>%providerName:-20s%</info> Account: <info>%userId%</info>';\n\tconst PANEL_INDEX_LINE_ACTION = '│ Action: <info>%action%</info>';\n\tconst PANEL_INDEX_LINE_DOCUMENT = '│ Document: <info>%documentId%</info>';\n\tconst PANEL_INDEX_LINE_INFO = '│ Info: <info>%info%</info>';\n\tconst PANEL_INDEX_LINE_TITLE = '│ Title: <info>%title%</info>';\n\tconst PANEL_INDEX_LINE_CONTENT = '│ Content size: <info>%content%</info>';\n\tconst PANEL_INDEX_LINE_CHUNK = '│ Chunk: %chunkCurrent:6s%/%chunkTotal%';\n\tconst PANEL_INDEX_LINE_PROGRESS = '│ Progress: %documentCurrent:6s%/%documentTotal%';\n\tconst PANEL_INDEX_LINE_FOOTER = '└──';\n\n\tconst PANEL_RESULT = 'result';\n\tconst PANEL_RESULT_LINE_HEADER = '┌─ Results ────';\n\tconst PANEL_RESULT_LINE_RESULT = '│ Result: <info>%resultCurrent:6s%</info>/<info>%resultTotal%</info>';\n\tconst PANEL_RESULT_LINE_INDEX = '│ Index: <info>%resultIndex%</info>';\n\tconst PANEL_RESULT_LINE_STATUS = '│ Status: %resultStatusColored%';\n\tconst PANEL_RESULT_LINE_MESSAGE1 = '│ Message: <info>%resultMessageA%</info>';\n\tconst PANEL_RESULT_LINE_MESSAGE2 = '│ <info>%resultMessageB%</info>';\n\tconst PANEL_RESULT_LINE_MESSAGE3 = '│ <info>%resultMessageC%</info>';\n\tconst PANEL_RESULT_LINE_FOOTER = '└──';\n\n\tconst PANEL_ERRORS = 'errors';\n\tconst PANEL_ERRORS_LINE_HEADER = '┌─ Errors ────';\n\tconst PANEL_ERRORS_LINE_ERRORS = '│ Error: <comment>%errorCurrent:6s%</comment>/<comment>%errorTotal%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_INDEX = '│ Index: <comment>%errorIndex%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_EXCEPTION = '│ Exception: <comment>%errorException%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE1 = '│ Message: <comment>%errorMessageA%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE2 = '│ <comment>%errorMessageB%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE3 = '│ <comment>%errorMessageC%</comment>';\n\tconst PANEL_ERRORS_LINE_FOOTER = '└──';\n\n\tconst PANEL_COMMANDS_ROOT = 'root';\n\tconst PANEL_COMMANDS_ROOT_LINE = '## q:quit ## p:pause ';\n\tconst PANEL_COMMANDS_PAUSED = 'paused';\n\tconst PANEL_COMMANDS_PAUSED_LINE = '## q:quit ## u:unpause ## n:next step';\n\tconst PANEL_COMMANDS_DONE = 'done';\n\tconst PANEL_COMMANDS_DONE_LINE = '## q:quit';\n\tconst PANEL_COMMANDS_NAVIGATION = 'navigation';\n\tconst PANEL_COMMANDS_ERRORS_LINE = '## f:first error ## h/j:prec/next error ## d:delete error ## l:last error';\n\tconst PANEL_COMMANDS_RESULTS_LINE = '## x:first result ## c/v:prec/next result ## b:last result';\n\n\n\t/** @var Runner */\n\tprivate $runner;\n\n\t/** @var Terminal */\n\tprivate $terminal;\n\n\t/** @var array */\n\tprivate $results = [];\n\n\t/** @var bool */\n\tprivate $navigateLastResult = true;\n\n\t/** @var array */\n\tprivate $errors = [];\n\n\t/** @var bool */\n\tprivate $navigateLastError = true;\n\n\tpublic function __construct(\n\t\tprivate IUserManager $userManager,\n\t\tprivate RunningService $runningService,\n\t\tprivate CliService $cliService,\n\t\tprivate IndexService $indexService,\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t\tprivate ConfigService $configService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:index')\n\t\t\t ->setDescription('Index files')\n\t\t\t ->addArgument('options', InputArgument::OPTIONAL, 'options')\n\t\t\t ->addOption(\n\t\t\t\t 'no-readline', 'r', InputOption::VALUE_NONE,\n\t\t\t\t 'disable readline - non interactive mode'\n\t\t\t );\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int|null|void\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$options = $this->generateIndexOptions($input);\n\n\t\tif ($options->getOptionBool(self::INDEX_OPTION_NO_READLINE, false) === false) {\n\t\t\t/** do not get stuck while waiting interactive input */\n\t\t\ttry {\n\t\t\t\treadline_callback_handler_install(\n\t\t\t\t\t'', function () {\n\t\t\t\t}\n\t\t\t\t);\n\t\t\t} catch (Throwable $t) {\n\t\t\t\tthrow new Exception('Please install php-readline, or use --no-readline');\n\t\t\t}\n\t\t}\n\n\t\tstream_set_blocking(STDIN, false);\n\n\t\t$this->terminal = new Terminal();\n\n//\t\t$outputStyle = new OutputFormatterStyle('white', 'black', ['bold']);\n//\t\t$output->getFormatter()\n//\t\t\t   ->setStyle('char', $outputStyle);\n\n\t\t$this->runner = new Runner($this->runningService, 'commandIndex', ['nextStep' => 'n']);\n\t\t$this->runner->onKeyPress([$this, 'onKeyPressed']);\n\t\t$this->runner->onNewIndexError([$this, 'onNewIndexError']);\n\t\t$this->runner->onNewIndexResult([$this, 'onNewIndexResult']);\n\t\t$this->runner->pause($options->getOptionBool('paused', false));\n\n\t\t$this->indexService->setRunner($this->runner);\n\t\t$this->cliService->setRunner($this->runner);\n\n\t\t$this->generatePanels($options);\n\t\t$this->runner->setInfo('options', json_encode($options));\n\n\t\ttry {\n\t\t\t$this->runner->sourceIsCommandLine($this, $output);\n\t\t\t$this->runner->start();\n\n\t\t\tif ($options->getOption('errors') === 'reset') {\n\t\t\t\t$this->indexService->resetErrorsAll();\n\t\t\t}\n\n\t\t\t$this->testPlatform();\n\t\t\t$this->cliService->runDisplay($output);\n\t\t\t$this->generateIndexErrors();\n\t\t\t$this->displayError();\n\t\t\t$this->displayResult();\n\n\t\t\t$providers = $this->providerService->getProviders();\n\t\t\tforeach ($providers as $providerWrapper) {\n\t\t\t\t$provider = $providerWrapper->getProvider();\n\n\t\t\t\tif (!$this->isIncludedProvider($options, $provider->getId())) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$provider->setRunner($this->runner);\n\t\t\t\t$provider->setIndexOptions($options);\n\t\t\t\t$this->indexProvider($provider, $options);\n\t\t\t}\n\n\t\t} catch (Exception $e) {\n\t\t\t$this->runner->exception($e->getMessage(), true);\n\t\t\tthrow $e;\n\t\t}\n\n\t\t$this->runner->setInfo('documentCurrent', 'all');\n\t\t$this->runner->stop();\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @param string $key\n\t */\n\tpublic function onKeyPressed(string $key) {\n\t\t$key = strtolower($key);\n\t\tif ($key === 'q') {\n\t\t\ttry {\n\t\t\t\t$this->runner->stop();\n\t\t\t} catch (TickDoesNotExistException $e) {\n\t\t\t\t/** we do nohtin' */\n\t\t\t}\n\t\t\texit();\n\t\t}\n\n\t\t$current = $this->cliService->currentPanel('commands');\n\t\tif ($current === self::PANEL_COMMANDS_ROOT && $key === 'p') {\n\t\t\t$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_PAUSED);\n\t\t\t$this->runner->pause(true);\n\t\t}\n\t\tif ($current === self::PANEL_COMMANDS_PAUSED && $key === 'u') {\n\t\t\t$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_ROOT);\n\t\t\t$this->runner->pause(false);\n\t\t}\n\n\t\tif ($key === 'x') {\n\t\t\t$this->displayResult(-99);\n\t\t}\n\t\tif ($key === 'c') {\n\t\t\t$this->displayResult(-1);\n\t\t}\n\t\tif ($key === 'v') {\n\t\t\t$this->displayResult(1);\n\t\t}\n\t\tif ($key === 'b') {\n\t\t\t$this->displayResult(99);\n\t\t}\n\n\t\tif ($key === 'f') {\n\t\t\t$this->displayError(-99);\n\t\t}\n\t\tif ($key === 'h') {\n\t\t\t$this->displayError(-1);\n\t\t}\n\t\tif ($key === 'j') {\n\t\t\t$this->displayError(1);\n\t\t}\n\t\tif ($key === 'l') {\n\t\t\t$this->displayError(99);\n\t\t}\n\t\tif ($key === 'd') {\n\t\t\t$this->deleteError();\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $error\n\t */\n\tpublic function onNewIndexError(array $error) {\n\t\t$this->errors[] = $error;\n\t\t$this->displayError();\n\t}\n\n\n\t/**\n\t * @param array $result\n\t */\n\tpublic function onNewIndexResult(array $result) {\n\t\t$this->results[] = $result;\n\t\t$this->displayResult();\n\t}\n\n\n\t/**\n\t * @throws Exception\n\t */\n\tprivate function testPlatform() {\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\n\t\tif (!$platform->testPlatform()) {\n\t\t\tthrow new Exception('failed platform test.');\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t * @param IndexOptions $options\n\t *\n\t * @throws Exception\n\t */\n\tprivate function indexProvider(IFullTextSearchProvider $provider, IndexOptions $options) {\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\n\t\t$platform->initializeIndex();\n\t\t$provider->onInitializingIndex($platform);\n\n\t\t$platform->setRunner($this->runner);\n\n\t\t$users = $this->generateUserList($options);\n\t\tforeach ($users as $user) {\n\t\t\tif ($user === null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t$this->indexService->indexProviderContentFromUser($platform, $provider, $user->getUID(), $options);\n\t\t\t} catch (PlatformTemporaryException $e) {\n\t\t\t\tthrow $e;\n\t\t\t} catch (Exception $e) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t *\n\t * @return IndexOptions\n\t */\n\tprivate function generateIndexOptions(InputInterface $input): IndexOptions {\n\t\t$jsonOptions = $input->getArgument('options');\n\n\t\t$options = [];\n\t\tif (is_string($jsonOptions)) {\n\t\t\t$options = json_decode($jsonOptions, true);\n\t\t}\n\n\t\tif (!is_array($options)) {\n\t\t\t$options = [];\n\t\t}\n\n\t\tif ($input->getOption('no-readline')) {\n\t\t\t$options['_no-readline'] = true;\n\t\t}\n\n\t\treturn new IndexOptions($options);\n\t}\n\n\n\t/**\n\t * @param IndexOptions $options\n\t * @param string $providerId\n\t *\n\t * @return bool\n\t */\n\tprivate function isIncludedProvider(IndexOptions $options, string $providerId): bool {\n\t\tif ($options->getOption('provider', '') !== ''\n\t\t\t&& $options->getOption('provider') !== $providerId) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($options->getOptionArray('providers', []) !== []) {\n\t\t\treturn (in_array($providerId, $options->getOptionArray('providers', [])));\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n\t/**\n\t * @param IndexOptions $options\n\t *\n\t * @return array\n\t */\n\tprivate function generateUserList(IndexOptions $options): array {\n\t\tif ($options->getOption('user', '') !== '') {\n\t\t\treturn [$this->userManager->get($options->getOption('user'))];\n\t\t}\n\n\t\tif ($options->getOptionArray('users', []) !== []) {\n\t\t\treturn array_map([$this->userManager, 'get'], $options->getOptionArray('users'));\n\t\t}\n\n\t\treturn $this->userManager->search('');\n\t}\n\n\n\t/**\n\t * @param IndexOptions $options\n\t */\n\tprivate function generatePanels(IndexOptions $options) {\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_RUN,\n\t\t\t[\n\t\t\t\tself::PANEL_RUN_LINE_OPTIONS,\n\t\t\t\tself::PANEL_RUN_LINE_MEMORY\n\t\t\t]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_INDEX, [\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_HEADER,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_ACTION,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_ACCOUNT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_DOCUMENT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_INFO,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_TITLE,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_CONTENT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_CHUNK,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_PROGRESS,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_FOOTER,\n\t\t\t\t\t\t\t ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_RESULT, [\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_HEADER,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_RESULT,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_INDEX,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_STATUS,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE1,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE2,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE3,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_FOOTER,\n\t\t\t\t\t\t\t  ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_ERRORS, [\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_HEADER,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERRORS,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_INDEX,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_EXCEPTION,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE1,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE2,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE3,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_FOOTER,\n\t\t\t\t\t\t\t  ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_PAUSED, [\n\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_PAUSED_LINE\n\t\t\t\t\t\t\t\t\t   ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_ROOT, [\n\t\t\t\t\t\t\t\t\t\t self::PANEL_COMMANDS_ROOT_LINE\n\t\t\t\t\t\t\t\t\t ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_NAVIGATION, [\n\t\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_RESULTS_LINE,\n\t\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_ERRORS_LINE\n\t\t\t\t\t\t\t\t\t\t   ]\n\t\t);\n\n\t\t$this->cliService->initDisplay();\n\t\t$this->cliService->displayPanel('run', self::PANEL_RUN);\n\t\t$this->cliService->displayPanel('indexPanel', self::PANEL_INDEX);\n\t\t$this->cliService->displayPanel('resultsPanel', self::PANEL_RESULT);\n\t\t$this->cliService->displayPanel('errorsPanel', self::PANEL_ERRORS);\n\n\t\tif ($options->getOptionBool(self::INDEX_OPTION_NO_READLINE, false) === false) {\n\t\t\t$this->cliService->displayPanel('navigation', self::PANEL_COMMANDS_NAVIGATION);\n\t\t\tif ($this->runner->isPaused()) {\n\t\t\t\t$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_PAUSED);\n\t\t\t} else {\n\t\t\t\t$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_ROOT);\n\t\t\t}\n\t\t}\n\n\t\t// full list of info that can be edited\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'userId' => '',\n\t\t\t\t'providerName' => '',\n\t\t\t\t'_memory' => '',\n\t\t\t\t'documentId' => '',\n\t\t\t\t'action' => '',\n\t\t\t\t'info' => '',\n\t\t\t\t'title' => '',\n\t\t\t\t'_paused' => '',\n\n\t\t\t\t'resultIndex' => '',\n\t\t\t\t'resultCurrent' => '',\n\t\t\t\t'resultTotal' => '',\n\t\t\t\t'resultMessageA' => '',\n\t\t\t\t'resultMessageB' => '',\n\t\t\t\t'resultMessageC' => '',\n\t\t\t\t'resultStatus' => '',\n\t\t\t\t'resultStatusColored' => '',\n\t\t\t\t'content' => '',\n\t\t\t\t'statusColored' => '',\n\t\t\t\t'chunkCurrent' => '',\n\t\t\t\t'chunkTotal' => '',\n\t\t\t\t'documentCurrent' => '',\n\t\t\t\t'documentTotal' => '',\n\t\t\t\t'progressStatus' => '',\n\t\t\t\t'errorCurrent' => '0',\n\t\t\t\t'errorTotal' => '0',\n\t\t\t\t'errorMessageA' => '',\n\t\t\t\t'errorMessageB' => '',\n\t\t\t\t'errorMessageC' => '',\n\t\t\t\t'errorException' => '',\n\t\t\t\t'errorIndex' => ''\n\t\t\t]\n\t\t);\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t */\n\tprivate function displayError(int $pos = 0) {\n\t\t$total = sizeof($this->errors);\n\n\t\tif ($total === 0) {\n\t\t\t$this->runner->setInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'errorCurrent' => 0,\n\t\t\t\t\t'errorTotal' => 0,\n\t\t\t\t]\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$current = key($this->errors) + 1;\n\t\t\t$error = $this->getNavigationError($pos, ($current === 1), ($current === $total));\n\t\t\t$current = key($this->errors) + 1;\n\t\t} catch (OutOfBoundsException $e) {\n\t\t\treturn;\n\t\t}\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $error['index'];\n\t\t$errorIndex = '';\n\t\tif ($index !== null) {\n\t\t\t$errorIndex = $index->getProviderId() . ':' . $index->getDocumentId();\n\t\t}\n\n\t\t$width = $this->terminal->getWidth() - 13;\n\t\t$message = $this->get('message', $error, '');\n\t\t$err1 = (string)substr($message, 0, $width);\n\t\t$err2 = (string)substr($message, $width, $width + 10);\n\t\t$err3 = (string)substr($message, $width + $width + 10, $width + 10);\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'errorCurrent' => $current,\n\t\t\t\t'errorTotal' => $total,\n\t\t\t\t'errorMessageA' => trim($err1),\n\t\t\t\t'errorMessageB' => trim($err2),\n\t\t\t\t'errorMessageC' => trim($err3),\n\t\t\t\t'errorException' => $this->get('exception', $error, ''),\n\t\t\t\t'errorIndex' => $errorIndex\n\t\t\t]\n\t\t);\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t */\n\tprivate function displayResult(int $pos = 0) {\n\t\t$total = sizeof($this->results);\n\n\t\tif ($total === 0) {\n\t\t\t$this->runner->setInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'resultCurrent' => 0,\n\t\t\t\t\t'resultTotal' => 0,\n\t\t\t\t]\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$current = key($this->results) + 1;\n\t\t\t$result = $this->getNavigationResult($pos, ($current === 1), ($current === $total));\n\t\t\t$current = key($this->results) + 1;\n\t\t} catch (OutOfBoundsException $e) {\n\t\t\treturn;\n\t\t}\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $result['index'];\n\t\t$resultIndex = '';\n\t\tif ($index !== null) {\n\t\t\t$resultIndex = $index->getProviderId() . ':' . $index->getDocumentId();\n\t\t}\n\n\n\t\t$width = $this->terminal->getWidth() - 13;\n\t\t$message = $this->get('message', $result, '');\n\t\t$msg1 = (string)substr($message, 0, $width);\n\t\t$msg2 = (string)substr($message, $width, $width + 10);\n\t\t$msg3 = (string)substr($message, $width + $width + 10, $width + 10);\n\n\t\t$status = $this->get('status', $result, '');\n\t\t$type = $this->getInt('type', $result, 0);\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'resultCurrent' => $current,\n\t\t\t\t'resultTotal' => $total,\n\t\t\t\t'resultMessageA' => trim($msg1),\n\t\t\t\t'resultMessageB' => trim($msg2),\n\t\t\t\t'resultMessageC' => trim($msg3),\n\t\t\t\t'resultStatus' => $status,\n\t\t\t\t'resultIndex' => $resultIndex\n\t\t\t]\n\t\t);\n\t\t$this->runner->setInfoColored('resultStatus', $type);\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t * @param bool $isFirst\n\t * @param bool $isLast\n\t *\n\t * @throw OutOfBoundsException\n\t * @return array\n\t */\n\tprivate function getNavigationError(int $pos, bool $isFirst, bool $isLast): array {\n\n\t\tif ($pos === 0) {\n\t\t\tif ($this->navigateLastError === true) {\n\t\t\t\treturn end($this->errors);\n\t\t\t} else {\n\t\t\t\treturn current($this->errors);\n\t\t\t}\n\t\t}\n\n\t\t$this->navigateLastError = false;\n\t\tif ($pos === -99) {\n\t\t\treturn reset($this->errors);\n\t\t}\n\n\t\tif ($pos === -1 && !$isFirst) {\n\t\t\treturn prev($this->errors);\n\t\t}\n\n\t\tif ($pos === 1 && !$isLast) {\n\t\t\treturn next($this->errors);\n\t\t}\n\n\t\tif ($pos === 99) {\n\t\t\t$this->navigateLastError = true;\n\n\t\t\treturn end($this->errors);\n\t\t}\n\n\t\tthrow new OutOfBoundsException();\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t * @param bool $isFirst\n\t * @param bool $isLast\n\t *\n\t * @throw OutOfBoundsException\n\t * @return array\n\t */\n\tprivate function getNavigationResult(int $pos, bool $isFirst, bool $isLast): array {\n\n\t\tif ($pos === 0) {\n\t\t\tif ($this->navigateLastResult === true) {\n\t\t\t\treturn end($this->results);\n\t\t\t} else {\n\t\t\t\treturn current($this->results);\n\t\t\t}\n\t\t}\n\n\t\t$this->navigateLastResult = false;\n\t\tif ($pos === -99) {\n\t\t\treturn reset($this->results);\n\t\t}\n\n\t\tif ($pos === -1 && !$isFirst) {\n\t\t\treturn prev($this->results);\n\t\t}\n\n\t\tif ($pos === 1 && !$isLast) {\n\t\t\treturn next($this->results);\n\t\t}\n\n\t\tif ($pos === 99) {\n\t\t\t$this->navigateLastResult = true;\n\n\t\t\treturn end($this->results);\n\t\t}\n\n\t\tthrow new OutOfBoundsException();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprivate function generateIndexErrors() {\n\t\t$indexes = $this->indexService->getErrorIndexes();\n\n\t\tforeach ($indexes as $index) {\n\t\t\tforeach ($index->getErrors() as $error) {\n\t\t\t\t$this->errors[] = [\n\t\t\t\t\t'index' => $index,\n\t\t\t\t\t'message' => $error['message'],\n\t\t\t\t\t'exception' => $error['exception'],\n\t\t\t\t\t'severity' => $error['severity']\n\t\t\t\t];\n\t\t\t}\n\n\t\t}\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprivate function deleteError() {\n\t\t$current = current($this->errors);\n\t\tif ($current === false) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'errorMessageA' => '',\n\t\t\t\t'errorMessageB' => '',\n\t\t\t\t'errorMessageC' => '',\n\t\t\t\t'errorException' => '',\n\t\t\t\t'errorIndex' => ''\n\t\t\t]\n\t\t);\n\n\t\t$pos = key($this->errors);\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $current['index'];\n\t\t$this->indexService->resetErrorFromIndex($index);\n\n\t\t$errors = [];\n\t\tforeach ($this->errors as $error) {\n\t\t\t/** @var ModelIndex $errorIndex */\n\t\t\t$errorIndex = $error['index'];\n\t\t\tif ($index->getProviderId() === $errorIndex->getProviderId()\n\t\t\t\t&& $index->getDocumentId() === $errorIndex->getDocumentId()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$errors[] = $error;\n\t\t}\n\n\t\t$this->errors = $errors;\n\t\twhile (key($this->errors) < $pos) {\n\t\t\tif (next($this->errors) === false) {\n\t\t\t\tend($this->errors);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t$this->displayError();\n\t}\n\n\n\t/**\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function abort() {\n\t\ttry {\n\t\t\t$this->abortIfInterrupted();\n\t\t} catch (InterruptedException $e) {\n\t\t\t$this->runner->stop();\n\t\t\texit();\n\t\t}\n\t}\n\n\n}\n\n"
  },
  {
    "path": "lib/Command/Live.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse OCA\\FullTextSearch\\Exceptions\\PlatformTemporaryException;\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse Exception;\nuse OC\\Core\\Command\\InterruptedException;\nuse OCA\\FullTextSearch\\ACommandBase;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Index as ModelIndex;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\CliService;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCP\\IUserManager;\nuse OutOfBoundsException;\nuse Psr\\Log\\LoggerInterface;\nuse Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Terminal;\nuse Throwable;\n\nclass Live extends ACommandBase {\n\n\n\tuse TArrayTools;\n\n\n\tconst INDEX_OPTION_NO_READLINE = '_no-readline';\n\n\tconst CYCLE_DELAY = 3000000;\n\n\tconst PANEL_RUN = 'run';\n\tconst PANEL_RUN_LINE_MEMORY = 'Memory: %_memory%';\n\n\tconst PANEL_INDEX = 'indexing';\n\tconst PANEL_INDEX_LINE_HEADER = '┌─ Indexing %_paused% ────';\n\tconst PANEL_INDEX_LINE_ACCOUNT = '│ Provider: <info>%providerName:-20s%</info> Account: <info>%userId%</info>';\n\tconst PANEL_INDEX_LINE_ACTION = '│ Action: <info>%action%</info>';\n\tconst PANEL_INDEX_LINE_DOCUMENT = '│ Document: <info>%documentId%</info>';\n\tconst PANEL_INDEX_LINE_INFO = '│ Info: <info>%info%</info>';\n\tconst PANEL_INDEX_LINE_TITLE = '│ Title: <info>%title%</info>';\n\tconst PANEL_INDEX_LINE_CONTENT = '│ Content size: <info>%content%</info>';\n\tconst PANEL_INDEX_LINE_FOOTER = '└──';\n\n\tconst PANEL_RESULT = 'result';\n\tconst PANEL_RESULT_LINE_HEADER = '┌─ Results ────';\n\tconst PANEL_RESULT_LINE_RESULT = '│ Result: <info>%resultCurrent:6s%</info>/<info>%resultTotal%</info>';\n\tconst PANEL_RESULT_LINE_INDEX = '│ Index: <info>%resultIndex%</info>';\n\tconst PANEL_RESULT_LINE_STATUS = '│ Status: %resultStatusColored%';\n\tconst PANEL_RESULT_LINE_MESSAGE1 = '│ Message: <info>%resultMessageA%</info>';\n\tconst PANEL_RESULT_LINE_MESSAGE2 = '│ <info>%resultMessageB%</info>';\n\tconst PANEL_RESULT_LINE_MESSAGE3 = '│ <info>%resultMessageC%</info>';\n\tconst PANEL_RESULT_LINE_FOOTER = '└──';\n\n\tconst PANEL_ERRORS = 'errors';\n\tconst PANEL_ERRORS_LINE_HEADER = '┌─ Errors ────';\n\tconst PANEL_ERRORS_LINE_ERRORS = '│ Error: <comment>%errorCurrent:6s%</comment>/<comment>%errorTotal%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_INDEX = '│ Index: <comment>%errorIndex%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_EXCEPTION = '│ Exception: <comment>%errorException%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE1 = '│ Message: <comment>%errorMessageA%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE2 = '│ <comment>%errorMessageB%</comment>';\n\tconst PANEL_ERRORS_LINE_ERROR_MESSAGE3 = '│ <comment>%errorMessageC%</comment>';\n\tconst PANEL_ERRORS_LINE_FOOTER = '└──';\n\n\tconst PANEL_COMMANDS_ROOT = 'root';\n\tconst PANEL_COMMANDS_ROOT_LINE = '## q:quit ## p:pause ';\n\tconst PANEL_COMMANDS_PAUSED = 'paused';\n\tconst PANEL_COMMANDS_PAUSED_LINE = '## q:quit ## u:unpause ## n:next step';\n\tconst PANEL_COMMANDS_DONE = 'done';\n\tconst PANEL_COMMANDS_DONE_LINE = '## q:quit';\n\tconst PANEL_COMMANDS_NAVIGATION = 'navigation';\n\tconst PANEL_COMMANDS_ERRORS_LINE = '## f:first error ## h/j:prec/next error ## d:delete error ## l:last error';\n\tconst PANEL_COMMANDS_RESULTS_LINE = '## x:first result ## c/v:prec/next result ## b:last result';\n\n\t/** @var Runner */\n\tprivate $runner;\n\n\t/** @var Terminal */\n\tprivate $terminal;\n\n\t/** @var array */\n\tprivate $errors = [];\n\n\t/** @var bool */\n\tprivate $navigateLastError = true;\n\n\t/** @var array */\n\tprivate $results = [];\n\n\t/** @var bool */\n\tprivate $navigateLastResult = true;\n\n\t/**\n\t * Live constructor.\n\t *\n\t * @param IUserManager $userManager\n\t * @param RunningService $runningService\n\t * @param ConfigService $configService\n\t * @param CliService $cliService\n\t * @param IndexService $indexService\n\t * @param PlatformService $platformService\n\t * @param ProviderService $providerService\n\t */\n\tpublic function __construct(\n\t\tprivate IUserManager $userManager,\n\t\tprivate RunningService $runningService,\n\t\tprivate ConfigService $configService,\n\t\tprivate CliService $cliService,\n\t\tprivate IndexService $indexService,\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t\tprivate LoggerInterface $logger,\n\t) {\n\t\tparent::__construct();\n\t\t$this->runner = new Runner($runningService, 'commandLive');\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:live')\n\t\t\t ->setDescription('Index files')\n\t\t\t ->addOption(\n\t\t\t\t 'no-readline', 'r', InputOption::VALUE_NONE,\n\t\t\t\t 'disable readline - non interactive mode'\n\t\t\t )\n\t\t\t ->addOption(\n\t\t\t\t 'service', 's', InputOption::VALUE_NONE,\n\t\t\t\t 'disable interface'\n\t\t\t );\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int|null|void\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\tif (!$input->getOption('service') && !$input->getOption('no-readline')) {\n\t\t\ttry {\n\t\t\t\t/** do not get stuck while waiting interactive input */\n\t\t\t\treadline_callback_handler_install(\n\t\t\t\t\t'', function() {\n\t\t\t\t}\n\t\t\t\t);\n\t\t\t} catch (Throwable $t) {\n\t\t\t\tthrow new Exception('Please install php-readline, or use --no-readline');\n\t\t\t}\n\t\t}\n\t\tstream_set_blocking(STDIN, false);\n\n\t\t$this->terminal = new Terminal();\n\n\t\t$outputStyle = new OutputFormatterStyle('white', 'black', ['bold']);\n\t\t$output->getFormatter()\n\t\t\t   ->setStyle('char', $outputStyle);\n\n\t\t$this->runner = new Runner($this->runningService, 'commandIndex', ['nextStep' => 'n']);\n\n\t\tif (!$input->getOption('service')) {\n\t\t\t$this->runner->onKeyPress([$this, 'onKeyPressed']);\n\t\t\t$this->runner->onNewIndexError([$this, 'onNewIndexError']);\n\t\t\t$this->runner->onNewIndexResult([$this, 'onNewIndexResult']);\n\t\t\t$this->runner->sourceIsCommandLine($this, $output);\n\n\t\t\t$this->generatePanels(!$input->getOption('no-readline'));\n\t\t}\n\n\t\t$this->indexService->setRunner($this->runner);\n\t\t$this->cliService->setRunner($this->runner);\n\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\t$this->runner->start();\n\n\t\t\t\tif (!$input->getOption('service')) {\n\t\t\t\t\t$this->cliService->runDisplay($output);\n\t\t\t\t\t$this->generateIndexErrors();\n\t\t\t\t\t$this->displayError();\n\t\t\t\t\t$this->displayResult();\n\t\t\t\t}\n\n\t\t\t\t$this->liveCycle();\n\n\t\t\t} catch (Exception $e) {\n\t\t\t\t$this->logger->warning('Exception while live index', ['exception' => $e]);\n\t\t\t\tif (!$input->getOption('service')) {\n\t\t\t\t\tthrow $e;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!$input->getOption('service')) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tsleep(120);\n\t\t}\n\n\t\t$this->runner->stop();\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @throws Exception\n\t * @throws TickDoesNotExistException\n\t */\n\tprivate function liveCycle() {\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\t\t$platform->setRunner($this->runner);\n\t\t$connected = true;\n\n\t\twhile (true) {\n\n\t\t\tif (!$connected) {\n\t\t\t\t$platform->loadPlatform();\n\t\t\t}\n\n\t\t\t$indexes = $this->indexService->getQueuedIndexes();\n\n\t\t\tforeach ($indexes as $index) {\n\t\t\t\t$this->runner->updateAction('indexing');\n\n\t\t\t\ttry {\n\t\t\t\t\t$providerWrapper = $this->providerService->getProvider($index->getProviderId());\n\t\t\t\t\t$provider = $providerWrapper->getProvider();\n\n\t\t\t\t\t$provider->setRunner($this->runner);\n\t\t\t\t\t$this->indexService->updateDocument($platform, $provider, $index);\n\t\t\t\t} catch (PlatformTemporaryException $e) {\n\t\t\t\t\t$connected = false; // assuming we're not connected anymore\n\t\t\t\t\tbreak; // we'll connect and try again next tick\n\t\t\t\t} catch (Exception $e) {\n\t\t\t\t\t$this->runner->exception($e->getMessage(), false);\n\t\t\t\t\t// TODO - upgrade error number - after too many errors, delete index\n\t\t\t\t\t// TODO - do not count error if elasticsearch is down.\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$this->runner->updateAction('waiting', true);\n\n\t\t\tusleep(self::CYCLE_DELAY);\n\t\t}\n\n\t\t$this->runner->stop();\n\n\t}\n\n\n\t/**\n\t * @param string $key\n\t */\n\tpublic function onKeyPressed(string $key) {\n\t\t$key = strtolower($key);\n\t\tif ($key === 'q') {\n\t\t\ttry {\n\t\t\t\t$this->runner->stop();\n\t\t\t} catch (TickDoesNotExistException $e) {\n\t\t\t\t/** we do nohtin' */\n\t\t\t}\n\t\t\texit();\n\t\t}\n\n\t\t$current = $this->cliService->currentPanel('commands');\n\t\tif ($current === self::PANEL_COMMANDS_ROOT && $key === 'p') {\n\t\t\t$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_PAUSED);\n\t\t\t$this->runner->pause(true);\n\t\t}\n\t\tif ($current === self::PANEL_COMMANDS_PAUSED && $key === 'u') {\n\t\t\t$this->cliService->switchPanel('commands', self::PANEL_COMMANDS_ROOT);\n\t\t\t$this->runner->pause(false);\n\t\t}\n\n\t\tif ($key === 'x') {\n\t\t\t$this->displayResult(-99);\n\t\t}\n\t\tif ($key === 'c') {\n\t\t\t$this->displayResult(-1);\n\t\t}\n\t\tif ($key === 'v') {\n\t\t\t$this->displayResult(1);\n\t\t}\n\t\tif ($key === 'b') {\n\t\t\t$this->displayResult(99);\n\t\t}\n\n\t\tif ($key === 'f') {\n\t\t\t$this->displayError(-99);\n\t\t}\n\t\tif ($key === 'h') {\n\t\t\t$this->displayError(-1);\n\t\t}\n\t\tif ($key === 'j') {\n\t\t\t$this->displayError(1);\n\t\t}\n\t\tif ($key === 'l') {\n\t\t\t$this->displayError(99);\n\t\t}\n\t\tif ($key === 'd') {\n\t\t\t$this->deleteError();\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $error\n\t */\n\tpublic function onNewIndexError(array $error) {\n\t\t$this->errors[] = $error;\n\t\t$this->displayError();\n\t}\n\n\t/**\n\t * @param array $result\n\t */\n\tpublic function onNewIndexResult(array $result) {\n\t\t$this->results[] = $result;\n\t\t$this->displayResult();\n\t}\n\n\n\t/**\n\t * @param bool $commands\n\t */\n\tprivate function generatePanels(bool $commands) {\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_RUN,\n\t\t\t[\n\t\t\t\tself::PANEL_RUN_LINE_MEMORY\n\t\t\t]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_INDEX, [\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_HEADER,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_ACTION,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_ACCOUNT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_DOCUMENT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_INFO,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_TITLE,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_CONTENT,\n\t\t\t\t\t\t\t\t self::PANEL_INDEX_LINE_FOOTER,\n\t\t\t\t\t\t\t ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_RESULT, [\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_HEADER,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_RESULT,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_INDEX,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_STATUS,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE1,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE2,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_MESSAGE3,\n\t\t\t\t\t\t\t\t  self::PANEL_RESULT_LINE_FOOTER,\n\t\t\t\t\t\t\t  ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_ERRORS, [\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_HEADER,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERRORS,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_INDEX,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_EXCEPTION,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE1,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE2,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_ERROR_MESSAGE3,\n\t\t\t\t\t\t\t\t  self::PANEL_ERRORS_LINE_FOOTER,\n\t\t\t\t\t\t\t  ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_ROOT, [\n\t\t\t\t\t\t\t\t\t\t self::PANEL_COMMANDS_ROOT_LINE\n\t\t\t\t\t\t\t\t\t ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_PAUSED, [\n\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_PAUSED_LINE\n\t\t\t\t\t\t\t\t\t   ]\n\t\t);\n\n\t\t$this->cliService->createPanel(\n\t\t\tself::PANEL_COMMANDS_NAVIGATION, [\n\t\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_RESULTS_LINE,\n\t\t\t\t\t\t\t\t\t\t\t   self::PANEL_COMMANDS_ERRORS_LINE\n\t\t\t\t\t\t\t\t\t\t   ]\n\t\t);\n\n\t\t$this->cliService->initDisplay();\n\t\t$this->cliService->displayPanel('run', self::PANEL_RUN);\n\t\t$this->cliService->displayPanel('indexPanel', self::PANEL_INDEX);\n\t\t$this->cliService->displayPanel('resultsPanel', self::PANEL_RESULT);\n\t\t$this->cliService->displayPanel('errorsPanel', self::PANEL_ERRORS);\n\n\t\tif ($commands) {\n\t\t\t$this->cliService->displayPanel('navigation', self::PANEL_COMMANDS_NAVIGATION);\n\t\t\tif ($this->runner->isPaused()) {\n\t\t\t\t$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_PAUSED);\n\t\t\t} else {\n\t\t\t\t$this->cliService->displayPanel('commands', self::PANEL_COMMANDS_ROOT);\n\t\t\t}\n\t\t}\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'userId'              => '',\n\t\t\t\t'providerName'        => '',\n\t\t\t\t'_memory'             => '',\n\t\t\t\t'documentId'          => '',\n\t\t\t\t'action'              => '',\n\t\t\t\t'info'                => '',\n\t\t\t\t'title'               => '',\n\t\t\t\t'_paused'             => '',\n\t\t\t\t'resultIndex'         => '',\n\t\t\t\t'resultCurrent'       => '',\n\t\t\t\t'resultTotal'         => '',\n\t\t\t\t'resultMessageA'      => '',\n\t\t\t\t'resultMessageB'      => '',\n\t\t\t\t'resultMessageC'      => '',\n\t\t\t\t'resultStatus'        => '',\n\t\t\t\t'resultStatusColored' => '',\n\t\t\t\t'content'             => '',\n\t\t\t\t'statusColored'       => '',\n\t\t\t\t'progressStatus'      => '',\n\t\t\t\t'errorCurrent'        => '0',\n\t\t\t\t'errorTotal'          => '0',\n\t\t\t\t'errorMessageA'       => '',\n\t\t\t\t'errorMessageB'       => '',\n\t\t\t\t'errorMessageC'       => '',\n\t\t\t\t'errorException'      => '',\n\t\t\t\t'errorIndex'          => ''\n\t\t\t]\n\t\t);\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprivate function generateIndexErrors() {\n\t\t$indexes = $this->indexService->getErrorIndexes();\n\n\t\tforeach ($indexes as $index) {\n\t\t\tforeach ($index->getErrors() as $error) {\n\t\t\t\t$this->errors[] = [\n\t\t\t\t\t'index'     => $index,\n\t\t\t\t\t'message'   => $error['message'],\n\t\t\t\t\t'exception' => $error['exception'],\n\t\t\t\t\t'severity'  => $error['severity']\n\t\t\t\t];\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t */\n\tprivate function displayResult(int $pos = 0) {\n\t\t$total = sizeof($this->results);\n\n\t\tif ($total === 0) {\n\t\t\t$this->runner->setInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'resultCurrent' => 0,\n\t\t\t\t\t'resultTotal'   => 0,\n\t\t\t\t]\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$current = key($this->results) + 1;\n\t\t\t$result = $this->getNavigationResult($pos, ($current === 1), ($current === $total));\n\t\t\t$current = key($this->results) + 1;\n\t\t} catch (OutOfBoundsException $e) {\n\t\t\treturn;\n\t\t}\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $result['index'];\n\t\t$resultIndex = '';\n\t\tif ($index !== null) {\n\t\t\t$resultIndex = $index->getProviderId() . ':' . $index->getDocumentId();\n\t\t}\n\n\n\t\t$width = $this->terminal->getWidth() - 13;\n\t\t$message = $this->get('message', $result, '');\n\t\t$msg1 = (string)substr($message, 0, $width);\n\t\t$msg2 = (string)substr($message, $width, $width + 10);\n\t\t$msg3 = (string)substr($message, $width + $width + 10, $width + 10);\n\n\n\t\t$status = $this->get('status', $result, '');\n\t\t$type = $this->getInt('type', $result, 0);\n\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'resultCurrent'  => $current,\n\t\t\t\t'resultTotal'    => $total,\n\t\t\t\t'resultMessageA' => trim($msg1),\n\t\t\t\t'resultMessageB' => trim($msg2),\n\t\t\t\t'resultMessageC' => trim($msg3),\n\t\t\t\t'resultStatus'   => $status,\n\t\t\t\t'resultIndex'    => $resultIndex\n\t\t\t]\n\t\t);\n\t\t$this->runner->setInfoColored('resultStatus', $type);\n\t}\n\n\t/**\n\t * @param int $pos\n\t */\n\tprivate function displayError(int $pos = 0) {\n\t\t$total = sizeof($this->errors);\n\n\t\tif ($total === 0) {\n\t\t\t$this->runner->setInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'errorCurrent' => 0,\n\t\t\t\t\t'errorTotal'   => 0,\n\t\t\t\t]\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$current = key($this->errors) + 1;\n\t\t\t$error = $this->getNavigationError($pos, ($current === 1), ($current === $total));\n\t\t\t$current = key($this->errors) + 1;\n\t\t} catch (OutOfBoundsException $e) {\n\t\t\treturn;\n\t\t}\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $error['index'];\n\t\t$errorIndex = '';\n\t\tif ($index !== null) {\n\t\t\t$errorIndex = $index->getProviderId() . ':' . $index->getDocumentId();\n\t\t}\n\n\t\t$width = $this->terminal->getWidth() - 13;\n\t\t$message = $this->get('message', $error, '');\n\t\t$err1 = (string)substr($message, 0, $width);\n\t\t$err2 = (string)substr($message, $width, $width + 10);\n\t\t$err3 = (string)substr($message, $width + $width + 10, $width + 10);\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'errorCurrent'   => $current,\n\t\t\t\t'errorTotal'     => $total,\n\t\t\t\t'errorMessageA'  => trim($err1),\n\t\t\t\t'errorMessageB'  => trim($err2),\n\t\t\t\t'errorMessageC'  => trim($err3),\n\t\t\t\t'errorException' => $this->get('exception', $error, ''),\n\t\t\t\t'errorIndex'     => $errorIndex\n\t\t\t]\n\t\t);\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t * @param bool $isFirst\n\t * @param bool $isLast\n\t *\n\t * @return array\n\t * @throws OutOfBoundsException\n\t */\n\tprivate function getNavigationResult(int $pos, bool $isFirst, bool $isLast): array {\n\n\t\tif ($pos === 0) {\n\t\t\tif ($this->navigateLastResult === true) {\n\t\t\t\treturn end($this->results);\n\t\t\t} else {\n\t\t\t\treturn current($this->results);\n\t\t\t}\n\t\t}\n\n\t\t$this->navigateLastResult = false;\n\t\tif ($pos === -99) {\n\t\t\treturn reset($this->results);\n\t\t}\n\n\t\tif ($pos === -1 && !$isFirst) {\n\t\t\treturn prev($this->results);\n\t\t}\n\n\t\tif ($pos === 1 && !$isLast) {\n\t\t\treturn next($this->results);\n\t\t}\n\n\t\tif ($pos === 99) {\n\t\t\t$this->navigateLastResult = true;\n\n\t\t\treturn end($this->results);\n\t\t}\n\n\t\tthrow new OutOfBoundsException();\n\t}\n\n\n\t/**\n\t * @param int $pos\n\t * @param bool $isFirst\n\t * @param bool $isLast\n\t *\n\t * @return array\n\t */\n\tprivate function getNavigationError(int $pos, bool $isFirst, bool $isLast): array {\n\n\t\tif ($pos === 0) {\n\t\t\tif ($this->navigateLastError === true) {\n\t\t\t\treturn end($this->errors);\n\t\t\t} else {\n\t\t\t\treturn current($this->errors);\n\t\t\t}\n\t\t}\n\n\t\t$this->navigateLastError = false;\n\t\tif ($pos === -99) {\n\t\t\treturn reset($this->errors);\n\t\t}\n\n\t\tif ($pos === -1 && !$isFirst) {\n\t\t\treturn prev($this->errors);\n\t\t}\n\n\t\tif ($pos === 1 && !$isLast) {\n\t\t\treturn next($this->errors);\n\t\t}\n\n\t\tif ($pos === 99) {\n\t\t\t$this->navigateLastError = true;\n\n\t\t\treturn end($this->errors);\n\t\t}\n\n\t\tthrow new OutOfBoundsException();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprivate function deleteError() {\n\t\t$current = current($this->errors);\n\t\tif ($current === false) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfoArray(\n\t\t\t[\n\t\t\t\t'errorMessageA'  => '',\n\t\t\t\t'errorMessageB'  => '',\n\t\t\t\t'errorMessageC'  => '',\n\t\t\t\t'errorException' => '',\n\t\t\t\t'errorIndex'     => ''\n\t\t\t]\n\t\t);\n\n\t\t$pos = key($this->errors);\n\n\t\t/** @var ModelIndex $index */\n\t\t$index = $current['index'];\n\t\t$this->indexService->resetErrorFromIndex($index);\n\n\t\t$errors = [];\n\t\tforeach ($this->errors as $error) {\n\t\t\t/** @var ModelIndex $errorIndex */\n\t\t\t$errorIndex = $error['index'];\n\t\t\tif ($index->getProviderId() === $errorIndex->getProviderId()\n\t\t\t\t&& $index->getDocumentId() === $errorIndex->getDocumentId()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$errors[] = $error;\n\t\t}\n\n\t\t$this->errors = $errors;\n\t\twhile (key($this->errors) < $pos) {\n\t\t\tif (next($this->errors) === false) {\n\t\t\t\tend($this->errors);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t$this->displayError();\n\t}\n\n\n\t/**\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function abort() {\n\t\ttry {\n\t\t\t$this->abortIfInterrupted();\n\t\t} catch (InterruptedException $e) {\n\t\t\t$this->runner->stop();\n\t\t\texit();\n\t\t}\n\t}\n\n\n}\n\n"
  },
  {
    "path": "lib/Command/Reset.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\n\nuse Exception;\nuse OC\\Core\\Command\\InterruptedException;\nuse OCA\\FullTextSearch\\ACommandBase;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Question\\ConfirmationQuestion;\nuse Symfony\\Component\\Console\\Question\\Question;\n\n\n/**\n * Class Reset\n *\n * @package OCA\\FullTextSearch\\Command\n */\nclass Reset extends ACommandBase {\n\n\tprivate Runner $runner;\n\n\tpublic function __construct(\n\t\tRunningService $runningService,\n\t\tprivate IndexService $indexService\n\t) {\n\t\tparent::__construct();\n\n\t\t$this->runner = new Runner($runningService, 'commandReset');\n\t}\n\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:reset')\n\t\t\t->setDescription('Reset index')\n\t\t\t->addOption('provider', '', InputOption::VALUE_REQUIRED, 'provider id', '')\n\t\t\t->addOption('collection', '', InputOption::VALUE_REQUIRED, 'name of the collection', '');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output): int {\n\t\t$provider = $input->getOption('provider');\n\t\t$collection = $input->getOption('collection');\n\t\t$helper = $this->getHelper('question');\n\n\t\t$output->writeln('<error>WARNING! You are about to reset your indexed documents:</error>');\n\t\t$output->writeln('- provider: <info>' . (($provider === '') ? 'ALL' : $provider) . '</info>');\n\t\t$output->writeln('- collection: <info>' . (($collection === '') ? 'ALL' : $collection) . '</info>');\n\t\t$output->writeln('');\n\n\t\t$question = new ConfirmationQuestion(\n\t\t\t'<comment>Do you really want to reset your indexed documents ?</comment> (y/N) ', false,\n\t\t\t'/^(y|Y)/i'\n\t\t);\n\n\t\tif (!$helper->ask($input, $output, $question)) {\n\t\t\t$output->writeln('');\n\t\t\t$output->writeln('aborted.');\n\n\t\t\treturn 0;\n\t\t}\n\n\t\t$output->writeln('');\n\t\t$output->writeln('<error>WARNING! This operation is not reversible.</error>');\n\t\t$action = 'reset ' . (($provider === '') ? 'ALL' : $provider)\n\t\t\t. ' ' . (($collection === '') ? 'ALL' : $collection);\n\n\t\t$question = new Question('<comment>Please confirm this destructive operation by typing \\'' . $action . '\\'</comment>: ', '');\n\n\t\t$helper = $this->getHelper('question');\n\t\t$confirmation = $helper->ask($input, $output, $question);\n\t\tif (strtolower($confirmation) !== strtolower($action)) {\n\t\t\t$output->writeln('');\n\t\t\t$output->writeln('aborted.');\n\n\t\t\treturn 0;\n\t\t}\n\n\t\ttry {\n\t\t\t$this->runner->sourceIsCommandLine($this, $output);\n\t\t\t$this->runner->start();\n\t\t} catch (Exception $e) {\n\t\t\t$this->runner->exception($e->getMessage(), true);\n\t\t\tthrow $e;\n\t\t}\n\n\t\t$this->indexService->setRunner($this->runner);\n\t\ttry {\n\t\t\t$this->indexService->resetIndex($provider, $collection);\n\t\t\t$output->writeln('');\n\t\t\t$output->writeln('done.');\n\n\t\t} catch (Exception $e) {\n\t\t\tthrow $e;\n\t\t} finally {\n\t\t\t$this->runner->stop();\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function abort() {\n\t\ttry {\n\t\t\t$this->abortIfInterrupted();\n\t\t} catch (InterruptedException $e) {\n\t\t\t$this->runner->stop();\n\t\t\texit();\n\t\t}\n\t}\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/Search.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Model\\SearchRequest;\nuse OCA\\FullTextSearch\\Service\\SearchService;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass Search extends Base {\n\tpublic function __construct(\n\t\tprivate SearchService $searchService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:search')\n\t\t\t ->setDescription('Search something')\n\t\t\t ->addArgument('user', InputArgument::OPTIONAL, 'user')\n\t\t\t ->addArgument('string', InputArgument::OPTIONAL, 'needle');\n\n\t}\n\n\n\t/**\n\t *\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$searchRequest = new SearchRequest();\n\t\t$searchRequest->importFromArray(\n\t\t\t[\n\t\t\t\t'providers' => 'all',\n\t\t\t\t'search' => $input->getArgument('string')\n\t\t\t]\n\t\t);\n\n\t\t$searchResult = $this->searchService->search($input->getArgument('user'), $searchRequest);\n\n\t\t$results = [];\n\t\tforeach ($searchResult as $entry) {\n\t\t\t$list = [];\n\t\t\tforeach ($entry->getDocuments() as $document) {\n\t\t\t\t$list[] = json_decode(json_encode($document, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR);\n\t\t\t}\n\n\t\t\t$results[$entry->getProvider()->getId()] = array_values($list);\n\t\t}\n\n\t\t$this->writeArrayInOutputFormat($input, $output, $results, ' * ');\n\t\treturn 0;\n\t}\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/Stop.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse OC\\Core\\Command\\Base;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass Stop extends Base {\n\tpublic function __construct(\n\t\tprivate RunningService $runningService\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:stop')\n\t\t\t ->setDescription('Stop all indexing');\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$output->writeln('stopping all running indexes');\n\n\t\t$this->runningService->forceStop();\n\n\t\treturn 0;\n\t}\n\n\n}\n\n\n\n"
  },
  {
    "path": "lib/Command/Test.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Command;\n\nuse Exception;\nuse OC\\Core\\Command\\InterruptedException;\nuse OC\\FullTextSearch\\Model\\DocumentAccess;\nuse OCA\\FullTextSearch\\ACommandBase;\nuse OCA\\FullTextSearch\\Exceptions\\InterruptException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderIsNotCompatibleException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderIsNotUniqueException;\nuse OCA\\FullTextSearch\\Exceptions\\RunnerAlreadyUpException;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Model\\SearchRequest;\nuse OCA\\FullTextSearch\\Model\\SearchResult;\nuse OCA\\FullTextSearch\\Provider\\TestProvider;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCA\\FullTextSearch\\Service\\TestService;\nuse OCP\\AppFramework\\QueryException;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IDocumentAccess;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Input\\InputOption;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\nclass Test extends ACommandBase {\n\n\tconst DELAY_STABILIZE_PLATFORM = 3;\n\n\tprivate Runner $runner;\n\n\t/** @var boolean */\n\tprivate $isJson = false;\n\tpublic function __construct(\n\t\tprivate RunningService $runningService,\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t\tprivate IndexService $indexService,\n\t\tprivate TestService $testService,\n\t) {\n\t\tparent::__construct();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprotected function configure() {\n\t\tparent::configure();\n\t\t$this->setName('fulltextsearch:test')\n\t\t\t ->setDescription('Testing the platform setup')\n\t\t\t ->addOption('json', 'j', InputOption::VALUE_NONE, 'return result as JSON')\n\t\t\t ->addOption(\n\t\t\t\t 'platform_delay', 'd', InputOption::VALUE_REQUIRED,\n\t\t\t\t 'change DELAY_STABILIZE_PLATFORM'\n\t\t\t );\n\t}\n\n\n\t/**\n\t * @param InputInterface $input\n\t * @param OutputInterface $output\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tprotected function execute(InputInterface $input, OutputInterface $output) {\n\t\t$this->isJson = ($input->getOption('json') === true);\n\t\t$platformDelay = ($input->getOption('platform_delay') > 0) ? (int)$input->getOption(\n\t\t\t'platform_delay'\n\t\t) : self::DELAY_STABILIZE_PLATFORM;\n\n\t\t$this->output($output, '.Testing your current setup:');\n\n\t\ttry {\n\t\t\t$testProvider = $this->testCreatingProvider($output);\n\t\t\t$this->testMockedProvider($output, $testProvider);\n\t\t\t$testPlatform = $this->testLoadingPlatform($output);\n\t\t\t$this->testLockingProcess($output, $testPlatform, $testProvider);\n\t\t} catch (Exception $e) {\n\t\t\t$this->outputResult($output, false);\n\t\t\tthrow $e;\n\t\t}\n\n\t\ttry {\n\t\t\t$this->testResetTest($output, $testProvider);\n\t\t\t$this->pause($output, $platformDelay);\n\t\t\t$this->testInitIndexing($output, $testPlatform);\n\t\t\t$this->testIndexingDocuments($output, $testPlatform, $testProvider);\n\t\t\t$this->pause($output, $platformDelay);\n\t\t\t$this->testContentLicense($output, $testPlatform);\n\t\t\t$this->testSearchSimple($output, $testPlatform, $testProvider);\n\t\t\t$this->testUpdatingDocumentsAccess($output, $testPlatform, $testProvider);\n\t\t\t$this->pause($output, $platformDelay);\n\t\t\t$this->testSearchAccess($output, $testPlatform, $testProvider);\n\t\t\t$this->testSearchShare($output, $testPlatform, $testProvider);\n\n\t\t\t$this->testResetTest($output, $testProvider);\n\t\t\t$this->testUnlockingProcess($output);\n\t\t} catch (Exception $e) {\n\t\t\t$this->outputResult($output, false);\n\t\t\t$this->output($output, 'Error detected, unlocking process');\n\t\t\t$this->runner->stop();\n\t\t\t$this->outputResult($output, true);\n\n\t\t\tthrow $e;\n\t\t}\n\n\t\t$this->output($output, '', true);\n\n\t\treturn 0;\n\t}\n\n\n\t/**\n\t * @return IFullTextSearchProvider\n\t * @throws ProviderIsNotCompatibleException\n\t * @throws QueryException\n\t * @throws ProviderDoesNotExistException\n\t * @throws ProviderIsNotUniqueException\n\t */\n\tprivate function generateMockProvider(): IFullTextSearchProvider {\n\t\t$this->providerService->loadProvider(\n\t\t\t'fulltextsearch', 'OCA\\FullTextSearch\\Provider\\TestProvider'\n\t\t);\n\t\t$providerWrapper = $this->providerService->getProvider(TestProvider::TEST_PROVIDER_ID);\n\n\t\treturn $providerWrapper->getProvider();\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param string $line\n\t * @param bool $isNewLine\n\t */\n\tprivate function output(OutputInterface $output, string $line, bool $isNewLine = true) {\n\t\tif ($isNewLine) {\n\t\t\t$output->write(' ', true);\n\t\t}\n\n\t\t$output->write($line . ' ', false);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param bool $result\n\t */\n\tprivate function outputResult(OutputInterface $output, bool $result) {\n\t\t$isNewLine = false;\n\t\t$line = $this->convertBoolToLine($result, $isNewLine);\n\n\t\t$this->output($output, $line, $isNewLine);\n\t}\n\n\n\t/**\n\t * @param bool $result\n\t * @param bool $isNewLine\n\t *\n\t * @return string\n\t */\n\tprivate function convertBoolToLine(bool $result, bool &$isNewLine): string {\n\t\t$isNewLine = false;\n\t\tif ($result === false) {\n\t\t\treturn '<error>fail</error>';\n\t\t}\n\n\t\treturn '<info>ok</info>';\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t *\n\t * @return IFullTextSearchProvider\n\t * @throws ProviderDoesNotExistException\n\t * @throws ProviderIsNotCompatibleException\n\t * @throws ProviderIsNotUniqueException\n\t * @throws QueryException\n\t */\n\tprivate function testCreatingProvider(OutputInterface $output): IFullTextSearchProvider {\n\t\t$this->output($output, 'Creating mocked content provider.');\n\t\t$testProvider = $this->generateMockProvider();\n\t\t$this->outputResult($output, true);\n\n\t\treturn $testProvider;\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchProvider $testProvider\n\t */\n\tprivate function testMockedProvider(\n\t\tOutputInterface $output, IFullTextSearchProvider $testProvider\n\t) {\n\t\t$this->output($output, 'Testing mocked provider: get indexable documents.');\n\t\t$testProvider->setIndexOptions(new IndexOptions());\n\t\t$indexableDocuments =\n\t\t\t$testProvider->generateIndexableDocuments(TestService::DOCUMENT_USER1, '');\n\t\t$this->output($output, '(' . sizeof($indexableDocuments) . ' items)', false);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t *\n\t * @return IFullTextSearchPlatform\n\t * @throws Exception\n\t */\n\tprivate function testLoadingPlatform(OutputInterface $output): IFullTextSearchPlatform {\n\t\t$this->output($output, 'Loading search platform.');\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$testPlatform = $wrapper->getPlatform();\n\n\t\t$this->output($output, '(' . $testPlatform->getName() . ')', false);\n\t\t$this->outputResult($output, true);\n\n\t\t$this->output($output, 'Testing search platform.');\n\t\tif (!$testPlatform->testPlatform()) {\n\t\t\tthrow new Exception ('Search platform (' . $testPlatform->getName() . ') down ?');\n\t\t}\n\t\t$this->outputResult($output, true);\n\n\t\treturn $testPlatform;\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t *\n\t * @throws RunnerAlreadyUpException\n\t */\n\tprivate function testLockingProcess(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider\n\t) {\n\t\t$this->output($output, 'Locking process');\n\t\t$this->runner = new Runner($this->runningService, 'test');\n\t\t$this->runner->sourceIsCommandLine($this, $output);\n\t\t$this->runner->start();\n\t\t$this->indexService->setRunner($this->runner);\n\t\t$testPlatform->setRunner($this->runner);\n\t\t$testProvider->setRunner($this->runner);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchProvider $testProvider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testResetTest(OutputInterface $output, IFullTextSearchProvider $testProvider\n\t) {\n\t\t$this->output($output, 'Removing test.');\n\t\t$this->indexService->resetIndex($testProvider->getId());\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t */\n\tprivate function testInitIndexing(OutputInterface $output, IFullTextSearchPlatform $testPlatform\n\t) {\n\t\t$this->output($output, 'Initializing index mapping.');\n\t\t$testPlatform->initializeIndex();\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testIndexingDocuments(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider\n\t) {\n\t\t$this->output($output, 'Indexing generated documents.');\n\t\t$options = new IndexOptions(\n\t\t\t[\n\t\t\t\t'provider' => TestProvider::TEST_PROVIDER_ID\n\t\t\t]\n\t\t);\n\t\t$this->indexService->indexProviderContentFromUser(\n\t\t\t$testPlatform, $testProvider, TestService::DOCUMENT_USER1, $options\n\t\t);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testContentLicense(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform\n\t) {\n\n\t\ttry {\n\t\t\t$this->output($output, 'Retreiving content from a big index (license).');\n\t\t\t$indexDocument = $testPlatform->getDocument(\n\t\t\t\tTestProvider::TEST_PROVIDER_ID, TestService::DOCUMENT_TYPE_LICENSE\n\t\t\t);\n\n\t\t\t$this->output(\n\t\t\t\t$output, '(size: ' . $indexDocument->getContentSize() . ')', false\n\t\t\t);\n\t\t\t$this->outputResult($output, true);\n\t\t} catch (Exception $e) {\n\t\t\tthrow new Exception(\n\t\t\t\t\"Issue while getting test document '\" . TestService::DOCUMENT_TYPE_LICENSE\n\t\t\t\t. \"' from search platform: \" . $e->getMessage()\n\t\t\t);\n\t\t}\n\n\t\t$this->output($output, 'Comparing document with source.');\n\t\t$this->testService->compareIndexDocument(\n\t\t\t$this->testService->generateIndexDocumentContentLicense(new IndexOptions()),\n\t\t\t$indexDocument\n\t\t);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testSearchSimple(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider\n\t) {\n\n\t\t$this->output($output, 'Searching basic keywords:');\n\n\t\t$access = new DocumentAccess();\n\t\t$access->setViewerId(TestService::DOCUMENT_USER1);\n\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'test',\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a simple test',\n//\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE, TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, '\"document is a test\"',\n\t\t\t[]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, '\"document is a simple test\"',\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a simple -test',\n\t\t\t[TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a simple +test',\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, '-document is a simple test',\n\t\t\t[]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a simple +test +testing',\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a simple +test -testing',\n\t\t\t[]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a +simple -test -testing',\n\t\t\t[]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, '+document is a simple -test -testing',\n\t\t\t[TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'document is a +simple -license +testing',\n\t\t\t[TestService::DOCUMENT_TYPE_SIMPLE]\n\t\t);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testUpdatingDocumentsAccess(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider\n\t) {\n\t\t$this->output($output, 'Updating documents access.');\n\t\t$options = new IndexOptions(\n\t\t\t[\n\t\t\t\t'provider'                            => TestProvider::TEST_PROVIDER_ID,\n\t\t\t\tTestService::DOCUMENT_INDEXING_OPTION => TestService::DOCUMENT_INDEXING_ACCESS\n\t\t\t]\n\t\t);\n\t\t$testProvider->setIndexOptions($options);\n\t\t$this->indexService->indexProviderContentFromUser(\n\t\t\t$testPlatform, $testProvider, TestService::DOCUMENT_USER1, $options\n\t\t);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testSearchAccess(\n\t\tOutputInterface $output, IFullTextSearchPlatform $platform,\n\t\tIFullTextSearchProvider $provider\n\t) {\n\t\t$this->output($output, 'Searching with group access rights:');\n\n\t\t$this->searchGroups($output, $platform, $provider, [], []);\n\t\t$this->searchGroups(\n\t\t\t$output, $platform, $provider, [TestService::DOCUMENT_GROUP1],\n\t\t\t[TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->searchGroups(\n\t\t\t$output, $platform, $provider,\n\t\t\t[TestService::DOCUMENT_GROUP1, TestService::DOCUMENT_GROUP2],\n\t\t\t[TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->searchGroups(\n\t\t\t$output, $platform, $provider,\n\t\t\t[TestService::DOCUMENT_NOTGROUP, TestService::DOCUMENT_GROUP2],\n\t\t\t[TestService::DOCUMENT_TYPE_LICENSE]\n\t\t);\n\t\t$this->searchGroups($output, $platform, $provider, [TestService::DOCUMENT_NOTGROUP], []);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @throws Exception\n\t */\n\tprivate function testSearchShare(\n\t\tOutputInterface $output, IFullTextSearchPlatform $platform,\n\t\tIFullTextSearchProvider $provider\n\t) {\n\n\t\t$this->output($output, 'Searching with share rights:');\n\n\t\t$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_NOTUSER, []);\n\t\t$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_USER2, ['license']);\n\t\t$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_USER3, ['license']);\n\t\t$this->searchUsers($output, $platform, $provider, TestService::DOCUMENT_USER4, ['license']);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t *\n\t * @throws TickDoesNotExistException\n\t */\n\tprivate function testUnlockingProcess(OutputInterface $output) {\n\t\t$this->output($output, 'Unlocking process');\n\t\t$this->runner->stop();\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t * @param IDocumentAccess $access\n\t * @param string $search\n\t * @param array $expected\n\t * @param string $moreOutput\n\t *\n\t * @throws Exception\n\t */\n\tprivate function search(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider,\n\t\tIDocumentAccess $access, string $search, array $expected, string $moreOutput = ''\n\t) {\n\t\t$this->output(\n\t\t\t$output,\n\t\t\t\" - '\" . $search . \"'\" . (($moreOutput === '') ? '' : ' - ' . $moreOutput . ' - ')\n\t\t);\n\t\t$request = new SearchRequest();\n\n\t\t$request->setSearch($search);\n\n\t\t$searchResult = new SearchResult($request);\n\t\t$searchResult->setProvider($testProvider);\n\t\t$searchResult->setPlatform($testPlatform);\n\n\t\t$testPlatform->searchRequest($searchResult, $access);\n\n\t\t$this->output(\n\t\t\t$output,\n\t\t\t'(result: ' . $searchResult->getCount() . ', expected: ' . json_encode($expected) . ')',\n\t\t\tfalse\n\t\t);\n\t\t$this->compareSearchResult($searchResult, $expected);\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t * @param array $groups\n\t * @param array $expected\n\t *\n\t * @throws Exception\n\t */\n\tprivate function searchGroups(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider, array $groups, array $expected\n\t) {\n\n\t\t$access = new DocumentAccess();\n\t\t$access->setViewerId(TestService::DOCUMENT_NOTUSER);\n\t\t$access->setGroups($groups);\n\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'license',\n\t\t\t$expected, json_encode($groups)\n\t\t);\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param IFullTextSearchPlatform $testPlatform\n\t * @param IFullTextSearchProvider $testProvider\n\t * @param string $user\n\t * @param array $expected\n\t *\n\t * @throws Exception\n\t */\n\tprivate function searchUsers(\n\t\tOutputInterface $output, IFullTextSearchPlatform $testPlatform,\n\t\tIFullTextSearchProvider $testProvider, string $user, array $expected\n\t) {\n\t\t$access = new DocumentAccess();\n\t\t$access->setViewerId($user);\n\t\t$this->search(\n\t\t\t$output, $testPlatform, $testProvider, $access, 'license',\n\t\t\t$expected, $user\n\t\t);\n\t}\n\n\n\t/**\n\t * @param SearchResult $searchResult\n\t * @param array $entries\n\t *\n\t * @throws Exception\n\t */\n\tprivate function compareSearchResult(SearchResult $searchResult, array $entries) {\n\t\t$documents = $searchResult->getDocuments();\n\t\tif (sizeof($documents) !== sizeof($entries)) {\n\t\t\tthrow new Exception('Unexpected SearchResult: ' . json_encode($searchResult));\n\t\t}\n\n\t\tforeach ($documents as $document) {\n\t\t\tif (!in_array($document->getId(), $entries)) {\n\t\t\t\tthrow new Exception('Unexpected Document: ' . json_encode($document));\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param OutputInterface $output\n\t * @param int $s\n\t *\n\t * @throws InterruptException\n\t */\n\tprivate function pause(OutputInterface $output, int $s) {\n\t\t$this->output($output, 'Pausing ' . $s . ' seconds');\n\n\t\tfor ($i = 1; $i <= $s; $i++) {\n\t\t\tif (time_nanosleep(1, 0) !== true) {\n\t\t\t\tthrow new InterruptException('Interrupted by user');\n\t\t\t}\n\n\t\t\t$this->output($output, (string)$i, false);\n\t\t}\n\n\t\t$this->outputResult($output, true);\n\t}\n\n\n\t/**\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function abort() {\n\t\ttry {\n\t\t\t$this->abortIfInterrupted();\n\t\t} catch (InterruptedException $e) {\n\t\t\t$this->runner->stop();\n\t\t\texit();\n\t\t}\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/ConfigLexicon.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch;\n\nuse OCP\\Config\\Lexicon\\Entry;\nuse OCP\\Config\\Lexicon\\ILexicon;\nuse OCP\\Config\\Lexicon\\Strictness;\nuse OCP\\Config\\ValueType;\n\n/**\n * Config Lexicon for fulltextsearch.\n *\n * Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!\n */\nclass ConfigLexicon implements ILexicon {\n\tpublic const APP_NAVIGATION = 'app_navigation';\n\tpublic const SEARCH_PLATFORM = 'search_platform';\n\tpublic const CRON_LAST_ERR_RESET = 'cron_err_reset';\n\tpublic const TICK_TTL = 'tick_ttl';\n\tpublic const COLLECTION_INDEXING_LIST = 'collection_indexing_list';\n\tpublic const COLLECTION_INTERNAL = 'collection_internal';\n\tpublic const COLLECTION_LINKS = 'collection_links';\n\n\tpublic function getStrictness(): Strictness {\n\t\treturn Strictness::NOTICE;\n\t}\n\n\tpublic function getAppConfigs(): array {\n\t\treturn [\n\t\t\tnew Entry(key: self::APP_NAVIGATION, type: ValueType::BOOL, defaultRaw: false, definition: 'add an app navigation page for search'),\n\t\t\tnew Entry(key: self::SEARCH_PLATFORM, type: ValueType::STRING, defaultRaw: '', definition: 'configured search platform'),\n\t\t\tnew Entry(key: self::CRON_LAST_ERR_RESET, type: ValueType::INT, defaultRaw: 0, definition: '(internal) estimated time before retrying failed indexes'),\n\t\t\tnew Entry(key: self::TICK_TTL, type: ValueType::INT, defaultRaw: 1800, definition: 'Time to Live before indexing is estimated as blocked'),\n\t\t\tnew Entry(key: self::COLLECTION_INDEXING_LIST, type: ValueType::INT, defaultRaw: 50, definition: 'size of chunks of async documents on collection queue request'),\n\t\t\tnew Entry(key: self::COLLECTION_INTERNAL, type: ValueType::STRING, defaultRaw: 'local', definition: 'name of the local collection'),\n\t\t\tnew Entry(key: self::COLLECTION_LINKS, type: ValueType::ARRAY, defaultRaw: [], definition: '(internal) data relative to collections'),\n\t\t];\n\t}\n\n\tpublic function getUserConfigs(): array {\n\t\treturn [];\n\t}\n}\n"
  },
  {
    "path": "lib/Controller/ApiController.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Controller;\n\nuse Exception;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Model\\SearchRequest;\nuse OCA\\FullTextSearch\\Service\\SearchService;\nuse OCP\\AppFramework\\Controller;\nuse OCP\\AppFramework\\Http;\nuse OCP\\AppFramework\\Http\\DataResponse;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\IRequest;\nuse OCP\\IUserSession;\n\nclass ApiController extends Controller {\n\n\tpublic function __construct(\n\t\tIRequest $request,\n\t\tprivate IUserSession $userSession,\n\t\tprivate readonly IAppConfig $appConfig,\n\t\tprivate SearchService $searchService,\n\t) {\n\t\tparent::__construct(Application::APP_ID, $request);\n\t}\n\n\n\t/**\n\t * @NoAdminRequired\n\t * @NoSubAdminRequired\n\t *\n\t * @param string $request\n\t *\n\t * @return DataResponse\n\t */\n\tpublic function search(string $request): DataResponse {\n\t\treturn $this->searchDocuments(SearchRequest::fromJSON($request));\n\t}\n\n\n\t/**\n\t * @NoAdminRequired\n\t * @NoSubAdminRequired\n\t * @NoCSRFRequired\n\t *\n\t * @param string $request\n\t *\n\t * @return DataResponse\n\t */\n\tpublic function searchFromRemote(string $request): DataResponse {\n\t\treturn $this->searchDocuments(SearchRequest::fromJSON($request));\n\t}\n\n\n\t/**\n\t * @param SearchRequest $request\n\t *\n\t * @return DataResponse\n\t */\n\tprivate function searchDocuments(SearchRequest $request): DataResponse {\n\t\ttry {\n\t\t\t$user = $this->userSession->getUser();\n\n\t\t\treturn new DataResponse([\n\t\t\t\t'result' => $this->searchService->search($user->getUID(), $request),\n\t\t\t\t'status' => 1,\n\t\t\t\t'request' => $request,\n\t\t\t\t'version' => $this->appConfig->getAppValueString('installed_version')\n\t\t\t], Http::STATUS_OK);\n\t\t} catch (Exception $e) {\n\t\t\treturn new DataResponse(\n\t\t\t\t[\n\t\t\t\t\t'status' => -1,\n\t\t\t\t\t'exception' => get_class($e),\n\t\t\t\t\t'message' => $e->getMessage(),\n\t\t\t\t\t'request' => $request,\n\t\t\t\t\t'version' => $this->appConfig->getAppValueString('installed_version')\n\t\t\t\t],\n\t\t\t\tHttp::STATUS_INTERNAL_SERVER_ERROR\n\t\t\t);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "lib/Controller/CollectionController.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Controller;\n\n\nuse OC\\ForbiddenException;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Exceptions\\CollectionArgumentException;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse OCP\\AppFramework\\Http\\DataResponse;\nuse OCP\\AppFramework\\OCS\\OCSException;\nuse OCP\\AppFramework\\OCSController;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\IGroupManager;\nuse OCP\\IRequest;\nuse OCP\\IUserSession;\n\nclass CollectionController extends OCSController {\n\tpublic function __construct(\n\t\tIRequest $request,\n\t\tprivate IUserSession $userSession,\n\t\tprivate IGroupManager $groupManager,\n\t\tprivate CollectionService $collectionService\n\t) {\n\t\tparent::__construct(Application::APP_ID, $request);\n\t}\n\n\t/**\n\t * @NoAdminRequired\n\t *\n\t * @param string $collection\n\t * @param int $length\n\t *\n\t * @return DataResponse\n\t * @throws OCSException\n\t */\n\tpublic function getQueue(string $collection, int $length = 0): DataResponse {\n\t\ttry {\n\t\t\t$this->collectionService->confirmCollection($collection);\n\t\t\t$this->confirmAccess($collection);\n\n\t\t\treturn new DataResponse($this->collectionService->getQueue($collection, $length));\n\t\t} catch (\\Exception $e) {\n\t\t\tthrow new OCSException($e->getMessage(), $e->getCode());\n\t\t}\n\t}\n\n\n\t/**\n\t * @NoAdminRequired\n\t *\n\t * @param string $collection\n\t * @param int $length\n\t *\n\t * @return DataResponse\n\t * @throws OCSException\n\t */\n\tpublic function resetCollection(string $collection): DataResponse {\n\t\ttry {\n\t\t\t$this->collectionService->confirmCollection($collection);\n\t\t\t$this->confirmAccess($collection);\n\n\t\t\t$this->collectionService->resetCollection($collection);\n\n\t\t\treturn new DataResponse(['done']);\n\t\t} catch (\\Exception $e) {\n\t\t\tthrow new OCSException($e->getMessage(), $e->getCode());\n\t\t}\n\t}\n\n\t/**\n\t * @NoAdminRequired\n\t *\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return DataResponse\n\t * @throws OCSException\n\t */\n\tpublic function indexDocument(string $collection, string $providerId, string $documentId): DataResponse {\n\t\ttry {\n\t\t\t$this->collectionService->confirmCollection($collection);\n\t\t\t$this->confirmAccess($collection);\n\n\t\t\t$document = $this->collectionService->getDocument(\n\t\t\t\t$collection,\n\t\t\t\t$providerId,\n\t\t\t\t$documentId\n\t\t\t);\n\n\t\t\treturn new DataResponse($this->displayDocument($document));\n\t\t} catch (\\Exception $e) {\n\t\t\tthrow new OCSException($e->getMessage(), $e->getCode());\n\t\t}\n\t}\n\n\n\t/**\n\t * @NoAdminRequired\n\t *\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return DataResponse\n\t * @throws OCSException\n\t */\n\tpublic function updateStatusDone(\n\t\tstring $collection,\n\t\tstring $providerId,\n\t\tstring $documentId\n\t): DataResponse {\n\t\ttry {\n\t\t\t$this->collectionService->confirmCollection($collection);\n\t\t\t$this->confirmAccess($collection);\n\n\t\t\t$this->collectionService->setAsDone($collection, $providerId, $documentId);\n\n\t\t\treturn new DataResponse([]);\n\t\t} catch (\\Exception $e) {\n\t\t\tthrow new OCSException($e->getMessage(), $e->getCode());\n\t\t}\n\t}\n\n\t/**\n\t * confirm that current session have access to collection\n\t *\n\t * @param string $collection\n\t *\n\t * @return void\n\t * @throws ForbiddenException\n\t */\n\tprivate function confirmAccess(string $collection): void {\n\t\t$currentUserId = $this->userSession->getUser()->getUID();\n\t\tif ($this->groupManager->isAdmin($currentUserId)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tif ($this->collectionService->getLinkedAccount($collection) === $currentUserId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (CollectionArgumentException) {\n\t\t}\n\n\t\tthrow new ForbiddenException('API access not allowed');\n\t}\n\n\t/**\n\t * @param IIndexDocument $document\n\t *\n\t * @return array\n\t */\n\tprivate function displayDocument(IIndexDocument $document): array {\n\t\t$display = [\n\t\t\t'id' => $document->getId(),\n\t\t\t'providerId' => $document->getProviderId(),\n\t\t\t'access' => $document->getAccess(),\n\t\t\t'index' => $document->getIndex(),\n\t\t\t'title' => $document->getTitle(),\n\t\t\t'link' => $document->getLink(),\n\t\t\t'parts' => $document->getParts(),\n\t\t\t'tags' => $document->getTags(),\n\t\t\t'metatags' => $document->getMetaTags(),\n\t\t\t'source' => $document->getSource(),\n\t\t\t'info' => $document->getInfoAll(),\n\t\t\t'hash' => $document->getHash(),\n\t\t\t'modifiedTime' => $document->getModifiedTime()\n\t\t];\n\n\t\tforeach ($document->getMore() as $k => $v) {\n\t\t\t$display[$k] = $v;\n\t\t}\n\n\t\t$display['content'] = $document->getContent();\n\t\t$display['isContentEncoded'] = $document->isContentEncoded();\n\n\t\treturn json_decode(json_encode($display), true);\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Controller/NavigationController.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Controller;\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCP\\AppFramework\\Controller;\nuse OCP\\AppFramework\\Http\\TemplateResponse;\nuse OCP\\FullTextSearch\\IFullTextSearchManager;\nuse OCP\\IConfig;\nuse OCP\\IRequest;\n\nclass NavigationController extends Controller {\n\tpublic function __construct(\n\t\tIRequest $request,\n\t\tprivate IConfig $config,\n\t\tprivate IFullTextSearchManager $fullTextSearchManager,\n\t) {\n\t\tparent::__construct(Application::APP_ID, $request);\n\t}\n\n\n\t/**\n\t * @NoCSRFRequired\n\t * @NoAdminRequired\n\t * @NoSubAdminRequired\n\t *\n\t * @return TemplateResponse\n\t */\n\tpublic function navigate(): TemplateResponse {\n\t\t$themingName = $this->config->getAppValue('theming', 'name', 'Nextcloud');\n\t\t$data = ['themingName' => $themingName];\n\n\t\t$this->fullTextSearchManager->addJavascriptAPI();\n\n\t\treturn new TemplateResponse(Application::APP_ID, 'navigate', $data);\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Controller/SettingsController.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nnamespace OCA\\FullTextSearch\\Controller;\n\nuse Exception;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\SettingsService;\nuse OCP\\AppFramework\\Controller;\nuse OCP\\AppFramework\\Http;\nuse OCP\\AppFramework\\Http\\DataResponse;\nuse OCP\\IRequest;\n\nclass SettingsController extends Controller {\n\tpublic function __construct(\n\t\tIRequest $request,\n\t\tprivate ConfigService $configService,\n\t\tprivate SettingsService $settingsService,\n\t) {\n\t\tparent::__construct(Application::APP_ID, $request);\n\t}\n\n\tpublic function getSettingsAdmin(): DataResponse {\n\t\t$data = $this->configService->getConfig();\n\t\t$this->settingsService->completeSettings($data);\n\n\t\treturn new DataResponse($data, Http::STATUS_OK);\n\t}\n\n\tpublic function setSettingsAdmin(array $data): DataResponse {\n\t\tif ($this->settingsService->checkConfig($data)) {\n\t\t\t$this->configService->setConfig($data);\n\t\t}\n\n\t\treturn $this->getSettingsAdmin();\n\t}\n\n}\n"
  },
  {
    "path": "lib/Controller/TemplatesController.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Controller;\n\nuse Exception;\nuse OC\\AppFramework\\Http;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCP\\AppFramework\\Controller;\nuse OCP\\AppFramework\\Http\\DataResponse;\nuse OCP\\AppFramework\\Http\\TemplateResponse;\nuse OCP\\IRequest;\n\n\n/**\n * Class TemplatesController\n *\n * @package OCA\\FullTextSearch\\Controller\n */\nclass TemplatesController extends Controller {\n\tpublic function __construct(\n\t\tIRequest $request,\n\t\tprivate ProviderService $providerService\n\t) {\n\t\tparent::__construct(Application::APP_ID, $request);\n\t}\n\n\t/**\n\t * @NoAdminRequired\n\t * @NoSubAdminRequired\n\t *\n\t * @param string $providerId\n\t *\n\t * @return DataResponse\n\t * @throws Exception\n\t * @throws ProviderDoesNotExistException\n\t */\n\tpublic function getOptionsPanel(string $providerId): DataResponse {\n\t\t$providerWrapper = $this->providerService->getProvider($providerId);\n\t\t$provider = $providerWrapper->getProvider();\n\n\t\t$searchTemplate = $provider->getSearchTemplate();\n\n\t\t$template = '';\n\t\tif ($searchTemplate->getTemplate() !== '') {\n\t\t\t$tmpl =\n\t\t\t\tnew TemplateResponse(\n\t\t\t\t\t$providerWrapper->getAppId(), $searchTemplate->getTemplate(), [], 'blank'\n\t\t\t\t);\n\t\t\t$template = $tmpl->render();\n\t\t}\n\n\t\t$ret[$providerId] =\n\t\t\t[\n\t\t\t\t'options'  => $searchTemplate->getPanelOptions(),\n\t\t\t\t'template' => $template\n\t\t\t];\n\n\t\treturn new DataResponse($ret, Http::STATUS_OK);\n\t}\n\n\n\t/**\n\t * @NoAdminRequired\n\t * @NoSubAdminRequired\n\t *\n\t * @return DataResponse\n\t * @throws Exception\n\t */\n\tpublic function getNavigationPanels(): DataResponse {\n\t\t$providers = $this->providerService->getProviders();\n\n\t\t$ret = [];\n\t\tforeach ($providers as $providerWrapper) {\n\t\t\t$provider = $providerWrapper->getProvider();\n\t\t\t$providerAppId = $providerWrapper->getAppId();\n\n\t\t\t$searchTemplate = $provider->getSearchTemplate();\n\t\t\t$ret[$providerAppId] =\n\t\t\t\t[\n\t\t\t\t\t'provider' => $provider->getId(),\n\t\t\t\t\t'title'    => $provider->getName(),\n\t\t\t\t\t'options'  => $searchTemplate->getNavigationOptions(),\n\t\t\t\t\t'css'      => $searchTemplate->getCss(),\n\t\t\t\t\t'icon'     => $searchTemplate->getIcon()\n\t\t\t\t];\n\t\t}\n\n\t\treturn new DataResponse($ret, Http::STATUS_OK);\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Cron/Index.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Cron;\n\nuse Exception;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformTemporaryException;\nuse OCA\\FullTextSearch\\Exceptions\\RunnerAlreadyUpException;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\IndexService;\nuse OCA\\FullTextSearch\\Service\\PlatformService;\nuse OCA\\FullTextSearch\\Service\\ProviderService;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCP\\AppFramework\\QueryException;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\AppFramework\\Utility\\ITimeFactory;\nuse OCP\\BackgroundJob\\IJob;\nuse OCP\\BackgroundJob\\TimedJob;\nuse Psr\\Log\\LoggerInterface;\nuse Throwable;\n\nclass Index extends TimedJob {\n\tconst HOUR_ERR_RESET = 240;\n\n\tprivate Runner $runner;\n\n\tpublic function __construct(\n\t\tITimeFactory $timeFactory,\n\t\tprivate RunningService $runningService,\n\t\tprivate IndexService $indexService,\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t\tprivate IAppConfig $appConfig,\n\t\tprivate LoggerInterface $logger,\n\t) {\n\t\tparent::__construct($timeFactory);\n\t\t$this->setInterval(12 * 60); // 12 minutes\n\t\t$this->setTimeSensitivity(IJob::TIME_SENSITIVE);\n\t}\n\n\n\t/**\n\t * @param mixed $argument\n\t *\n\t * @throws QueryException\n\t */\n\tprotected function run($argument) {\n\t\t$this->runner = new Runner($this->runningService, 'cronIndex');\n\n\t\ttry {\n\t\t\t$this->runner->start();\n\t\t\t$this->liveCycle();\n\t\t\t$this->runner->stop();\n\t\t} catch (RunnerAlreadyUpException $e) {\n\t\t} catch (Exception $e) {\n\t\t\t$this->logger->notice(\n\t\t\t\t'exception encountered while running fulltextsearch/lib/Cron/Index.php',\n\t\t\t\t['exception' => $e]\n\t\t\t);\n\t\t\t$this->runner->exception($e->getMessage(), true);\n\t\t}\n\n\t}\n\n\n\t/**\n\t * @throws Exception\n\t */\n\tprivate function liveCycle() {\n\n\t\t$wrapper = $this->platformService->getPlatform(true);\n\t\t$platform = $wrapper->getPlatform();\n\n\t\t$all = $this->shouldWeGetAllIndex();\n\t\t$indexes = $this->indexService->getQueuedIndexes('', $all);\n\n\t\tforeach ($indexes as $index) {\n\t\t\t$this->runner->updateAction('indexing');\n\n\t\t\ttry {\n\t\t\t\t$providerWrapper = $this->providerService->getProvider($index->getProviderId());\n\t\t\t\t$provider = $providerWrapper->getProvider();\n\n\t\t\t\t$this->indexService->updateDocument($platform, $provider, $index);\n\t\t\t} catch (PlatformTemporaryException $e) {\n\t\t\t\t$this->logger->warning('platform seems down. we will update index next cron tick');\n\t\t\t\treturn;\n\t\t\t} catch (Throwable|Exception $e) {\n\t\t\t\t$this->runner->exception(get_class($e) . ' - ' . $e->getMessage(), false);\n\t\t\t\t$this->logger->notice(\n\t\t\t\t\t'exception encountered while running fulltextsearch/lib/Cron/Index.php',\n\t\t\t\t\t['exception' => $e]\n\t\t\t\t);\n\t\t\t\t// TODO - upgrade error number - after too many errors, delete index\n\t\t\t\t// TODO - do not count error if elasticsearch is down.\n\t\t\t}\n\t\t}\n\n\t}\n\n\n\t/**\n\t * @return bool\n\t */\n\tprivate function shouldWeGetAllIndex(): bool {\n\t\t$lastErrReset = $this->appConfig->getAppValueInt(ConfigLexicon::CRON_LAST_ERR_RESET);\n\t\tif ($lastErrReset === 0) {\n\t\t\t$this->setLastErrReset();\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($lastErrReset < (time() - (self::HOUR_ERR_RESET * 3600))) {\n\t\t\t$this->setLastErrReset();\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t *\n\t */\n\tprivate function setLastErrReset() {\n\t\t$this->appConfig->setAppValueInt(ConfigLexicon::CRON_LAST_ERR_RESET, time());\n\t}\n}\n"
  },
  {
    "path": "lib/Db/CoreRequestBuilder.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Db;\n\nuse Doctrine\\DBAL\\Exception\\ConnectionException;\nuse Doctrine\\DBAL\\Query\\QueryBuilder;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCP\\DB\\Exception;\nuse OCP\\DB\\QueryBuilder\\IQueryBuilder;\nuse OCP\\IDBConnection;\nuse OCP\\IL10N;\nuse OCP\\Server;\nuse Psr\\Log\\LoggerInterface;\n\nclass CoreRequestBuilder {\n\tconst TABLE_INDEXES = 'fulltextsearch_index';\n\tconst TABLE_TICKS = 'fulltextsearch_ticks';\n\n\tprotected string $defaultSelectAlias;\n\tprivate int $lastReconnect = 0;\n\n\tpublic function __construct(\n\t\tprotected IL10N $l10n,\n\t\tprotected IDBConnection $dbConnection,\n\t\tprotected ConfigService $configService\n\t) {\n\t}\n\n\tprotected function reconnect(Exception $ex): void {\n\t\tif ($this->lastReconnect > time() - 2) {\n\t\t\t// in case we just reconnected a second ago\n\t\t\tthrow $ex;\n\t\t}\n\n\t\ttry {\n\t\t\t$this->dbConnection->getInner()->close();\n\t\t} catch (\\Exception) {\n\t\t}\n\n\t\tfor ($i = 0; $i < 30; $i++) {\n\t\t\tsleep(10);\n\t\t\ttry {\n\t\t\t\t$this->dbConnection->getInner()->connect();\n\t\t\t\t$this->lastReconnect = time();\n\t\t\t\treturn;\n\t\t\t} catch (\\Exception $e) {\n\t\t\t\tServer::get(LoggerInterface::class)->warning('failed to reconnect', ['exception' => $e, 'i' => $i]);\n\t\t\t}\n\t\t}\n\n\t\tthrow $ex;\n\t}\n\n\t/**\n\t * Limit the request to the Id\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param int $id\n\t */\n\tprotected function limitToId(IQueryBuilder $qb, int $id) {\n\t\t$this->limitToDBFieldInt($qb, 'id', $id);\n\t}\n\n\n\t/**\n\t * Limit the request to the OwnerId\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $userId\n\t */\n\tprotected function limitToOwnerId(IQueryBuilder $qb, string $userId) {\n\t\t$this->limitToDBField($qb, 'owner_id', $userId);\n\t}\n\n\n\t/**\n\t * Limit to the providerId\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $providerId\n\t */\n\tprotected function limitToProviderId(IQueryBuilder $qb, string $providerId) {\n\t\t$this->limitToDBField($qb, 'provider_id', $providerId);\n\t}\n\n\n\t/**\n\t * Limit to the documentId\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $documentId\n\t */\n\tprotected function limitToDocumentId(IQueryBuilder $qb, string $documentId) {\n\t\t$this->limitToDBField($qb, 'document_id', $documentId);\n\t}\n\n\n\t/**\n\t * Limit to the documentId\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $collection\n\t */\n\tprotected function limitToCollection(IQueryBuilder $qb, string $collection) {\n\t\t$this->limitToDBField($qb, 'collection', $collection);\n\t}\n\n\n\t/**\n\t * Limit to the entry with at least one Error\n\t *\n\t * @param IQueryBuilder $qb\n\t */\n\tprotected function limitToErr(IQueryBuilder $qb) {\n\t\t$expr = $qb->expr();\n\t\t$qb->andWhere($expr->gte('err', $qb->createNamedParameter(1)));\n\t}\n\n\n\t/**\n\t * Limit to the entry with no error\n\t *\n\t * @param IQueryBuilder $qb\n\t */\n\tprotected function limitToNoErr(IQueryBuilder $qb) {\n\t\t$expr = $qb->expr();\n\t\t$qb->andWhere($expr->eq('err', $qb->createNamedParameter(0)));\n\t}\n\n\n\t/**\n\t * Limit to documentIds\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param array $documentIds\n\t */\n\tprotected function limitToDocumentIds(IQueryBuilder $qb, array $documentIds) {\n\t\t$this->limitToDBFieldArray($qb, 'document_id', $documentIds);\n\t}\n\n\n\t/**\n\t * Limit the request to source\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $source\n\t */\n\tprotected function limitToSource(IQueryBuilder $qb, string $source) {\n\t\t$this->limitToDBField($qb, 'id', $source);\n\t}\n\n\n\t/**\n\t * Limit the request to status\n\t *\n\t * @param IQueryBuilder $qb\n\t * @param string $status\n\t */\n\tprotected function limitToStatus(IQueryBuilder $qb, string $status) {\n\t\t$this->limitToDBField($qb, 'status', $status);\n\t}\n\n\n\t/**\n\t * @param IQueryBuilder $qb\n\t * @param string $field\n\t * @param string $value\n\t */\n\tprivate function limitToDBField(IQueryBuilder $qb, string $field, string $value) {\n\t\t$expr = $qb->expr();\n\t\t$pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : '';\n\t\t$field = $pf . $field;\n\n\t\t$qb->andWhere($expr->eq($field, $qb->createNamedParameter($value)));\n\t}\n\n\t/**\n\t * @param IQueryBuilder $qb\n\t * @param string $field\n\t * @param int $value\n\t */\n\tprivate function limitToDBFieldInt(IQueryBuilder $qb, string $field, int $value) {\n\t\t$expr = $qb->expr();\n\t\t$pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : '';\n\t\t$field = $pf . $field;\n\n\t\t$qb->andWhere($expr->eq($field, $qb->createNamedParameter($value)));\n\t}\n\n\n\t/**\n\t * @param IQueryBuilder $qb\n\t * @param string $field\n\t * @param string|integer|array $values\n\t */\n\tprivate function limitToDBFieldArray(IQueryBuilder $qb, string $field, array $values) {\n\t\t$expr = $qb->expr();\n\t\t$pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : '';\n\t\t$field = $pf . $field;\n\n\t\tif (!is_array($values)) {\n\t\t\t$values = [$values];\n\t\t}\n\n\t\t$orX = $expr->orX();\n\t\tforeach ($values as $value) {\n\t\t\t$orX->add($expr->eq($field, $qb->createNamedParameter($value)));\n\t\t}\n\n\t\t$qb->andWhere($orX);\n\t}\n\n\n\t/**\n\t * @param IQueryBuilder $qb\n\t */\n\tprotected function limitToQueuedIndexes(IQueryBuilder $qb) {\n\t\t$expr = $qb->expr();\n\t\t$pf = ($qb->getType() === QueryBuilder::SELECT) ? $this->defaultSelectAlias . '.' : '';\n\t\t$qb->andWhere($expr->neq($pf . 'status', $qb->createNamedParameter(Index::INDEX_OK)));\n\t}\n\n}\n\n\n\n"
  },
  {
    "path": "lib/Db/IndexesRequest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Db;\n\n\nuse OCA\\FullTextSearch\\Exceptions\\IndexDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCP\\DB\\Exception;\nuse OCP\\DB\\QueryBuilder\\IQueryBuilder;\nuse OCP\\FullTextSearch\\Model\\IIndex;\n\n\n/**\n * Class IndexesRequest\n *\n * @package OCA\\FullTextSearch\\Db\n */\nclass IndexesRequest extends IndexesRequestBuilder {\n\n\t/**\n\t * @param Index $index\n\t *\n\t * @return bool\n\t */\n\tpublic function create(Index $index): bool {\n\t\tif (empty($index->getCollection())) {\n\t\t\t$index->setCollection($this->configService->getInternalCollection());\n\t\t}\n\n\t\t$qb = $this->getIndexesInsertSql();\n\t\t$qb->setValue('owner_id', $qb->createNamedParameter($index->getOwnerId()))\n\t\t   ->setValue('provider_id', $qb->createNamedParameter($index->getProviderId()))\n\t\t   ->setValue('collection', $qb->createNamedParameter($index->getCollection()))\n\t\t   ->setValue('document_id', $qb->createNamedParameter($index->getDocumentId()))\n\t\t   ->setValue('source', $qb->createNamedParameter($index->getSource()))\n\t\t   ->setValue('err', $qb->createNamedParameter($index->getErrorCount()))\n\t\t   ->setValue('message', $qb->createNamedParameter(json_encode($index->getErrors())))\n\t\t   ->setValue('status', $qb->createNamedParameter($index->getStatus()))\n\t\t   ->setValue('options', $qb->createNamedParameter(json_encode($index->getOptions())))\n\t\t   ->setValue('indexed', $qb->createNamedParameter($index->getLastIndex()));\n\n\t\ttry {\n\t\t\t $qb->executeStatement();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->create($index);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n\t/**\n\t * @param Index $index\n\t *\n\t * @return bool\n\t */\n\tpublic function resetError(Index $index): bool {\n\t\ttry {\n\t\t\t$this->getIndex($index->getProviderId(), $index->getDocumentId(), $index->getCollection());\n\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('message', $qb->createNamedParameter(json_encode([])));\n\t\t$qb->set('err', $qb->createNamedParameter(0));\n\n\t\t$this->limitToProviderId($qb, $index->getProviderId());\n\t\t$this->limitToDocumentId($qb, $index->getDocumentId());\n\t\t$this->limitToCollection($qb, $index->getCollection());\n\t\t$qb->executeStatement();\n\n\t\treturn true;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function resetAllErrors() {\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('message', $qb->createNamedParameter(json_encode([])));\n\t\t$qb->set('err', $qb->createNamedParameter(0));\n\n\t\t$qb->executeStatement();\n\t}\n\n\n\t/**\n\t * @return Index[]\n\t */\n\tpublic function getErrorIndexes(): array {\n\t\t$qb = $this->getIndexesSelectSql();\n\t\t$this->limitToErr($qb);\n\n\t\t$indexes = [];\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getErrorIndexes();\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$indexes[] = $this->parseIndexesSelectSql($data);\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\treturn $indexes;\n\t}\n\n\n\t/**\n\t * @param Index $index\n\t */\n\tpublic function update(Index $index, bool $statusOnly = false): void {\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('status', $qb->createNamedParameter($index->getStatus()));\n\n\t\tif (!$statusOnly) {\n\t\t\t$qb->set('source', $qb->createNamedParameter($index->getSource()));\n\n\t\t\t$qb->set('options', $qb->createNamedParameter(json_encode($index->getOptions())));\n\n\t\t\tif ($index->getOwnerId() !== '') {\n\t\t\t\t$qb->set('owner_id', $qb->createNamedParameter($index->getOwnerId()));\n\t\t\t}\n\n\t\t\tif ($index->getLastIndex() > 0) {\n\t\t\t\t$qb->set('indexed', $qb->createNamedParameter($index->getLastIndex()));\n\t\t\t}\n\n\t\t\t$qb->set('message', $qb->createNamedParameter(json_encode($index->getErrors())));\n\t\t\t$qb->set('err', $qb->createNamedParameter($index->getErrorCount()));\n\t\t}\n\n\t\t$this->limitToProviderId($qb, $index->getProviderId());\n\t\t$this->limitToDocumentId($qb, $index->getDocumentId());\n\t\t$this->limitToCollection($qb, $index->getCollection());\n\n\t\ttry {\n\t\t\t$qb->executeStatement();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\t$this->update($index, $statusOnly);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $documentId\n\t * @param int $status\n\t */\n\tpublic function updateStatus(string $collection, string $providerId, string $documentId, int $status) {\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('status', $qb->createNamedParameter($status));\n\n\t\t$this->limitToProviderId($qb, $providerId);\n\t\t$this->limitToDocumentId($qb, $documentId);\n\t\t$this->limitToCollection($qb, $collection);\n\n\t\ttry {\n\t\t\t$qb->executeStatement();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\t$this->updateStatus($collection, $providerId, $documentId, $status);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\t}\n\n\t/**\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param array $indexes\n\t * @param int $status\n\t */\n\tpublic function updateStatuses(string $collection, string $providerId, array $indexes, int $status) {\n\t\t$collection = ($collection === '') ? $this->configService->getInternalCollection() : $collection;\n\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('status', $qb->createNamedParameter($status));\n\n\t\t$this->limitToProviderId($qb, $providerId);\n\t\t$this->limitToDocumentIds($qb, $indexes);\n\t\t$this->limitToCollection($qb, $collection);\n\n\t\ttry {\n\t\t\t$qb->executeStatement();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\t$this->updateStatuses($collection, $providerId, $indexes, $status);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\t}\n\n\t/**\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param array $indexes\n\t * @param int $status\n\t */\n\tpublic function resetCollection(string $collection) {\n\t\t$collection = ($collection === '') ? $this->configService->getInternalCollection() : $collection;\n\n\t\t$qb = $this->getIndexesUpdateSql();\n\t\t$qb->set('status', $qb->createNamedParameter(IIndex::INDEX_FULL));\n\t\t$this->limitToCollection($qb, $collection);\n\n\t\t$qb->executeStatement();\n\t}\n\n\t/**\n\t * @param IIndex $index\n\t */\n\tpublic function deleteIndex(IIndex $index) {\n\t\t$qb = $this->getIndexesDeleteSql();\n\t\t$this->limitToProviderId($qb, $index->getProviderId());\n\t\t$this->limitToDocumentId($qb, $index->getDocumentId());\n\t\t$this->limitToCollection($qb, $index->getCollection());\n\n\t\ttry {\n\t\t\t$qb->executeStatement();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->deleteIndex($index);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t */\n\tpublic function deleteCollection(string $collection): void {\n\t\t$qb = $this->getIndexesDeleteSql();\n\t\t$this->limitToCollection($qb, $collection);\n\n\t\t$qb->executeStatement();\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t */\n\tpublic function deleteFromProviderId(string $providerId, string $collection = '') {\n\t\t$qb = $this->getIndexesDeleteSql();\n\t\t$this->limitToProviderId($qb, $providerId);\n\n\t\t$qb->executeStatement();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function reset(string $collection = ''): void {\n\t\t$qb = $this->getIndexesDeleteSql();\n\t\tif ($collection !== '') {\n\t\t\t$this->limitToCollection($qb, $collection);\n\t\t}\n\n\t\t$qb->executeStatement();\n\t}\n\n\n\t/**\n\t * return index.\n\t *\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return Index\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function getIndex(string $providerId, string $documentId, string $collection = ''): Index {\n\t\t$collection = ($collection === '') ? $this->configService->getInternalCollection() : $collection;\n\n\t\t$qb = $this->getIndexesSelectSql();\n\t\t$this->limitToProviderId($qb, $providerId);\n\t\t$this->limitToDocumentId($qb, $documentId);\n\t\t$this->limitToCollection($qb, $collection);\n\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getIndex($providerId, $documentId, $collection);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\t$data = $cursor->fetch();\n\t\t$cursor->closeCursor();\n\n\t\tif ($data === false) {\n\t\t\tthrow new IndexDoesNotExistException($this->l10n->t('Index not found'));\n\t\t}\n\n\t\treturn $this->parseIndexesSelectSql($data);\n\t}\n\n\n\t/**\n\t * return index.\n\t *\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return Index[]\n\t */\n\tpublic function getIndexes(string $providerId, string $documentId): array {\n\t\t$qb = $this->getIndexesSelectSql();\n\t\t$this->limitToProviderId($qb, $providerId);\n\t\t$this->limitToDocumentId($qb, $documentId);\n\n\t\t$indexes = [];\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getIndexes($providerId, $documentId);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$indexes[] = $this->parseIndexesSelectSql($data);\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\treturn $indexes;\n\t}\n\n\n\t/**\n\t * @param bool $all\n\t *\n\t * @return Index[]\n\t */\n\tpublic function getQueuedIndexes(string $collection = '', bool $all = false, int $length = 0): array {\n\t\t$collection = ($collection === '') ? $this->configService->getInternalCollection() : $collection;\n\n\t\t$qb = $this->getIndexesSelectSql();\n\t\t$this->limitToQueuedIndexes($qb);\n\t\tif ($all === false) {\n\t\t\t$this->limitToNoErr($qb);\n\t\t}\n\n\t\t$this->limitToCollection($qb, $collection);\n\t\tif ($length > 0) {\n\t\t\t$qb->setMaxResults($length);\n\t\t}\n\n\t\t$indexes = [];\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getQueuedIndexes($collection, $all, $length);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$indexes[] = $this->parseIndexesSelectSql($data);\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\treturn $indexes;\n\t}\n\n\n\t/**\n\t * return list of last indexes from a providerId.\n\t *\n\t * @param string $providerId\n\t *\n\t * @return Index[]\n\t */\n\tpublic function getIndexesFromProvider(string $providerId): array {\n\t\t$qb = $this->getIndexesSelectSql();\n\t\t$this->limitToProviderId($qb, $providerId);\n\n\t\t$indexes = [];\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getIndexesFromProvider($providerId);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$index = $this->parseIndexesSelectSql($data);\n\t\t\t$indexes[$index->getDocumentId()] = $index;\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\treturn $indexes;\n\t}\n\n\n\t/**\n\t * @return string[]\n\t */\n\tpublic function getCollections(bool $local = true): array {\n\t\t$internal = $this->configService->getInternalCollection();\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\n\t\t$qb->select('li.collection')\n\t\t   ->from(self::TABLE_INDEXES, 'li');\n\t\t$qb->andWhere($qb->expr()->nonEmptyString('li.collection'));\n\t\t$qb->andWhere($qb->expr()->neq('li.collection', $qb->createNamedParameter($internal, IQueryBuilder::PARAM_STR)));\n\t\t$qb->groupBy('li.collection');\n\n\t\t$collections = [];\n\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (Exception $e) {\n\t\t\tif ($e->getReason() === Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getCollections(true);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$collections[] = $this->get('collection', $data);\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\tif (!$local) {\n\t\t\treturn $collections;\n\t\t}\n\n\t\treturn array_merge([$internal], $collections);\n\t}\n}\n"
  },
  {
    "path": "lib/Db/IndexesRequestBuilder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Db;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCP\\DB\\QueryBuilder\\IQueryBuilder;\n\n\n/**\n * Class IndexesRequestBuilder\n *\n * @package OCA\\FullTextSearch\\Db\n */\nclass IndexesRequestBuilder extends CoreRequestBuilder {\n\n\n\tuse TArrayTools;\n\n\n\t/**\n\t * Base of the Sql Insert request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getIndexesInsertSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->insert(self::TABLE_INDEXES);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Update request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getIndexesUpdateSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->update(self::TABLE_INDEXES);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Select request for Shares\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getIndexesSelectSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\n\t\t/** @noinspection PhpMethodParametersCountMismatchInspection */\n\t\t$qb->select(\n\t\t\t'li.owner_id', 'li.provider_id', 'li.document_id', 'li.collection', 'li.source',\n\t\t\t'li.status', 'li.options', 'li.err', 'li.message', 'li.indexed'\n\t\t)\n\t\t   ->from(self::TABLE_INDEXES, 'li');\n\n\t\t$this->defaultSelectAlias = 'li';\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Delete request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getIndexesDeleteSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->delete(self::TABLE_INDEXES);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * @param array $data\n\t *\n\t * @return Index\n\t */\n\tprotected function parseIndexesSelectSql(array $data): Index {\n\t\t$index =\n\t\t\tnew Index($this->get('provider_id', $data, ''), $this->get('document_id', $data, ''));\n\n\t\t$index->setStatus($this->getInt('status', $data))\n\t\t\t  ->setSource($this->get('source', $data, ''))\n\t\t\t  ->setOwnerId($this->get('owner_id', $data, ''))\n\t\t\t  ->setLastIndex($this->getInt('indexed', $data, 0))\n\t\t\t  ->setCollection($this->get('collection', $data));\n\t\t$index->setOptions($this->getArray('options', $data, []));\n\t\t$index->setErrorCount($this->getInt('err', $data, 0));\n\t\t$index->setErrors($this->getArray('message', $data, []));\n\n\t\treturn $index;\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Db/TickRequest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Db;\n\nuse Exception;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Tick;\n\n/**\n * Class TickRequest\n *\n * @package OCA\\FullTextSearch\\Db\n */\nclass TickRequest extends TickRequestBuilder {\n\n\t/**\n\t * @param Tick $tick\n\t *\n\t * @return int\n\t * @throws Exception\n\t */\n\tpublic function create(Tick $tick): int {\n\t\ttry {\n\t\t\t$qb = $this->getTickInsertSql();\n\t\t\t$qb->setValue('source', $qb->createNamedParameter($tick->getSource()))\n\t\t\t   ->setValue('data', $qb->createNamedParameter(json_encode($tick->getData())))\n\t\t\t   ->setValue('action', $qb->createNamedParameter($tick->getAction()))\n\t\t\t   ->setValue('first_tick', $qb->createNamedParameter($tick->getFirstTick()))\n\t\t\t   ->setValue('tick', $qb->createNamedParameter($tick->getTick()))\n\t\t\t   ->setValue('status', $qb->createNamedParameter($tick->getStatus()));\n\n\t\t\ttry {\n\t\t\t\t$qb->executeStatement();\n\t\t\t} catch (\\OCP\\DB\\Exception $e) {\n\t\t\t\tif ($e->getReason() === \\OCP\\DB\\Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t\t$this->reconnect($e);\n\t\t\t\t\treturn $this->create($tick);\n\t\t\t\t}\n\t\t\t\tthrow $e;\n\t\t\t}\n\n\t\t\treturn $qb->getLastInsertId();\n\t\t} catch (Exception $e) {\n\t\t\tthrow $e;\n\t\t}\n\t}\n\n\t/**\n\t * @param Tick $tick\n\t *\n\t * @return bool\n\t */\n\tpublic function update(Tick $tick): bool {\n\t\ttry {\n\t\t\t$this->getTickById($tick->getId());\n\t\t} catch (TickDoesNotExistException $e) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$qb = $this->getTickUpdateSql();\n\t\t$qb->set('data', $qb->createNamedParameter(json_encode($tick->getData())))\n\t\t   ->set('tick', $qb->createNamedParameter($tick->getTick()))\n\t\t   ->set('action', $qb->createNamedParameter($tick->getAction()))\n\t\t   ->set('status', $qb->createNamedParameter($tick->getStatus()));\n\n\t\t$this->limitToId($qb, $tick->getId());\n\n\t\ttry {\n\t\t\t$qb->executeStatement();\n\t\t} catch (\\OCP\\DB\\Exception $e) {\n\t\t\tif ($e->getReason() === \\OCP\\DB\\Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->update($tick);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * return tick.\n\t *\n\t * @param int $id\n\t *\n\t * @return Tick\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function getTickById(int $id): Tick {\n\t\t$qb = $this->getTickSelectSql();\n\t\t$this->limitToId($qb, $id);\n\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (\\OCP\\DB\\Exception $e) {\n\t\t\tif ($e->getReason() === \\OCP\\DB\\Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getTickById($id);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\t$data = $cursor->fetch();\n\t\t$cursor->closeCursor();\n\n\t\tif ($data === false) {\n\t\t\tthrow new TickDoesNotExistException($this->l10n->t('Process timed out'));\n\t\t}\n\n\t\treturn $this->parseTickSelectSql($data);\n\t}\n\n\t/**\n\t * return ticks.\n\t *\n\t * @param string $status\n\t *\n\t * @return Tick[]\n\t */\n\tpublic function getTicksByStatus(string $status): array {\n\t\t$ticks = [];\n\n\t\t$qb = $this->getTickSelectSql();\n\t\t$this->limitToStatus($qb, $status);\n\n\t\ttry {\n\t\t\t$cursor = $qb->executeQuery();\n\t\t} catch (\\OCP\\DB\\Exception $e) {\n\t\t\tif ($e->getReason() === \\OCP\\DB\\Exception::REASON_CONNECTION_LOST) {\n\t\t\t\t$this->reconnect($e);\n\t\t\t\treturn $this->getTicksByStatus($status);\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\twhile ($data = $cursor->fetch()) {\n\t\t\t$ticks[] = $this->parseTickSelectSql($data);\n\t\t}\n\t\t$cursor->closeCursor();\n\n\t\treturn $ticks;\n\t}\n}\n"
  },
  {
    "path": "lib/Db/TickRequestBuilder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Db;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse OCA\\FullTextSearch\\Model\\Tick;\nuse OCP\\DB\\QueryBuilder\\IQueryBuilder;\n\n\n/**\n * Class TickRequestBuilder\n *\n * @package OCA\\FullTextSearch\\Db\n */\nclass TickRequestBuilder extends CoreRequestBuilder {\n\n\n\tuse TArrayTools;\n\n\n\t/**\n\t * Base of the Sql Insert request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getTickInsertSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->insert(self::TABLE_TICKS);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Update request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getTickUpdateSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->update(self::TABLE_TICKS);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Select request for Shares\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getTickSelectSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\n\t\t/** @noinspection PhpMethodParametersCountMismatchInspection */\n\t\t$qb->select(\n\t\t\t't.id', 't.source', 't.data', 't.first_tick', 't.tick', 't.status', 't.action'\n\t\t)\n\t\t   ->from(self::TABLE_TICKS, 't');\n\n\t\t$this->defaultSelectAlias = 't';\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * Base of the Sql Delete request\n\t *\n\t * @return IQueryBuilder\n\t */\n\tprotected function getTickDeleteSql(): IQueryBuilder {\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$qb->delete(self::TABLE_TICKS);\n\n\t\treturn $qb;\n\t}\n\n\n\t/**\n\t * @param array $data\n\t *\n\t * @return Tick\n\t */\n\tprotected function parseTickSelectSql(array $data): Tick {\n\t\t$tick = new Tick($this->get('source', $data, ''), $this->getInt('id', $data, 0));\n\t\t$tick->setData($this->getArray('data', $data, []))\n\t\t\t ->setTick($this->getInt('tick', $data, 0))\n\t\t\t ->setFirstTick($this->getInt('first_tick', $data, 0))\n\t\t\t ->setStatus($this->get('status', $data, ''))\n\t\t\t ->setAction($this->get('action', $data, ''));\n\n\t\treturn $tick;\n\t}\n\n}\n"
  },
  {
    "path": "lib/Exceptions/CollectionArgumentException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass CollectionArgumentException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/DatabaseException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass DatabaseException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/EmptySearchException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass EmptySearchException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/IndexDoesNotExistException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass IndexDoesNotExistException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/InterruptException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass InterruptException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/MissingDocumentException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass MissingDocumentException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/NoResultException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass NoResultException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/NotIndexableDocumentException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass NotIndexableDocumentException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformDoesNotExistException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass PlatformDoesNotExistException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformIsNotCompatibleException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass PlatformIsNotCompatibleException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformMustBeSingleException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass PlatformMustBeSingleException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformNotDefinedException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass PlatformNotDefinedException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformNotSelectedException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass PlatformNotSelectedException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/PlatformTemporaryException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\nuse Exception;\n\n/**\n * @deprecated - nc28 use \\OCP\\FullTextSearch\\Exceptions\\PlatformTemporaryException\n */\nclass PlatformTemporaryException extends Exception {\n}\n"
  },
  {
    "path": "lib/Exceptions/ProviderDoesNotExistException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass ProviderDoesNotExistException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/ProviderIsNotCompatibleException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass ProviderIsNotCompatibleException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/ProviderIsNotUniqueException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass ProviderIsNotUniqueException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/ProviderOptionsDoesNotExistException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass ProviderOptionsDoesNotExistException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/RunnerAlreadyUpException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass RunnerAlreadyUpException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/TickDoesNotExistException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass TickDoesNotExistException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Exceptions/TickIsNotAliveException.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Exceptions;\n\n\nuse Exception;\n\n\nclass TickIsNotAliveException extends Exception {\n\n}\n\n"
  },
  {
    "path": "lib/Migration/Version2000Date20201208130255.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\DB\\Types;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\n/**\n * Auto-generated migration step: Please modify to your needs!\n */\nclass Version2000Date20201208130255 extends SimpleMigrationStep {\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t */\n\tpublic function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @return null|ISchemaWrapper\n\t */\n\tpublic function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\n\t\tif (!$schema->hasTable('fulltextsearch_ticks')) {\n\t\t\t$table = $schema->createTable('fulltextsearch_ticks');\n\t\t\t$table->addColumn('id', 'bigint', [\n\t\t\t\t'autoincrement' => true,\n\t\t\t\t'notnull' => true,\n\t\t\t\t'length' => 7,\n\t\t\t\t'unsigned' => true,\n\t\t\t]);\n\t\t\t$table->addColumn('source', 'string', [\n\t\t\t\t'notnull' => false,\n\t\t\t\t'length' => 128,\n\t\t\t]);\n\t\t\t$table->addColumn('data', Types::TEXT, [\n\t\t\t\t'notnull' => false\n\t\t\t]);\n\t\t\t$table->addColumn('status', 'string', [\n\t\t\t\t'notnull' => false,\n\t\t\t\t'length' => 32,\n\t\t\t]);\n\t\t\t$table->addColumn('action', 'string', [\n\t\t\t\t'notnull' => false,\n\t\t\t\t'length' => 64,\n\t\t\t]);\n\t\t\t$table->addColumn('first_tick', 'bigint', [\n\t\t\t\t'notnull' => false,\n\t\t\t\t'length' => 6,\n\t\t\t]);\n\t\t\t$table->addColumn('tick', 'bigint', [\n\t\t\t\t'notnull' => false,\n\t\t\t\t'length' => 6,\n\t\t\t]);\n\t\t\t$table->setPrimaryKey(['id']);\n\t\t}\n\n\t\treturn $schema;\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t */\n\tpublic function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {\n\t}\n}\n"
  },
  {
    "path": "lib/Migration/Version23001Date20220408140253.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse Doctrine\\DBAL\\Types\\Type;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\DB\\Types;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\nclass Version23001Date20220408140253 extends SimpleMigrationStep {\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @return null|ISchemaWrapper\n\t */\n\tpublic function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\n\t\tif (!$schema->hasTable('fulltextsearch_indexes')) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$table = $schema->getTable('fulltextsearch_indexes');\n\t\t$column = $table->getColumn('message');\n\n\t\tif ($column->getType()->getName() === Types::TEXT) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$column->setType(Type::getType(Types::TEXT));\n\n\t\treturn $schema;\n\t}\n}\n"
  },
  {
    "path": "lib/Migration/Version23001Date20220505144434.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse Doctrine\\DBAL\\Types\\Type;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\DB\\Types;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\nclass Version23001Date20220505144434 extends SimpleMigrationStep {\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @return null|ISchemaWrapper\n\t */\n\tpublic function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\n\t\tif (!$schema->hasTable('fulltextsearch_ticks')) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$table = $schema->getTable('fulltextsearch_ticks');\n\t\t$column = $table->getColumn('data');\n\n\t\tif ($column->getType()->getName() === Types::TEXT) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$column->setType(Type::getType(Types::TEXT));\n\n\t\treturn $schema;\n\t}\n}\n"
  },
  {
    "path": "lib/Migration/Version2400Date202201301329.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCP\\DB\\Exception;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\IDBConnection;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\n/**\n * Auto-generated migration step: Please modify to your needs!\n */\nclass Version2400Date202201301329 extends SimpleMigrationStep {\n\n\n\t/** @var IDBConnection */\n\tprivate $dbConnection;\n\n\t/** @var ConfigService */\n\tprivate $configService;\n\n\n\t/**\n\t * @param IDBConnection $dbConnection\n\t * @param ConfigService $configService\n\t */\n\tpublic function __construct(IDBConnection $dbConnection, ConfigService $configService) {\n\t\t$this->dbConnection = $dbConnection;\n\t\t$this->configService = $configService;\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t */\n\tpublic function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @return null|ISchemaWrapper\n\t */\n\tpublic function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\n\t\tif (!$schema->hasTable('fulltextsearch_index')) {\n\t\t\t$table = $schema->createTable('fulltextsearch_index');\n\t\t\t$table->addColumn(\n\t\t\t\t'provider_id', 'string',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => true,\n\t\t\t\t\t'length' => 254,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'document_id', 'string',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => true,\n\t\t\t\t\t'length' => 254,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'collection', 'string',\n\t\t\t\t[\n\t\t\t\t\t'default' => '',\n\t\t\t\t\t'notnull' => false,\n\t\t\t\t\t'length' => 31\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'source', 'string',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => false,\n\t\t\t\t\t'length' => 64,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'owner_id', 'string',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => true,\n\t\t\t\t\t'length' => 64,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'status', 'smallint',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => true,\n\t\t\t\t\t'length' => 1,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'options', 'string',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => false,\n\t\t\t\t\t'length' => 511,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'err', 'smallint',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => true,\n\t\t\t\t\t'length' => 1,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'message', 'text',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => false,\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->addColumn(\n\t\t\t\t'indexed', 'bigint',\n\t\t\t\t[\n\t\t\t\t\t'notnull' => false,\n\t\t\t\t\t'length' => 6,\n\t\t\t\t\t'unsigned' => true\n\t\t\t\t]\n\t\t\t);\n\t\t\t$table->setPrimaryKey(['provider_id', 'document_id', 'collection']);\n\t\t\t$table->addIndex(['collection']);\n\t\t\t$table->addIndex(['collection', 'provider_id', 'document_id', 'status'], 'cpds');\n\t\t}\n\n\t\treturn $schema;\n\t}\n}\n"
  },
  {
    "path": "lib/Migration/Version2401Date202301170001.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse OCP\\DB\\Exception;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\IDBConnection;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\nclass Version2401Date202301170001 extends SimpleMigrationStep {\n\n\n\t/** @var IDBConnection */\n\tprivate $dbConnection;\n\n\n\t/**\n\t * @param IDBConnection $dbConnection\n\t */\n\tpublic function __construct(IDBConnection $dbConnection) {\n\t\t$this->dbConnection = $dbConnection;\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @throws Exception\n\t */\n\tpublic function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\t\tif (!$schema->hasTable('fulltextsearch_index')) {\n\t\t\treturn;\n\t\t}\n\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\t$expr = $qb->expr();\n\t\t$orX = $expr->orX();\n\t\t$orX->add($expr->eq('collection', $qb->createNamedParameter('')));\n\t\t$orX->add($expr->isNull('collection'));\n\n\t\t$qb->update('fulltextsearch_index')\n\t\t   ->set('collection', $qb->createNamedParameter('local'))\n\t\t   ->where($orX);\n\t\t$qb->executeStatement();\n\t}\n\n}\n"
  },
  {
    "path": "lib/Migration/Version2801Date202309200001.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse OCP\\DB\\Exception;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\IDBConnection;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\nclass Version2801Date202309200001 extends SimpleMigrationStep {\n\n\t/** @var IDBConnection */\n\tprivate $dbConnection;\n\n\t/**\n\t * @param IDBConnection $dbConnection\n\t */\n\tpublic function __construct(IDBConnection $dbConnection) {\n\t\t$this->dbConnection = $dbConnection;\n\t}\n\n\t/**\n\t * @param IOutput $output\n\t * @param Closure $schemaClosure The `\\Closure` returns a `ISchemaWrapper`\n\t * @param array $options\n\t *\n\t * @throws Exception\n\t */\n\tpublic function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\t\tif (!$schema->hasTable('fulltextsearch_index')) {\n\t\t\treturn;\n\t\t}\n\n\t\t$qb = $this->dbConnection->getQueryBuilder();\n\t\ttry {\n\t\t\t$qb->update('fulltextsearch_index')\n\t\t\t\t->set('collection', $qb->createNamedParameter('local'))\n\t\t\t\t->where(\n\t\t\t\t\t$qb->expr()->orX(\n\t\t\t\t\t\t$qb->expr()->eq('collection', $qb->createNamedParameter('')),\n\t\t\t\t\t\t$qb->expr()->isNull('collection')\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t$qb->executeStatement();\n\t\t} catch (\\Exception $e) {\n\n\t\t\t// in case on failure, because of deprecated/duplicate entry,\n\t\t\t// we update row per row and delete those duplicate\n\t\t\t$select = $this->dbConnection->getQueryBuilder();\n\t\t\t$select->select('provider_id', 'document_id', 'collection')\n\t\t\t\t->from('fulltextsearch_index')\n\t\t\t\t->where(\n\t\t\t\t\t$select->expr()->orX(\n\t\t\t\t\t\t$select->expr()->eq('collection', $select->createNamedParameter('')),\n\t\t\t\t\t\t$select->expr()->isNull('collection')\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t$result = $select->executeQuery();\n\t\t\twhile ($data = $result->fetch()) {\n\t\t\t\t$update = $this->dbConnection->getQueryBuilder();\n\n\t\t\t\t$update->update('fulltextsearch_index')\n\t\t\t\t\t->set('collection', $update->createNamedParameter('local'))\n\t\t\t\t\t->where($update->expr()->andX(\n\t\t\t\t\t\t$update->expr()->eq('provider_id', $update->createNamedParameter($data['provider_id'])),\n\t\t\t\t\t\t$update->expr()->eq('document_id', $update->createNamedParameter($data['document_id'])),\n\t\t\t\t\t\t$update->expr()->eq('collection', $update->createNamedParameter($data['collection'])),\n\t\t\t\t\t));\n\n\t\t\t\ttry {\n\t\t\t\t\t$update->executeStatement();\n\t\t\t\t} catch (\\Exception $e) {\n\t\t\t\t\t$delete = $this->dbConnection->getQueryBuilder();\n\t\t\t\t\t$delete->delete('fulltextsearch_index')->where($delete->expr()->andX(\n\t\t\t\t\t\t$delete->expr()->eq('provider_id', $delete->createNamedParameter($data['provider_id'])),\n\t\t\t\t\t\t$delete->expr()->eq('document_id', $delete->createNamedParameter($data['document_id'])),\n\t\t\t\t\t\t$delete->expr()->eq('collection', $delete->createNamedParameter($data['collection'])),\n\t\t\t\t\t));\n\t\t\t\t\t$delete->executeStatement();\n\t\t\t\t}\n\t\t\t}\n\t\t\t$result->closeCursor();\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "lib/Migration/Version31001Date20250210105322.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Migration;\n\nuse Closure;\nuse Doctrine\\DBAL\\Types\\Type;\nuse OCP\\DB\\ISchemaWrapper;\nuse OCP\\DB\\Types;\nuse OCP\\Migration\\Attributes\\AddIndex;\nuse OCP\\Migration\\Attributes\\IndexType;\nuse OCP\\Migration\\IOutput;\nuse OCP\\Migration\\SimpleMigrationStep;\n\n#[AddIndex('fulltextsearch_index', IndexType::INDEX, 'fix live index select')]\nclass Version31001Date20250210105322 extends SimpleMigrationStep {\n\tpublic function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {\n\t\t/** @var ISchemaWrapper $schema */\n\t\t$schema = $schemaClosure();\n\n\t\tif (!$schema->hasTable('fulltextsearch_index')) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$table = $schema->getTable('fulltextsearch_index');\n\t\tif ($table->hasIndex('fts_ces')) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$table->addIndex(['collection', 'err', 'status'], 'fts_ces');\n\n\t\treturn $schema;\n\t}\n}\n"
  },
  {
    "path": "lib/Model/Index.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse JsonSerializable;\nuse OCA\\FullTextSearch\\Service\\CollectionService;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse OCP\\IURLGenerator;\n\n\n/**\n * Class Index\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass Index implements IIndex, JsonSerializable {\n\n\n\tuse TArrayTools;\n\n\n\t/** @var string */\n\tprivate $providerId;\n\n\t/** @var string */\n\tprivate $documentId;\n\n\tprivate string $collection = '';\n\n\t/** @var string */\n\tprivate $source = '';\n\n\t/** @var string */\n\tprivate $ownerId = '';\n\n\t/** @var int */\n\tprivate $status = 0;\n\n\t/** @var array */\n\tprivate $options = [];\n\n\t/** @var int */\n\tprivate $err = 0;\n\n\t/** @var array */\n\tprivate $errors = [];\n\n\t/** @var int */\n\tprivate $lastIndex = 0;\n\n\n\t/**\n\t * Index constructor.\n\t *\n\t * @param string $providerId\n\t * @param string $documentId\n\t * @param string $collection\n\t */\n\tpublic function __construct(string $providerId, string $documentId, string $collection = '') {\n\t\t$this->providerId = $providerId;\n\t\t$this->documentId = $documentId;\n\t\t$this->collection = $collection;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getProviderId(): string {\n\t\treturn $this->providerId;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDocumentId(): string {\n\t\treturn $this->documentId;\n\t}\n\n\n\t/**\n\t * @param string $source\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setSource(string $source): IIndex {\n\t\t$this->source = $source;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getSource(): string {\n\t\treturn $this->source;\n\t}\n\n\n\t/**\n\t * @param string $ownerId\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setOwnerId(string $ownerId): IIndex {\n\t\t$this->ownerId = $ownerId;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getOwnerId(): string {\n\t\treturn $this->ownerId;\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t *\n\t * @return Index\n\t */\n\tpublic function setCollection(string $collection): self {\n\t\t$this->collection = $collection;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getCollection(): string {\n\t\treturn $this->collection;\n\t}\n\n\n\t/**\n\t * @param int $status\n\t * @param bool $reset\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setStatus(int $status, bool $reset = false): IIndex {\n\t\tif ($reset === true) {\n\t\t\t$this->status = $status;\n\t\t} else if (!$this->isStatus($status)) {\n\t\t\t$this->status += $status;\n\t\t}\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getStatus(): int {\n\t\treturn $this->status;\n\t}\n\n\t/**\n\t * @param int $status\n\t *\n\t * @return bool\n\t */\n\tpublic function isStatus(int $status): bool {\n\t\treturn (bool)((int)$status & $this->getStatus());\n\t}\n\n\t/**\n\t * @param int $status\n\t *\n\t * @return IIndex\n\t */\n\tpublic function unsetStatus(int $status): IIndex {\n\t\tif (!$this->isStatus($status)) {\n\t\t\treturn $this;\n\t\t}\n\n\t\t$this->status -= $status;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param string $value\n\t *\n\t * @return IIndex\n\t */\n\tpublic function addOption(string $option, string $value): IIndex {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param int $value\n\t *\n\t * @return IIndex\n\t */\n\tpublic function addOptionInt(string $option, int $value): IIndex {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param array $options\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setOptions(array $options): IIndex {\n\t\t$this->options = $options;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getOptions(): array {\n\t\treturn $this->options;\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param string $default\n\t *\n\t * @return string\n\t */\n\tpublic function getOption(string $option, string $default = ''): string {\n\t\treturn $this->get($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param int $default\n\t *\n\t * @return int\n\t */\n\tpublic function getOptionInt(string $option, int $default = 0): int {\n\t\treturn $this->getInt($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @param int $err\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setErrorCount(int $err): IIndex {\n\t\t$this->err = $err;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getErrorCount(): int {\n\t\treturn $this->err;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getLastError(): array {\n\t\treturn array_values(array_slice($this->errors, -1))[0];\n\t}\n\n\t/**\n\t * @return IIndex\n\t */\n\tpublic function resetErrors(): IIndex {\n\t\t$this->setErrors([]);\n\t\t$this->setErrorCount(0);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getErrors(): array {\n\t\treturn $this->errors;\n\t}\n\n\t/**\n\t * @param array $messages\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setErrors(array $messages): IIndex {\n\t\t$this->errors = $messages;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $message\n\t * @param string $exception\n\t * @param int $sev\n\t *\n\t * @return IIndex\n\t */\n\tpublic function addError(string $message, string $exception = '', int $sev = IIndex::ERROR_SEV_3\n\t): IIndex {\n\t\t$this->errors[] = [\n\t\t\t'message' => substr($message, 0, 1800),\n\t\t\t'exception' => $exception,\n\t\t\t'severity' => $sev\n\t\t];\n\n\t\t$this->err++;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param int $lastIndex\n\t *\n\t * @return IIndex\n\t */\n\tpublic function setLastIndex(int $lastIndex = -1): IIndex {\n\t\tif ($lastIndex === -1) {\n\t\t\t$lastIndex = time();\n\t\t}\n\n\t\t$this->lastIndex = $lastIndex;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getLastIndex(): int {\n\t\treturn $this->lastIndex;\n\t}\n\n\n\t/**\n\t * @param IURLGenerator $urlGenerator\n\t *\n\t * @return array{url: string, status: int}\n\t */\n\tpublic function asSitemap(IURLGenerator $urlGenerator): array {\n\t\treturn  [\n\t\t\t'url' => $urlGenerator->linkToOCSRouteAbsolute('fulltextsearch.Collection.indexDocument',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   'collection' => $this->getCollection(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   'providerId' => $this->getProviderId(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   'documentId' => $this->getDocumentId()\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t   ]\n\t\t\t),\n\t\t\t'status' => $this->getStatus()\n\t\t];\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function jsonSerialize(): array {\n\t\treturn [\n\t\t\t'ownerId' => $this->getOwnerId(),\n\t\t\t'providerId' => $this->getProviderId(),\n\t\t\t'collection' => $this->getCollection(),\n\t\t\t'source' => $this->getSource(),\n\t\t\t'documentId' => $this->getDocumentId(),\n\t\t\t'lastIndex' => $this->getLastIndex(),\n\t\t\t'errors' => $this->getErrors(),\n\t\t\t'errorCount' => $this->getErrorCount(),\n\t\t\t'status' => (int)$this->getStatus(),\n\t\t\t'options' => $this->getOptions()\n\t\t];\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function __destruct() {\n\t\tunset($this->providerId);\n\t\tunset($this->documentId);\n\t\tunset($this->source);\n\t\tunset($this->ownerId);\n\t\tunset($this->status);\n\t\tunset($this->options);\n\t\tunset($this->err);\n\t\tunset($this->errors);\n\t\tunset($this->lastIndex);\n\t}\n\n}\n"
  },
  {
    "path": "lib/Model/IndexOptions.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse JsonSerializable;\nuse OCP\\FullTextSearch\\Model\\IIndexOptions;\n\n\n/**\n * Class IndexOptions\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass IndexOptions implements IIndexOptions, JsonSerializable {\n\n\n\tuse TArrayTools;\n\n\n\t/**\n\t * @var array\n\t */\n\tprivate $options = [];\n\n\n\t/**\n\t * IndexOptions constructor.\n\t *\n\t * @param array $options\n\t */\n\tpublic function __construct($options = []) {\n\t\t$this->options = $options;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getOptions(): array {\n\t\treturn $this->options;\n\t}\n\n\t/**\n\t * @param array $options\n\t *\n\t * @return IIndexOptions\n\t */\n\tpublic function setOptions(array $options): IIndexOptions {\n\t\t$this->options = $options;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param string $value\n\t *\n\t * @return IIndexOptions\n\t */\n\tpublic function addOption(string $option, string $value): IIndexOptions {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param array $value\n\t *\n\t * @return IIndexOptions\n\t */\n\tpublic function addOptionArray(string $option, array $value): IIndexOptions {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param bool $value\n\t *\n\t * @return IIndexOptions\n\t */\n\tpublic function addOptionBool(string $option, bool $value): IIndexOptions {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param string $default\n\t *\n\t * @return string\n\t */\n\tpublic function getOption(string $option, string $default = ''): string {\n\t\treturn $this->get($option, $this->options, $default);\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param array $default\n\t *\n\t * @return array\n\t */\n\tpublic function getOptionArray(string $option, array $default = []): array {\n\t\treturn $this->getArray($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param bool $default\n\t *\n\t * @return bool\n\t */\n\tpublic function getOptionBool(string $option, bool $default): bool {\n\t\treturn $this->getBool($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function jsonSerialize(): array {\n\t\treturn $this->options;\n\t}\n}\n\n"
  },
  {
    "path": "lib/Model/PlatformWrapper.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\n\n\n/**\n * Class PlatformWrapper\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass PlatformWrapper {\n\n\n\t/** @var string */\n\tprivate $appId;\n\n\t/** @var string */\n\tprivate $class;\n\n\t/** @var IFullTextSearchPlatform */\n\tprivate $platform;\n\n\t/** @var string */\n\tprivate $version;\n\n\n\t/**\n\t * Provider constructor.\n\t *\n\t * @param string $appId\n\t * @param string $class\n\t */\n\tpublic function __construct(string $appId, string $class) {\n\t\t$this->appId = $appId;\n\t\t$this->class = $class;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getAppId(): string {\n\t\treturn $this->appId;\n\t}\n\n\t/**\n\t * @param string $appId\n\t *\n\t * @return PlatformWrapper\n\t */\n\tpublic function setAppId(string $appId): PlatformWrapper {\n\t\t$this->appId = $appId;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getClass(): string {\n\t\treturn $this->class;\n\t}\n\n\t/**\n\t * @param string $class\n\t *\n\t * @return PlatformWrapper\n\t */\n\tpublic function setClass(string $class): PlatformWrapper {\n\t\t$this->class = $class;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return IFullTextSearchPlatform\n\t */\n\tpublic function getPlatform(): IFullTextSearchPlatform {\n\t\treturn $this->platform;\n\t}\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t *\n\t * @return PlatformWrapper\n\t */\n\tpublic function setPlatform(IFullTextSearchPlatform $platform): PlatformWrapper {\n\t\t$this->platform = $platform;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getVersion(): string {\n\t\treturn $this->version;\n\t}\n\n\t/**\n\t * @param string $version\n\t *\n\t * @return PlatformWrapper\n\t */\n\tpublic function setVersion(string $version): PlatformWrapper {\n\t\t$this->version = $version;\n\n\t\treturn $this;\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Model/ProviderIndexes.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Exceptions\\IndexDoesNotExistException;\nuse OCP\\FullTextSearch\\Model\\IIndex;\n\n\n/**\n * Class ProviderIndexes\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass ProviderIndexes {\n\n\n\t/** @var IIndex[] */\n\tprivate $indexes;\n\n\n\tpublic function __construct(array $indexes) {\n\t\t$this->indexes = $indexes;\n\t}\n\n\n\t/**\n\t * @return IIndex[]\n\t */\n\tpublic function getIndexes(): array {\n\t\treturn $this->indexes;\n\t}\n\n\n\t/**\n\t * @param string $documentId\n\t *\n\t * @return IIndex\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function getIndex(string $documentId): IIndex {\n\t\tif (!array_key_exists($documentId, $this->indexes)) {\n\t\t\tthrow new IndexDoesNotExistException();\n\t\t}\n\n\t\treturn $this->indexes[$documentId];\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Model/ProviderWrapper.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\n\n\n/**\n * Class ProviderWrapper\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass ProviderWrapper {\n\n\n\t/** @var string */\n\tprivate $appId;\n\n\t/** @var IFullTextSearchProvider */\n\tprivate $provider;\n\n\t/** @var string */\n\tprivate $version;\n\n\n\t/**\n\t * Provider constructor.\n\t *\n\t * @param string $appId\n\t * @param IFullTextSearchProvider $provider\n\t */\n\tpublic function __construct(string $appId, IFullTextSearchProvider $provider) {\n\t\t$this->appId = $appId;\n\t\t$this->provider = $provider;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getAppId(): string {\n\t\treturn $this->appId;\n\t}\n\n\t/**\n\t * @param string $appId\n\t *\n\t * @return ProviderWrapper\n\t */\n\tpublic function setAppId(string $appId): ProviderWrapper {\n\t\t$this->appId = $appId;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return IFullTextSearchProvider\n\t */\n\tpublic function getProvider(): IFullTextSearchProvider {\n\t\treturn $this->provider;\n\t}\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @return ProviderWrapper\n\t */\n\tpublic function setProvider(IFullTextSearchProvider $provider): ProviderWrapper {\n\t\t$this->provider = $provider;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getVersion(): string {\n\t\treturn $this->version;\n\t}\n\n\t/**\n\t * @param string $version\n\t *\n\t * @return ProviderWrapper\n\t */\n\tpublic function setVersion(string $version): ProviderWrapper {\n\t\t$this->version = $version;\n\n\t\treturn $this;\n\t}\n\n\n}\n\n"
  },
  {
    "path": "lib/Model/Runner.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse Exception;\nuse OCA\\FullTextSearch\\ACommandBase;\nuse OCA\\FullTextSearch\\Exceptions\\RunnerAlreadyUpException;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\TickIsNotAliveException;\nuse OCA\\FullTextSearch\\Service\\RunningService;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse OCP\\FullTextSearch\\Model\\IRunner;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n\n/**\n * Class Runner\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass Runner implements IRunner {\n\n\n\tuse TArrayTools;\n\n\n\tconst TICK_MINIMUM = 2;\n\tconst TICK_UPDATE = 10;\n\tconst MEMORY_UPDATE = 5;\n\n\t/** @var RunningService */\n\tprivate $runningService;\n\n\t/** @var string */\n\tprivate $source;\n\n\t/** @var int */\n\tprivate $tickId = 0;\n\n\t/** @var ACommandBase */\n\tprivate $base = null;\n\n\t/** @var OutputInterface */\n\tprivate $outputInterface = null;\n\n\t/** @var array */\n\tprivate $info = [];\n\n\t/** @var int */\n\tprivate $oldTick = 0;\n\n\t/** @var string */\n\tprivate $oldAction = '';\n\n\t/** @var int */\n\tprivate $ramUpdate = 0;\n\n\t/** @var int */\n\tprivate $tickUpdate = 0;\n\n\t/** @var array */\n\tprivate $methodOnKeyPress = [];\n\n\t/** @var array */\n\tprivate $methodOnInfoUpdate = [];\n\n\t/** @var array */\n\tprivate $methodOnIndexError = [];\n\n\t/** @var array */\n\tprivate $methodOnIndexResult = [];\n\n\t/** @var bool */\n\tprivate $paused = false;\n\n\t/** @var bool */\n\tprivate $pauseRunning = false;\n\n\t/** @var array */\n\tprivate $keys = ['nextStep' => 'n'];\n\n\n\t/**\n\t * Runner constructor.\n\t *\n\t * @param RunningService $runningService\n\t * @param string $source\n\t * @param array $keys\n\t */\n\tpublic function __construct(RunningService $runningService, string $source, array $keys = []) {\n\t\t$this->runningService = $runningService;\n\t\t$this->source = $source;\n\n\t\tif (sizeof($keys) > 0) {\n\t\t\t$this->keys = $keys;\n\t\t}\n\t}\n\n\n\t/**\n\t * @throws RunnerAlreadyUpException\n\t */\n\tpublic function start() {\n\t\t$this->tickId = $this->runningService->start($this->source);\n\t}\n\n\n\t/**\n\t * @param string $action\n\t * @param bool $force\n\t *\n\t * @return string\n\t * @throws Exception\n\t */\n\tpublic function updateAction(string $action = '', bool $force = false): string {\n\n\t\tif ($this->base !== null) {\n\t\t\t$this->base->abort();\n\t\t}\n\n\t\t$n = '';\n\t\tif (sizeof($this->methodOnKeyPress) > 0) {\n\t\t\t$n = fread(STDIN, 9999);\n\t\t\tif ($n !== '') {\n\t\t\t\t$n = substr($n, 0, 1);\n\t\t\t\t$this->keyPressed($n);\n\t\t\t}\n\t\t}\n\n\t\tif ($action === '') {\n\t\t\treturn $n;\n\t\t}\n\n\t\t$tick = time();\n\t\tif ($this->oldAction !== $action || $force) {\n\t\t\twhile (true) {\n\t\t\t\tif (!$this->isPaused()) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t$this->pauseRunning(true);\n\t\t\t\t$pressed = strtolower($this->updateAction(''));\n\t\t\t\tif ($pressed === $this->keys['nextStep']) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tusleep(300000);\n\t\t\t\tif ($this->base !== null) {\n\t\t\t\t\t$this->base->abort();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$this->pauseRunning(false);\n\t\t}\n\n\t\tif ($this->oldAction === $action && ($this->oldTick + self::TICK_MINIMUM > $tick)) {\n\t\t\treturn '';\n\t\t}\n\n\t\t$this->setInfo('action', $action);\n\n\t\t$this->updateTick($tick, $action);\n\t\t$this->updateRamInfo($tick);\n\n\t\t$this->oldAction = $action;\n\t\t$this->oldTick = $tick;\n\n\t\treturn '';\n\t}\n\n\n\t/**\n\t * @param string $info\n\t * @param string $value\n\t * @param int $type\n\t */\n\tpublic function setInfo(string $info, string $value, int $type = 0) {\n\t\t$this->info[$info] = $value;\n\t\t$this->setInfoColored($info, $type);\n\t\t$this->infoUpdated();\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param int $value\n\t */\n\tpublic function setInfoInt(string $info, int $value): void {\n\t\t$this->info[$info] = $value;\n\t\t$this->infoUpdated();\n\t}\n\n\t/**\n\t * @param array $data\n\t */\n\tpublic function setInfoArray(array $data) {\n\t\t$keys = array_keys($data);\n\t\tforeach ($keys as $k) {\n\t\t\t$this->info[$k] = $data[$k];\n\t\t}\n\n\t\t$this->infoUpdated();\n\t}\n\n\n\t/**\n\t * @param string $info\n\t * @param int $level\n\t */\n\tpublic function setInfoColored(string $info, int $level) {\n\n\t\t$value = $this->getInfo($info);\n\t\tif ($value === '') {\n\t\t\treturn;\n\t\t}\n\n\t\t$color = '';\n\t\tswitch ($level) {\n\t\t\tcase IRunner::RESULT_TYPE_SUCCESS:\n\t\t\t\t$color = 'info';\n\t\t\t\tbreak;\n\n\t\t\tcase IRunner::RESULT_TYPE_WARNING:\n\t\t\t\t$color = 'comment';\n\t\t\t\tbreak;\n\n\t\t\tcase IRunner::RESULT_TYPE_FAIL:\n\t\t\t\t$color = 'error';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif ($color !== '') {\n\t\t\t$this->info[$info . 'Colored'] = '<' . $color . '>' . $value . '</' . $color . '>';\n\t\t}\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getInfoAll(): array {\n\t\treturn $this->info;\n\t}\n\n\n\t/**\n\t * @param string $info\n\t *\n\t * @return string\n\t */\n\tpublic function getInfo(string $info): string {\n\t\treturn $this->get($info, $this->info, '');\n\t}\n\n\t/**\n\t * @param string $info\n\t *\n\t * @return int\n\t */\n\tpublic function getInfoInt(string $info): int {\n\t\treturn $this->getInt($info, $this->info);\n\t}\n\n\t/**\n\t * @param array $method\n\t */\n\tpublic function onKeyPress(array $method) {\n\t\t$this->methodOnKeyPress[] = $method;\n\t}\n\n\t/**\n\t * @param string $key\n\t */\n\tpublic function keyPressed(string $key) {\n\t\tforeach ($this->methodOnKeyPress as $method) {\n\t\t\tcall_user_func($method, $key);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $method\n\t */\n\tpublic function onInfoUpdate(array $method) {\n\t\t$this->methodOnInfoUpdate[] = $method;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function infoUpdated() {\n\t\tforeach ($this->methodOnInfoUpdate as $method) {\n\t\t\tcall_user_func($method, $this->info);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $method\n\t */\n\tpublic function onNewIndexError(array $method) {\n\t\t$this->methodOnIndexError[] = $method;\n\t}\n\n\t/**\n\t * @param IIndex $index\n\t * @param string $message\n\t * @param string $class\n\t * @param int $sev\n\t */\n\tpublic function newIndexError(IIndex $index, string $message, string $class = '', int $sev = 3\n\t) {\n\t\t$error = [\n\t\t\t'index' => $index,\n\t\t\t'message' => $message,\n\t\t\t'exception' => $class,\n\t\t\t'severity' => $sev\n\t\t];\n\n\t\tforeach ($this->methodOnIndexError as $method) {\n\t\t\tcall_user_func($method, $error);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $method\n\t */\n\tpublic function onNewIndexResult(array $method) {\n\t\t$this->methodOnIndexResult[] = $method;\n\t}\n\n\n\t/**\n\t * @param IIndex $index\n\t * @param string $message\n\t * @param string $status\n\t * @param int $type\n\t */\n\tpublic function newIndexResult(IIndex $index, string $message, string $status, int $type) {\n\t\t$result = [\n\t\t\t'index' => $index,\n\t\t\t'message' => $message,\n\t\t\t'status' => $status,\n\t\t\t'type' => $type\n\t\t];\n\n\t\tforeach ($this->methodOnIndexResult as $method) {\n\t\t\tcall_user_func($method, $result);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param int $tick\n\t * @param string $action\n\t *\n\t * @throws TickDoesNotExistException\n\t */\n\tprivate function updateTick(int $tick, string $action) {\n\t\tif ($this->oldAction === $action && ($this->tickUpdate + self::TICK_UPDATE > $tick)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$this->runningService->update($this->tickId, $action);\n\t\t} catch (TickIsNotAliveException $e) {\n\t\t\t$this->output('Force Quit');\n\t\t\texit();\n\t\t}\n\n\t\t$this->tickUpdate = $tick;\n\t}\n\n\n\t/**\n\t * @param int $tick\n\t */\n\tprivate function updateRamInfo(int $tick) {\n\t\tif (($this->ramUpdate + self::MEMORY_UPDATE) > $tick) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->setInfo('_memory', round((memory_get_usage() / 1024 / 1024)) . ' MB');\n\t\t$this->ramUpdate = $tick;\n\t}\n\n\n\t/**\n\t * // TODO: finalize this exception handling about error logs.\n\t *\n\t * @param string $reason\n\t * @param bool $stop\n\t */\n\tpublic function exception(string $reason, bool $stop) {\n\t\tif (!$stop) {\n\t\t\t$this->output('Exception: ' . $reason);\n\t\t\t// TODO: feed an array of exceptions for log;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t$this->runningService->stop($this->tickId, $reason);\n\t\t\t} catch (TickDoesNotExistException $e) {\n\t\t\t\t/** exception will be managed somewhere else */\n\t\t\t\t// TODO: Check if above statement is correct.\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function stop() {\n\t\t$this->runningService->stop($this->tickId);\n\t}\n\n\n\t/**\n\t * @param ACommandBase $base\n\t * @param OutputInterface $output\n\t */\n\tpublic function sourceIsCommandLine(ACommandBase $base, OutputInterface $output) {\n\t\t$this->base = $base;\n\t\t$this->outputInterface = $output;\n\t}\n\n\n\t/**\n\t * @param bool $pause\n\t */\n\tpublic function pause(bool $pause) {\n\t\t$this->paused = $pause;\n\t\t$this->infoUpdated();\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isPaused(): bool {\n\t\treturn $this->paused;\n\t}\n\n\n\t/**\n\t * @param bool $running\n\t */\n\tpublic function pauseRunning(bool $running) {\n\t\t$this->pauseRunning = $running;\n\t\t$this->infoUpdated();\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isPauseRunning(): bool {\n\t\treturn $this->pauseRunning;\n\t}\n\n\n\t/**\n\t * @param string $line\n\t */\n\tpublic function output(string $line) {\n\t\tif ($this->outputInterface === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->outputInterface->writeln($line);\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Model/SearchRequest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse JsonSerializable;\nuse OCP\\FullTextSearch\\Model\\ISearchRequest;\nuse OCP\\FullTextSearch\\Model\\ISearchRequestSimpleQuery;\n\n\n/**\n * Class SearchRequest\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass SearchRequest implements ISearchRequest, JsonSerializable {\n\n\n\tuse TArrayTools;\n\n\n\t/** @var array */\n\tprivate $providers = [];\n\n\t/** @var string */\n\tprivate $search = '';\n\n\t/** @var bool */\n\tprivate $emptySearch = false;\n\n\t/** @var int */\n\tprivate $page = 1;\n\n\t/** @var int */\n\tprivate $size = 10;\n\n\t/** @var string */\n\tprivate $author = '';\n\n\t/** @var array */\n\tprivate $tags = [];\n\n\t/** @var array */\n\tpublic $metaTags = [];\n\n\t/** @var array */\n\tpublic $subTags = [];\n\n\t/** @var array */\n\tprivate $options = [];\n\n\t/** @var array */\n\tprivate $parts = [];\n\n\t/** @var array */\n\tprivate $fields = [];\n\n\t/** @var array */\n\tprivate $limitFields = [];\n\n\t/** @var array */\n\tprivate $wildcardFields = [];\n\n//\t/** @var array */\n//\tprivate $wildcardQueries = [];\n\n\t/** @var array */\n\tprivate $wildcardFilters = [];\n\n\t/** @var array */\n\tprivate $regexFilters = [];\n\n\t/** @var array */\n\tprivate $simpleQueries = [];\n\n\n\t/**\n\t * SearchRequest constructor.\n\t */\n\tpublic function __construct() {\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getProviders(): array {\n\t\treturn $this->providers;\n\t}\n\n\t/**\n\t * @param array $providers\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setProviders(array $providers): ISearchRequest {\n\t\t$this->providers = $providers;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getAuthor(): string {\n\t\treturn $this->author;\n\t}\n\n\t/**\n\t * @param string $author\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setAuthor(string $author): ISearchRequest {\n\t\t$this->author = $author;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getSearch(): string {\n\t\treturn $this->search;\n\t}\n\n\t/**\n\t * @param string $search\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setSearch(string $search): ISearchRequest {\n\t\t$this->search = $search;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $search\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addSearch(string $search): ISearchRequest {\n\t\t$this->search .= ' ' . $search;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isEmptySearch(): bool {\n\t\treturn $this->emptySearch;\n\t}\n\n\t/**\n\t * @param bool $emptySearch\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setEmptySearch(bool $emptySearch): ISearchRequest {\n\t\t$this->emptySearch = $emptySearch;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function cleanSearch(): ISearchRequest {\n\t\t$search = trim(str_replace('  ', ' ', $this->getSearch()));\n\n\t\tpreg_match_all('/[^?]\"(?:\\\\\\\\.|[^\\\\\\\\\"])*\"|\\S+/', \" $search \", $words);\n\t\t$searchItems = [];\n\t\tforeach ($words[0] as $word) {\n\t\t\tif ($this->searchQueryOptions($word)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$searchItems[] = $word;\n\t\t}\n\n\t\t$this->setSearch(implode(\" \", $searchItems));\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $word\n\t *\n\t * @return bool\n\t */\n\tprivate function searchQueryOptions(string $word): bool {\n\t\tif (($pos = strpos($word, ':')) === false || $pos === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlist($kw, $value) = explode(':', $word, 2);\n\n\t\t$options = ['is', 'show'];\n\t\tif (in_array($kw, $options)) {\n\t\t\t$this->addOption($kw . '_' . $value, '1');\n\n\t\t\treturn true;\n\t\t}\n\n\t\t$valuedOptions = ['in', 'meta'];\n\t\tif (in_array($kw, $valuedOptions)) {\n\t\t\t$this->addMultipleOption($kw, $value);\n\n\t\t\treturn true;\n\t\t}\n\n\t\t$valuedSubOptions = ['and'];\n\t\tif (in_array($kw, $valuedSubOptions)) {\n\t\t\tlist($key, $value) = explode(':', $value, 2);\n\t\t\t$this->addMultipleOption($kw . ':' . $key, $value);\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getPage(): int {\n\t\treturn $this->page;\n\t}\n\n\t/**\n\t * @param int $page\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setPage(int $page): ISearchRequest {\n\t\tif ($page < 1) {\n\t\t\t$page = 1;\n\t\t}\n\n\t\t$this->page = $page;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getSize(): int {\n\t\treturn $this->size;\n\t}\n\n\t/**\n\t * @param int $size\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setSize(int $size): ISearchRequest {\n\t\t$this->size = $size;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getOptions(): array {\n\t\treturn $this->options;\n\t}\n\n\t/**\n\t * @param array $options\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setOptions(array $options): ISearchRequest {\n\t\t$this->options = $options;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param $option\n\t * @param $value\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addOption(string $option, string $value): ISearchRequest {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param array $value\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addOptionArray(string $option, array $value): ISearchRequest {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param bool $value\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addOptionBool(string $option, bool $value): ISearchRequest {\n\t\t$this->options[$option] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param string $value\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addMultipleOption(string $option, string $value): ISearchRequest {\n\t\tif (!array_key_exists($option, $this->options)) {\n\t\t\t$this->options[$option] = [];\n\t\t}\n\n\t\t$this->options[$option][] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $option\n\t * @param string $default\n\t *\n\t * @return string\n\t */\n\tpublic function getOption(string $option, string $default = ''): string {\n\t\treturn $this->get($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @param string $option\n\t * @param array $default\n\t *\n\t * @return array\n\t */\n\tpublic function getOptionArray(string $option, array $default = []): array {\n\t\treturn $this->getArray($option, $this->options, $default);\n\t}\n\n\n\t/**\n\t * @param string $part\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addPart(string $part): ISearchRequest {\n\t\t$this->parts[] = $part;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getParts(): array {\n\t\treturn $this->parts;\n\t}\n\n\n\t/**\n\t * @param array $parts\n\t *\n\t * @return ISearchRequest\n\t * @since 15.0.0\n\t *\n\t */\n\tpublic function setParts(array $parts): ISearchRequest {\n\t\t$this->parts = $parts;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getFields(): array {\n\t\treturn $this->fields;\n\t}\n\n\t/**\n\t * @param array $fields\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setFields(array $fields): ISearchRequest {\n\t\t$this->fields = $fields;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $field\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addLimitField(string $field): ISearchRequest {\n\t\tarray_push($this->limitFields, $field);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getLimitFields(): array {\n\t\treturn $this->limitFields;\n\t}\n\n\n\t/**\n\t * @param string $field\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addField(string $field): ISearchRequest {\n\t\t$this->fields[] = $field;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $tag\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addTag(string $tag): ISearchRequest {\n\t\t$this->tags[] = $tag;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getTags(): array {\n\t\treturn $this->tags;\n\t}\n\n\t/**\n\t * @param array $tags\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setTags(array $tags): ISearchRequest {\n\t\t$this->tags = $tags;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param array $tags\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setMetaTags(array $tags): ISearchRequest {\n\t\t$this->metaTags = $tags;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getMetaTags(): array {\n\t\treturn $this->metaTags;\n\t}\n\n\t/**\n\t * @param string $tag\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addMetaTag(string $tag): ISearchRequest {\n\t\t$this->metaTags[] = $tag;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param array $tags\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function setSubTags(array $tags): ISearchRequest {\n\t\t$this->subTags = $tags;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param bool $formatted\n\t *\n\t * @return array\n\t */\n\tpublic function getSubTags(bool $formatted = false): array {\n\t\tif ($formatted === false) {\n\t\t\treturn $this->subTags;\n\t\t}\n\n\t\t$subTags = [];\n\t\t$ak = array_keys($this->subTags);\n\t\tforeach ($ak as $source) {\n\t\t\t$tags = $this->subTags[$source];\n\t\t\tforeach ($tags as $tag) {\n\t\t\t\t$subTags[] = $source . '_' . $tag;\n\t\t\t}\n\t\t}\n\n\t\treturn $subTags;\n\t}\n\n\t/**\n\t * @param string $source\n\t * @param string $tag\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addSubTag(string $source, string $tag): ISearchRequest {\n\t\tif (!array_key_exists($source, $this->subTags)) {\n\t\t\t$this->subTags[$source] = [];\n\t\t}\n\n\t\t$this->subTags[$source][] = $tag;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $field\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addWildcardField(string $field): ISearchRequest {\n\t\t$this->wildcardFields[] = $field;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getWildcardFields(): array {\n\t\treturn $this->wildcardFields;\n\t}\n\n//\n//\t/**\n//\t * @param array $query\n//\t *\n//\t * @return ISearchRequest\n//\t */\n//\tpublic function addWildcardQuery($query) {\n//\t\t$this->addWildcardQueries([$query]);\n//\n//\t\treturn $this;\n//\t}\n//\n//\t/**\n//\t * @param array $query\n//\t *\n//\t * @return ISearchRequest\n//\t */\n//\tpublic function addWildcardQueries($query) {\n//\t\tarray_push($this->wildcardQueries, $query);\n//\n//\t\treturn $this;\n//\t}\n//\n//\t/**\n//\t * @return array\n//\t */\n//\tpublic function getWildcardQueries() {\n//\t\treturn $this->wildcardQueries;\n//\t}\n\n\n\t/**\n\t * @param array $filter\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addWildcardFilter(array $filter): ISearchRequest {\n\t\t$this->addWildcardFilters([$filter]);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param array $filters\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addWildcardFilters(array $filters): ISearchRequest {\n\t\tarray_push($this->wildcardFilters, $filters);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getWildcardFilters(): array {\n\t\treturn $this->wildcardFilters;\n\t}\n\n\n\t/**\n\t * @param string $filter\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addRegexFilter(string $filter): ISearchRequest {\n\t\t$this->addRegexFilters([$filter]);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param array $filters\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addRegexFilters(array $filters): ISearchRequest {\n\t\tarray_push($this->regexFilters, $filters);\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getRegexFilters(): array {\n\t\treturn $this->regexFilters;\n\t}\n\n\n\t/**\n\t * @param ISearchRequestSimpleQuery $query\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function addSimpleQuery(ISearchRequestSimpleQuery $query): ISearchRequest {\n\t\t$this->simpleQueries[] = $query;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return ISearchRequestSimpleQuery[]\n\t */\n\tpublic function getSimpleQueries(): array {\n\t\treturn $this->simpleQueries;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function jsonSerialize(): array {\n\t\treturn [\n\t\t\t'providers' => $this->getProviders(),\n\t\t\t'author' => $this->getAuthor(),\n\t\t\t'search' => $this->getSearch(),\n\t\t\t'empty_search' => $this->isEmptySearch(),\n\t\t\t'page' => $this->getPage(),\n\t\t\t'size' => $this->getSize(),\n\t\t\t'parts' => $this->getParts(),\n\t\t\t'queries' => $this->getSimpleQueries(),\n\t\t\t'options' => $this->getOptions(),\n\t\t\t'metatags' => $this->getMetaTags(),\n\t\t\t'subtags' => $this->getSubTags(),\n\t\t\t'tags' => $this->getTags()\n\t\t];\n\t}\n\n\n\t/**\n\t * @param array $arr\n\t *\n\t * @return SearchRequest\n\t */\n\tpublic function importFromArray($arr): SearchRequest {\n\t\t$providers = $arr['providers'];\n\t\tif (!is_array($providers)) {\n\t\t\t$providers = [$providers];\n\t\t}\n\n\t\t$this->setProviders($providers);\n\t\t$this->setAuthor($this->get('author', $arr, ''));\n\t\t$this->setSearch($this->get('search', $arr, ''));\n\n\t\t// TODO: remove this in nc19:\n\t\tif ($this->get('empty_search', $arr, '') === 'true') {\n\t\t\t$this->setEmptySearch(true);\n\t\t} else {\n\t\t\t$this->setEmptySearch($this->getBool('empty_search', $arr, false));\n\t\t}\n\t\t// END TODO\n\n//\t\t$this->setEmptySearch($this->getBool('empty_search', $arr, false));\n\t\t$this->setPage($this->getInt('page', $arr, 0));\n\t\t$this->setParts($this->getArray('parts', $arr, []));\n\t\t$this->setSize($this->getInt('size', $arr, 10));\n\t\t$this->setOptions($this->getArray('options', $arr, []));\n\t\t$this->setMetaTags($this->getArray('metatags', $arr, []));\n\t\t$this->setSubTags($this->getArray('subtags', $arr, []));\n\t\t$this->setTags($this->getArray('tags', $arr, []));\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $json\n\t *\n\t * @return SearchRequest\n\t */\n\tpublic static function fromJSON(string $json): SearchRequest {\n\t\t$searchRequest = new SearchRequest();\n\t\t$searchRequest->importFromArray(json_decode($json, true));\n\n\t\treturn $searchRequest;\n\t}\n\n}\n"
  },
  {
    "path": "lib/Model/SearchResult.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse JsonSerializable;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\FullTextSearch\\Model\\ISearchRequest;\nuse OCP\\FullTextSearch\\Model\\ISearchResult;\n\n\n/**\n * Class SearchResult\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass SearchResult implements ISearchResult, JsonSerializable {\n\n\t/** @var IIndexDocument[] */\n\tprivate $documents = [];\n\n\t/** @var string */\n\tprivate $rawResult;\n\n\t/** @var IFullTextSearchProvider */\n\tprivate $provider;\n\n\t/** @var IFullTextSearchPlatform */\n\tprivate $platform;\n\n\t/** @var int */\n\tprivate $total = 0;\n\n\t/** @var int */\n\tprivate $maxScore = 0;\n\n\t/** @var int */\n\tprivate $time = 0;\n\n\t/** @var boolean */\n\tprivate $timedOut = false;\n\n\t/** @var ISearchRequest */\n\tprivate $request;\n\n\n\t/**\n\t * SearchResult constructor.\n\t *\n\t * @param SearchRequest $searchRequest\n\t */\n\tpublic function __construct(SearchRequest $searchRequest) {\n\t\t$this->request = $searchRequest;\n\t}\n\n\n\t/**\n\t * @param IIndexDocument[] $documents\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setDocuments(array $documents): ISearchResult {\n\t\t$this->documents = $documents;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return IIndexDocument[]\n\t */\n\tpublic function getDocuments(): array {\n\t\treturn $this->documents;\n\t}\n\n\t/**\n\t * @param IIndexDocument $document\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function addDocument(IIndexDocument $document): ISearchResult {\n\t\t$this->documents[] = $document;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getCount(): int {\n\t\treturn count($this->documents);\n\t}\n\n\n\t/**\n\t * @param string $result\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setRawResult(string $result): ISearchResult {\n\t\t$this->rawResult = $result;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getRawResult(): string {\n\t\treturn $this->rawResult;\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setProvider(IFullTextSearchProvider $provider): ISearchResult {\n\t\t$this->provider = $provider;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return IFullTextSearchProvider\n\t */\n\tpublic function getProvider(): IFullTextSearchProvider {\n\t\treturn $this->provider;\n\t}\n\n\n\t/**\n\t * @return IFullTextSearchPlatform\n\t */\n\tpublic function getPlatform(): IFullTextSearchPlatform {\n\t\treturn $this->platform;\n\t}\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setPlatform(IFullTextSearchPlatform $platform): ISearchResult {\n\t\t$this->platform = $platform;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getTotal(): int {\n\t\treturn $this->total;\n\t}\n\n\t/**\n\t * @param int $total\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setTotal(int $total): ISearchResult {\n\t\t$this->total = $total;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getMaxScore() {\n\t\treturn $this->maxScore;\n\t}\n\n\t/**\n\t * @param int $maxScore\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setMaxScore(int $maxScore): ISearchResult {\n\t\t$this->maxScore = $maxScore;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getTime(): int {\n\t\treturn $this->time;\n\t}\n\n\t/**\n\t * @param int $time\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setTime(int $time): ISearchResult {\n\t\t$this->time = $time;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isTimedOut(): bool {\n\t\treturn $this->timedOut;\n\t}\n\n\t/**\n\t * @param bool $timedOut\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setTimedOut(bool $timedOut): ISearchResult {\n\t\t$this->timedOut = $timedOut;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return ISearchRequest\n\t */\n\tpublic function getRequest(): ISearchRequest {\n\t\treturn $this->request;\n\t}\n\n\t/**\n\t * @param ISearchRequest $request\n\t *\n\t * @return ISearchResult\n\t */\n\tpublic function setRequest(ISearchRequest $request): ISearchResult {\n\t\t$this->request = $request;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $category\n\t * @param string $value\n\t * @param int $count\n\t *\n\t * @return ISearchResult\n\t * @since 15.0.0\n\t *\n\t */\n\tpublic function addAggregation(string $category, string $value, int $count): ISearchResult {\n\t\t// TODO: Implement addAggregation() method.\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $category\n\t *\n\t * @return array\n\t * @since 15.0.0\n\t *\n\t */\n\tpublic function getAggregations(string $category): array {\n\t\t// TODO: Implement getAggregations() method.\n\n\t\treturn [];\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function jsonSerialize(): array {\n\n\t\t$providerObj = $this->getProvider();\n\t\t$provider = [];\n\t\tif ($providerObj !== null) {\n\t\t\t$provider = [\n\t\t\t\t'id'   => $providerObj->getId(),\n\t\t\t\t'name' => $providerObj->getName()\n\t\t\t];\n\t\t}\n\n\t\t$platformObj = $this->getPlatform();\n\t\t$platform = [];\n\t\tif ($platformObj !== null) {\n\t\t\t$platform = [\n\t\t\t\t'id'   => $platformObj->getId(),\n\t\t\t\t'name' => $platformObj->getName()\n\t\t\t];\n\t\t}\n\n\t\treturn [\n\t\t\t'provider'  => $provider,\n\t\t\t'platform'  => $platform,\n\t\t\t'documents' => $this->getDocuments(),\n\t\t\t'info'      => $this->getInfosAll(),\n\t\t\t'meta'      =>\n\t\t\t\t[\n\t\t\t\t\t'timedOut' => $this->isTimedOut(),\n\t\t\t\t\t'time'     => $this->getTime(),\n\t\t\t\t\t'count'    => $this->getCount(),\n\t\t\t\t\t'total'    => $this->getTotal(),\n\t\t\t\t\t'maxScore' => $this->getMaxScore()\n\t\t\t\t]\n\t\t];\n\t}\n\n\tpublic function addInfo(string $k, string $value): ISearchResult {\n\t\treturn $this;\n\t}\n\n\tpublic function getInfo(string $k): string {\n\t\treturn '';\n\t}\n\n\tpublic function getInfosAll(): array {\n\t\treturn [];\n\t}\n}\n\n"
  },
  {
    "path": "lib/Model/Tick.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Model;\n\n\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\n\n/**\n * Class Tick\n *\n * @package OCA\\FullTextSearch\\Model\n */\nclass Tick {\n\n\n\tuse TArrayTools;\n\n\n\t/** @var int */\n\tprivate $id;\n\n\t/** @var string */\n\tprivate $source;\n\n\t/** @var array */\n\tprotected $data;\n\n\t/** @var int */\n\tprivate $tick;\n\n\t/** @var int */\n\tprivate $firstTick;\n\n\t/** @var string */\n\tprivate $status;\n\n\t/** @var string */\n\tprivate $action = '';\n\n\n\t/**\n\t * Tick constructor.\n\t *\n\t * @param string $source\n\t * @param int $id\n\t */\n\tpublic function __construct(string $source, int $id = 0) {\n\t\t$this->source = $source;\n\t\t$this->id = $id;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getId(): int {\n\t\treturn $this->id;\n\t}\n\n\t/**\n\t * @param int $id\n\t *\n\t * @return $this\n\t */\n\tpublic function setId(int $id): Tick {\n\t\t$this->id = $id;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getSource(): string {\n\t\treturn $this->source;\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getData(): array {\n\t\treturn $this->data;\n\t}\n\n\t/**\n\t * @param array $data\n\t *\n\t * @return $this\n\t */\n\tpublic function setData(array $data): Tick {\n\t\t$this->data = $data;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getTick(): int {\n\t\treturn $this->tick;\n\t}\n\n\t/**\n\t * @param int $tick\n\t *\n\t * @return $this\n\t */\n\tpublic function setTick(int $tick = 0): Tick {\n\t\tif ($tick === 0) {\n\t\t\t$tick = time();\n\t\t}\n\n\t\t$this->tick = $tick;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return int\n\t */\n\tpublic function getFirstTick(): int {\n\t\treturn $this->firstTick;\n\t}\n\n\t/**\n\t * @param int $tick\n\t *\n\t * @return $this\n\t */\n\tpublic function setFirstTick(int $tick = 0): Tick {\n\t\tif ($tick === 0) {\n\t\t\t$tick = time();\n\t\t}\n\n\t\t$this->firstTick = $tick;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getStatus(): string {\n\t\treturn $this->status;\n\t}\n\n\t/**\n\t * @param string $status\n\t *\n\t * @return $this\n\t */\n\tpublic function setStatus(string $status): Tick {\n\t\t$this->status = $status;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getAction(): string {\n\t\treturn $this->action;\n\t}\n\n\t/**\n\t * @param string $action\n\t *\n\t * @return $this\n\t */\n\tpublic function setAction(string $action): Tick {\n\t\t$this->action = $action;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @param string $info\n\t * @param string $value\n\t *\n\t * @return $this\n\t */\n\tpublic function setInfo(string $info, string $value): Tick {\n\t\t$this->data[$info] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param int $value\n\t *\n\t * @return $this\n\t */\n\tpublic function setInfoInt(string $info, int $value): Tick {\n\t\t$this->data[$info] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param float $value\n\t *\n\t * @return $this\n\t */\n\tpublic function setInfoFloat(string $info, float $value): Tick {\n\t\t$this->data[$info] = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @param string $info\n\t */\n\tpublic function unsetInfo(string $info) {\n\t\tunset($this->data[$info]);\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param string $default\n\t *\n\t * @return string\n\t */\n\tpublic function getInfo(string $info, string $default = ''): string {\n\t\treturn $this->get($info, $this->data, $default);\n\t}\n\n\n\t/**\n\t * @param string $info\n\t * @param int $default\n\t *\n\t * @return int\n\t */\n\tpublic function getInfoInt(string $info, int $default = 0): int {\n\t\treturn $this->getInt($info, $this->data, $default);\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param float $default\n\t *\n\t * @return float\n\t */\n\tpublic function getInfoFloat(string $info, float $default = 0): float {\n\t\treturn $this->getFloat($info, $this->data, $default);\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Provider/TestProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Provider;\n\nuse OC\\FullTextSearch\\Model\\IndexDocument;\nuse OC\\FullTextSearch\\Model\\SearchTemplate;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\TestService;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\FullTextSearch\\Model\\IIndexOptions;\nuse OCP\\FullTextSearch\\Model\\IRunner;\nuse OCP\\FullTextSearch\\Model\\ISearchRequest;\nuse OCP\\FullTextSearch\\Model\\ISearchResult;\nuse OCP\\FullTextSearch\\Model\\ISearchTemplate;\n\n\n/**\n * Class TestProvider\n *\n * @package OCA\\FullTextSearch\\Provider\n */\nclass TestProvider implements IFullTextSearchProvider {\n\tconst TEST_PROVIDER_ID = 'test_provider';\n\n\tprivate IRunner $runner;\n\tprivate IndexOptions $indexOptions;\n\n\n\t/**\n\t * TestProvider constructor.\n\t *\n\t * @param ConfigService $configService\n\t * @param TestService $testService\n\t */\n\tpublic function __construct(\n\t\tprivate ConfigService $configService,\n\t\tprivate TestService $testService\n\t) {\n\t}\n\n\n\t/**\n\t * return unique id of the provider\n\t */\n\tpublic function getId(): string {\n\t\treturn self::TEST_PROVIDER_ID;\n\t}\n\n\n\t/**\n\t * return name of the provider\n\t */\n\tpublic function getName(): string {\n\t\treturn 'Test Provider';\n\t}\n\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getConfiguration(): array {\n\t\treturn $this->configService->getConfig();\n\t}\n\n\n\tpublic function setRunner(IRunner $runner) {\n\t\t$this->runner = $runner;\n\t}\n\n\n\t/**\n\t * @param IIndexOptions $options\n\t */\n\tpublic function setIndexOptions(IIndexOptions $options) {\n\t\t$this->indexOptions = $options;\n\t}\n\n\n\t/**\n\t * @return ISearchTemplate\n\t */\n\tpublic function getSearchTemplate(): ISearchTemplate {\n\t\treturn new SearchTemplate();\n\t}\n\n\n\t/**\n\t * called when loading all providers.\n\t *\n\t * Loading some containers.\n\t */\n\tpublic function loadProvider() {\n\t}\n\n\n\tpublic function generateChunks(string $userId): array {\n\t\treturn [];\n\t}\n\n\n\t/**\n\t * returns all indexable document for a user.\n\t * There is no need to fill the document with content at this point.\n\t *\n\t * $platform is provided if the mapping needs to be changed.\n\t *\n\t * @param string $userId\n\t * @param string $chunk\n\t *\n\t * @return IIndexDocument[]\n\t */\n\tpublic function generateIndexableDocuments(string $userId, string $chunk): array {\n\t\t$result = [];\n\n\t\t$result[] = $this->testService->generateIndexDocumentContentLicense($this->indexOptions);\n\t\t$result[] = $this->testService->generateIndexDocumentSimple($this->indexOptions);\n\n\t\treturn $result;\n\t}\n\n\n\t/**\n\t * generate documents prior to the indexing.\n\t *\n\t * @param IIndexDocument $document\n\t */\n\tpublic function fillIndexDocument(IIndexDocument $document) {\n\t}\n\n\n\t/**\n\t * @param IIndexDocument $document\n\t *\n\t * @return bool\n\t */\n\tpublic function isDocumentUpToDate(IIndexDocument $document): bool {\n\t\treturn false;\n\t}\n\n\n\t/**\n\t * @param IIndex $index\n\t *\n\t * @return IIndexDocument\n\t */\n\tpublic function updateDocument(IIndex $index): IIndexDocument {\n\t\treturn new IndexDocument($index->getProviderId(), $index->getDocumentId());\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t */\n\tpublic function onInitializingIndex(IFullTextSearchPlatform $platform) {\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t */\n\tpublic function onResettingIndex(IFullTextSearchPlatform $platform) {\n\t}\n\n\n\t/**\n\t * not used yet\n\t */\n\tpublic function unloadProvider() {\n\t}\n\n\n\t/**\n\t * before a search, improve the request\n\t *\n\t * @param ISearchRequest $request\n\t */\n\tpublic function improveSearchRequest(ISearchRequest $request) {\n\t}\n\n\n\t/**\n\t * after a search, improve results\n\t *\n\t * @param ISearchResult $searchResult\n\t */\n\tpublic function improveSearchResult(ISearchResult $searchResult) {\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Search/UnifiedSearchProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Search;\n\nuse Exception;\nuse OCA\\FullTextSearch\\Model\\SearchRequest;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCA\\FullTextSearch\\Service\\SearchService;\nuse OCA\\FullTextSearch\\Tools\\Traits\\TArrayTools;\nuse OCP\\FullTextSearch\\Model\\ISearchRequest;\nuse OCP\\FullTextSearch\\Model\\ISearchResult;\nuse OCP\\IL10N;\nuse OCP\\IURLGenerator;\nuse OCP\\IUser;\nuse OCP\\Search\\IFilteringProvider;\nuse OCP\\Search\\ISearchQuery;\nuse OCP\\Search\\SearchResult;\n\n/**\n * Class UnifiedSearchProvider\n *\n * @package OCA\\FullTextSearch\\Search\n */\nclass UnifiedSearchProvider implements IFilteringProvider {\n\tconst PROVIDER_ID = 'fulltextsearch';\n\tconst ORDER = 1;\n\n\tuse TArrayTools;\n\n\tpublic function __construct(\n\t\tprivate IL10N $l10n,\n\t\tprivate IURLGenerator $urlGenerator,\n\t\tprivate SearchService $searchService,\n\t\tprivate ConfigService $configService\n\t) {\n\t}\n\n\n\t/**\n\t * return unique id of the provider\n\t */\n\tpublic function getId(): string {\n\t\treturn self::PROVIDER_ID;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getName(): string {\n\t\treturn $this->l10n->t('Full Text Search');\n\t}\n\n\n\t/**\n\t * @param string $route\n\t * @param array $routeParameters\n\t *\n\t * @return int\n\t */\n\tpublic function getOrder(string $route, array $routeParameters): int {\n\t\treturn self::ORDER;\n\t}\n\n\n\t/**\n\t * @param IUser $user\n\t * @param ISearchQuery $query\n\t *\n\t * @return SearchResult\n\t */\n\tpublic function search(IUser $user, ISearchQuery $query): SearchResult {\n\t\t$result = [];\n\n\t\t$searchRequest = $this->generateSearchRequest($query);\n\t\ttry {\n\t\t\t$ftsResult = $this->searchService->search($user->getUID(), $searchRequest);\n\t\t\t$result = $this->convertSearchResult($ftsResult);\n\t\t} catch (Exception $e) {\n\t\t}\n\n\t\treturn SearchResult::paginated(\n\t\t\t$this->l10n->t('Full Text Search'), $result, ($query->getCursor() ?? 0) + $query->getLimit()\n\t\t);\n\t}\n\n\n\t/**\n\t * @param $query\n\t *\n\t * @return ISearchRequest\n\t */\n\tprivate function generateSearchRequest(ISearchQuery $query): ISearchRequest {\n\t\t$searchRequest = new SearchRequest();\n\n\t\t$since = $query->getFilter('since')?->get();\n\t\tif ($since instanceof \\DateTimeImmutable) {\n\t\t\t$searchRequest->addOption('since', (string)$since->getTimestamp());\n\t\t}\n\t\t$until = $query->getFilter('until')?->get();\n\t\tif ($until instanceof \\DateTimeImmutable) {\n\t\t\t$searchRequest->addOption('until', (string)$until->getTimestamp());\n\t\t}\n\n\t\t$searchRequest->setProviders(['all']);\n\t\t$searchRequest->setSearch($query->getTerm());\n\t\t$searchRequest->setPage((int)floor(($query->getCursor() ?? 0) / $query->getLimit()) + 1);\n\t\t$searchRequest->setParts([]);\n\t\t$searchRequest->setSize($query->getLimit());\n\t\t$searchRequest->setTags([]);\n\t\t$searchRequest->setSubTags([]);\n\t\t$searchRequest->setSize($query->getLimit());\n\n\t\treturn $searchRequest;\n\t}\n\n\n\t/**\n\t * @param ISearchResult[] $searchResult\n\t *\n\t * @return UnifiedSearchResult[]\n\t */\n\tprivate function convertSearchResult(array $searchResult): array {\n\t\t$result = [];\n\t\tforeach ($searchResult as $ftsSearch) {\n\t\t\tforeach ($ftsSearch->getDocuments() as $document) {\n\t\t\t\t$excerpts = $document->getExcerpts();\n\t\t\t\t$title = '(' . $document->getProviderId() . ') ';\n\t\t\t\tif (empty($excerpts)) {\n\t\t\t\t\t$title .= $document->getTitle();\n\t\t\t\t\t$subline = '';\n\t\t\t\t} else {\n\t\t\t\t\t$title .= (sizeof($excerpts) > 0) ? $excerpts[0]['excerpt'] : '';\n\t\t\t\t\t$subline = $document->getTitle();\n\t\t\t\t}\n\n\t\t\t\t$unified = $document->getInfoArray('unified');\n\t\t\t\t$result[] = new UnifiedSearchResult(\n\t\t\t\t\t$this->get('thumbUrl', $unified, ''),\n\t\t\t\t\t$this->get('title', $unified, $title),\n\t\t\t\t\t$this->get('subline', $unified, $subline),\n\t\t\t\t\t$this->get('link', $unified, $document->getLink()),\n\t\t\t\t\t$this->get('icon', $unified, '')\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\tpublic function getSupportedFilters(): array {\n\t\treturn [\n\t\t\t'term',\n\t\t\t'since',\n\t\t\t'until',\n\t\t];\n\t}\n\n\tpublic function getAlternateIds(): array {\n\t\treturn [];\n\t}\n\n\tpublic function getCustomFilters(): array {\n\t\treturn [];\n\t}\n}\n\n"
  },
  {
    "path": "lib/Search/UnifiedSearchResult.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Search;\n\n\nuse OCP\\Search\\SearchResultEntry;\n\n\n/**\n * Class SearchResultEntry\n *\n * @package OCA\\FullTextSearch\\Search\n */\nclass UnifiedSearchResult extends SearchResultEntry {\n\n\n\t/**\n\t * UnifiedSearchResult constructor.\n\t *\n\t * @param string $thumbnailUrl\n\t * @param string $title\n\t * @param string $subline\n\t * @param string $resourceUrl\n\t * @param string $icon\n\t * @param bool $rounded\n\t */\n\tpublic function __construct(\n\t\tstring $thumbnailUrl = '', string $title = '', string $subline = '', string $resourceUrl = '',\n\t\tstring $icon = '',\n\t\tbool $rounded = false\n\t) {\n\t\tparent::__construct($thumbnailUrl, $title, $subline, $resourceUrl, $icon, $rounded);\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getThumbnailUrl(): string {\n\t\treturn $this->thumbnailUrl;\n\t}\n\n\t/**\n\t * @param string $thumbnailUrl\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setThumbnailUrl(string $thumbnailUrl): self {\n\t\t$this->thumbnailUrl = $thumbnailUrl;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getTitle(): string {\n\t\treturn $this->title;\n\t}\n\n\t/**\n\t * @param string $title\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setTitle(string $title): self {\n\t\t$this->title = $title;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getSubline(): string {\n\t\treturn $this->subline;\n\t}\n\n\t/**\n\t * @param string $subline\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setSubline(string $subline): self {\n\t\t$this->subline = $subline;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getResourceUrl(): string {\n\t\treturn $this->resourceUrl;\n\t}\n\n\t/**\n\t * @param string $resourceUrl\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setResourceUrl(string $resourceUrl): self {\n\t\t$this->resourceUrl = $resourceUrl;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getIcon(): string {\n\t\treturn $this->icon;\n\t}\n\n\t/**\n\t * @param string $icon\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setIcon(string $icon): self {\n\t\t$this->icon = $icon;\n\n\t\treturn $this;\n\t}\n\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isRounded(): bool {\n\t\treturn $this->rounded;\n\t}\n\n\t/**\n\t * @param bool $rounded\n\t *\n\t * @return UnifiedSearchResult\n\t */\n\tpublic function setRounded(bool $rounded): self {\n\t\t$this->rounded = $rounded;\n\n\t\treturn $this;\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Service/CliService.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\n\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse Symfony\\Component\\Console\\Helper\\ProgressBar;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\n\n/**\n * Class CliService\n *\n * @package OCA\\FullTextSearch\\Service\n */\nclass CliService {\n\t/** @var Runner */\n\tprivate $runner;\n\n\t/** @var array */\n\tprivate $panels = [];\n\n\t/** @var array */\n\tprivate $displayedPanel = [];\n\n\t/** @var ProgressBar */\n\tprivate $display;\n\n\t/** @var OutputInterface */\n\tprivate $output = null;\n\n\tpublic function __construct() {\n\t}\n\n\n\t/**\n\t * @param Runner $runner\n\t */\n\tpublic function setRunner(Runner $runner) {\n\t\t$this->runner = $runner;\n\n\t\t$this->runner->onInfoUpdate([$this, 'onInfoUpdated']);\n\t}\n\n\n\t/**\n\t * @param string $panelId\n\t * @param array $lines\n\t */\n\tpublic function createPanel(string $panelId, array $lines) {\n\t\tif (!is_array($lines)) {\n\t\t\t$lines = [$lines];\n\t\t}\n\n\t\t$this->panels[$panelId] = $lines;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function initDisplay() {\n\t\t$this->displayedPanel = [];\n\t}\n\n\n\t/**\n\t * @param string $panelSlot\n\t * @param string $panelId\n\t */\n\tpublic function displayPanel(string $panelSlot, string $panelId) {\n\t\t$this->displayedPanel[] = [\n\t\t\t'slot' => $panelSlot,\n\t\t\t'id'   => $panelId\n\t\t];\n\t}\n\n\n\t/**\n\t * @param string $panelSlot\n\t *\n\t * @return string\n\t */\n\tpublic function currentPanel(string $panelSlot): string {\n\t\tforeach ($this->displayedPanel as $panel) {\n\t\t\tif ($panel['slot'] === $panelSlot) {\n\t\t\t\treturn $panel['id'];\n\t\t\t}\n\t\t}\n\n\t\treturn '';\n\t}\n\n\n\t/**\n\t * @param string $panelSlot\n\t * @param string $panelId\n\t */\n\tpublic function switchPanel(string $panelSlot, string $panelId) {\n\t\t$this->displayedPanel = array_map(\n\t\t\tfunction($item) use ($panelId, $panelSlot) {\n\t\t\t\tif ($item['slot'] === $panelSlot) {\n\t\t\t\t\t$item['id'] = $panelId;\n\t\t\t\t}\n\n\t\t\t\treturn $item;\n\t\t\t}, $this->displayedPanel\n\t\t);\n\n\t\t$this->refreshDisplay();\n\t}\n\n\t/**\n\t * @param OutputInterface $output\n\t */\n\tpublic function runDisplay(OutputInterface $output) {\n\t\t$this->output = $output;\n\n\t\t$output->writeLn('');\n\t\tforeach ($this->displayedPanel as $displayedPanel) {\n\t\t\t$panel = $this->panels[$displayedPanel['id']];\n\t\t\tfor ($i = 0; $i < sizeof($panel); $i++) {\n\t\t\t\t$output->writeLn('');\n\t\t\t}\n\t\t}\n\n\t\t$this->display = new ProgressBar($this->output);\n\t\t$this->display->setOverwrite(true);\n\n\t\t$initVar = $this->runner->getInfoAll();\n\t\t$keys = array_keys($initVar);\n\t\tforeach ($keys as $key) {\n\t\t\t$this->display->setMessage((string)$initVar[$key], (string)$key);\n\t\t}\n\n\t\t$this->display->clear();\n\n\t\t$this->refreshDisplay();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function refreshDisplay() {\n\n\t\tif ($this->display === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$format = [];\n\t\tforeach ($this->displayedPanel as $displayedPanel) {\n\t\t\t$panel = $this->panels[$displayedPanel['id']];\n\t\t\tfor ($i = 0; $i < sizeof($panel); $i++) {\n\t\t\t\t$format[] = $panel[$i];\n\t\t\t}\n\t\t}\n\n\t\t$this->display->setFormat(implode(\"\\n\", $format) . \"\\n\");\n\t\t$this->refreshInfo();\n\t\t$this->display->start();\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function refreshInfo() {\n\t\tif ($this->runner->isPauseRunning()) {\n\t\t\t$this->display->setMessage('(paused)', '_paused');\n\t\t} else {\n\t\t\t$this->display->setMessage('', '_paused');\n\t\t}\n\n\t\t$this->display->display();\n\t}\n\n\t/**\n\t * @param array $info\n\t */\n\tpublic function onInfoUpdated(array $info) {\n\t\tif ($this->display === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$keys = array_keys($info);\n\t\tforeach ($keys as $k) {\n\t\t\t$this->display->setMessage((string)$info[$k], (string)$k);\n\t\t}\n\t\t$this->refreshInfo();\n\n\t\t$this->display->display();\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/Service/CollectionService.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Db\\IndexesRequest;\nuse OCA\\FullTextSearch\\Exceptions\\CollectionArgumentException;\nuse OCA\\FullTextSearch\\Exceptions\\IndexDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\IURLGenerator;\n\nclass CollectionService {\n\n\t/** @var IURLGenerator */\n\tprivate $urlGenerator;\n\n\t/** @var IndexesRequest */\n\tprivate $indexesRequest;\n\n\t/** @var ProviderService */\n\tprivate $providerService;\n\n\t/** @var IndexService */\n\tprivate $indexService;\n\n\t/** @var ConfigService */\n\tprivate $configService;\n\n\n\t/** @var Runner */\n\tprivate $runner;\n\n\n\t/**\n\t * @param IURLGenerator $urlGenerator\n\t * @param IndexesRequest $indexesRequest\n\t * @param ProviderService $providerService\n\t * @param IndexService $indexService\n\t * @param ConfigService $configService\n\t */\n\tpublic function __construct(\n\t\tprivate IAppConfig $appConfig,\n\t\tIURLGenerator $urlGenerator,\n\t\tIndexesRequest $indexesRequest,\n\t\tProviderService $providerService,\n\t\tIndexService $indexService,\n\t\tConfigService $configService\n\t) {\n\t\t$this->urlGenerator = $urlGenerator;\n\t\t$this->indexesRequest = $indexesRequest;\n\t\t$this->providerService = $providerService;\n\t\t$this->indexService = $indexService;\n\t\t$this->configService = $configService;\n\t}\n\n\n\tpublic function setRunner(Runner $runner): void {\n\t\t$this->runner = $runner;\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t */\n\tpublic function deleteCollection(string $collection): void {\n\t\t$this->indexesRequest->deleteCollection($collection);\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t *\n\t * @return bool\n\t */\n\tpublic function hasCollection(string $collection): bool {\n\t\tforeach ($this->getCollections() as $item) {\n\t\t\tif ($item === $collection) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return string[]\n\t */\n\tpublic function getCollections(bool $local = true): array {\n\t\treturn $this->indexesRequest->getCollections($local);\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t * @param int $length\n\t *\n\t * @return array\n\t */\n\tpublic function getQueue(string $collection, int $length = 0): array {\n\t\tif ($length === 0) {\n\t\t\t$length = $this->appConfig->getAppValueInt(ConfigLexicon::COLLECTION_INDEXING_LIST);\n\t\t}\n\n\t\treturn array_map(\n\t\t\tfunction (Index $index): array {\n\t\t\t\treturn $index->asSitemap($this->urlGenerator);\n\t\t\t},\n\t\t\t$this->indexesRequest->getQueuedIndexes($collection, false, $length)\n\t\t);\n\t}\n\n\tpublic function resetCollection(string $collection): void {\n\t\t$this->indexesRequest->resetCollection($collection);\n\t}\n\n\t/**\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function setAsDone(string $collection, string $providerId, string $documentId): void {\n\t\t$index = $this->indexesRequest->getIndex($providerId, $documentId, $collection);\n\t\t$index->setStatus(IIndex::INDEX_DONE);\n\t\t$this->indexService->updateIndex($index);\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t * @param string $collection\n\t * @param string $userId\n\t * @param IndexOptions $options\n\t */\n\tpublic function initCollectionIndexes(\n\t\tIFullTextSearchProvider $provider,\n\t\tstring $collection,\n\t\tstring $userId,\n\t\tIndexOptions $options\n\t) {\n\t\t$chunks = $provider->generateChunks($userId);\n\t\tif (empty($chunks)) {\n\t\t\t$chunks = [$userId];\n\t\t}\n\n\t\t$this->updateRunnerInfo('chunkTotal', (string)sizeof($chunks));\n\t\t$chunkCount = 0;\n\t\tforeach ($chunks as $chunk) {\n\t\t\t$documents = $provider->generateIndexableDocuments($userId, (string)$chunk);\n\t\t\t$this->updateRunnerInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'chunkCurr' => ++$chunkCount,\n\t\t\t\t\t'documentChunk' => sizeof($documents)\n\t\t\t\t]\n\t\t\t);\n\n\t\t\t$documentCount = 0;\n\t\t\tforeach ($documents as $document) {\n\t\t\t\t$this->updateRunnerInfo('documentCurr', (string)++$documentCount);\n\t\t\t\t$curr = $this->runner->getInfoInt('documentTotal');\n\t\t\t\t$this->runner->setInfoInt('documentTotal', ++$curr);\n\n\t\t\t\ttry {\n\t\t\t\t\t$this->indexesRequest->getIndex(\n\t\t\t\t\t\t$document->getProviderId(),\n\t\t\t\t\t\t$document->getId(),\n\t\t\t\t\t\t$collection\n\t\t\t\t\t);\n\t\t\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\t\t\t$index = new Index($document->getProviderId(), $document->getId(), $collection);\n\t\t\t\t\t$index->setStatus(IIndex::INDEX_FULL);\n\t\t\t\t\t$index->setOwnerId($document->getAccess()->getOwnerId());\n\t\t\t\t\t$index->setSource($document->getSource());\n\t\t\t\t\t$index->setLastIndex();\n\t\t\t\t\t$this->indexesRequest->create($index);\n\n\t\t\t\t\t$curr = $this->runner->getInfoInt('indexCount');\n\t\t\t\t\t$this->runner->setInfoInt('indexCount', ++$curr);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t *\n\t * @throws CollectionArgumentException\n\t */\n\tpublic function confirmCollectionString(string $collection): void {\n\t\tif (strtolower($collection) === $this->configService->getInternalCollection()) {\n\t\t\tthrow new CollectionArgumentException('invalid name');\n\t\t}\n\t}\n\n\t/**\n\t * @param string $collection\n\t *\n\t * @throws CollectionArgumentException\n\t */\n\tpublic function confirmCollection(string $collection): void {\n\t\t$this->confirmCollectionString($collection);\n\n\t\tif (!$this->hasCollection($collection)) {\n\t\t\tthrow new CollectionArgumentException('collection does not exist');\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return IIndexDocument\n\t * @throws ProviderDoesNotExistException\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function getDocument(string $collection, string $providerId, string $documentId): IIndexDocument {\n\t\t$wrapped = $this->providerService->getProvider($providerId);\n\t\t$provider = $wrapped->getProvider();\n\n\t\t$index = $this->indexService->getIndex($providerId, $documentId, $collection);\n\t\t$index->setStatus(IIndex::INDEX_FULL);\n\n\t\treturn $provider->updateDocument($index);\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic function getLinks(): array {\n\t\treturn $this->appConfig->getAppValueArray(ConfigLexicon::COLLECTION_LINKS);\n\t}\n\n\t/**\n\t * @param array $links\n\t */\n\tpublic function saveLinks(array $links): void {\n\t\t$this->appConfig->setAppValueArray(ConfigLexicon::COLLECTION_LINKS, $links);\n\t}\n\n\t/**\n\t * @param string $collection\n\t * @param string $userId\n\t */\n\tpublic function addLink(string $collection, string $userId): void {\n\t\t$links = $this->getLinks();\n\t\t$links[$collection] = $userId;\n\t\t$this->saveLinks($links);\n\t}\n\n\t/**\n\t * @param string $collection\n\t */\n\tpublic function removeLink(string $collection): void {\n\t\t$links = $this->getLinks();\n\t\tunset($links[$collection]);\n\t\t$this->saveLinks($links);\n\t}\n\n\t/**\n\t * @param string $collection\n\t *\n\t * @return string\n\t * @throws CollectionArgumentException\n\t */\n\tpublic function getLinkedAccount(string $collection): string {\n\t\tif (!$this->hasCollection($collection)) {\n\t\t\tthrow new CollectionArgumentException('unknown collection');\n\t\t}\n\n\t\t$links = $this->getLinks();\n\t\t$userId = $links[$collection] ?? '';\n\t\tif ($userId === '') {\n\t\t\tthrow new CollectionArgumentException('no linked account');\n\t\t}\n\n\t\treturn $userId;\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param string $value\n\t */\n\tprivate function updateRunnerInfo(string $info, string $value): void {\n\t\tif (is_null($this->runner)) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfo($info, $value);\n\t}\n\n\n\t/**\n\t * @param array $data\n\t */\n\tprivate function updateRunnerInfoArray(array $data): void {\n\t\tif (is_null($this->runner)) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfoArray($data);\n\t}\n\n}\n"
  },
  {
    "path": "lib/Service/ConfigService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\nnamespace OCA\\FullTextSearch\\Service;\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\IConfig;\n\nclass ConfigService {\n\tpublic function __construct(\n\t\tprivate readonly IAppConfig $appConfig,\n\t\tprivate readonly IConfig $config,\n\t) {\n\t}\n\n\tpublic function getConfig(): array {\n\t\treturn [\n\t\t\tConfigLexicon::APP_NAVIGATION => $this->appConfig->getAppValueBool(ConfigLexicon::APP_NAVIGATION),\n\t\t\tConfigLexicon::SEARCH_PLATFORM => $this->appConfig->getAppValueString(ConfigLexicon::SEARCH_PLATFORM),\n\t\t\tConfigLexicon::COLLECTION_INTERNAL => $this->appConfig->getAppValueString(ConfigLexicon::COLLECTION_INTERNAL),\n\t\t\tConfigLexicon::CRON_LAST_ERR_RESET => $this->appConfig->getAppValueInt(ConfigLexicon::CRON_LAST_ERR_RESET),\n\t\t\tConfigLexicon::TICK_TTL => $this->appConfig->getAppValueInt(ConfigLexicon::TICK_TTL),\n\t\t\tConfigLexicon::COLLECTION_INDEXING_LIST => $this->appConfig->getAppValueInt(ConfigLexicon::COLLECTION_INDEXING_LIST),\n\t\t\tConfigLexicon::COLLECTION_LINKS => $this->appConfig->getAppValueArray(ConfigLexicon::COLLECTION_LINKS),\n\t\t];\n\t}\n\n\tpublic function setConfig(array $save): void {\n\t\tforeach(array_keys($save) as $k) {\n\t\t\tswitch($k) {\n\t\t\t\tcase ConfigLexicon::APP_NAVIGATION:\n\t\t\t\t\t$this->appConfig->setAppValueBool($k, $save[$k]);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase ConfigLexicon::SEARCH_PLATFORM:\n\t\t\t\tcase ConfigLexicon::COLLECTION_INTERNAL:\n\t\t\t\t\t$this->appConfig->setAppValueString($k, $save[$k]);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase ConfigLexicon::CRON_LAST_ERR_RESET:\n\t\t\t\tcase ConfigLexicon::TICK_TTL:\n\t\t\t\tcase ConfigLexicon::COLLECTION_INDEXING_LIST:\n\t\t\t\t\t$this->appConfig->setAppValueInt($k, $save[$k]);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase ConfigLexicon::COLLECTION_LINKS:\n\t\t\t\t\t$this->appConfig->setAppValueArray($k, $save[$k]);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic function getAppValue(string $key): string {\n\t\treturn $this->config->getSystemValueString(Application::APP_ID . '.' . $key,\n\t\t\t(string)$this->appConfig->getAppValueString($key)\n\t\t);\n\t}\n\n\tpublic function getInternalCollection(): string {\n\t\treturn $this->appConfig->getAppValueString(ConfigLexicon::COLLECTION_INTERNAL);\n\t}\n}\n"
  },
  {
    "path": "lib/Service/IndexService.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\n\nuse Exception;\nuse OCA\\FullTextSearch\\Db\\IndexesRequest;\nuse OCA\\FullTextSearch\\Exceptions\\IndexDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\NotIndexableDocumentException;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformTemporaryException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\Index;\nuse OCA\\FullTextSearch\\Model\\IndexOptions;\nuse OCA\\FullTextSearch\\Model\\Runner;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IIndex;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\FullTextSearch\\Model\\IIndexOptions;\nuse OCP\\FullTextSearch\\Model\\IRunner;\nuse OCP\\FullTextSearch\\Service\\IIndexService;\n\n\n/**\n * Class IndexService\n *\n * @package OCA\\FullTextSearch\\Service\n */\nclass IndexService implements IIndexService {\n\n\n\t/** @var IndexesRequest */\n\tprivate $indexesRequest;\n\n\t/** @var ProviderService */\n\tprivate $providerService;\n\n\t/** @var PlatformService */\n\tprivate $platformService;\n\n\n\t/** @var Runner */\n\tprivate $runner = null;\n\n\t/** @var array */\n\tprivate $queuedDeleteIndex = [];\n\n\t/** @var int */\n\tprivate $currentTotalDocuments = 0;\n\n\n\t/**\n\t * IndexService constructor.\n\t *\n\t * @param IndexesRequest $indexesRequest\n\t * @param ProviderService $providerService\n\t * @param PlatformService $platformService\n\t */\n\tpublic function __construct(\n\t\tIndexesRequest $indexesRequest,\n\t\tProviderService $providerService,\n\t\tPlatformService $platformService\n\t) {\n\t\t$this->indexesRequest = $indexesRequest;\n\t\t$this->providerService = $providerService;\n\t\t$this->platformService = $platformService;\n\t}\n\n\n\t/**\n\t * @param Runner $runner\n\t */\n\tpublic function setRunner(Runner $runner) {\n\t\t$this->runner = $runner;\n\t}\n\n\n\t/**\n\t * @param string $action\n\t * @param bool $force\n\t *\n\t * @throws Exception\n\t */\n\tprivate function updateRunnerAction(string $action, bool $force = false) {\n\t\tif ($this->runner === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->updateAction($action, $force);\n\t}\n\n\t/**\n\t * @param string $info\n\t * @param string $value\n\t * @param int $color\n\t */\n\tprivate function updateRunnerInfo(\n\t\tstring $info, string $value, int $color = IRunner::RESULT_TYPE_SUCCESS\n\t) {\n\t\tif ($this->runner === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfo($info, $value, $color);\n\t}\n\n\t/**\n\t * @param array $data\n\t */\n\tprivate function updateRunnerInfoArray(array $data) {\n\t\tif ($this->runner === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->runner->setInfoArray($data);\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IFullTextSearchProvider $provider\n\t * @param string $userId\n\t * @param IndexOptions $options\n\t *\n\t * @throws Exception\n\t */\n\tpublic function indexProviderContentFromUser(\n\t\tIFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, string $userId,\n\t\tIndexOptions $options\n\t) {\n\t\t$this->updateRunnerAction('generateIndex' . $provider->getName());\n\t\t$this->updateRunnerInfoArray(\n\t\t\t[\n\t\t\t\t'userId' => $userId,\n\t\t\t\t'providerId' => $provider->getId(),\n\t\t\t\t'providerName' => $provider->getName(),\n\t\t\t\t'chunkCurrent' => 0,\n\t\t\t\t'chunkTotal' => 0,\n\t\t\t\t'documentCurrent' => 0,\n\t\t\t\t'documentTotal' => 0,\n\t\t\t\t'info' => '',\n\t\t\t\t'title' => ''\n\t\t\t]\n\t\t);\n\n\t\t$chunks = $provider->generateChunks($userId);\n\t\tif (empty($chunks)) {\n\t\t\t$chunks = [$userId];\n\t\t}\n\n\t\t$this->updateRunnerInfo('chunkTotal', (string)count($chunks));\n\t\t$curr = 0;\n\t\tforeach ($chunks as $chunk) {\n\t\t\t$this->updateRunnerInfo('chunkCurrent', (string)++$curr);\n\n\t\t\t$documents = $provider->generateIndexableDocuments($userId, (string)$chunk);\n\t\t\t$this->currentTotalDocuments = sizeof($documents);\n\t\t\t$this->updateRunnerInfoArray(\n\t\t\t\t[\n\t\t\t\t\t'documentTotal' => $this->currentTotalDocuments,\n\t\t\t\t\t'documentCurrent' => 0\n\t\t\t\t]\n\t\t\t);\n\n\t\t\t//$maxSize = sizeof($documents);\n\n\t\t\t$toIndex = $this->updateDocumentsWithCurrIndex($provider, '', $documents, $options);\n\t\t\t$this->indexDocuments($platform, $provider, $toIndex, $options);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t * @param IIndexDocument[] $documents\n\t * @param IIndexOptions $options\n\t *\n\t * @return IIndexDocument[]\n\t * @throws Exception\n\t */\n\tpublic function updateDocumentsWithCurrIndex(\n\t\tIFullTextSearchProvider $provider,\n\t\tstring $collection,\n\t\tarray $documents,\n\t\tIIndexOptions $options\n\t): array {\n\n\t\t$result = [];\n\t\t$count = 0;\n\t\tforeach ($documents as $document) {\n\t\t\tif ($count % 1000 === 0) {\n\t\t\t\t$this->updateRunnerAction('compareWithCurrentIndex', true);\n\t\t\t\t$this->updateRunnerInfo('documentCurrent', (string)$count);\n\t\t\t}\n\t\t\t$count++;\n\n\t\t\ttry {\n\t\t\t\t$index = $this->indexesRequest->getIndex(\n\t\t\t\t\t$document->getProviderId(),\n\t\t\t\t\t$document->getId(),\n\t\t\t\t\t$collection\n\t\t\t\t);\n\t\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\t\t$index = new Index($document->getProviderId(), $document->getId(), $collection);\n\t\t\t\t$index->setStatus(Index::INDEX_FULL);\n\t\t\t\t$index->setLastIndex();\n\t\t\t}\n\n\t\t\tif ($index->isStatus(IIndex::INDEX_IGNORE)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($options->getOption('errors', '') !== 'ignore' && $index->getErrorCount() > 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($options->getOptionBool('force', false) === true) {\n\t\t\t\t$index->setStatus(Index::INDEX_FULL);\n\t\t\t}\n\n\t\t\t$index->resetErrors();\n\t\t\t$document->setIndex($index);\n\t\t\tif ($options->getOptionBool('force', false) === true\n\t\t\t\t|| !$this->isDocumentUpToDate($provider, $document)) {\n\t\t\t\t$result[] = $document;\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t * @param IIndexDocument $document\n\t *\n\t * @return bool\n\t */\n\tprivate function isDocumentUpToDate(\n\t\tIFullTextSearchProvider $provider,\n\t\tIIndexDocument $document\n\t): bool {\n\t\t$index = $document->getIndex();\n\t\tif (!$index->isStatus(Index::INDEX_OK)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($index->isStatus(Index::INDEX_META) || $index->isStatus(Index::INDEX_CONTENT)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $provider->isDocumentUpToDate($document);\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IFullTextSearchProvider $provider\n\t * @param IIndexDocument[] $documents\n\t * @param IndexOptions $options\n\t *\n\t * @throws Exception\n\t */\n\tprivate function indexDocuments(\n\t\tIFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, array $documents,\n\t\tIndexOptions $options\n\t) {\n\t\twhile ($document = array_shift($documents)) {\n\t\t\ttry {\n\n\t\t\t\t$this->updateRunnerInfoArray(\n\t\t\t\t\t[\n\t\t\t\t\t\t'documentCurrent' => ($this->currentTotalDocuments - sizeof($documents) - 1)\n\t\t\t\t\t]\n\t\t\t\t);\n\t\t\t\t$this->updateRunnerAction('fillDocument', true);\n\t\t\t\t$this->updateRunnerInfoArray(\n\t\t\t\t\t[\n\t\t\t\t\t\t'documentId' => $document->getId(),\n\t\t\t\t\t\t'info' => '',\n\t\t\t\t\t\t'title' => '',\n\t\t\t\t\t\t'content' => '',\n\t\t\t\t\t\t'status' => '',\n\t\t\t\t\t\t'statusColored' => ''\n\t\t\t\t\t]\n\t\t\t\t);\n\n\t\t\t\t$provider->fillIndexDocument($document);\n\t\t\t\t$this->updateRunnerInfoArray(\n\t\t\t\t\t[\n\t\t\t\t\t\t'title' => $document->getTitle(),\n\t\t\t\t\t\t'content' => $document->getContentSize()\n\t\t\t\t\t]\n\t\t\t\t);\n\t\t\t\t$this->filterDocumentBeforeIndex($document);\n\n\t\t\t\t$index = $this->indexDocument($platform, $document);\n\t\t\t\t$this->updateIndex($index);\n\t\t\t} catch (PlatformTemporaryException $e) {\n\t\t\t\tthrow $e;\n\t\t\t} catch (Exception $e) {\n\t\t\t}\n\n\t\t\t$document->__destruct();\n\t\t\tunset($document);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IIndexDocument $document\n\t *\n\t * @throws NotIndexableDocumentException\n\t */\n\tprivate function filterDocumentBeforeIndex(IIndexDocument $document) {\n\t\t// TODO - rework the index/not_index\n\t\t$index = $document->getIndex();\n\t\t$access = $document->getAccess();\n\n\t\tif ($access === null || $index->isStatus(Index::INDEX_IGNORE)) {\n\t\t\tthrow new NotIndexableDocumentException();\n\t\t}\n\n\t\t$index->setOwnerId($access->getOwnerId());\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IIndexDocument $document\n\t *\n\t * @return IIndex\n\t * @throws Exception\n\t */\n\tpublic function indexDocument(IFullTextSearchPlatform $platform, IIndexDocument $document\n\t): IIndex {\n\t\t$this->updateRunnerAction('indexDocument', true);\n\t\t$this->updateRunnerInfoArray(\n\t\t\t[\n\t\t\t\t'documentId' => $document->getId(),\n\t\t\t\t'title' => $document->getTitle(),\n\t\t\t\t'content' => $document->getContentSize()\n\t\t\t]\n\t\t);\n\n\t\ttry {\n\t\t\treturn $platform->indexDocument($document);\n\t\t} catch (PlatformTemporaryException $e) {\n\t\t\tthrow $e;\n\t\t} catch (Exception $e) {\n\t\t\tthrow new IndexDoesNotExistException();\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IFullTextSearchProvider $provider\n\t * @param Index $index\n\t *\n\t * @throws PlatformTemporaryException\n\t * @throws Exception\n\t */\n\tpublic function updateDocument(\n\t\tIFullTextSearchPlatform $platform, IFullTextSearchProvider $provider, Index $index\n\t) {\n\t\t$document = null;\n\t\t$this->updateRunnerInfoArray(\n\t\t\t[\n\t\t\t\t'providerName' => $provider->getName(),\n\t\t\t\t'userId' => $index->getOwnerId(),\n\t\t\t]\n\t\t);\n\n\t\tif ($index->isStatus(IIndex::INDEX_IGNORE)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!$index->isStatus(Index::INDEX_REMOVE)) {\n\t\t\ttry {\n\t\t\t\t$document = $provider->updateDocument($index);\n\t\t\t\tif (!$document->hasIndex()) {\n\t\t\t\t\t$document->setIndex($index);\n\t\t\t\t}\n\t\t\t} catch (Exception $e) {\n\t\t\t\t/** we do nothing, because we're not sure provider manage the right MissingDocumentException */\n\t\t\t}\n\t\t}\n\n\t\tif ($document === null) {\n\t\t\t$platform->deleteIndexes([$index]);\n\t\t\t$this->indexesRequest->deleteIndex($index);\n\n\t\t\treturn;\n\t\t}\n\n\t\t$this->updateRunnerAction('indexDocument', true);\n\t\t$this->updateRunnerInfoArray(\n\t\t\t[\n\t\t\t\t'documentId' => $document->getId(),\n\t\t\t\t'title' => $document->getTitle(),\n\t\t\t\t'content' => $document->getContentSize()\n\t\t\t]\n\t\t);\n\n\t\t$document->getIndex()\n\t\t\t\t ->resetErrors();\n\t\t$index = $platform->indexDocument($document);\n\t\t$this->updateIndex($index);\n\t}\n\n\n\t/**\n\t * @param Index[] $indexes\n\t */\n\tpublic function updateIndexes(array $indexes) {\n\t\tforeach ($indexes as $index) {\n\t\t\t$this->updateIndex($index);\n\t\t}\n\t\t$this->resetErrorFromQueue();\n\t}\n\n\n\t/**\n\t * @param IIndex $index\n\t */\n\tpublic function updateIndex(IIndex $index) {\n\t\t/** @var Index $index */\n\t\t$this->updateIndexError($index);\n\t\tif ($index->isStatus(IIndex::INDEX_REMOVE)) {\n\n\t\t\tif ($index->isStatus(IIndex::INDEX_DONE)) {\n\t\t\t\t$this->indexesRequest->deleteIndex($index);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t$this->indexesRequest->update($index);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ($index->isStatus(IIndex::INDEX_DONE)) {\n\t\t\t$index->setStatus(IIndex::INDEX_OK, true);\n\t\t}\n\n\t\ttry {\n\t\t\t$this->indexesRequest->getIndex($index->getProviderId(), $index->getDocumentId(), $index->getCollection());\n\t\t\t$this->indexesRequest->update($index);\n\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\t$this->indexesRequest->create($index);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IIndex $index\n\t */\n\tprivate function updateIndexError(IIndex $index) {\n\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $documentId\n\t * @param int $status\n\t * @param bool $reset\n\t *\n\t * @throws Exception\n\t */\n\tpublic function updateIndexStatus(\n\t\tstring $providerId,\n\t\tstring $documentId,\n\t\tint $status,\n\t\tbool $reset = false\n\t) {\n\t\t$indexes = $this->indexesRequest->getIndexes($providerId, $documentId);\n\t\tforeach ($indexes as $index) {\n\t\t\t$index->setStatus($status);\n\t\t\t$this->updateIndex($index);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param array $documentIds\n\t * @param int $status\n\t * @param bool $reset\n\t */\n\tpublic function updateIndexesStatus(\n\t\tstring $providerId,\n\t\tarray $documentIds,\n\t\tint $status,\n\t\tbool $reset = false\n\t) {\n\t\tforeach ($documentIds as $documentId) {\n\t\t\ttry {\n\t\t\t\t$this->updateIndexStatus($providerId, $documentId, $status, $reset);\n\t\t\t} catch (Exception $e) {\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param Index $index\n\t */\n\tpublic function resetErrorFromIndex(Index $index) {\n\t\tif (!$this->indexesRequest->resetError($index)) {\n\t\t\t$this->queuedDeleteIndex[] = $index;\n\t\t}\n\t}\n\n\n\t/**\n\t *\n\t */\n\tprivate function resetErrorFromQueue() {\n\t\tforeach ($this->queuedDeleteIndex as $index) {\n\t\t\t$this->indexesRequest->resetError($index);\n\t\t}\n\t}\n\n\t/**\n\t *\n\t */\n\tpublic function resetErrorsAll() {\n\t\t$this->indexesRequest->resetAllErrors();\n\t}\n\n\n\t/**\n\t * @return Index[]\n\t */\n\tpublic function getErrorIndexes(): array {\n\t\treturn $this->indexesRequest->getErrorIndexes();\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return Index[]\n\t */\n\tpublic function getIndexes(string $providerId, string $documentId): array {\n\t\treturn $this->indexesRequest->getIndexes($providerId, $documentId);\n\t}\n\n\n\t/**\n\t * @param string $collection\n\t * @param bool $all\n\t * @param int $length\n\t *\n\t * @return Index[]\n\t */\n\tpublic function getQueuedIndexes(string $collection = '', bool $all = false, int $length = -1): array {\n\t\treturn $this->indexesRequest->getQueuedIndexes($collection, $all, $length);\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $collection\n\t *\n\t * @throws ProviderDoesNotExistException\n\t */\n\tpublic function resetIndex(string $providerId = '', string $collection = '') {\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\t\tif ($providerId === '') {\n\t\t\t$platform->resetIndex('all');\n\t\t\t$this->indexesRequest->reset($collection);\n\n\t\t\treturn;\n\t\t}\n\n\t\t$providerWrapper = $this->providerService->getProvider($providerId);\n\t\t$provider = $providerWrapper->getProvider();\n//\t\t\t$provider->onResettingIndex($platform);\n\n\t\t$platform->resetIndex($provider->getId());\n\t\t$this->indexesRequest->deleteFromProviderId($provider->getId(), $collection);\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $documentId\n\t *\n\t * @return IIndex\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function getIndex(string $providerId, string $documentId, string $collection = ''): IIndex {\n\t\treturn $this->indexesRequest->getIndex($providerId, $documentId, $collection);\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t * @param string $documentId\n\t * @param string $userId\n\t * @param int $status\n\t *\n\t * @return IIndex\n\t * @throws IndexDoesNotExistException\n\t */\n\tpublic function createIndex(\n\t\tstring $providerId,\n\t\tstring $documentId,\n\t\tstring $userId,\n\t\tint $status\n\t): IIndex {\n\t\t$index = null;\n\t\tforeach ($this->indexesRequest->getCollections() as $collection) {\n\t\t\ttry {\n\t\t\t\t$index = $this->indexesRequest->getIndex($providerId, $documentId, $collection);\n\t\t\t\t$index->setStatus($status, true);\n\t\t\t\t$this->indexesRequest->update($index, true);\n\t\t\t} catch (IndexDoesNotExistException $e) {\n\t\t\t\t$index = new Index($providerId, $documentId, $collection);\n\t\t\t\t$index->setOwnerId($userId);\n\t\t\t\t$index->setStatus($status);\n\t\t\t\t$this->indexesRequest->create($index);\n\t\t\t}\n\t\t}\n\n\t\tif (is_null($index)) {\n\t\t\tthrow new IndexDoesNotExistException();\n\t\t}\n\n\t\t$index->setCollection('');\n\n\t\treturn $index;\n\t}\n\n}\n"
  },
  {
    "path": "lib/Service/PlatformService.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\n\nuse Exception;\nuse OC;\nuse OC\\App\\AppManager;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformIsNotCompatibleException;\nuse OCA\\FullTextSearch\\Exceptions\\PlatformNotSelectedException;\nuse OCA\\FullTextSearch\\Model\\PlatformWrapper;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Log\\LoggerInterface;\n\n/**\n * Class PlatformService\n *\n * @package OCA\\FullTextSearch\\Service\n */\nclass PlatformService {\n\t/** @var PlatformWrapper[] */\n\tprivate array $platforms = [];\n\tprivate ?PlatformWrapper $platform = null;\n\tprivate bool $platformsLoaded = false;\n\n\tpublic function __construct(\n\t\tprivate AppManager $appManager,\n\t\tprivate readonly IAppConfig $appConfig,\n\t\tprivate LoggerInterface $logger,\n\t) {\n\t}\n\n\n\t/**\n\t * @param bool $silent\n\t *\n\t * @return PlatformWrapper\n\t * @throws Exception\n\t */\n\tpublic function getPlatform(bool $silent = false): PlatformWrapper {\n\t\ttry {\n\t\t\t$this->loadPlatform();\n\t\t} catch (Exception $e) {\n\t\t\tif (!$silent) {\n\t\t\t\t$this->logger->warning($e->getMessage());\n\t\t\t}\n\t\t\tthrow $e;\n\t\t}\n\n\t\treturn $this->platform;\n\t}\n\n\n\t/**\n\t * @return PlatformWrapper[]\n\t * @throws Exception\n\t */\n\tpublic function getPlatforms(): array {\n\t\t$this->loadPlatforms();\n\n\t\t$platforms = [];\n\t\tforeach ($this->platforms as $wrapper) {\n\t\t\t$class = $wrapper->getClass();\n\t\t\ttry {\n\t\t\t\t$platform = \\OCP\\Server::get((string)$class);\n\t\t\t\tif (!($platform instanceof IFullTextSearchPlatform)) {\n\t\t\t\t\t$this->logger->warning($class . ' does not implement ' . IFullTextSearchPlatform::class);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$wrapper->setPlatform($platform);\n\t\t\t\t$platforms[] = $wrapper;\n\t\t\t} catch (ContainerExceptionInterface $e) {\n\t\t\t\t/** we cycle */\n\t\t\t\t$this->logger->warning($e->getMessage());\n\t\t\t}\n\n\t\t}\n\n\t\treturn $platforms;\n\t}\n\n\n\t/**\n\t * @throws Exception\n\t */\n\tprivate function loadPlatforms() {\n\t\tif ($this->platformsLoaded) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$apps = $this->appManager->getInstalledApps();\n\t\t\tforeach ($apps as $appId) {\n\t\t\t\t$this->loadPlatformsFromApp($appId);\n\t\t\t}\n\n\t\t\t$this->platformsLoaded = true;\n\t\t} catch (Exception $e) {\n\t\t\t$this->logger->warning($e->getMessage());\n\t\t\tthrow $e;\n\t\t}\n\n\t}\n\n\n\t/**\n\t * @throws Exception\n\t * @throws PlatformDoesNotExistException\n\t * @throws PlatformIsNotCompatibleException\n\t * @throws PlatformNotSelectedException\n\t * @throws QueryException\n\t */\n\tprivate function loadPlatform() {\n\t\tif ($this->platform !== null) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->loadPlatforms();\n\n\t\t$selected = $this->getSelectedPlatform();\n\t\t$platform = OC::$server->query((string)$selected->getClass());\n\t\tif (!($platform instanceof IFullTextSearchPlatform)) {\n\t\t\tthrow new PlatformIsNotCompatibleException(\n\t\t\t\t$selected->getClass() . ' is not a compatible FullTextSearchPlatform'\n\t\t\t);\n\t\t}\n\n\t\t$platform->loadPlatform();\n\t\t$selected->setPlatform($platform);\n\n\t\t$this->platform = $selected;\n\t}\n\n\n\t/**\n\t * @return PlatformWrapper\n\t * @throws PlatformDoesNotExistException\n\t * @throws PlatformNotSelectedException\n\t */\n\tprivate function getSelectedPlatform(): PlatformWrapper {\n\t\t$selected = $this->appConfig->getAppValueString(ConfigLexicon::SEARCH_PLATFORM);\n\t\tif ($selected === '') {\n\t\t\tthrow new PlatformNotSelectedException(\n\t\t\t\t'Admin have not selected any IFullTextSearchPlatform'\n\t\t\t);\n\t\t}\n\n\t\tforeach ($this->platforms as $wrapper) {\n\t\t\tif ($wrapper->getClass() === $selected) {\n\t\t\t\treturn $wrapper;\n\t\t\t}\n\t\t}\n\n\t\tthrow new PlatformDoesNotExistException(\n\t\t\t'FullTextSearchPlatform ' . $selected . ' is not available'\n\t\t);\n\t}\n\n\n\t/**\n\t * @param string $appId\n\t */\n\tprivate function loadPlatformsFromApp(string $appId) {\n\t\t$appInfo = $this->appManager->getAppInfo($appId);\n\t\tif (!is_array($appInfo) || !key_exists('fulltextsearch', $appInfo)\n\t\t\t|| !is_array($appInfo['fulltextsearch'])\n\t\t\t|| !key_exists('platform', $appInfo['fulltextsearch'])) {\n\t\t\treturn;\n\t\t}\n\n\t\t$platforms = $appInfo['fulltextsearch']['platform'];\n\t\tif (!is_array($platforms)) {\n\t\t\t$platforms = [$platforms];\n\t\t}\n\n\t\t$wrappers = [];\n\t\tforeach ($platforms as $class) {\n\t\t\t$wrapper = new PlatformWrapper($appId, $class);\n\t\t\t$wrapper->setVersion($this->appConfig->getAppValueString('installed_version'));\n\t\t\t$wrappers[] = $wrapper;\n\t\t}\n\n\t\t$this->platforms = array_merge($this->platforms, $wrappers);\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Service/ProviderService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\nuse Exception;\nuse OC;\nuse OC\\App\\AppManager;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderIsNotCompatibleException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderIsNotUniqueException;\nuse OCA\\FullTextSearch\\Model\\ProviderWrapper;\nuse OCP\\AppFramework\\QueryException;\nuse OCP\\AppFramework\\Services\\IAppConfig;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Service\\IProviderService;\nuse OCP\\ServerVersion;\nuse OCP\\Util;\nuse Psr\\Log\\LoggerInterface;\n\nclass ProviderService implements IProviderService {\n\t/** @var ProviderWrapper[] */\n\tprivate array $providers = [];\n\tprivate bool $providersLoaded = false;\n\n\tpublic function __construct(\n\t\tprivate AppManager $appManager,\n\t\tprivate readonly IAppConfig $appConfig,\n\t\tprivate readonly ServerVersion $serverVersion,\n\t\tprivate LoggerInterface $logger,\n\t) {\n\t}\n\n\n\t/**\n\t * Load all FullTextSearchProviders set in any info.xml file\n\t *\n\t * @throws Exception\n\t */\n\tprivate function loadProviders() {\n\t\tif ($this->providersLoaded) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t$apps = $this->appManager->getInstalledApps();\n\t\t\tforeach ($apps as $appId) {\n\t\t\t\t$this->loadProvidersFromApp($appId);\n\t\t\t}\n\t\t} catch (Exception $e) {\n\t\t\t$this->logger->warning('could not load providers', ['exception' => $e]);\n\t\t}\n\n\t\t$this->providersLoaded = true;\n\t}\n\n\n\t/**\n\t * @param string $appId\n\t * @param string $providerId\n\t *\n\t * @throws ProviderIsNotCompatibleException\n\t * @throws ProviderIsNotUniqueException\n\t * @throws QueryException\n\t */\n\tpublic function loadProvider(string $appId, string $providerId) {\n\t\t$provider = OC::$server->query((string)$providerId);\n\t\tif (!($provider instanceof IFullTextSearchProvider)) {\n\t\t\tthrow new ProviderIsNotCompatibleException($providerId . ' is not a compatible IFullTextSearchProvider');\n\t\t}\n\n\t\t$this->providerIdMustBeUnique($provider);\n\t\ttry {\n\t\t\t$provider->loadProvider();\n\t\t\t$wrapper = new ProviderWrapper($appId, $provider);\n\t\t\t$wrapper->setVersion($this->appConfig->getAppValueString('installed_version'));\n\t\t\t$this->providers[] = $wrapper;\n\t\t} catch (Exception $e) {\n\t\t}\n\t}\n\n\t/**\n\t * @return ProviderWrapper[]\n\t * @throws Exception\n\t */\n\tpublic function getProviders(): array {\n\t\t$this->loadProviders();\n\n\t\treturn $this->providers;\n\t}\n\n\t/**\n\t * @return IFullTextSearchProvider[]\n\t * @throws Exception\n\t */\n\tpublic function getConfiguredProviders(): array {\n\t\t$this->loadProviders();\n\n\t\t$providers = [];\n\t\tforeach ($this->providers as $providerWrapper) {\n\t\t\t$providers[] = $providerWrapper->getProvider();\n\t\t}\n\n\t\treturn $providers;\n\t}\n\n\n\t/**\n\t * @param array $providerList\n\t *\n\t * @return IFullTextSearchProvider[]\n\t * @throws Exception\n\t * @throws ProviderDoesNotExistException\n\t */\n\tpublic function getFilteredProviders(array $providerList): array {\n\t\t$this->loadProviders();\n\n\t\t$providers = $this->getConfiguredProviders();\n\t\tif (in_array('all', $providerList)) {\n\t\t\treturn $providers;\n\t\t}\n\n\t\t$ret = [];\n\t\tforeach ($providerList as $providerId) {\n\t\t\t$providerWrapper = $this->getProvider($providerId);\n\t\t\t$ret[] = $providerWrapper->getProvider();\n\t\t}\n\n\t\treturn $ret;\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t *\n\t * @return ProviderWrapper\n\t * @throws Exception\n\t * @throws ProviderDoesNotExistException\n\t */\n\tpublic function getProvider(string $providerId): ProviderWrapper {\n\t\t$providers = $this->getProviders();\n\t\tforeach ($providers as $providerWrapper) {\n\t\t\t$provider = $providerWrapper->getProvider();\n\t\t\tif ($provider->getId() === $providerId) {\n\t\t\t\treturn $providerWrapper;\n\t\t\t}\n\t\t}\n\n\t\tthrow new ProviderDoesNotExistException('Provider \\'' . $providerId . '\\' does not exist');\n\t}\n\n\n\t/**\n\t * @param string $providerId\n\t *\n\t * @return bool\n\t * @deprecated will always return true to fit OC\\FullTextSearch\\FullTextSearchManager\n\t */\n\tpublic function isProviderIndexed(string $providerId): bool {\n\t\treturn true;\n\t}\n\n\n\t/**\n\t * @param string $appId\n\t */\n\tprivate function loadProvidersFromApp(string $appId) {\n\t\t$appInfo = $this->appManager->getAppInfo($appId);\n\t\tif (!is_array($appInfo) || !array_key_exists('fulltextsearch', $appInfo)\n\t\t\t|| !is_array($appInfo['fulltextsearch'])\n\t\t\t|| !key_exists('provider', $appInfo['fulltextsearch'])) {\n\t\t\treturn;\n\t\t}\n\n\t\t$providers = $appInfo['fulltextsearch']['provider'];\n\t\tif (!is_array($providers)) {\n\t\t\t$providers = [$providers];\n\t\t}\n\n\t\t$this->loadProvidersFromList($appId, $providers);\n\t}\n\n\n\t/**\n\t * @param string $appId\n\t * @param array $providers\n\t */\n\tprivate function loadProvidersFromList(string $appId, array $providers) {\n\t\tif (array_key_exists('@attributes', $providers)) {\n\t\t\t$providers = [$providers];\n\t\t}\n\t\tforeach ($providers as $provider) {\n\t\t\tif (is_array($provider)) {\n\t\t\t\t$attributes = $provider['@attributes'];\n\t\t\t\tif (array_key_exists('min-version', $attributes)\n\t\t\t\t\t&& $this->serverVersion->getMajorVersion() < (int)$attributes['min-version']) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (array_key_exists('max-version', $attributes)\n\t\t\t\t\t&& $this->serverVersion->getMajorVersion() > (int)$attributes['max-version']) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$provider = $provider['@value'];\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t$this->loadProvider($appId, $provider);\n\t\t\t} catch (Exception $e) {\n\t\t\t\t$this->logger->warning('Issue while loading Provider: ' . $appId . '/' . $provider, ['exception' => $e]);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider $provider\n\t *\n\t * @throws ProviderIsNotUniqueException\n\t * @throws Exception\n\t */\n\tprivate function providerIdMustBeUnique(IFullTextSearchProvider $provider) {\n\t\tforeach ($this->providers as $providerWrapper) {\n\t\t\t$knownProvider = $providerWrapper->getProvider();\n\t\t\tif ($knownProvider->getId() === $provider->getId()) {\n\t\t\t\tthrow new ProviderIsNotUniqueException(\n\t\t\t\t\t'FullTextSearchProvider ' . $provider->getId() . ' already exist'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchProvider[] $providers\n\t *\n\t * @return array\n\t */\n\tpublic function serialize(array $providers): array {\n\t\t$arr = [];\n\t\tforeach ($providers as $provider) {\n\t\t\t$arr[] = [\n\t\t\t\t'id' => $provider->getId(),\n\t\t\t\t'name' => $provider->getName()\n\t\t\t];\n\t\t}\n\n\t\treturn $arr;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function addJavascriptAPI() {\n\t\tUtil::addStyle(Application::APP_ID, 'fulltextsearch');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1.api');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1.settings');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1.searchbox');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1.result');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1.navigation');\n\t\tUtil::addScript(Application::APP_ID, 'fulltextsearch.v1');\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Service/RunningService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\nuse Exception;\nuse OCA\\FullTextSearch\\ConfigLexicon;\nuse OCA\\FullTextSearch\\Db\\TickRequest;\nuse OCA\\FullTextSearch\\Exceptions\\RunnerAlreadyUpException;\nuse OCA\\FullTextSearch\\Exceptions\\TickDoesNotExistException;\nuse OCA\\FullTextSearch\\Exceptions\\TickIsNotAliveException;\nuse OCA\\FullTextSearch\\Model\\Tick;\nuse OCP\\AppFramework\\Services\\IAppConfig;\n\nclass RunningService {\n\tpublic function __construct(\n\t\tprivate TickRequest $tickRequest,\n\t\tprivate readonly IAppConfig $appConfig,\n\t) {\n\t}\n\n\t/**\n\t * @param string $source\n\t *\n\t * @return int\n\t * @throws RunnerAlreadyUpException\n\t * @throws Exception\n\t */\n\tpublic function start(string $source): int {\n\n\t\tif ($this->isAlreadyRunning()) {\n\t\t\tthrow new RunnerAlreadyUpException('Index is already running');\n\t\t}\n\n\t\t$tick = new Tick($source);\n\t\t$tick->setStatus('run')\n\t\t\t ->setTick()\n\t\t\t ->setFirstTick()\n\t\t\t ->setInfoInt('runStart ', time());\n\n\t\treturn $this->tickRequest->create($tick);\n\t}\n\n\n\t/**\n\t * @param int $runId\n\t * @param string $action\n\t *\n\t * @throws TickDoesNotExistException\n\t * @throws TickIsNotAliveException\n\t */\n\tpublic function update(int $runId, string $action = '') {\n\t\t$tick = $this->tickRequest->getTickById($runId);\n\n\t\t$this->isStillAlive($tick, true);\n\t\t$tick->setTick();\n\n\t\tif ($action !== '' && $action !== $tick->getAction()) {\n\t\t\t$this->assignActionToTick($tick, $action);\n\t\t}\n\n\t\t$this->tickRequest->update($tick);\n\t}\n\n\n\t/**\n\t * @param int $runId\n\t * @param string $reason\n\t *\n\t * @throws TickDoesNotExistException\n\t */\n\tpublic function stop(int $runId, string $reason = '') {\n\t\t$tick = $this->tickRequest->getTickById($runId);\n\t\t$tick->setStatus('stop')\n\t\t\t ->setTick()\n\t\t\t ->setInfoInt('runStop', time())\n\t\t\t ->setInfoInt('totalDocuments', 42);\n\n\t\tif ($reason !== '') {\n\t\t\t$tick->setStatus('exception');\n\t\t\t$tick->setInfo('exception', $reason);\n\t\t}\n\n\t\t$this->tickRequest->update($tick);\n\t}\n\n\n\t/**\n\t * @param int $runId\n\t *\n\t * @return bool\n\t * @throws TickIsNotAliveException\n\t */\n\tpublic function isAlive(int $runId): bool {\n\t\t$tick = null;\n\t\ttry {\n\t\t\t$tick = $this->tickRequest->getTickById($runId);\n\t\t} catch (TickDoesNotExistException $e) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $this->isStillAlive($tick);\n\t}\n\n\n\t/**\n\t * @param Tick $tick\n\t * @param bool $exception\n\t *\n\t * @return bool\n\t * @throws TickIsNotAliveException\n\t */\n\tpublic function isStillAlive(Tick $tick, bool $exception = false): bool {\n\t\tif ($tick->getStatus() !== 'run') {\n\t\t\tif ($exception) {\n\t\t\t\tthrow new TickIsNotAliveException();\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isAlreadyRunning(): bool {\n\t\t$ttl = $this->appConfig->getAppValueInt(ConfigLexicon::TICK_TTL);\n\t\t$ticks = $this->tickRequest->getTicksByStatus('run');\n\n\t\t$isAlreadyRunning = false;\n\t\tforeach ($ticks as $tick) {\n\t\t\tif ($tick->getTick() < (time() - $ttl)) {\n\t\t\t\t$tick->setStatus('timeout');\n\t\t\t\t$this->tickRequest->update($tick);\n\t\t\t} else {\n\t\t\t\t$isAlreadyRunning = true;\n\t\t\t}\n\t\t}\n\n\t\treturn $isAlreadyRunning;\n\t}\n\n\n\t/**\n\t *\n\t */\n\tpublic function forceStop() {\n\t\t$ticks = $this->tickRequest->getTicksByStatus('run');\n\n\t\tforeach ($ticks as $tick) {\n\t\t\t$tick->setStatus('forceStop');\n\t\t\t$this->tickRequest->update($tick);\n\t\t}\n\t}\n\n\n\t/**\n\t * @param Tick $tick\n\t * @param string $action\n\t */\n\tprivate function assignActionToTick(Tick $tick, string $action) {\n\t\t$now = microtime(true);\n\t\t$preAction = $tick->getAction();\n\n\t\tif ($preAction !== '') {\n\t\t\t$preActionTotal = $tick->getInfoFloat($preAction . 'Total', 0);\n\t\t\t$preActionStart = $tick->getInfoFloat($preAction . 'Init', 0);\n\n\t\t\tif ($preActionStart > 0) {\n\n\t\t\t\t$preActionTotal += ($now - $preActionStart);\n\t\t\t\t$tick->setInfoFloat($preAction . 'Total', $preActionTotal);\n\t\t\t\t$tick->unsetInfo($preAction . 'Init');\n\t\t\t}\n\t\t}\n\t\t$tick->setAction($action)\n\t\t\t ->setInfoFloat($action . 'Init', $now);\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Service/SearchService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\nuse Exception;\nuse OC;\nuse OC\\FullTextSearch\\Model\\DocumentAccess;\nuse OC\\User\\NoUserException;\nuse OCA\\Circles\\CirclesManager;\nuse OCA\\Circles\\Model\\Circle;\nuse OCA\\FullTextSearch\\Exceptions\\EmptySearchException;\nuse OCA\\FullTextSearch\\Exceptions\\ProviderDoesNotExistException;\nuse OCA\\FullTextSearch\\Model\\SearchRequest;\nuse OCA\\FullTextSearch\\Model\\SearchResult;\nuse OCP\\FullTextSearch\\IFullTextSearchPlatform;\nuse OCP\\FullTextSearch\\IFullTextSearchProvider;\nuse OCP\\FullTextSearch\\Model\\IDocumentAccess;\nuse OCP\\FullTextSearch\\Model\\ISearchRequest;\nuse OCP\\FullTextSearch\\Model\\ISearchResult;\nuse OCP\\FullTextSearch\\Service\\ISearchService;\nuse OCP\\IGroupManager;\nuse OCP\\IUser;\nuse OCP\\IUserManager;\nuse Psr\\Log\\LoggerInterface;\n\nclass SearchService implements ISearchService {\n\n\tpublic function __construct(\n\t\tprivate IUserManager $userManager,\n\t\tprivate IGroupManager $groupManager,\n\t\tprivate ProviderService $providerService,\n\t\tprivate PlatformService $platformService,\n\t) {\n\t}\n\n\n\t/**\n\t * @param array $request\n\t *\n\t * @return ISearchRequest\n\t */\n\tpublic function generateSearchRequest(array $request): ISearchRequest {\n\t\t$searchRequest = new SearchRequest();\n\t\t$searchRequest->importFromArray($request);\n\n\t\treturn $searchRequest;\n\t}\n\n\n\t/**\n\t * @param string $userId\n\t * @param ISearchRequest $request\n\t *\n\t * @return ISearchResult[]\n\t * @throws EmptySearchException\n\t * @throws Exception\n\t * @throws ProviderDoesNotExistException\n\t */\n\tpublic function search(string $userId, ISearchRequest $request): array {\n\t\t$this->searchRequestCannotBeEmpty($request);\n\n\t\t$user = $this->userManager->get($userId);\n\t\tif ($user === null) {\n\t\t\tthrow new NoUserException('User does not exist');\n\t\t}\n\n\t\t/** @var $request SearchRequest */\n\t\t$request->setAuthor($user->getUID());\n\t\t$request->cleanSearch();\n\n\t\t$providers = $this->providerService->getFilteredProviders($request->getProviders());\n\t\t$wrapper = $this->platformService->getPlatform();\n\t\t$platform = $wrapper->getPlatform();\n\n\t\t$access = $this->getDocumentAccessFromUser($user);\n\n\t\treturn $this->searchFromProviders($platform, $providers, $access, $request);\n\t}\n\n\n\t/**\n\t * @param ISearchRequest $request\n\t *\n\t * @throws EmptySearchException\n\t */\n\tprivate function searchRequestCannotBeEmpty(ISearchRequest $request) {\n\t\tif ($request === null || (strlen($request->getSearch()) < 1 && !$request->isEmptySearch())) {\n\t\t\tthrow new EmptySearchException('search cannot be empty');\n\t\t}\n\t}\n\n\n\t/**\n\t * @param IFullTextSearchPlatform $platform\n\t * @param IDocumentAccess $access\n\t * @param IFullTextSearchProvider[] $providers\n\t * @param SearchRequest $request\n\t *\n\t * @return ISearchResult[]\n\t */\n\tprivate function searchFromProviders(\n\t\tIFullTextSearchPlatform $platform,\n\t\tarray $providers,\n\t\tIDocumentAccess $access,\n\t\tSearchRequest $request\n\t): array {\n\t\t$result = [];\n\t\tforeach ($providers as $provider) {\n\t\t\t$provider->improveSearchRequest($request);\n\n\t\t\t$searchResult = new SearchResult($request);\n\t\t\t$searchResult->setProvider($provider);\n\t\t\t$searchResult->setPlatform($platform);\n\n\t\t\t$platform->searchRequest($searchResult, $access);\n\t\t\t$provider->improveSearchResult($searchResult);\n\n\t\t\t$result[] = $searchResult;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\n\t/**\n\t * @param IUser $user\n\t *\n\t * @return IDocumentAccess\n\t */\n\tprivate function getDocumentAccessFromUser(IUser $user): IDocumentAccess {\n\t\t$rights = new DocumentAccess();\n\n\t\t$rights->setViewerId($user->getUID());\n\t\t$rights->setGroups($this->groupManager->getUserGroupIds($user));\n\n\t\ttry {\n\t\t\t/** @var CirclesManager $circleManager */\n\t\t\t$circleManager = OC::$server->get(CirclesManager::class);\n\t\t\t$circleManager->startsession();\n\t\t\t$circleIds = array_map(\n\t\t\t\tfunction (Circle $circle): string {\n\t\t\t\t\treturn $circle->getSingleId();\n\t\t\t\t}, $circleManager->getCircles()\n\t\t\t);\n\t\t\t$rights->setCircles($circleIds);\n\t\t} catch (Exception $e) {\n\t\t}\n\n\t\treturn $rights;\n\t}\n}\n"
  },
  {
    "path": "lib/Service/SettingsService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\nuse Exception;\nuse OCA\\FullTextSearch\\ConfigLexicon;\n\nclass SettingsService {\n\tpublic function __construct(\n\t\tprivate PlatformService $platformService,\n\t\tprivate ProviderService $providerService,\n\t) {\n\t}\n\n\t/**\n\t * @param array $data\n\t *\n\t * @return bool\n\t */\n\tpublic function checkConfig(array &$data): bool {\n\t\t// convert to bool\n\t\tif (is_string($data[ConfigLexicon::APP_NAVIGATION])) {\n\t\t\t$data[ConfigLexicon::APP_NAVIGATION] = in_array($data[ConfigLexicon::APP_NAVIGATION], ['1', 'yes', 'on', 'true']);\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n\t/**\n\t * @param array $data\n\t *\n\t * @throws Exception\n\t */\n\tpublic function completeSettings(array &$data) {\n\t\t$data = array_merge(\n\t\t\t$data, [\n\t\t\t\t\t 'platforms_all' => $this->completeSettingsPlatforms(),\n\t\t\t\t\t 'providers_all' => $this->completeSettingsProviders()\n\t\t\t\t ]\n\t\t);\n\n\t}\n\n\n\t/**\n\t * @return array\n\t * @throws Exception\n\t */\n\tprivate function completeSettingsPlatforms(): array {\n\t\t$list = [];\n\t\t$platforms = $this->platformService->getPlatforms();\n\t\tforeach ($platforms as $wrapper) {\n\t\t\t$platform = $wrapper->getPlatform();\n\t\t\t$list[$wrapper->getClass()] = [\n\t\t\t\t'id'   => $platform->getId(),\n\t\t\t\t'name' => $platform->getName()\n\t\t\t];\n\t\t}\n\n\t\treturn $list;\n\t}\n\n\n\t/**\n\t * @return array\n\t * @throws Exception\n\t */\n\tprivate function completeSettingsProviders(): array {\n\t\t$list = [];\n\t\t$providers = $this->providerService->getProviders();\n\t\tforeach ($providers as $providerWrapper) {\n\t\t\t$provider = $providerWrapper->getProvider();\n\n\t\t\t$list[$provider->getId()] = $provider->getName();\n\t\t}\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "lib/Service/TestService.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Service;\n\n\nuse Exception;\nuse OC\\FullTextSearch\\Model\\DocumentAccess;\nuse OC\\FullTextSearch\\Model\\IndexDocument;\nuse OCA\\FullTextSearch\\Provider\\TestProvider;\nuse OCP\\FullTextSearch\\Model\\IIndexDocument;\nuse OCP\\FullTextSearch\\Model\\IIndexOptions;\n\n\n/**\n * Class TestService\n *\n * @package OCA\\FullTextSearch\\Service\n */\nclass TestService {\n\n\tconst DOCUMENT_USER1 = 'user1';\n\tconst DOCUMENT_USER2 = 'User number_2';\n\tconst DOCUMENT_USER3 = 'User3';\n\tconst DOCUMENT_USER4 = 'User@4';\n\tconst DOCUMENT_NOTUSER = 'notuser';\n\n\tconst DOCUMENT_GROUP1 = 'group_1';\n\tconst DOCUMENT_GROUP2 = 'Group_2';\n\tconst DOCUMENT_NOTGROUP = 'group_3';\n\n\tconst DOCUMENT_TYPE_LICENSE = 'license';\n\tconst DOCUMENT_TYPE_SIMPLE = 'simple';\n\n\tconst DOCUMENT_INDEXING_OPTION = 'indexing';\n\tconst DOCUMENT_INDEXING_ACCESS = 'access';\n\n\tconst LICENSE_HASH = '108322602bb857915803a84e23a2cc2f';\n\n\tpublic function __construct() {\n\t}\n\n\t/**\n\t * @param IIndexOptions $options\n\t *\n\t * @return IIndexDocument\n\t */\n\tpublic function generateIndexDocumentContentLicense(IIndexOptions $options): IIndexDocument {\n\t\t$indexDocument = $this->generateIndexDocument(self::DOCUMENT_TYPE_LICENSE);\n\n\t\t$content = file_get_contents(__DIR__ . '/../../LICENSE');\n\t\t$indexDocument->setContent($content);\n\n\t\tif ($options === null) {\n\t\t\treturn $indexDocument;\n\t\t}\n\n\t\tif ($options->getOption(self::DOCUMENT_INDEXING_OPTION, '')\n\t\t\t=== self::DOCUMENT_INDEXING_ACCESS) {\n\t\t\t$indexDocument->getAccess()\n\t\t\t\t\t\t  ->setGroups([self::DOCUMENT_GROUP1, self::DOCUMENT_GROUP2]);\n\t\t\t$indexDocument->getAccess()\n\t\t\t\t\t\t  ->setUsers([self::DOCUMENT_USER2, self::DOCUMENT_USER3, self::DOCUMENT_USER4]);\n\t\t}\n\n\t\treturn $indexDocument;\n\t}\n\n\n\t/**\n\t * @param IIndexOptions $options\n\t *\n\t * @return IIndexDocument\n\t */\n\tpublic function generateIndexDocumentSimple(IIndexOptions $options): IIndexDocument {\n\n\t\t$indexDocument = $this->generateIndexDocument(self::DOCUMENT_TYPE_SIMPLE);\n\t\t$indexDocument->setContent('testing document is a simple test');\n\n\t\treturn $indexDocument;\n\t}\n\n\n\t/**\n\t * @param IIndexDocument $origIndex\n\t * @param IIndexDocument $compareIndex\n\t *\n\t * @throws Exception\n\t */\n\tpublic function compareIndexDocument(IIndexDocument $origIndex, IIndexDocument $compareIndex) {\n\t\tif ($origIndex->getAccess()\n\t\t\t\t\t  ->getOwnerId() !== $compareIndex->getAccess()\n\t\t\t\t\t\t\t\t\t\t\t\t\t  ->getOwnerId()) {\n\t\t\tthrow new Exception('issue with AccessDocument');\n\t\t}\n\n\t\t$methods = [\n\t\t\t'getId',\n\t\t\t'getProviderId',\n\t\t\t'getTitle',\n\t\t\t'getSource'\n\t\t];\n\n\t\tforeach ($methods as $method) {\n\t\t\t$orig = call_user_func([$origIndex, $method]);\n\t\t\t$compare = call_user_func([$compareIndex, $method]);\n\t\t\tif ($orig !== $compare) {\n\t\t\t\tthrow new Exception($method . '() orig:' . $orig . ' compare:' . $compare);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param string $documentType\n\t *\n\t * @return IIndexDocument\n\t */\n\tprivate function generateIndexDocument(string $documentType): IIndexDocument {\n\t\t$indexDocument = new IndexDocument(TestProvider::TEST_PROVIDER_ID, $documentType);\n\n\t\t$access = new DocumentAccess(self::DOCUMENT_USER1);\n\t\t$indexDocument->setAccess($access);\n\n\t\treturn $indexDocument;\n\t}\n\n}\n"
  },
  {
    "path": "lib/Settings/Admin.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Settings;\n\n\nuse Exception;\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCA\\FullTextSearch\\Service\\ConfigService;\nuse OCP\\AppFramework\\Http\\TemplateResponse;\nuse OCP\\IL10N;\nuse OCP\\IURLGenerator;\nuse OCP\\Settings\\ISettings;\n\n/**\n * Class Admin\n *\n * @package OCA\\FullTextSearch\\Settings\n */\nclass Admin implements ISettings {\n\tpublic function __construct(\n\t) {\n\t}\n\n\n\t/**\n\t * @return TemplateResponse\n\t * @throws Exception\n\t */\n\tpublic function getForm(): TemplateResponse {\n\t\treturn new TemplateResponse(Application::APP_ID, 'settings.admin', []);\n\t}\n\n\n\t/**\n\t * @return string the section ID, e.g. 'sharing'\n\t */\n\tpublic function getSection(): string {\n\t\treturn Application::APP_ID;\n\t}\n\n\n\t/**\n\t * @return int whether the form should be rather on the top or bottom of\n\t * the admin section. The forms are arranged in ascending order of the\n\t * priority values. It is required to return a value between 0 and 100.\n\t *\n\t * keep the server setting at the top, right after \"server settings\"\n\t */\n\tpublic function getPriority(): int {\n\t\treturn 0;\n\t}\n\n\n}\n"
  },
  {
    "path": "lib/Settings/AdminSection.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Settings;\n\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCP\\IL10N;\nuse OCP\\IURLGenerator;\nuse OCP\\Settings\\IIconSection;\n\n\n/**\n * Class AdminSection\n *\n * @package OCA\\FullTextSearch\\Settings\n */\nclass AdminSection implements IIconSection {\n\n\n\t/** @var IL10N */\n\tprivate $l10n;\n\n\t/** @var IURLGenerator */\n\tprivate $urlGenerator;\n\n\n\t/**\n\t * @param IL10N $l10n\n\t * @param IURLGenerator $urlGenerator\n\t */\n\tpublic function __construct(IL10N $l10n, IURLGenerator $urlGenerator) {\n\t\t$this->l10n = $l10n;\n\t\t$this->urlGenerator = $urlGenerator;\n\t}\n\n\t/**\n\t * {@inheritdoc}\n\t */\n\tpublic function getID(): string {\n\t\treturn Application::APP_ID;\n\t}\n\n\t/**\n\t * {@inheritdoc}\n\t */\n\tpublic function getName(): string {\n\t\treturn $this->l10n->t('Full text search');\n\t}\n\n\t/**\n\t * {@inheritdoc}\n\t */\n\tpublic function getPriority(): int {\n\t\treturn 55;\n\t}\n\n\t/**\n\t * {@inheritdoc}\n\t */\n\tpublic function getIcon(): string {\n\t\treturn $this->urlGenerator->imagePath(Application::APP_ID, 'fulltextsearch_black.svg');\n\t}\n}\n"
  },
  {
    "path": "lib/Tools/Exceptions/ArrayNotFoundException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Tools\\Exceptions;\n\nuse Exception;\n\nclass ArrayNotFoundException extends Exception {\n}\n\n"
  },
  {
    "path": "lib/Tools/Exceptions/ItemNotFoundException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Tools\\Exceptions;\n\nuse Exception;\n\nclass ItemNotFoundException extends Exception {\n}\n"
  },
  {
    "path": "lib/Tools/Exceptions/MalformedArrayException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Tools\\Exceptions;\n\nuse Exception;\n\nclass MalformedArrayException extends Exception {\n}\n"
  },
  {
    "path": "lib/Tools/Exceptions/UnknownTypeException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Tools\\Exceptions;\n\nuse Exception;\n\nclass UnknownTypeException extends Exception {\n}\n"
  },
  {
    "path": "lib/Tools/Traits/TArrayTools.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nnamespace OCA\\FullTextSearch\\Tools\\Traits;\n\nuse OCA\\FullTextSearch\\Tools\\Exceptions\\ArrayNotFoundException;\nuse OCA\\FullTextSearch\\Tools\\Exceptions\\ItemNotFoundException;\nuse OCA\\FullTextSearch\\Tools\\Exceptions\\MalformedArrayException;\nuse OCA\\FullTextSearch\\Tools\\Exceptions\\UnknownTypeException;\nuse Exception;\nuse JsonSerializable;\n\ntrait TArrayTools {\n\n\tstatic string $TYPE_NULL = 'Null';\n\tstatic  string $TYPE_STRING = 'String';\n\tstatic  string $TYPE_ARRAY = 'Array';\n\tstatic string $TYPE_BOOLEAN = 'Boolean';\n\tstatic string $TYPE_INTEGER = 'Integer';\n\tstatic string $TYPE_SERIALIZABLE = 'Serializable';\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param string $default\n\t *\n\t * @return string\n\t */\n\tprotected function get(string $k, array $arr, string $default = ''): string {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\t$r = $arr[$subs[0]];\n\t\t\t\tif (!is_array($r)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->get($subs[1], $r, $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\tif ($arr[$k] === null || !is_string($arr[$k]) && (!is_int($arr[$k]))) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn (string)$arr[$k];\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param int $default\n\t *\n\t * @return int\n\t */\n\tprotected function getInt(string $k, array $arr, int $default = 0): int {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\t$r = $arr[$subs[0]];\n\t\t\t\tif (!is_array($r)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->getInt($subs[1], $r, $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\tif ($arr[$k] === null) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn intval($arr[$k]);\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param float $default\n\t *\n\t * @return float\n\t */\n\tprotected function getFloat(string $k, array $arr, float $default = 0): float {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\t$r = $arr[$subs[0]];\n\t\t\t\tif (!is_array($r)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->getFloat($subs[1], $r, $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\tif ($arr[$k] === null) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn intval($arr[$k]);\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param bool $default\n\t *\n\t * @return bool\n\t */\n\tprotected function getBool(string $k, array $arr, bool $default = false): bool {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->getBool($subs[1], $arr[$subs[0]], $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\tif ($arr[$k] === null) {\n\t\t\treturn $default;\n\t\t}\n\n\t\tif (is_bool($arr[$k])) {\n\t\t\treturn $arr[$k];\n\t\t}\n\n\t\t$sk = (string)$arr[$k];\n\t\tif ($sk === '1' || strtolower($sk) === 'true') {\n\t\t\treturn true;\n\t\t}\n\n\t\tif ($sk === '0' || strtolower($sk) === 'false') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $default;\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param JsonSerializable|null $default\n\t *\n\t * @return mixed\n\t */\n\tprotected function getObj(string $k, array $arr, ?JsonSerializable $default = null): ?JsonSerializable {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->getObj($subs[1], $arr[$subs[0]], $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\treturn $arr[$k];\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param array $default\n\t *\n\t * @return array\n\t */\n\tprotected function getArray(string $k, array $arr, array $default = []): array {\n\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t$subs = explode('.', $k, 2);\n\t\t\tif (sizeof($subs) > 1) {\n\t\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\t$r = $arr[$subs[0]];\n\t\t\t\tif (!is_array($r)) {\n\t\t\t\t\treturn $default;\n\t\t\t\t}\n\n\t\t\t\treturn $this->getArray($subs[1], $r, $default);\n\t\t\t} else {\n\t\t\t\treturn $default;\n\t\t\t}\n\t\t}\n\n\t\t$r = $arr[$k];\n\t\tif ($r === null || (!is_array($r) && !is_string($r))) {\n\t\t\treturn $default;\n\t\t}\n\n\t\tif (is_string($r)) {\n\t\t\t$r = json_decode($r, true);\n\t\t}\n\n\t\tif (!is_array($r)) {\n\t\t\treturn $default;\n\t\t}\n\n\t\treturn $r;\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t *\n\t * @return bool\n\t */\n\tpublic function validKey(string $k, array $arr): bool {\n\t\tif (array_key_exists($k, $arr)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t$subs = explode('.', $k, 2);\n\t\tif (sizeof($subs) > 1) {\n\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t$r = $arr[$subs[0]];\n\t\t\tif (!is_array($r)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn $this->validKey($subs[1], $r);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param array $arr\n\t * @param array $import\n\t * @param array $default\n\t *\n\t * @return array\n\t */\n\tprotected function getList(string $k, array $arr, array $import, array $default = []): array {\n\t\t$list = $this->getArray($k, $arr, $default);\n\n\t\t$r = [];\n\t\tlist ($obj, $method) = $import;\n\t\tforeach ($list as $item) {\n\t\t\ttry {\n\t\t\t\t$o = new $obj();\n\t\t\t\t$o->$method($item);\n\n\t\t\t\t$r[] = $o;\n\t\t\t} catch (Exception $e) {\n\t\t\t}\n\t\t}\n\n\t\treturn $r;\n\t}\n\n\n\t/**\n\t * @param string $k\n\t * @param string $value\n\t * @param array $list\n\t *\n\t * @return mixed\n\t * @throws ArrayNotFoundException\n\t */\n\tprotected function extractArray(string $k, string $value, array $list) {\n\t\tforeach ($list as $arr) {\n\t\t\tif (!array_key_exists($k, $arr)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($arr[$k] === $value) {\n\t\t\t\treturn $arr;\n\t\t\t}\n\t\t}\n\n\t\tthrow new ArrayNotFoundException();\n\t}\n\n\n\t/**\n\t * @param string $key\n\t * @param array $arr\n\t * @param bool $root\n\t *\n\t * @return string\n\t * @throws ItemNotFoundException\n\t * @throws UnknownTypeException\n\t */\n\tpublic function typeOf(string $key, array $arr, bool $root = true): string {\n\t\tif (array_key_exists($key, $arr)) {\n\t\t\t$item = $arr[$key];\n\n\t\t\tif (is_null($item)) {\n\t\t\t\treturn self::$TYPE_NULL;\n\t\t\t}\n\n\t\t\tif (is_string($item)) {\n\t\t\t\treturn self::$TYPE_STRING;\n\t\t\t}\n\n\t\t\tif (is_array($item)) {\n\t\t\t\treturn self::$TYPE_ARRAY;\n\t\t\t}\n\n\t\t\tif (is_bool($item)) {\n\t\t\t\treturn self::$TYPE_BOOLEAN;\n\t\t\t}\n\n\t\t\tif (is_int($item)) {\n\t\t\t\treturn self::$TYPE_INTEGER;\n\t\t\t}\n\n\t\t\tif ($item instanceof JsonSerializable) {\n\t\t\t\treturn self::$TYPE_SERIALIZABLE;\n\t\t\t}\n\n\t\t\tthrow new UnknownTypeException();\n\t\t}\n\n\t\t$subs = explode('.', $key, 2);\n\t\tif (sizeof($subs) > 1) {\n\t\t\tif (!array_key_exists($subs[0], $arr)) {\n\t\t\t\tthrow new ItemNotFoundException();\n\t\t\t}\n\n\t\t\t$r = $arr[$subs[0]];\n\t\t\tif (is_array($r)) {\n\t\t\t\treturn $this->typeOf($subs[1], $r);\n\t\t\t}\n\t\t}\n\n\t\tthrow new ItemNotFoundException();\n\t}\n\n\n\t/**\n\t * @param array $keys\n\t * @param array $arr\n\t *\n\t * @throws MalformedArrayException\n\t */\n\tprotected function mustContains(array $keys, array $arr) {\n\t\tforeach ($keys as $key) {\n\t\t\tif (!array_key_exists($key, $arr)) {\n\t\t\t\tthrow new MalformedArrayException(\n\t\t\t\t\t'source: ' . json_encode($arr) . ' - missing key: ' . $key\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * @param array $arr\n\t */\n\tprotected function cleanArray(array &$arr) {\n\t\t$arr = array_filter(\n\t\t\t$arr,\n\t\t\tfunction($v) {\n\t\t\t\tif (is_string($v)) {\n\t\t\t\t\treturn ($v !== '');\n\t\t\t\t}\n\t\t\t\tif (is_array($v)) {\n\t\t\t\t\treturn !empty($v);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t);\n\t}\n}\n\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "# SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n# SPDX-License-Identifier: AGPL-3.0-or-later\nsite_name: Nextcloud FullTextSearch\n\ntheme:\n  name: readthedocs\n  highlightjs: true\n  hljs_languages:\n    - php\n    - js\n    - bash\n\nmarkdown_extensions:\n  - admonition\n  - def_list\n  - toc:\n      permalink: \"#\"\n      baselevel: 2\n\nnav:\n  - 'Nextcloud FullTextSearch': 'index.md'\n  - 'Installation': 'installation.md'\n  - 'Configuration': 'configuration.md'\n  - Commands: 'commands.md'\n  - 'API documentation':\n      - 'Collections': 'api/collections.md'\n"
  },
  {
    "path": "screenshots/draw.io/Index.xml",
    "content": "<mxfile userAgent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/8.8.0 Chrome/61.0.3163.100 Electron/2.0.2 Safari/537.36\" version=\"9.0.8\" editor=\"www.draw.io\" type=\"device\"><diagram id=\"9de7b454-8821-2687-c9e0-4898eca13e57\" name=\"Page-1\">7Vttb6M4EP41+djIxpjAxzZt91balSp1pdv9dKLBTbgSnAWnL/frzwab4BcoSUgvt1pW2sIYjP3MM+OZMZmg+fr1UxFvVl9pQrKJB5LXCbqeeB6EIOB/hOStlmBxJQTLIk3kTTvBffoPkUIgpds0IaV2I6M0Y+lGFy5onpMF02RxUdAX/bZHmulv3cRLYgnuF3FmS/9ME7aqpSEGO/kfJF2uWDNh2fIQL56WBd3m8n0TDz1WR928jlVf8v5yFSf0pSVCNxM0Lyhl9dn6dU4yga2CrX7utqM1lLg/x9lWTuVqNyIPeGr8BcnZoA4jq0M5cPamwKo6J+IBOEFXL6uUkftNvBCtL5weXLZi60w2l6ygT2ROM1pUT6OoOnjLY5plLflNIP5xuT3kZpakYOS1pTE5h0+Ergkr3vgtslXB/aZfvux06yvdrlp69SMpjCWflk3PO8z4iYTNDaHqYzQIu4DqhnYMCBssJIYosEFELhA9gEcAEf4SIGLwLhGR78AQgTGI6DkwDDIm0KB8Tm0wg59bqhouyso/Xwr/4W9ed438bCn+3m6r93wjr6KTexIXi5XqmQ+q7ry+VYkfip3EUCMT3fT4jJzmxFCgFMVZusz55YJriHD5lVBMyr36pWxYp0kiXuMkx44+YCRtQ6hr28eWtr3AaTEjKBudSNlzfhOpOrgr6DNfpovfquaG7f+HqvZPpGplyeAui9kjLda/Nc01jbC+DqouPkTVuFPVSfrs1LRA/kKCKFSdkUdmq3pfwsDQRZiGCD3Oonq8RaTm3ts0I+VfYi0RC4niHocEXFH6tI6LJ3frdDp9h5dcXKGjS8cB7KELQZXTqAdRTx+FY8QdE7Hed8rJHTKXbufRrXlNq3/dZHHJLbwcEkeYs/9l3U6gry8YOVIY4PA6cAy3A2Fo+Z3rmMUPcUm6SdzoV9YHbj9zTF77uNyzgOiI7ptkguqww/7b6hBPqDwcjpUuGYE+tgOCRjVtfQUjqCucWdpKBfRpvrSn1p//R3ZBoV+913SxXVcu36Xmo5T6jopm+6rIM8JzhD17JYcn0lFk60hws1JREK/F3POHcvOuOZl4O9eS5tFvKePYV8tmszSLi8vFgpRlfW69r8dsQVyVldJcOPiYpTQXCt6QRfqYLiaiaCf+WxEtWzgrEmCsh3NcuzYJkIsE3ggksN2qwrncxLnCeWe8bmbw17RvP8ZATRJtN0nMyJ6s0EdZsphty9Fd/OhEQEYG54c2EWYOIoT+CESILDRIsiT38lLGJDpALTA4O9h3KRbnP8T5FLdhEk1/E8beZLE93nLbRFe0YCu6pHmcfaF08+7y2Q10SbfFoqniS5jEHHqxLwgPDtNnveruAlI+ekfTKiJtCizRFIQAhTjEHogA1P05NAqPLC6WhMk+DOU0gxoWEKkZtiz3a5yLXQUP3BQFLRyEv4+fj7OkVHjXpCfuOiuDMpdXH9nLK3QZlFqGj4pYwYCCMcmTS7FZJMJ8nnOUfMlym9UUgEgzrQggeX1HipSPTqQItfnlfKTfVQfion7Cw8MxbtuSihNq7upB2bvm1ULaVZlXsiOt0NwZsHZN6vlYdmd1ZBXRfKOjMQ3YG48fUOPGzNJ0wwkwhYGv8wJwq5ACk0oDCRI4CBKeFUEw0GOswwliBGtDCcK1GL+1btuIG8qeAWODiHqWxE/qHg9nn6s6vy/7OjwN7g0IRBFFhRXVg9lD9QpV3jjQS4U2CYdHAR/DQmTsveHZgSxEBjuwWWIZz02FdoIwL6okS/TqgS9pFVLICGK/1B5C2wUuSU4KDnKVa5j1TXBE4vlY0PWesc8pgxkF68HBTBP1v1crwAY3Diu/jeAuOlYk3XfM2s7Dsax1Q6nF/4pZZ2L5vlE8bfL5A9afqd86kN7tzJt67eNUXgFC1zbQED7wXLxgw2gCehPBLgJ188fr54+2csDZWfEH4mgato7IUPuBbDJYOTNdxZiECWx+HFJfaPT7ww5tscaED6k9OH2PI2Ea7pAGM2W477YLvWJcHqBbJmpryX713iF1AntJ3sji6+ekrvEmcvEW1x9bRjh+5YWOTytOtvIC+9uKOV2v67J3b9gFntMyfVAYvhOKAduha6HYIRsC+GqCr4dFZ206nFD5OzQPLs+rQulHVOebr5j33/zSsD/pTufoiDf2JRGHvveR9mZ7y4L83JKSjWEF/ZqQOQqJq68O+l/m+Pbtf6zj0P6Y4IQqdm16HZXMOOPNJhxxRKkdsWiDpL6Z4QoolMM+kwjV93SFXkBvGrQPL9K7HJzxBEbHntEx0vsds0I74KcHgyv4dirbVz4banE6T7CLJ+dVqjc/XrzwRUm6W52DS2LRTKPFzNdfE0xD1DpmxsDHzHQG/Nri2NS4r4KCXTnMfB4EHTlMw6Vzpg30oqkXzAKAA4RCYJQ9MD6MNDAKNPIZpIFBZLDmdKQZYbNwEDu8vetrHb7mvPjhc370mD9Sm20j19t8OIXtYyR+8MvdD/Dq23e/ckQ3/wI=</diagram></mxfile>"
  },
  {
    "path": "screenshots/draw.io/Search.xml",
    "content": "<mxfile userAgent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/8.8.0 Chrome/61.0.3163.100 Electron/2.0.2 Safari/537.36\" version=\"9.0.8\" editor=\"www.draw.io\" type=\"device\"><diagram id=\"9de7b454-8821-2687-c9e0-4898eca13e57\" name=\"Page-1\">7Vpbb6M4FP41eWxkc89jm7azI81KVTvSdh5d4ibsAM4Yp0n3168NNmDsUJqQprsaRprAMRyb833nZjpx59nuC0Xr1Z9kgdOJAxa7iXs9cRzoAsh/hOS1kgTQqwRLmizkTY3gIfkHSyGQ0k2ywIV2IyMkZclaF8Ykz3HMNBmilGz1255Jqs+6RktsCB5ilJrSv5IFW1XSyAeN/A+cLFdqZgjkyBOKfy4p2eRyvonjPpdHNZwhpUveX6zQgmxbIvdm4s4pIaw6y3ZznArbKrNVz93uGY2CSu0LSjfyVa6aFTnAUeunOGeDFM4MhXLh7FUZq1SOxQNw4l5tVwnDD2sUi9EtpweXrViWyuGCUfITz0lKaPm0OysPPvKcpGlLfhOIf1xuLrl+S0wZ3rUQk+/wBZMMM/rKb5GjkTS3pKOy/rbB1p1J2aqFqxNKIZJ8WtaaG5vxE2k2uwmV4tFMuM9Q+007hgl9oNvQhRYjejYjAn8EI8L/pRFtRLTacBQiOhYbBikT1iD8ndrGDH5tiBq4KMr4fCnih7feNYP8bCl+bzflPN/xTih5wIjGK6WZL6pSXt2qxE+0kXRgZEJNT8zISY47AEoRSpNlzi9jjhDm8isBTMKj+qUcyJLFQkxjJUdDHzAS2tDzNbTVZQttJ7B6zAhguycCe85vwqWCO0peeJqmv6Hmju2B80HtnQhq5cngLkXsmdDsN9Icab+TB93AhFrBPzbU0BmQB3G+uBQ1sDBaiooiiUtzI8pMcRsQVYpayzRQHnwE7xL2KO8S5z+Ebad+bV28MEpr09wF2dC4rvnDSshXuMSqoq6KBhOFlpV9iz8pGcWcssmLvhKb5eUMdyQpPUWC7AV65IZOOA28EPhR5Lg+D64zXWP1PlJJu5zu6PWhrjfsV1tZxFBbsqS2yUDimJ3BMOIcwJCcL+uxpEXoq+sfLco8SncchT6zyMae4JzscfQIEXY9fyhbQl1P0C0Cx6RHaLKBI/EgL2Us1qNpixk1qhJxCSzU2QBDt02HCzAF9R13mCZ8wSLCl8o1rv2NGXuVuwRowwgXEcpWZElylH4jZP0mJ8eMTRXjTHINZs3waB993mjf4NrG9MhMEFisHZ41ETjeVAZnD8IwCPS0ELhTtxn0wk6nOzgt+J10EwbTIIqg5828yAOegv0Eju+aBcX9zcN3LrnHvza4YAbh9CAw1nbPbXnU3DHqMRt79jdeeuCEljYb2trsLn4HOa1ra7NPlGmt3un0uWd4jH8qsui59qwOGuhQX7gH5lrY4cxFdLpk63rjJNse1KEF9c+SUi1BXjLrA3JqFBremXAz75J8aa6/f298ZqoSQa3UBOqeWLXEX8Us1yTeZOXuSYAyESrzp2Ld6qD5ZPUD24SJ3nuBGUrS4sgw3IbegpN6k3d0weG0k7MiM8g6rsXxA+d4FGdmYbTEOaacK3bry09Ut9WWxj0uNimzbVuYMPC1IQEDKn9Q+RmFyufFO+xBci/uxgQDdk/OjHVnb8tV1n9rcyvyRvBXE+k5JXmJ8S3//5sIDg4orbzPgcFLUiRPypj9Tg2hmb7b1Oqgy29U1LIC3c+tZ0qyvbHCzpVTMiN6LzO6hVaNeJsZsCf5H1VoQdsW9zGFVm/NZK+1WuWUzZRa4lPM6qTM8+1xdcA7tHTi3cvUax2urjZ0pk77OFlZpfbWTtktg4E7X2ZP3FOr7+ePViPB8FPxB/oz3ho3x6wD+4Fs6rAy7IaKMQkTmPw4aNOrZ89L2wAFXSqNVYW/HXtse+yDA9L4O1vQrJ3FuhxANoyXWtzcb9VVWtrdU1fZNdQpeS0/YH7ls81FjSeTt7j+2Jrs+MwLLR8cT5Z5gfnFcU6yrCqOe0uxvWUXMIN3kpUA2Qt6VcjLPSob/OAJFYJIoCwX4xUhBRZnZM0Skp+2o2pM9I6WSm+oVED9iH6qbsot35AH9VD9DQ1bIeHbMcl5Lyssv684tnt3XVT7VxP/etiUfb4trvGOURRb6/TPRQsHdveGLX22zdO7G1iH8cIWp1vd0LAYPcBZ/2swzAaicEC85ZfNH4BWdU3zV7buzb8=</diagram></mxfile>"
  },
  {
    "path": "templates/navigate.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCP\\Util;\n\nUtil::addScript(Application::APP_ID, 'navigate');\nUtil::addStyle(Application::APP_ID, 'navigate');\n\n?>\n\n<div id=\"app-navigation\">\n\t<ul id=\"search_navigation\">\n\t\t<li style=\"height: 50px\">\n\t\t\t<div id=\"search_header\">\n\t\t\t\t<div>\n\t\t\t\t\t<input id=\"search_input\"\n\t\t\t\t\t\t   placeholder=\"<?php p(\n\t\t\t\t\t\t\t   $l->t('Search on %s', [$_['themingName']])\n\t\t\t\t\t\t   ); ?>\">\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</li>\n\t</ul>\n</div>\n\n<div id=\"app-content\">\n\t<div id=\"search_error\"></div>\n\t<div id=\"search_result\"></div>\n</div>\n\n<!-- <div id=\"search_json\"></div> -->\n\n\n<script id=\"template_entry\" type=\"text/template\">\n\t<div class=\"result_entry_default\">\n\t\t<div class=\"result_entry_left\">\n\t\t\t<div id=\"title\">&nbsp;</div>\n\t\t\t<div id=\"line1\">&nbsp;</div>\n\t\t\t<div id=\"line2\">&nbsp;</div>\n\t\t</div>\n\t\t<div class=\"result_entry_right\">\n\t\t\t<div id=\"source\">&nbsp;</div>\n\t\t\t<div id=\"score\">&nbsp;</div>\n\t\t</div>\n\t</div>\n\n</script>\n"
  },
  {
    "path": "templates/settings.admin.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nuse OCA\\FullTextSearch\\AppInfo\\Application;\nuse OCP\\Util;\n\n\nUtil::addScript(Application::APP_ID, 'admin.elements');\nUtil::addScript(Application::APP_ID, 'admin.settings');\nUtil::addScript(Application::APP_ID, 'admin');\n\nUtil::addStyle(Application::APP_ID, 'admin');\n\n?>\n\n\n<div id=\"fns\" class=\"section\">\n\t<span>\n\t\t<a href=\"https://github.com/nextcloud/fulltextsearch/wiki\" target=\"_blank\">\n\t\t\t<?php p(\n\t\t\t\t$l->t(\n\t\t\t\t\t'Please check the wiki for documentation related to the installation and the configuration of the full text search within your Nextcloud'\n\t\t\t\t)\n\t\t\t); ?></a>\n\n\t</span>\n\t&nbsp;<br/>\n\t&nbsp;<br/>\n\t<h2><?php p($l->t('General')) ?></h2>\n\t<div class=\"div-table\">\n\n\t\t<div class=\"div-table-row\">\n\t\t\t<div class=\"div-table-col div-table-col-left\">\n\t\t\t\t<span class=\"leftcol\"><?php p($l->t('Search Platform')); ?>:</span>\n\t\t\t\t<br/>\n\t\t\t\t<em><?php p(\n\t\t\t\t\t\t$l->t('Select the app to index content and answer search queries.')\n\t\t\t\t\t); ?></em>\n\t\t\t</div>\n\t\t\t<div class=\"div-table-col\">\n\t\t\t\t<select id=\"fts_platforms\">\n\t\t\t\t\t<option></option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div class=\"div-table-row\">\n\t\t\t<div class=\"div-table-col div-table-col-left\">\n\t\t\t\t<span class=\"leftcol\"><?php p($l->t('Navigation Icon')); ?>:</span>\n\t\t\t\t<br/>\n\t\t\t\t<em><?php p($l->t('Enable global search within all your content.')); ?></em>\n\t\t\t</div>\n\t\t\t<div class=\"div-table-col\">\n\t\t\t\t<input type=\"checkbox\" id=\"fts_navigation\" value=\"1\"/>\n\t\t\t</div>\n\t\t</div>\n\n\t</div>\n\n\n</div>\n"
  }
]