Browse Source

Initial commit. Process files and send response via webhook

hidden_tags_with_bookmarks
Orzu Ionut 5 years ago
commit
e45e5d27bb
  1. 15
      .editorconfig
  2. 20
      .env.example
  3. 5
      .gitattributes
  4. 16
      .gitignore
  5. 13
      .styleci.yml
  6. 491
      README.md
  7. 76
      app/Console/Commands/DeployWorker.php
  8. 57
      app/Console/Commands/TestMachine.php
  9. 46
      app/Console/Kernel.php
  10. 71
      app/Exceptions/Handler.php
  11. 20
      app/Helpers/array.php
  12. 13
      app/Http/Controllers/Controller.php
  13. 31
      app/Http/Controllers/IngestController.php
  14. 82
      app/Http/Kernel.php
  15. 21
      app/Http/Middleware/Authenticate.php
  16. 17
      app/Http/Middleware/CheckForMaintenanceMode.php
  17. 17
      app/Http/Middleware/EncryptCookies.php
  18. 27
      app/Http/Middleware/RedirectIfAuthenticated.php
  19. 18
      app/Http/Middleware/TrimStrings.php
  20. 23
      app/Http/Middleware/TrustProxies.php
  21. 24
      app/Http/Middleware/VerifyCsrfToken.php
  22. 190
      app/Ingest/Convertor.php
  23. 56
      app/Ingest/DocumentHandler.php
  24. 45
      app/Ingest/MDConvertor.php
  25. 172
      app/Jobs/IngestDocuments.php
  26. 101
      app/Jobs/SendToCore.php
  27. 30
      app/Listeners/Test.php
  28. 19
      app/Listeners/WebhookFailedToSendToCoreListener.php
  29. 19
      app/Listeners/WebhookSuccessfullySentToCoreListener.php
  30. 17
      app/Parser/DocxParser/Footer.php
  31. 41
      app/Parser/DocxParser/Footnote.php
  32. 11
      app/Parser/DocxParser/Header.php
  33. 26
      app/Parser/DocxParser/Link.php
  34. 77
      app/Parser/DocxParser/ListItemRun.php
  35. 11
      app/Parser/DocxParser/PageBreak.php
  36. 269
      app/Parser/DocxParser/ParseDocx.php
  37. 32
      app/Parser/DocxParser/PreserveText.php
  38. 41
      app/Parser/DocxParser/Section.php
  39. 35
      app/Parser/DocxParser/Table.php
  40. 41
      app/Parser/DocxParser/Table/Cell.php
  41. 41
      app/Parser/DocxParser/Table/Row.php
  42. 147
      app/Parser/DocxParser/Text.php
  43. 17
      app/Parser/DocxParser/TextBreak.php
  44. 74
      app/Parser/DocxParser/TextRun.php
  45. 72
      app/Parser/DocxParser/Title.php
  46. 117
      app/Parser/DocxParser/Traits/Helper.php
  47. 527
      app/Parser/HtmlParser/ParseHtml.php
  48. 670
      app/Parser/ParseHtmlArray.php
  49. 747
      app/Parser/ParseTextArray.php
  50. 406
      app/Parser/ParseXml.php
  51. 28
      app/Providers/AppServiceProvider.php
  52. 30
      app/Providers/AuthServiceProvider.php
  53. 21
      app/Providers/BroadcastServiceProvider.php
  54. 41
      app/Providers/EventServiceProvider.php
  55. 80
      app/Providers/RouteServiceProvider.php
  56. 53
      artisan
  57. 55
      bootstrap/app.php
  58. 2
      bootstrap/cache/.gitignore
  59. 69
      composer.json
  60. 5876
      composer.lock
  61. 231
      config/app.php
  62. 117
      config/auth.php
  63. 59
      config/broadcasting.php
  64. 103
      config/cache.php
  65. 147
      config/database.php
  66. 79
      config/filesystems.php
  67. 52
      config/hashing.php
  68. 104
      config/logging.php
  69. 136
      config/mail.php
  70. 88
      config/queue.php
  71. 33
      config/services.php
  72. 199
      config/session.php
  73. 36
      config/view.php
  74. 61
      config/webhook-server.php
  75. 2
      database/.gitignore
  76. 35
      database/migrations/2019_08_19_000000_create_failed_jobs_table.php
  77. 16
      database/seeds/DatabaseSeeder.php
  78. 24306
      get-pip.py
  79. 21
      package.json
  80. 37
      phpunit.xml
  81. 22
      public/.htaccess
  82. BIN
      public/favicon.ico
  83. 60
      public/index.php
  84. 2
      public/robots.txt
  85. 28
      public/web.config
  86. 1
      resources/js/app.js
  87. 28
      resources/js/bootstrap.js
  88. 19
      resources/lang/en/auth.php
  89. 19
      resources/lang/en/pagination.php
  90. 22
      resources/lang/en/passwords.php
  91. 151
      resources/lang/en/validation.php
  92. 1
      resources/sass/app.scss
  93. 5
      resources/views/errors/401.blade.php
  94. 5
      resources/views/errors/403.blade.php
  95. 4
      resources/views/errors/404.blade.php
  96. 4
      resources/views/errors/405.blade.php
  97. 5
      resources/views/errors/419.blade.php
  98. 6
      resources/views/errors/429.blade.php
  99. 5
      resources/views/errors/500.blade.php
  100. 5
      resources/views/errors/503.blade.php

15
.editorconfig

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

20
.env.example

@ -0,0 +1,20 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
WEBHOOK_CORE_URL=
WEBHOOK_CORE_SECRET=

5
.gitattributes

@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

16
.gitignore

@ -0,0 +1,16 @@
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.idea
/storage/app/ingest_conversions/*
/storage/app/ingest_queue/*
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.idea

13
.styleci.yml

@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

491
README.md

@ -0,0 +1,491 @@
## About S&D Ingest
S&D INGEST it's the module that receives row files in different formats and send's them to any module after the file's are being processed.
## :cyclone: Server Requirements:
- php7.4 [https://www.php.net] [LICENSE](https://www.php.net/license/index.php)
- apache [https://httpd.apache.org] [LICENSE](hhttps://www.apache.org/licenses/LICENSE-2.0)
- redis [https://redis.io] [LICENSE](https://redislabs.com/legal/licenses/)
- postgresql-server [https://www.postgresql.org] [LICENSE](https://tldrlegal.com/license/postgresql-license-(postgresql))
- supervisor [http://supervisord.org] [LICENSE](https://github.com/Supervisor/supervisor/blob/master/LICENSES.txt)
- libraoffice [https://www.libreoffice.org] [LICENSE](https://www.libreoffice.org/about-us/licenses)
- python [https://www.python.org/] [LICENSE](https://www.python.org/download/releases/2.7/license/)
- pdftotext [https://github.com/jalan/pdftotext] [LICENSE](https://github.com/jalan/pdftotext/blob/master/LICENSE)
## :zap: Build with:
- Laravel Framework ^6.2
## :rocket: Installation
```bash
apt-get update
apt-get install software-properies-common
add-apt-repository ppa:deadsnakes/ppa
apt-get install supervisor python3.8 python3.8-dev
supervisorctl restart all
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py
rm -rf get-pip.py
apt install libpoppler-cpp-dev
pip install --upgrade pip
pip install pdftotext supervisor
systemctl enable supervisor
php artisan queue:deploy-supervisor
systemctl restart supervisor
composer install
npm install
cp .env.example .env
php artisan key:generate
sudo -u postgres psql
postgres=# create database mydb;
postgres=# create user myuser with encrypted password 'mypass';
postgres=# grant all privileges on database mydb to myuser;
#update the .env with the current postgres credentials
sudo mkdir /var/log/amqp
sudo mkdir /var/log/queue
php artisan migrate
php artisan queue:deploy-supervisor
supervisorctl start all
```
## Local Usage
```python
php artisan serve
php artisan queue:work
```
## PHP Packages
- fideloper/proxy [LICENSE](https://github.com/fideloper/TrustedProxy/blob/master/LICENSE.md)
- laravel/framework [LICENSE](https://github.com/laravel/framework/blob/7.x/LICENSE.md)
- laravel/tinker [LICENSE](https://github.com/laravel/tinker/blob/2.x/LICENSE.md)
- phpoffice/phpword [LICENSE](https://github.com/PHPOffice/PHPWord/blob/0.17.0/LICENSE)
- predis/predis [LICENSE](https://github.com/php-enqueue/amqp-bunny/blob/master/LICENSE)
- spatie/laravel-webhook-server [LICENSE](https://github.com/spatie/laravel-webhook-server/blob/master/LICENSE.md)
## Current running process
- [DOC,DOCX,RTF etc..] are first being converted to docx and then converted to .txt using https://www.libreoffice.org
- [PDF] files are converted to .txt
- The resulting .txt file is processed using our own logic/alogorithm and clause breaking point to an array that looks similar to this:
```json
{
"content": "Definitions and Interpretation",
"spaces": 4,
"numbering": "1.",
"children": [
{
"content": "In this Agreement, the following expressions shall have the meanings set opposite them, unless inconsistent with the context or otherwise specified:",
"spaces": 8,
"numbering": "1.1",
"children": [
{
"content": "“Agreement” this agreement including all schedules, appendices and exhibits attached herein;",
"spaces": 0
},
{
"content": "“Associated Company” any company which is listed in Schedule 2 (as may be amended from time to time in writing) and which is in relation to either party its Parent undertaking or its subsidiary undertaking or a subsidiary undertaking of its Parent undertaking or any other person controlled by it or under the same control (where “control” is to be construed in accordance with section 1124 of the Corporation Tax Act 2010) whether direct or indirect. “Parent undertaking” shall have the meaning attributed thereto in Section 1162 of the Companies Act 2006;",
"spaces": 0
},
{
"content": "“Commencement Date” [TBC] “Confidential Information” collectively and individually, all or any document or information of any nature in any format, including oral, written or electronic form relating to either party or their Associated Companies’ or either of their businesses, including technology, Customers, Customer Information, supplier, employees, finances, data, products, services, trade secrets, processes, designs, drawings, diagrams, plans, specifications, formulae, testing procedures, computer software, reports, investigative studies, manuals, assets, costs, prices, marketing opportunities, proprietary information, Know-how, the terms of this Agreement and any other information or material relating to the information described above which (i) is disclosed by either party (or by any individual or legal entity acting in their name or on their behalf, including employees, consultants, sub-contractors, advisors of any kind and agents) or (ii) which comes to the attention of either party (or any individual or legal entity acting in their name or on their behalf, including employees, consultants, sub-contractors, advisors of any kind and agents) during the course of the carrying out of the rights or obligations under this Agreement;",
"spaces": 0
},
{
"content": "“Customers” customers of {P1_Name} and/or its Associated Companies from time to time who owe a Debt to {P1_Name};",
"spaces": 0
},
{
"content": "“Customer Data” any information given by the Customer directly to the {P2_Name} or its personnel;",
"spaces": 0
},
{
"content": "“Customer Information” any Customers’ personal information supplied to the {P2_Name} by or on behalf of {P1_Name} during the performance of this Agreement, including Personal Data, but excluding Customer Data;",
"spaces": 0
},
{
"content": "“Data Protection Legislation” all applicable legislation concerning the protection of individuals with regard to the processing of Personal Data and the free movement of such data including the Data Protection Act 1998 and any regulations made under such legislation and any relevant codes of practice and guidance notes issued from time to time by the Information Commissioner (or its successor);",
"spaces": 0
},
{
"content": "“Debt” any monies owed by the Customer to {P1_Name} which have remained unpaid by the Customer contrary to the terms and conditions between {P1_Name} and the Customer governing the repayment of sums owed;",
"spaces": 0
},
{
"content": "“Debt Management Plan” a plan outlined by the {P2_Name} and agreed by the Customer which details the amount and frequency of payments to be made to each of the Customer’s creditors;",
"spaces": 0
},
{
"content": "“Disbursement” total amount remitted to {P1_Name} on a monthly basis by the {P2_Name} for application to the Customer’s account in respect of their Offer;",
"spaces": 0
},
{
"content": "“European Economic Area” the European Economic Area comprising of the following countries as at the Commencement Date: Austria; Belgium; Bulgaria; Cyprus; the Czech Republic; Denmark; Estonia; Finland; France; Germany; Greece; Hungary; Ireland; Italy; Latvia; Lithuania; Luxembourg; Malta; the Netherlands; Poland; Portugal; Romania; Slovakia; Slovenia; Spain; Sweden; the United Kingdom; Iceland; Liechtenstein and Norway, as amended from time to time;",
"spaces": 0
},
{
"content": "“EU Model Terms” the set of model contractual clauses which the Information Commissioner has authorised for use by Data Controllers (as such term is defined in the Data Protection Legislation) established in the European Union where there is a transfer of Personal Data to Data Processors (as such term is defined in the Data Protection Legislation) outside of the European Economic Area;",
"spaces": 0
},
{
"content": "“Facility” the {P2_Name} site authorised by {P1_Name} where the processing and/or storage of Personal Data supplied by {P1_Name} pursuant to this Agreement takes place. For the purposes of this Agreement that site shall be located at {P1_Reg} or such other place as may be notified in writing to {P1_Name} from time to time;",
"spaces": 0
},
{
"content": "“Force Majeure” any acts, events, omissions or accidents beyond the reasonable control of either Party, including but not limited to acts of God, extreme adverse weather conditions or natural disaster, war, threat of or preparation for war, armed conflict, imposition of sanctions, embargo, breaking off of diplomatic relations or similar actions, terrorist attack, civil war, civil commotion or riots, nuclear, chemical or biological contamination or sonic boom, compliance with any law, regulation or directive, fire, explosion or accidental damage, failure of plant machinery, machinery, computers or vehicles;",
"spaces": 0
},
{
"content": "“Information Commissioner” the independent authority in the UK (or its successor body) which regulates information rights;",
"spaces": 0
},
{
"content": "“Initial Period” three (3) years from the Commencement Date;",
"spaces": 0
},
{
"content": "“Lending Code” a voluntary code of practice (enforced by the Lending Standards Board) which sets standards for financial institutions and provides consumers with protection and explanation on how such institutions are expected to deal with them day-to-day and in times of financial difficulties;",
"spaces": 0
},
{
"content": "“Notification” written notification from the {P2_Name} to {P1_Name} that it has obtained Permission;",
"spaces": 0
},
{
"content": "“Offer” a statement of proposed amount to be repaid by the Customer to {P1_Name} in respect of the Customer’s Debt including instalment plans;",
"spaces": 0
},
{
"content": "“Payment Break” instance where the Customer fails to make an agreed repayment to the {P2_Name} for payment to their creditors;",
"spaces": 0
},
{
"content": "“Permission” written confirmation (which may be confirmation by email or other electronic means) from the Customer to the {P2_Name} that they are appointing the {P2_Name} to act on the Customer’s behalf in the management of the Customer’s Debt and authorising the {P2_Name} to negotiate payment terms with {P1_Name} in respect of the Customer’s Debt and authorising the {P2_Name} to have access to Customer Information;",
"spaces": 0
},
{
"content": "“Personal Data” personal data as defined in the Data Protection Legislation;",
"spaces": 0
},
{
"content": "“Regulatory Authorities” any body who, from time to time, has competent rule-making, investigatory and/or enforcement powers in relation to the business of {P1_Name} and/or its Associated Companies, including, without limitation, the Financial Conduct Authority, the Consumer Financial Protection Bureau, the Office of Fair Trading, the Information Commissioner’s Office, the Lending Standards Board, UK and US Government departments and organisations, the Office of the Comptroller of Currency, the Federal Reserve and other governmental or non-governmental regulatory authorities in the UK, US or other competent jurisdictions;",
"spaces": 0
},
{
"content": "“Regulatory Requirements”",
"spaces": 0
},
{
"content": "(a) all applicable laws, statutes, regulations, ordinances or subordinate legislation in force from time to time to which this Agreement or a party is subject;",
"spaces": 4
},
{
"content": "(b) the common law as applicable to the parties from time to time;",
"spaces": 4
},
{
"content": "(c) all binding court orders, judgements or decrees;",
"spaces": 4
},
{
"content": "all applicable directives, policies, rules, orders, code of conduct or practice or applicable guidance (including the Lending Code and the Financial Conduct Authority TCF principles that are binding on a party and that are made or given by any government, an agency thereof, any Regulatory Authority or other regulatory authority, including in the case of the {P2_Name}, laws and rules imposed by local regulatory authorities in the country where it is located;",
"spaces": 0
},
{
"content": "“Working Day” any day on which banks in London are open for the transaction of normal business excluding Saturdays, Sundays and bank and public holidays in England and Wales.",
"spaces": 0
}
]
},
{
"content": "In this Agreement:",
"spaces": 8,
"numbering": "1.2",
"children": [
{
"content": "references to Recitals, Clauses and Schedules and their sub-divisions are to the Recitals to, Clauses of and Schedules to this Agreement and their sub-divisions respectively, unless specified otherwise;",
"spaces": 12,
"numbering": "1.2.1"
},
{
"content": "the index and headings are included for convenience only and shall not affect the construction or interpretation of this Agreement;",
"spaces": 12,
"numbering": "1.2.2"
},
{
"content": "words importing gender include the other gender and the singular includes the plural and vice versa;",
"spaces": 12,
"numbering": "1.2.3"
},
{
"content": "references to persons include individuals, bodies corporate, firms, unincorporated associations and governmental, semi-governmental and local authorities or agencies;",
"spaces": 12,
"numbering": "1.2.4"
},
{
"content": "references to the words “include”, “including”, “in particular” or similar words or expressions will be construed without limitation and accordingly will not limit the words preceding or following them;",
"spaces": 12,
"numbering": "1.2.5"
},
{
"content": "where expressions used in this Agreement are not specifically defined and are capable of having a special meaning according to the usage or custom of the card services sector or banking services sector, such expressions are to be interpreted accordingly. Any meaning given in this Agreement to a defined term shall prevail over such other special meaning;",
"spaces": 12,
"numbering": "1.2.6"
},
{
"content": "references to a “party” or “parties” will mean either {P1_Name} and/or the {P2_Name} as the context requires and references to a third party will mean any person other than the parties;",
"spaces": 12,
"numbering": "1.2.7"
},
{
"content": "except where expressly stated otherwise, references to any statute, legislation, code of practice or other regulation will include any sub-ordinate legislation and any equivalent regulation in any relevant jurisdiction, as amended, modified, consolidated, re-enacted and/or replaced and in force from time to time;",
"spaces": 12,
"numbering": "1.2.8"
},
{
"content": "any negative obligation imposed on any party shall be construed as if it were also an obligation not to permit or suffer the act or thing in question and any positive obligation imposed on any party shall be construed as if it were also an obligation to procure that the act or thing in question be done;",
"spaces": 12,
"numbering": "1.2.9"
},
{
"content": "the Schedules and Appendices (if any) form part of this Agreement and shall be construed and have the same full force and effect as if expressly set out in the body of this Agreement. To the extent only of any conflict or inconsistency between the Clauses, Schedules and Appendices (if any), the Clauses will prevail and the order of precedence will be as follows:",
"spaces": 12,
"numbering": "1.2.10"
},
{
"content": "1 the provisions of the Clauses;",
"spaces": 16,
"numbering": "1.2.10"
},
{
"content": "2 the provisions of the Schedules; and",
"spaces": 16,
"numbering": "1.2.10"
},
{
"content": "3 the provisions of the Appendices.",
"spaces": 16,
"numbering": "1.2.10"
}
]
}
]
},
{
"content": "Obligations of the {P2_Name}",
"spaces": 4,
"numbering": "2.",
"children": [
{
"content": "The {P2_Name} shall obtain the Permission from the Customer before proceeding with the Debt Management Plan.",
"spaces": 8,
"numbering": "2.1"
},
{
"content": "Subject at all times to the {P2_Name} being in receipt of the applicable Permission, {P2_Name} shall provide the corresponding Notification to {P1_Name} before or at the time of making the first Offer to {P1_Name}. In the absence of such Permission or Notification {P1_Name} shall not be obliged to provide any Customer Information to the {P2_Name}.",
"spaces": 8,
"numbering": "2.2"
},
{
"content": "{P1_Name} may request, and the {P2_Name} shall provide, any Permission to {P1_Name} within two (2) Working Days of such request by {P1_Name} to enable {P1_Name} to verify the Permissions stated in the Notifications provided that in the event that {P1_Name} requests ten (10) or more Permissions in any 12 hour period then the {P2_Name} shall provide such Permissions as promptly as is reasonably possible.",
"spaces": 8,
"numbering": "2.3"
},
{
"content": "Any delay or failure by the {P2_Name} to comply with Clause 2.3 shall be deemed a material breach of this Agreement and the provisions of clause 9.3 shall apply.",
"spaces": 8,
"numbering": "2.4"
},
{
"content": "Subject to Clause 2.1 and in accordance with the Debt Management Plan, the {P2_Name} shall make an Offer to {P1_Name} for the repayment of the Debt detailing the amount and frequency of proposed payments. Such Offer will be made in accordance with the Lending Code guidelines and based upon the principle of equitable distribution of available income (after priority payments) in line with the amount outstanding to each creditor.",
"spaces": 8,
"numbering": "2.5"
},
{
"content": "Upon receipt of the Offer from the {P2_Name} {P1_Name} may either;",
"spaces": 8,
"numbering": "2.6",
"children": [
{
"content": "accept the Offer; or",
"spaces": 12,
"numbering": "2.6.1"
},
{
"content": "reject the Offer where it considers the offer to be unreasonable by written notice to the {P2_Name}.",
"spaces": 12,
"numbering": "2.6.2"
}
]
},
{
"content": "In the event that {P1_Name} accepts an Offer, then the {P2_Name} shall arrange for the Disbursement to be repaid to {P1_Name} in accordance with the Offer within five (5) Working Days of receipt by the {P2_Name} of cleared funds from the Customer.",
"spaces": 8,
"numbering": "2.7"
},
{
"content": "In the event that {P1_Name} rejects the Offer, then the {P2_Name} shall review the Debt Management Plan and the {P2_Name} may make a new Offer to {P1_Name}.",
"spaces": 8,
"numbering": "2.8"
},
{
"content": "For the avoidance of doubt nothing in this Agreement constitutes an obligation on {P1_Name} to accept any unreasonable Offer made by the {P2_Name}.",
"spaces": 8,
"numbering": "2.9"
},
{
"content": "The {P2_Name} shall notify {P1_Name} in writing as soon as reasonably possible:",
"spaces": 8,
"numbering": "2.10",
"children": [
{
"content": "upon becoming aware of any withdrawal of a Permission or any amendment thereto made by a Customer; and",
"spaces": 12,
"numbering": "2.10.1"
},
{
"content": "of any circumstance or event which is reasonably likely to materially affect the {P2_Name}’s ability to comply with its obligations under this Agreement.",
"spaces": 12,
"numbering": "2.10.2"
}
]
},
{
"content": "Failure by the {P2_Name} to notify {P1_Name} pursuant to Clause 2.10.1 shall be deemed a material breach of this Agreement and the provisions of clause 9.3 shall apply.",
"spaces": 8,
"numbering": "2.11"
},
{
"content": "The {P2_Name} shall;",
"spaces": 8,
"numbering": "2.12",
"children": [
{
"content": "at all times act in accordance with and subject to any limitations set out in (i) the Permission and (ii) the requirements of this Agreement;",
"spaces": 12,
"numbering": "2.12.1"
},
{
"content": "comply with the reporting and review requirements set out in Schedule I.",
"spaces": 12,
"numbering": "2.12.2"
},
{
"content": "be at all times courteous and business like in its contact with the Customers;",
"spaces": 12,
"numbering": "2.12.3"
},
{
"content": "use its reasonable commercial endeavours to comply with any reasonable and lawful directions, orders and instructions which {P1_Name} may from time to time give to it in accordance with or to give effect to the provisions of this Agreement;",
"spaces": 12,
"numbering": "2.12.4"
},
{
"content": "identify, procure and keep in force all permits, certificates, licences, approvals, authorisations and consents which may be necessary in connection with the performance of its obligations under this Agreement;",
"spaces": 12,
"numbering": "2.12.5"
},
{
"content": "in performing its obligations under this Agreement, ensure that it is knowledgeable about and shall continue to be knowledgable about all Regulatory Requirements and that it shall comply with all Regulatory Requirements and (i) maintain evidence of its compliance with Regulatory Requirements, (ii) take all necessary steps required to comply with such Regulatory Requirements promptly upon becoming aware it is not so complying, and (iii) take all necessary steps to remedy any previous breaches of such Regulatory Requirements;",
"spaces": 12,
"numbering": "2.12.6"
},
{
"content": "where permitted to do so, promptly notify {P1_Name} in the event that a regulatory body who regulates {P1_Name} or the {P2_Name} conducts an audit or investigation of the {P2_Name} and disclose to {P1_Name} (subject always to the provisions of confidentiality set out at Clause 7) details of any adverse regulatory findings; and",
"spaces": 12,
"numbering": "2.12.7"
},
{
"content": "co-operate with {P1_Name} and assist them in their dealings with Regulatory Authorities to the extent reasonably required in relation to this Agreement including implementing such measures as are reasonably necessary and appropriate to effect compliance with Regulatory Requirements.",
"spaces": 12,
"numbering": "2.12.8"
}
]
},
{
"content": "{P1_Name} acknowledges and accepts that the {P2_Name} may give advice and assistance and provide services and products beyond the scope of the Debt Management Plan to Customers and that the {P2_Name} will not disclose any Customer Data to {P1_Name} without the Customer’s prior consent (which the {P2_Name} is under no obligation to seek).",
"spaces": 8,
"numbering": "2.13"
},
{
"content": "Any failure or inability of a Customer to agree to or comply with a Debt Management Plan or any other advice or assistance given by the {P2_Name} pursuant to this Agreement shall not cause the {P2_Name} to be in breach of the terms of this Agreement and shall not prevent the {P2_Name} from providing advice for debt negotiations, counselling and management solutions outside of the Services.",
"spaces": 8,
"numbering": "2.14"
},
{
"content": "The Parties acknowledge that the {P2_Name} is not acting as an agent of {P1_Name} and that it is not a debt collection agent of {P1_Name}.",
"spaces": 8,
"numbering": "2.15"
}
]
},
{
"content": "Rights and Obligations of {P1_Name}",
"spaces": 4,
"numbering": "3.",
"children": [
{
"content": "During the term of this Agreement {P1_Name} shall provide such information and assistance as is reasonably required for the {P2_Name} to perform its obligations under this Agreement.",
"spaces": 8,
"numbering": "2.16"
},
{
"content": "For the period of six months from termination or expiry of this Agreement {P1_Name} shall not, without the prior written agreement of the {P2_Name}, employ or engage on any basis or offer such employment or engagement to any of the {P2_Name}’s personnel provided that employment or engagement of any member of the {P2_Name}’s personnel pursuant to a bona fide recruitment campaign shall not be a breach of this clause.",
"spaces": 8,
"numbering": "2.17"
},
{
"content": "{P1_Name} represents and warrants that:",
"spaces": 8,
"numbering": "2.18",
"children": [
{
"content": "it has the requisite power and authority required by any applicable law or otherwise to enter into this Agreement and to carry out the obligations contemplated by the Agreement reliably and professionally and that the execution and performance of this Agreement has been duly authorised by the required corporate action by {P1_Name};",
"spaces": 12,
"numbering": "2.18.1"
},
{
"content": "it has and shall maintain during the continuance of this Agreement all necessary rights, licences and consents necessary to provide the Customer Information to the {P2_Name} and to perform its obligations under this Agreement.",
"spaces": 12,
"numbering": "2.18.2"
}
]
},
{
"content": "If {P1_Name} notifies the {P2_Name} in writing that amendments are required to be made to this Agreement (including any Schedule hereto) to ensure {P1_Name}’s compliance with its obligations to a Regulatory Authority and/or any Regulatory Requirements (including changes required in order to comply with any rules or guidance (including guidance as to interpretation of such rules) issued or published by or on behalf of such Regulatory Authorities or coming into force from time to time), the {P2_Name} shall be obliged to make such amendments as soon as reasonably practicable and in shall use reasonable commercial endeavours to ensure that such changes are made in sufficient time so as to ensure that {P1_Name} is complying with such obligations.",
"spaces": 8,
"numbering": "2.19"
},
{
"content": "In the event the {P2_Name} is unable to comply with any amendments as notified to it by {P1_Name} pursuant to Clause 3.1 or fails to comply within a reasonable time then {P1_Name} may terminate this Agreement immediately.",
"spaces": 8,
"numbering": "2.20"
},
{
"content": "{P1_Name} shall comply with the reporting and review requirements set out in Schedule I.",
"spaces": 8,
"numbering": "2.21"
},
{
"content": "Notwithstanding Clause 2.21 above {P1_Name} shall not make any changes to a Customer’s {P1_Name} account without direct contact with the Customer. For the avoidance of doubt the Permission shall only relate to the provision of information regarding a Customer’s {P1_Name} account.",
"spaces": 8,
"numbering": "2.22"
}
]
},
{
"content": "Conditions",
"spaces": 4,
"numbering": "4.",
"children": [
{
"content": "It is a condition of this Agreement that each party is entitled to enter into this Agreement and to perform its obligations set out herein.",
"spaces": 8,
"numbering": "2.23"
}
]
},
```

76
app/Console/Commands/DeployWorker.php

@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class DeployWorker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'queue:deploy-supervisor';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Adds the supervisorctl config file for laravel queues. Must be ran as root!';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$workerName = 'queue-worker-'.str_replace(' ', '-', strtolower(env('APP_NAME'))).'-'.str_replace(' ', '-', strtolower(env('APP_ENV')));
$workerFile = $workerName.'.conf';
try {
Storage::disk('supervisor')->put($workerFile, '[program:'.$workerName.']
process_name=%(program_name)s_%(process_num)02d');
Storage::disk('supervisor')->append($workerFile, 'command=php '.base_path().'/artisan queue:work');
Storage::disk('supervisor')->append($workerFile, 'autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/queue/'.$workerName.'.log');
} catch (Exception $e) {
$this->info('supervisor script failed to install. Did you install supervisor and are you running this script as root?');
return;
}
$this->info('supervisor script installed');
try {
exec('sudo supervisorctl reread');
exec('sudo supervisorctl update');
exec('sudo supervisorctl stop '.$workerName.':*');//in case it's already started
exec('sudo supervisorctl start '.$workerName.':*');
} catch (Exception $e) {
$this->info('failed to start queue worker');
return;
}
$this->info('queue worker started');
}
}

57
app/Console/Commands/TestMachine.php
File diff suppressed because it is too large
View File

46
app/Console/Kernel.php

@ -0,0 +1,46 @@
<?php
namespace App\Console;
use App\Console\Commands\DeployWorker;
use App\Console\Commands\TestMachine;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
DeployWorker::class,
TestMachine::class
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')
// ->hourly();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

71
app/Exceptions/Handler.php

@ -0,0 +1,71 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* @param \Exception $exception
*
* @return void
*
* @throws \Exception
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
*/
public function render($request, Exception $exception)
{
if ($this->isHttpException($exception))
{
$statusCode = $exception->getStatusCode();
return response()->view("errors.".$statusCode, ['code'=>$statusCode], $statusCode);
}
if ($exception instanceof HttpException) {
return response()->view('errors::404', [], 404);
}
return parent::render($request, $exception);
}
}

20
app/Helpers/array.php

@ -0,0 +1,20 @@
<?php
/**
* Flatten an array
*
* @param array $array
*
* @return array
*/
if (! function_exists('array_flatten')) {
function array_flatten(array $array)
{
$result_array = [];
foreach ($array as $item) {
$result_array = array_merge($result_array, $item);
}
return $result_array;
}
}

13
app/Http/Controllers/Controller.php

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

31
app/Http/Controllers/IngestController.php

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use App\Ingest\DocumentHandler;
class IngestController extends Controller
{
public function store()
{
request()->validate([
'id' => 'required',
'document' => 'required|file',
]);
try {
$handler = new DocumentHandler(request()->get('id'), request()->file('document'));
$handler->handle();
return response()->json([
'status' => 'processing',
]);
} catch (\Exception $exception) {
return response()->json([
'status' => 'error',
'message' => $exception->getMessage(),
], 400);
}
}
}

82
app/Http/Kernel.php

@ -0,0 +1,82 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

21
app/Http/Middleware/Authenticate.php

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

17
app/Http/Middleware/CheckForMaintenanceMode.php

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

17
app/Http/Middleware/EncryptCookies.php

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

27
app/Http/Middleware/RedirectIfAuthenticated.php

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}

18
app/Http/Middleware/TrimStrings.php

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

23
app/Http/Middleware/TrustProxies.php

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

24
app/Http/Middleware/VerifyCsrfToken.php

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'*' //
];
}

190
app/Ingest/Convertor.php

@ -0,0 +1,190 @@
<?php
namespace App\Ingest;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class Convertor
{
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
private $storage;
private $path;
protected $type;
public function __construct($path, $type)
{
$this->storage = Storage::disk('local');
$this->path = $path;
$this->type = $type;
}
public function execute()
{
if ($this->type === 'pdf') {
$this->convertPdfToText();
return $this->path;
}
if ($this->type !== 'docx') {
$this->convertToDocx();
}
$this->convertDocumentToText();
//$this->convertToHtml();
return $this->path;
}
/**
* Convert doc,dot,rtf,odt,pdf,docx to docx
*
*
* @return string|void
*/
private function convertToDocx()
{
/**
* Convert doc,dot,rtf,odt to docx
*/
$process = new Process([
'sudo',
'-S',
'soffice',
'--headless',
'--convert-to',
'docx',
$this->storage->path($this->path),
'--outdir',
$this->storage->path('contracts')
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$this->storage->delete($this->path);
$this->path = str_replace($this->type, 'docx', $this->path);
}
/**
* Convert docx file to text
*
*
* @return string|void
*/
private function convertDocumentToText()
{
$process = new Process([
'sudo',
'-S',
'soffice',
'--headless',
'--convert-to',
'txt',
$this->storage->path($this->path),
'--outdir',
$this->storage->path('contracts')
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$this->storage->delete($this->path);
$this->path = str_replace(['.docx', '.bin'], '.txt', $this->path);
}
private function convertPdfToText()
{
$process = new Process([
'pip',
'install',
"pdftotext"
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
/**
* Convert pdf to text
*/
$process = new Process([
'python3',
storage_path('scripts' . DIRECTORY_SEPARATOR . 'parse-pdf.py'),
'-i',
$this->storage->path($this->path),
'-o',
$this->storage->path(str_replace('.pdf', '.txt', $this->path))
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$this->storage->delete($this->path);
$this->path = str_replace('pdf', 'txt', $this->path);
}
private function convertToHtml()
{
$process = new Process([
'sudo',
'-S',
'soffice',
'--headless',
'--convert-to',
'html:HTML:EmbedImages',
$this->storage->path($this->path),
'--outdir',
$this->storage->path('contracts')
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$this->storage->delete($this->path);
$this->path = str_replace($this->type, 'html', $this->path);
}
private function convertToXML()
{
//Convert the file to xml using pdftohtml to xml and run a python scrypt to fix the paragraphs
$process = new Process([
'pdftohtml',
'-xml',
'-i',
$this->storage->path($this->path)
]);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$this->storage->delete($this->path);
$this->path = str_replace($this->type, 'xml', $this->path);
}
}

56
app/Ingest/DocumentHandler.php

@ -0,0 +1,56 @@
<?php
namespace App\Ingest;
use App\Jobs\IngestDocuments;
use Illuminate\Support\Facades\Storage;
class DocumentHandler
{
protected $id;
protected $document;
const DOCX_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
const DOC_MIME_TYPE = 'application/msword';
const RTF_MIME_TYPE = 'text/rtf';
const ODT_MIME_TYPE = 'application/vnd.oasis.opendocument.text';
const PDF_MIME_TYPE = 'application/pdf';
const PDF_WPS_MIME_TYPE = 'application/wps-office.pdf';
const DOCXOLD_MIME_TYPE = 'application/octet-stream';
const DOCX_WPS_TYPE = 'application/wps-office.docx';
protected $supportedFiles = [
self::DOCX_MIME_TYPE => 'docx',
self::DOCXOLD_MIME_TYPE => 'docx',
self::DOCX_WPS_TYPE => 'docx',
self::DOC_MIME_TYPE => 'doc',
self::RTF_MIME_TYPE => 'rtf',
self::ODT_MIME_TYPE => 'odt',
self::PDF_MIME_TYPE => 'pdf',
self::PDF_WPS_MIME_TYPE => 'pdf',
];
public function __construct($id, $document)
{
$this->id = $id;
$this->document = $document;
}
public function handle()
{
$storage = Storage::disk('local');
$file = request()->file('document');
$mimeType = $file->getClientMimeType();
if (!array_key_exists($mimeType, $this->supportedFiles)) {
throw new \Exception('File not supported.');
}
$type = $this->supportedFiles[$mimeType];
$path = $storage->putFileAs("contracts", $file, "$this->id.$type");
IngestDocuments::dispatch($path, $type);
}
}

45
app/Ingest/MDConvertor.php

@ -0,0 +1,45 @@
<?php
namespace App\Ingest;
class MDConvertor
{
protected $content;
public function __construct($content)
{
$this->content = json_decode($content, true);
}
public function execute()
{
return $this->handleParagraphs($this->content);
}
protected function handleParagraphs(array $paragraphs, $depth = 1)
{
$content = '';
foreach ($paragraphs as $paragraph) {
$content = $content .
str_repeat('#', $depth) .
' ' .
(isset($paragraph['numbering']) ? $paragraph['numbering'] : '') .
' ' .
$paragraph['content'] .
"\n";
if (
array_key_exists('children', $paragraph) &&
$paragraph['children'] &&
is_array($paragraph['children'])
) {
$childrenContent = $this->handleParagraphs($paragraph['children'], $depth + 1);
$content = $content . $childrenContent;
}
}
return $content;
}
}

172
app/Jobs/IngestDocuments.php

@ -0,0 +1,172 @@
<?php
namespace App\Jobs;
use App\Ingest\Convertor;
use App\Ingest\MDConvertor;
use App\Parser\ParseXml;
use App\Parser\DocxParser\ParseDocx;
use App\Parser\HtmlParser\ParseHtml;
use App\Parser\ParseHtmlArray;
use App\Parser\ParseTextArray;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class IngestDocuments implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
private $path;
protected $type;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
private $storage;
/**
* @var \App\Parser\DocxParser\ParseDocx
*/
private $parserDocx;
/**
* @var \App\Parser\ParseXml
*/
private $parserXml;
/**
* @var \App\Parser\HtmlParser\ParseHtml
*/
private $parserHtml;
/**
* @var \App\Parser\ParseHtmlArray
*/
private $parseHtmlArray;
/**
* @var \App\Parser\ParseTextArray
*/
private $parserText;
/**
* Create a new job instance.
*
* @param string $path
*/
public function __construct(string $path, $type)
{
$this->path = $path;
$this->type = $type;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->storage = Storage::disk('local');
$this->parserDocx = new ParseDocx();
$this->parserText = new ParseTextArray();
$this->parserXml = new ParseXml();
$this->parserHtml = new ParseHtml();
$this->parseHtmlArray = new ParseHtmlArray();
$convertor = new Convertor($this->path, $this->type);
$this->path = $convertor->execute();
$content = $this->getContent();
$content = $this->convertToUTF8($content);
try {
$filePath = $this->storeContent($content);
SendToCore::dispatch($filePath);
} catch (\Exception $e) {
Log::error('Error writing in to the file' . $e->getMessage());
// report($e);
}
}
protected function failed()
{
if ($this->storage->exists($this->path)) {
$this->storage->delete($this->path);
}
SendToCore::dispatch(null);
}
protected function getContent()
{
if ($this->type === 'pdf') {
// Wait while it finishes.
while (!$this->storage->exists($this->path)) {
sleep(1);
}
$textParser = new ParseTextArray(true);
return $textParser->fromFile($this->storage->path($this->path));
}
return $this->parserText->fromFile($this->storage->path($this->path));
}
protected function convertToUTF8($content)
{
array_walk_recursive(
$content,
function (&$entry) {
$entry = mb_convert_encoding(
$entry,
'UTF-8'
);
}
);
return utf8_encode(json_encode($content));
}
protected function storeContent($content)
{
$result = explode('.', $this->path);
$name = $result[0];
// Or json?
$filePath = $this->storeMD($name, $content);
// Delete converted file. We now have the .md file.
$this->storage->delete($this->path);
return $filePath;
}
protected function storeMD($name, $content)
{
$fileName = "$name.md";
$convertor = new MDConvertor($content);
$this->storage->put($fileName, $convertor->execute());
return $fileName;
}
protected function storeJson($name, $content)
{
$fileName = "$name.json";
$this->storage->put($fileName, $content);
return $fileName;
}
}

101
app/Jobs/SendToCore.php

@ -0,0 +1,101 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Spatie\WebhookServer\WebhookCall;
class SendToCore implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable;
private $url;
private $secret;
private $filePath;
private $id;
/**
* @var \Illuminate\Contracts\Filesystem\Filesystem
*/
private $storage;
/**
* Create a new job instance.
*
* @param $filePath
*/
public function __construct($filePath = null)
{
$this->url = env('WEBHOOK_CORE_URL') . '/webhooks';
$this->secret = env('WEBHOOK_CORE_SECRET');
$this->filePath = $filePath;
$string = str_replace('contracts/', '', $this->filePath);
$result = explode('.', $string);
$this->id = $result[0];
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$content = '';
// File exists, send content.
if ($this->filePath) {
$this->storage = Storage::disk('local');
// @TODO Check if the file exists multiple times?
if ( ! $this->storage->exists($this->filePath)) {
throw new \Exception('File does not exist yet.');
}
$content = $this->storage->get($this->filePath);
}
$sent = $this->sendTheData($content);
// if ($this->filePath && $sent) {
if ($this->filePath) {
$this->storage->delete($this->filePath);
}
}
/**
* Send the data to the core trough webhooks
*
* @param $content
* @param string $status
*/
private function sendTheData($content)
{
try {
WebhookCall::create()
->url($this->url)
->payload(['data' => [
'id' => $this->id,
'content' => $content,
'status' => $content ? 'success' : 'fail',
]])
->useSecret($this->secret)
->dispatch();
return true;
} catch (\Exception $exception) {
Log::error('SendToCore@sendTheData' . $exception->getMessage());
return false;
}
}
}

30
app/Listeners/Test.php

@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class Test
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
//
}
}

19
app/Listeners/WebhookFailedToSendToCoreListener.php

@ -0,0 +1,19 @@
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
class WebhookFailedToSendToCoreListener
{
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
Log::error('Webhook failed. :' . json_encode($event));
}
}

19
app/Listeners/WebhookSuccessfullySentToCoreListener.php

@ -0,0 +1,19 @@
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
class WebhookSuccessfullySentToCoreListener
{
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
Log::error('Webhook succesfuly handled. ');
}
}

17
app/Parser/DocxParser/Footer.php

@ -0,0 +1,17 @@
<?php
namespace App\Parser\DocxParser;
class Footer
{
public function handle($element){
dd('Footer',get_class_methods($element));
//return ['content' => [
// 'content' => '<'.$heading.(($inlineStyle) ? ' style="'.$inlineStyle.'"' : '').'>'.$element->getText().'</'.$heading.'>',
// 'type' => 'title',
//],
// 'type' => 'title',
// 'depth' => (int) $element->getDepth()];
}
}

41
app/Parser/DocxParser/Footnote.php

@ -0,0 +1,41 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use Exception;
class Footnote
{
use Helper;
public function handle($section)
{
$result = [];
$sectionElements = $this->getElements($section);
foreach ($sectionElements as $element) {
try {
$handler = $this->getHandler($element);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
finally {
$data = $handler->handle($element);
if ($data) {
$result[] = $handler->handle($element);
}
}
}
if (count($result) > 0) {
//dd($result);
return $result;
}
return;
}
}

11
app/Parser/DocxParser/Header.php

@ -0,0 +1,11 @@
<?php
namespace App\Parser\DocxParser;
class Header
{
public function handle($element){
//dd('Header',$element);
}
}

26
app/Parser/DocxParser/Link.php

@ -0,0 +1,26 @@
<?php
namespace App\Parser\DocxParser;
class Link
{
public function handle($element)
{
$text = $element->getText();
//if (! is_string($text)) {
// dd($element);
//}
return [
'content' => $this->buildHtmlLink($element, $text),
'type' => 'link'
];
}
private function buildHtmlLink($element, $text)
{
return "<a href='".$element->getLinkSrc()."' target='_blank'>".$text."</a>";
}
}

77
app/Parser/DocxParser/ListItemRun.php

@ -0,0 +1,77 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use Exception;
use PhpOffice\PhpWord\Reader\Word2007\Numbering;
use PhpOffice\PhpWord\Style;
class ListItemRun
{
use Helper;
public function handle($list)
{
$result = [];
$listElements = $this->getElements($list);
if (count($listElements)) {
foreach ($listElements as $index => $element) {
//dd($element->getFontStyle());
try {
$handler = $this->getHandler($element);
$data = $handler->handle($element);
if ($data && isset($data[ 'content' ]) && strlen(trim(strip_tags($data[ 'content' ])))) {
$styleName = $list->getParagraphStyle()->getStyleName();
if ($index === 0) {
$result[] = [
'content' => $data,
'type' => 'listItemRun',
'depth' => (int) $list->getDepth(),
'styleDepth' => $this->getStyleListDepth($styleName),
'styleName' => $styleName,
'index' => $list->getElementIndex(),
'children' => []
];
} else {
if (isset($result[ count($result) - 1 ])) {
$result[ count($result) - 1 ][ 'content' ][ 'content' ] .= ' '.$data[ 'content' ];
} else {
$result[] = [
'content' => $data,
'type' => 'listItemRun',
'depth' => (int) $list->getDepth(),
'styleDepth' => $this->getStyleListDepth($styleName),
'styleName' => $styleName,
'index' => $list->getElementIndex(),
'children' => []
];
}
}
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
if ($result) {
if (count($result) === 1) {
$result = reset($result);
$result[ 'content' ][ 'content' ] = '<p>'.$result[ 'content' ][ 'content' ].'</p>';
}
}
}
return $result;
}
}

11
app/Parser/DocxParser/PageBreak.php

@ -0,0 +1,11 @@
<?php
namespace App\Parser\DocxParser;
class PageBreak
{
public function handle($element)
{
return;
}
}

269
app/Parser/DocxParser/ParseDocx.php

@ -0,0 +1,269 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use Illuminate\Support\Facades\Log;
use PhpOffice\PhpWord\IOFactory;
use function GuzzleHttp\Psr7\str;
class ParseDocx
{
use Helper;
protected $currentNumberingIndex = 1;
public function fromUploadedFile($file)
{
try {
$docxFileLoader = IOFactory::load($file);
Log::info('Parse docx');
return $this->parseLoadedDocx($docxFileLoader);
} catch (\Exception $exception) {
dd($exception);
throw new \Exception($exception->getMessage());
}
}
private function parseLoadedDocx($docx)
{
$styles = 0;
foreach ($docx->getSections() as $page) {
$handler = $this->getHandler($page);
$paragraphs = $handler->handle($page);
if ($paragraphs) {
foreach ($paragraphs as $index => $paragraph) {
try {
if ($paragraph && $paragraph[ 'type' ] !== 'textBreak' && (isset($paragraph[ 'content' ][ 'type' ]) && $paragraph[ 'content' ][ 'type' ] !== 'textBreak') || $paragraph[ 'type' ] == 'table') {
$result[] = $paragraph;
if (isset($paragraph[ 'styleName' ])) {
$styles++;
}
}
} catch (\Exception $e) {
dd($e);
}
}
}
}
$depthTypeType = count($result) / 2 <= $styles ? 'styleDepth' : 'depth';
return $this->setTheNumbering($result, null, $depthTypeType);
}
private function setTheNumbering($paragraphs, $parentNumbering = null, $depthType = 'depth')
{
$result = [];
$paragraphs = $this->buildTheChildrens($paragraphs, $depthType);
for ($index = 0; $index < count($paragraphs); $index++) {
$paragraph = $paragraphs[ $index ];
try {
if ($paragraph[ 'type' ] !== 'table' && ($paragraph[ $depthType ] === 0 || $parentNumbering) && strpos($paragraph[ 'styleName' ],
'BodyText') === false) {
$paragraph[ 'content' ][ 'numbering' ] = ($parentNumbering) ? $parentNumbering.((int) $index + 1).'.' : $this->currentNumberingIndex.'.';
$paragraph[ 'content' ][ 'numbering_row' ] = ($parentNumbering) ? ((int) $index + 1) : $this->currentNumberingIndex;
if ($paragraph[ 'children' ] && count($paragraph[ 'children' ])) {
$paragraph[ 'children' ] = $this->setTheNumbering($paragraph[ 'children' ],
$paragraph[ 'content' ][ 'numbering' ], $depthType);
}
if (! $parentNumbering) {
$this->currentNumberingIndex++;
}
} elseif (isset($paragraph[ 'content' ][ 'numbering' ]) && isset($paragraph[ 'children' ]) && count($paragraph[ 'children' ])) {
$paragraphs[ $index ] = $this->setChildrenNumbering($paragraphs[ $index ]);
} elseif (isset($paragraphs[ $index ][ 'content' ][ 'numbering' ]) && isset(last($result)[ 'content' ][ 'numbering' ]) && $paragraphs[ $index ][ 'content' ][ 'numbering' ] == last($result)[ 'content' ][ 'numbering' ]) {
}
} catch (\Exception $e) {
dd($e);
}
$result[] = $paragraphs[ $index ];
}
return $result;
}
/**
* @param $parent
*
* @return mixed
*/
private function setChildrenNumbering($parent)
{
$numbering = 1;
for ($j = 0; $j < count($parent[ 'children' ]); $j++) {
$children = $parent[ 'children' ][ $j ];
if ($children[ 'type' ] == 'listItemRun' || isset($children[ 'content' ][ 'numbering' ])) {
$parentNumber = $parent[ 'content' ][ 'numbering' ];
$parent[ 'children' ][ $j ][ 'content' ][ 'numbering' ] = (substr(trim($parentNumber),
strlen(trim($parentNumber)) - 1) == '.') ? $parentNumber.$numbering : $parentNumber.'.'.$numbering;
if (count($parent[ 'children' ][ $j ][ 'children' ])) {
$parent[ 'children' ][ $j ] = $this->setChildrenNumbering($parent[ 'children' ][ $j ]);
}
$numbering++;
}
}
return $parent;
}
/**
* @param $paragraphs
*
* @return array
*/
private function buildTheChildrens($paragraphs, $depthType)
{
$alreadyHandledIndexes = [];
$result = [];
for ($i = 0; $i < count($paragraphs); $i++) {
if (in_array($i, $alreadyHandledIndexes)) {
continue;
}
$j = $i + 1;
for ($j; $j < count($paragraphs); $j++) {
if (in_array($j, $alreadyHandledIndexes)) {
continue;
}
if (isset($paragraphs[ $j ][ 'content' ][ 'content' ]) && $paragraphs[ $j ][ 'content' ][ 'content' ] === '<p></p>') {
$alreadyHandledIndexes[] = $j;
$j++;
}
if (isset($paragraphs[ $i ][ $depthType ]) && isset($paragraphs[ $j ][ $depthType ]) && $paragraphs[ $i ][ $depthType ] !== null && $paragraphs[ $j ][ $depthType ] !== null && $paragraphs[ $i ][ $depthType ] < $paragraphs[ $j ][ $depthType ]) {
$paragraphs[ $i ] = $this->handlePossibleChild($paragraphs[ $i ], $paragraphs[ $j ], $i,
$depthType);
} elseif (isset($paragraphs[ $j ][ 'styleName' ]) && $paragraphs[ $j ][ 'styleName' ] === 'ListParagraph' && $paragraphs[ $i ][ $depthType ] === null && substr(strip_tags($paragraphs[ $i ][ 'content' ][ 'content' ]),
-1) === ':') {
$paragraphs[ $i ] = $this->handlePossibleChild($paragraphs[ $i ], $paragraphs[ $j ], $i,
$depthType);
} elseif (isset($paragraphs[ $j + 1 ]) && isset($paragraphs[ $j + 1 ][ 'content' ][ 'content' ]) && isset($paragraphs[ $j ]) && isset($paragraphs[ $j ][ 'content' ][ 'content' ]) && substr(strip_tags($paragraphs[ $j ][ 'content' ][ 'content' ]),
-1) === ':' && (isset($paragraphs[ $j + 1 ]) && ctype_lower(substr(trim(strip_tags($paragraphs[ $j + 1 ][ 'content' ][ 'content' ])),
0,
1)) || (isset($paragraphs[ $j + 1 ]) && substr(trim(strip_tags($paragraphs[ $j + 1 ][ 'content' ][ 'content' ])),
strlen(trim(strip_tags($paragraphs[ $j + 1 ][ 'content' ][ 'content' ]))) - 1) == ';'))) {
$k = $j + 1;
$alreadyHandledIndexes[] = $k;
while (isset($paragraphs[ $k ]) && substr(str_replace('and', '',
trim(strip_tags(str_replace('and', '', $paragraphs[ $k ][ 'content' ][ 'content' ])))),
strlen(str_replace('and', '', trim(strip_tags(str_replace('and', '',
$paragraphs[ $k ][ 'content' ][ 'content' ]))))) - 1) == ';') {
$paragraphs[ $j ][ 'children' ][] = $paragraphs[ $k ];
$alreadyHandledIndexes[] = $k++;
}
$paragraphs[ $i ] = $this->handlePossibleChild($paragraphs[ $i ], $paragraphs[ $j ], $i,
$depthType);
} elseif (isset($paragraphs[ $i ][ 'styleName' ]) && $paragraphs[ $i ][ $depthType ] !== $paragraphs[ $j ][ $depthType ] && strpos($paragraphs[ $i ][ 'styleName' ],
'Heading2') !== false && ((isset($paragraphs[ $j ][ 'depth' ]) || ($paragraphs[ $j ][ 'type' ] == 'textRun' && isset($paragraphs[ $j ][ 'content' ][ 'numbering' ])) && is_null($paragraphs[ $j ][ 'styleName' ])))) {
$paragraphs[ $i ] = $this->handlePossibleChild($paragraphs[ $i ], $paragraphs[ $j ], $i,
$depthType);
} else {
break;
}
$alreadyHandledIndexes[] = $j;
}
$result[] = $paragraphs[ $i ];
$alreadyHandledIndexes[] = $i;
}
return $result;
}
/**
* @param $parent
* @param $child
* @param $i
*
* @return mixed
*/
private function handlePossibleChild($parent, $child, $i, $depthType)
{
// Must iterate through parent children
if (isset($parent[ 'children' ]) && count($parent[ 'children' ]) === 0) {
if ($parent[ $depthType ] < $child[ $depthType ] || $parent[ $depthType ] === null) {
$parent[ 'children' ][] = $child;
} elseif (strpos($parent[ 'styleName' ],
'Heading') !== false && isset($child[ 'content' ][ 'numbering' ]) && substr_count($child[ 'content' ][ 'numbering' ],
'.') == 1) {
$parent[ 'children' ][] = $child;
} else {
return $parent;
}
return $parent;
}
$lastParentChild = last($parent[ 'children' ]);
// Possible to be either child or grandchild
if ($lastParentChild[ $depthType ] && $child[ $depthType ] > $lastParentChild[ $depthType ]) {
$lastParentChild = $this->handlePossibleChild($lastParentChild, $child, $i, $depthType);
} else {
if ($child[ $depthType ] === $lastParentChild[ $depthType ]) {
$parent[ 'children' ][] = $child;
return $parent;
}
if (((isset($lastParentChild[ 'styleDepth' ]) && $lastParentChild[ 'styleDepth' ] === $child[ 'depth' ])) && $lastParentChild[ 'index' ] !== $child[ 'index' ]) {
$parent[ 'children' ][] = $child;
return $parent;
}
}
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
}

32
app/Parser/DocxParser/PreserveText.php

@ -0,0 +1,32 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
class PreserveText
{
use Helper;
public function handle($element)
{
$text = $element->getText();
if (is_array($text)) {
$text = implode(' ', $text);
}
return [
'content' => [
'content' => preg_replace("/\{[^)]+\}/", '{REF_NUMBER}', $text, 1),
'type' => 'text'
],
'type' => 'preserveText',
'index' => $element->getElementIndex(),
'children' => [],
'styleName' => 'Level2Number',
'styleDepth' => 1,
'depth' => 0
];
}
}

41
app/Parser/DocxParser/Section.php

@ -0,0 +1,41 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use Exception;
use PhpOffice\PhpWord\Element\Section as WordSection;
class Section
{
use Helper;
public function handle($section)
{
$result = [];
if ($section instanceof WordSection) {
$sectionElements = $this->getElements($section);
foreach ($sectionElements as $element) {
try {
$handler = $this->getHandler($element);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$data = $handler->handle($element);
if($data){
$result[] = $handler->handle($element);
}
}
}
if (count($result) > 0) {
return $result;
}
return;
}
}

35
app/Parser/DocxParser/Table.php

@ -0,0 +1,35 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
class Table
{
use Helper;
public function handle($table)
{
$result = [];
foreach ($table->getRows() as $row) {
$handlerName = "\App\Parser\DocxParser\\".substr(strrchr(__CLASS__, "\\"),
1).'\\'.$this->getReflectionClass($row);
$handler = new $handlerName;
$data = $handler->handle($row);
if ($data) {
$result [] = $handler->handle($row);
}
}
//dd($table->getNestedLevel(),get_class_methods($table));
//
return [
'content' => '',
'children' => $result,
'styleDepth' => $table->getNestedLevel() + 1,
'depth' => $table->getNestedLevel() + 1,
'type' => 'table',
];
}
}

41
app/Parser/DocxParser/Table/Cell.php

@ -0,0 +1,41 @@
<?php
namespace App\Parser\DocxParser\Table;
use App\Parser\DocxParser\Traits\Helper;
use Exception;
use Illuminate\Support\Arr;
use PhpOffice\PhpWord\Element\TextBreak;
class Cell
{
use Helper;
public function handle($cell)
{
$result = [];
$cells = $this->getElements($cell);
foreach ($this->getElements($cell) as $index => $element) {
if (! $element instanceof TextBreak) {
try {
$handler = $this->getHandler($element);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$data = $handler->handle($element);
$data['width']= $cell->getWidth();
$result[] = $data;
}
}
return [
'content' => '',
'children' => $result,
'depth' => null,
'type' => 'cell',
];
}
}

41
app/Parser/DocxParser/Table/Row.php

@ -0,0 +1,41 @@
<?php
namespace App\Parser\DocxParser\Table;
use App\Parser\DocxParser\Traits\Helper;
use Illuminate\Support\Arr;
class Row
{
use Helper;
/**
* @param $row
*
* @return mixed
*/
public function handle($row)
{
$rows = $row->getCells();
$result = [];
foreach ($rows as $index => $cell) {
$handler = new Cell();
$result[] = $handler->handle($cell);
}
return [
'content' => '',
'children' => $result,
'depth' => null,
'height' => $row->getHeight(),
'isTblHeader' => $row->getStyle()->isTblHeader(),
'index' => $row->getElementIndex(),
'type' => 'row',
];
}
}

147
app/Parser/DocxParser/Text.php

@ -0,0 +1,147 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
class Text
{
use Helper;
public function handle($textElement)
{
$data = $this->getElementData($textElement);
$data[ 'type' ] = 'text';
return $data;
}
/**
* @param $textElement
*
* @return array
*/
private function getElementData($textElement)
{
$text = $textElement->getText();
//if (strpos($text, 'PPOINTMENT AND GRANT OF LICENSE') !== false) {
// dd($textElement->getParent()->getDepth());
//}
$textData = $this->getNumberingFromText($text);
if (strlen($textData[ 'content' ])) {
$textData[ 'content' ] = $this->styleTheText($textData[ 'content' ], $textElement);
}
return $textData;
}
/**
* @param $text
*
* @return array
*/
private function getNumberingFromText($text)
{
$data = [];
preg_match('/^([0-9.])([^(A-Z)(a-z) ]*)/', trim($text), $match);
if ($match && isset($match[ 0 ]) && $match[ 0 ] !== '.') {
$data[ 'content' ] = trim(str_replace($match[ 0 ], '', $text));
$data[ 'numbering' ] = $match[ 0 ];
} else {
$data[ 'content' ] = trim(preg_replace('/\t+/', '', $text));
}
return $data;
}
private function styleTheText($textString, $textObject)
{
$textStyle = [
'font' => $textObject->getFontStyle(),
'paragraph' => $textObject->getParagraphStyle()
];
$fontStyle = $textStyle[ 'font' ]->getStyleValues();
$inlineStyle = $this->getInlineStyles(array_merge($fontStyle[ 'style' ], $fontStyle[ 'basic' ]));
return '<span'.(($inlineStyle) ? ' style="'.$inlineStyle.'"' : '').'>'.$this->getStyledText($textString,
$fontStyle[ 'style' ]).'</span>';
}
/**
* @param $styles
*
* @return string
*/
private function getInlineStyles($styles)
{
$styleString = '';
$acceptedInline = [
"dStrike" => 'text-decoration: line-through;text-decoration-style: double;',
"smallCaps" => 'text-transform: lowercase;',
"allCaps" => 'text-transform: capitalize;',
"fgColor" => 'background-color:'.$styles[ 'fgColor' ].';',
"hidden" => 'display:none;',
"size" => 'font-size:'.$styles[ 'size' ].'pt;',
"color" => 'color:#'.$styles[ 'color' ].';'
];
foreach ($styles as $style => $value) {
if (array_key_exists($style, $acceptedInline) && $value && ! in_array($value, ['none', 'auto'])) {
$styleString .= $acceptedInline[ $style ];
}
}
return $styleString;
}
/**
* @param $text
* @param $styles
*
* @return string
*/
private function getStyledText($text, $styles)
{
$mappedStyle = [
'bold' => 'strong',
'italic' => 'i',
'underline' => 'u',
'strike' => 'strike',
"super" => 'sup',
"sub" => 'sub',
];
foreach ($styles as $style => $active) {
if (array_key_exists($style, $mappedStyle) && $active && $active !== 'none') {
$text = $this->appendHtmlStyle($text, $mappedStyle[ $style ]);
}
}
return $text;
}
/**
* @param $text
* @param $styleType
*
* @return string
*/
private function appendHtmlStyle($text, $styleType)
{
return "<$styleType>$text</$styleType>";
}
}

17
app/Parser/DocxParser/TextBreak.php

@ -0,0 +1,17 @@
<?php
namespace App\Parser\DocxParser;
class TextBreak
{
public function handle($element)
{
return;
return [
'content' => '<br>',
'type' => 'textBreak'
];
}
}

74
app/Parser/DocxParser/TextRun.php

@ -0,0 +1,74 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use Exception;
class TextRun
{
use Helper;
public function handle($textRun)
{
$result = [];
$textRunElements = $this->getElements($textRun);
if (count($textRunElements)) {
foreach ($textRunElements as $index => $element) {
try {
$handler = $this->getHandler($element);
$data = $handler->handle($element);
if ($data) {
$styleName = $textRun->getParagraphStyle()->getStyleName();
if ($index === 0) {
$result[] = [
'content' => $handler->handle($element),
'type' => 'textRun',
'depth' => $textRun->getDepth(),
'styleDepth' => $this->getStyleListDepth($styleName),
'styleName' => $styleName,
'index' => $textRun->getElementIndex(),
'children' => []
];
} else {
if (isset($result[ count($result) - 1 ])) {
$result[ count($result) - 1 ][ 'content' ][ 'content' ] .= ' '.$data[ 'content' ];
} else {
$result[] = [
'content' => $data,
'type' => 'textRun',
'depth' => (int) $textRun->getDepth(),
'styleDepth' => $this->getStyleListDepth($styleName),
'styleName' => $styleName,
'index' => $textRun->getElementIndex(),
'children' => []
];
}
}
}
} catch (Exception $e) {
dd($e, 2);
throw new Exception($e->getMessage());
}
}
if ($result) {
if (count($result) === 1) {
$result = reset($result);
$result[ 'content' ][ 'content' ] = '<p>'.$result[ 'content' ][ 'content' ].'</p>';
}
}
}
return $result;
}
}

72
app/Parser/DocxParser/Title.php

@ -0,0 +1,72 @@
<?php
namespace App\Parser\DocxParser;
use App\Parser\DocxParser\Traits\Helper;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Element\Title as WordTitle;
class Title
{
use Helper;
public function handle($element)
{
if (! $element instanceof WordTitle) {
return;
}
$title = $element->getText();
if (! is_string($title)) {
$handler = $this->getHandler($title);
return $handler->handle($title);
}
//dd($element->getText(),get_class_methods($element),$element->getDepth());
$style = $this->getTitleStyle($element);
$headings = [
'Title' => 'h1',
'Subtitle' => 'h2',
'Heading1' => 'h1',
'Heading2' => 'h2',
'Heading3' => 'h3',
'Heading4' => 'h4',
'Heading5' => 'h5',
];
$fontStyle = $style[ 'font' ]->getStyleValues();
$inlineStyle = $this->getInlineStyles(array_merge($fontStyle[ 'style' ], $fontStyle[ 'basic' ]));
$heading = array_key_exists($style[ 'heading' ], $headings) ? $headings[ $style[ 'heading' ] ] : 'h5';
return [
'content' => [
'content' => '<'.$heading.(($inlineStyle) ? ' style="'.$inlineStyle.'"' : '').'>'.$element->getText().'</'.$heading.'>',
'type' => 'title',
],
'type' => 'title',
'depth' => null,
'styleDepth' => $this->getStyleListDepth($element->getStyle()),
'styleName' => $element->getStyle(),
'index' => $element->getElementIndex(),
'children' => []
];
}
private function getTitleStyle($element)
{
if (strpos($element->getStyle(), 'Heading') !== false) {
$font = Style::getStyle(str_replace('Heading', 'Heading_', $element->getStyle()));
} else {
$font = Style::getStyle($element->getStyle());
}
return [
'font' => $font,
'heading' => $element->getStyle()
];
}
}

117
app/Parser/DocxParser/Traits/Helper.php

@ -0,0 +1,117 @@
<?php
namespace App\Parser\DocxParser\Traits;
use ReflectionClass;
trait Helper
{
/**
* @param $element
*
* @return string
* @throws \Exception
*/
public function getHandler($element)
{
try {
$reflectClass = $this->getReflectionClass($element);
} catch (\Exception $exception) {
throw new \Exception($exception->getMessage());
}
$handleClass = 'App\Parser\DocxParser\\'.$reflectClass;
if (class_exists($handleClass)) {
return new $handleClass;
} else {
throw new \Exception("Handler class $handleClass dose not exists!");
}
}
/**
* @param $element
*
* @return string
*/
public function getReflectionClass($element)
{
try {
$reflectClass = new ReflectionClass($element);
} catch (\ReflectionException $e) {
throwException($e);
}
return $reflectClass->getShortName();
}
/**
* Get the child elements of an element
*
* @param $element
*
* @return mixed
*/
public function getElements($element)
{
return $element->getElements();
}
/**
* Check if an element has childrens
*
* @param $element
*
* @return bool
*/
public function hasElements($element)
{
return (bool) count($this->getElements($element));
}
/**
* @param $styles
*
* @return string
*/
private function getInlineStyles($styles)
{
$styleString = '';
$acceptedInline = [
"dStrike" => 'text-decoration: line-through;text-decoration-style: double;',
"smallCaps" => 'text-transform: lowercase;',
"allCaps" => 'text-transform: capitalize;',
"fgColor" => 'background-color:'.$styles[ 'fgColor' ].';',
"hidden" => 'display:none;',
"size" => 'font-size:'.$styles[ 'size' ].'pt;',
"color" => 'color:#'.$styles[ 'color' ].';'
];
foreach ($styles as $style => $value) {
if (array_key_exists($style, $acceptedInline) && $value && ! in_array($value, ['none', 'auto'])) {
$styleString .= $acceptedInline[ $style ];
}
}
return $styleString;
}
public function getStyleListDepth($styleName)
{
$getNumberFromStyleName = filter_var($styleName, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
if (is_numeric($getNumberFromStyleName) && strpos(strtolower($styleName), 'definition') === false) {
$depth = (int) $getNumberFromStyleName - 1;
} else {
$depth = null;
}
return $depth;
}
}

527
app/Parser/HtmlParser/ParseHtml.php

@ -0,0 +1,527 @@
<?php
namespace App\Parser\HtmlParser;
use DOMDocument;
use Illuminate\Support\Facades\Log;
class ParseHtml
{
public function fromUploadedFile($file)
{
try {
$htmlDom = new DomDocument();
Log::info('Parse html from file:'.$file);
$htmlString = file_get_contents($file);
libxml_use_internal_errors(true);
$htmlDom->loadHTML($htmlString);
$htmlDom->preserveWhiteSpace = false;
return $this->parseLoadedHtml($htmlDom);
} catch (\Exception $exception) {
dd($exception);
}
}
private function parseLoadedHtml($htmlDom)
{
$response = [];
$page = $htmlDom->getElementsByTagName("body")[ 0 ];
$dataStructuredArray = $this->buildTheParsedResponse($this->domToArray($page));
foreach ($dataStructuredArray as $index => $item) {
if (isset($item[ '_type' ]) && $item[ '_type' ] !== 'table') {
$data = $this->handleChildrens($item);
if (isset($data[ 'content' ])) {
$data[ 'content' ] = $this->closetags($data[ 'content' ]);
$data[ 'clean_content' ] = preg_replace("/(\r\n|\t|\r|\n)+/", " ", strip_tags($data[ 'content' ]));
$response[] = $data;
}
}
}
return $this->fixChildrenStructure($response);
}
private function domToArray($root)
{
$result = [];
//handle classic node
if ($root->nodeType == XML_ELEMENT_NODE) {
$result[ '_type' ] = $root->nodeName;
if ($root->nodeName === 'ol') {
if ($root->hasAttribute('start')) {
$result[ '_startFrom' ] = $root->getAttribute('start');
} else {
$result[ '_startFrom' ] = 1;
}
}
$result[ '_numberOfChildren' ] = $root->childNodes->length;
if ($root->hasChildNodes()) {
$children = $root->childNodes;
for ($i = 0; $i < $children->length; $i++) {
$child = $this->domToArray($children->item($i));
//don't keep textnode with only spaces and newline
if (! empty($child)) {
$result[ '_children' ][] = $child;
}
}
}
//handle text node
} elseif ($root->nodeType == XML_TEXT_NODE || $root->nodeType == XML_CDATA_SECTION_NODE) {
$value = $root->nodeValue;
if (! empty($value)) {
$cleanText = preg_replace("/(\r\n|\t|\r|\n)+/", " ", $value);
if (! empty(str_replace(' ', '', $cleanText))) {
$result[ '_type' ] = '_text';
$result[ '_content' ] = ltrim($cleanText);
}
}
}
//list attributes
if ($root->hasAttributes()) {
foreach ($root->attributes as $attribute) {
$result[ '_attributes' ][ $attribute->name ] = $attribute->value;
}
}
return $result;
}
private function buildTheParsedResponse(array $htmElementsAsArray): array
{
$parsedResponse = [];
foreach ($htmElementsAsArray[ '_children' ] as $index => $elementArray) {
$data = [];
if ($elementArray[ '_type' ] === '_text') {
$data[ '_type' ] = $elementArray[ '_type' ];
$data[ 'content' ] = $this->parseParagraph($elementArray);
} elseif (isset($elementArray[ '_children' ])) {
$parsedResponseData = $this->buildTheParsedResponse($elementArray);
if (! empty($parsedResponseData)) {
$data[ '_type' ] = $elementArray[ '_type' ];
if (in_array($elementArray[ '_type' ], ['ul', 'ol'])) {
if (isset($elementArray[ '_startFrom' ])) {
$data[ 'start' ] = $elementArray[ '_startFrom' ];
}
$data [ 'children' ] = $parsedResponseData;
} else {
$data [ 'content' ] = $parsedResponseData;
}
}
}
if (! empty($data)) {
if (isset($elementArray[ '_attributes' ])) {
$data[ '_attributes' ] = $elementArray[ '_attributes' ];
}
$parsedResponse[] = $data;
}
}
return $parsedResponse;
}
private function remove_empty_tags_recursive($str, $repto = null)
{
//** Return if string not given or empty.
if (! is_string($str) || trim($str) == '') {
return $str;
}
//** Recursive empty HTML tags.
return preg_replace(
//** Pattern written by Junaid Atari.
'/<([^<\/>]*)>([\s]*?|(?R))<\/\1>/imsU',
//** Replace with nothing if string empty.
! is_string($repto) ? '' : $repto,
//** Source string
$str);
}
private function closetags($text)
{
$tagstack = [];
$stacksize = 0;
$tagqueue = '';
$newtext = '';
// Known single-entity/self-closing tags.
$single_tags = [
'area',
'base',
'basefont',
'br',
'col',
'command',
'embed',
'frame',
'hr',
'img',
'input',
'isindex',
'link',
'meta',
'param',
'source'
];
// Tags that can be immediately nested within themselves.
$nestable_tags = ['blockquote', 'div', 'object', 'q', 'span'];
// WP bug fix for comments - in case you REALLY meant to type '< !--'.
$text = str_replace('< !--', '< !--', $text);
// WP bug fix for LOVE <3 (and other situations with '<' before a number).
$text = preg_replace('#<([0-9]{1})#', '&lt;$1', $text);
/**
* Matches supported tags.
*
* To get the pattern as a string without the comments paste into a PHP
* REPL like `php -a`.
*
* @see https://html.spec.whatwg.org/#elements-2
* @see https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name
*
* @example
* ~# php -a
* php > $s = [paste copied contents of expression below including parentheses];
* php > echo $s;
*/
$tag_pattern = ('#<'. // Start with an opening bracket.
'(/?)'. // Group 1 - If it's a closing tag it'll have a leading slash.
'('. // Group 2 - Tag name.
// Custom element tags have more lenient rules than HTML tag names.
'(?:[a-z](?:[a-z0-9._]*)-(?:[a-z0-9._-]+)+)'.'|'.// Traditional tag rules approximate HTML tag names.
'(?:[\w:]+)'.')'.'(?:'.// We either immediately close the tag with its '>' and have nothing here.
'\s*'.'(/?)'. // Group 3 - "attributes" for empty tag.
'|'.// Or we must start with space characters to separate the tag name from the attributes (or whitespace).
'(\s+)'. // Group 4 - Pre-attribute whitespace.
'([^>]*)'. // Group 5 - Attributes.
')'.'>#' // End with a closing bracket.
);
while (preg_match($tag_pattern, $text, $regex)) {
$full_match = $regex[ 0 ];
$has_leading_slash = ! empty($regex[ 1 ]);
$tag_name = $regex[ 2 ];
$tag = strtolower($tag_name);
$is_single_tag = in_array($tag, $single_tags, true);
$pre_attribute_ws = isset($regex[ 4 ]) ? $regex[ 4 ] : '';
$attributes = trim(isset($regex[ 5 ]) ? $regex[ 5 ] : $regex[ 3 ]);
$has_self_closer = '/' === substr($attributes, -1);
$newtext .= $tagqueue;
$i = strpos($text, $full_match);
$l = strlen($full_match);
// Clear the shifter.
$tagqueue = '';
if ($has_leading_slash) { // End tag.
// If too many closing tags.
if ($stacksize <= 0) {
$tag = '';
// Or close to be safe $tag = '/' . $tag.
// If stacktop value = tag close value, then pop.
} elseif ($tagstack[ $stacksize - 1 ] === $tag) { // Found closing tag.
$tag = '</'.$tag.'>'; // Close tag.
array_pop($tagstack);
$stacksize--;
} else { // Closing tag not at top, search for it.
for ($j = $stacksize - 1; $j >= 0; $j--) {
if ($tagstack[ $j ] === $tag) {
// Add tag to tagqueue.
for ($k = $stacksize - 1; $k >= $j; $k--) {
$tagqueue .= '</'.array_pop($tagstack).'>';
$stacksize--;
}
break;
}
}
$tag = '';
}
} else { // Begin tag.
if ($has_self_closer) { // If it presents itself as a self-closing tag...
// ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such
// and immediately close it with a closing tag (the tag will encapsulate no text as a result).
if (! $is_single_tag) {
$attributes = trim(substr($attributes, 0, -1))."></$tag";
}
} elseif ($is_single_tag) { // Else if it's a known single-entity tag but it doesn't close itself, do so.
$pre_attribute_ws = ' ';
$attributes .= '/';
} else { // It's not a single-entity tag.
// If the top of the stack is the same as the tag we want to push, close previous tag.
if ($stacksize > 0 && ! in_array($tag, $nestable_tags,
true) && $tagstack[ $stacksize - 1 ] === $tag) {
$tagqueue = '</'.array_pop($tagstack).'>';
$stacksize--;
}
$stacksize = array_push($tagstack, $tag);
}
// Attributes.
if ($has_self_closer && $is_single_tag) {
// We need some space - avoid <br/> and prefer <br />.
$pre_attribute_ws = ' ';
}
$tag = '<'.$tag.$pre_attribute_ws.$attributes.'>';
// If already queuing a close tag, then put this tag on too.
if (! empty($tagqueue)) {
$tagqueue .= $tag;
$tag = '';
}
}
$newtext .= substr($text, 0, $i).$tag;
$text = substr($text, $i + $l);
}
// Clear tag queue.
$newtext .= $tagqueue;
// Add remaining text.
$newtext .= $text;
while ($x = array_pop($tagstack)) {
$newtext .= '</'.$x.'>'; // Add remaining tags to close.
}
// WP fix for the bug with HTML comments.
$newtext = str_replace('< !--', '<!--', $newtext);
$newtext = str_replace('< !--', '< !--', $newtext);
return $this->remove_empty_tags_recursive($newtext);
}
private function parseParagraph($elementArray, $type = null, $number = null)
{
$data = [];
$data[ '_content' ] = ($type) ? $this->closetags(implode('',
$type).$elementArray[ '_content' ]) : $elementArray[ '_content' ];
return $data;
}
private function handleChildrens($data, $parsed = [])
{
if ($data[ '_type' ] !== 'table') {
$parsed[ 'content' ] = '<'.$data[ '_type' ].'>';
if (in_array($data[ '_type' ], ['ol', 'ul'])) {
$parsed[ 'children' ] = [];
if (isset($data[ 'start' ])) {
$startFrom = $data[ 'start' ];
}
foreach ($data[ 'children' ] as $child) {
if (isset($child[ 'start' ])) {
$startFrom = $child[ 'start' ];
}
if (isset($child[ 'content' ])) {
foreach ($child[ 'content' ] as $li) {
$data = $this->handleChildrens($li);
if (isset($data[ 'content' ])) {
$data[ 'clean_content' ] = preg_replace("/(\r\n|\t|\r|\n)+/", " ",
strip_tags($data[ 'content' ]));
if (isset($startFrom) && strlen(trim($data[ 'clean_content' ])) > 0) {
$data[ 'numbering_row' ] = $startFrom;
$startFrom++;
}
$parsed[ 'children' ][] = $data;
}
}
} else {
$data = $this->handleChildrens($child);
$data[ 'clean_content' ] = preg_replace("/(\r\n|\t|\r|\n)+/", " ",
strip_tags($data[ 'content' ]));
$parsed[ 'children' ][] = $data;
}
}
} elseif (isset($data[ '_type' ]) && ($data[ '_type' ] === 'div')) {
foreach ($data[ 'content' ] as $child) {
$data = $this->handleChildrens($child);
if (isset($data[ 'content' ])) {
$data[ 'clean_content' ] = preg_replace("/(\r\n|\t|\r|\n)+/", " ",
strip_tags($data[ 'content' ]));
$data[ 'content' ] = $this->closetags($data[ 'content' ]);
}
$parsed[ 'children' ][] = $data;
}
} else {
$contentChilds = count($data[ 'content' ]);
foreach ($data[ 'content' ] as $index => $child) {
if ($child[ '_type' ] !== '_text') {
if (! isset($parsed[ 'content' ])) {
$parsed[ 'content' ] = '<'.$child[ '_type' ].'>';
} else {
$parsed[ 'content' ] .= '<'.$child[ '_type' ].'>';
}
$childs = $this->handleChildrens($child, $parsed);
if ($childs && isset($child[ 'content' ])) {
$parsed[ 'content' ] .= $childs[ 'content' ];
}
} else {
if (! isset($parsed[ 'content' ])) {
$parsed[ 'content' ] = $child[ 'content' ][ '_content' ];
} else {
$parsed[ 'content' ] .= $child[ 'content' ][ '_content' ];
}
}
if ($contentChilds == $index + 1) {
$parsed[ 'content' ] = $this->closetags($parsed[ 'content' ]);
}
$parsed[ 'children' ] = [];
}
}
return $parsed;
}
}
private function fixChildrenStructure($data)
{
$result = [];
$alreadyHandledIndexes = [];
for ($i = 0; $i < count($data); $i++) {
if (isset($data[ $i ][ 'content' ]) && $data[ $i ][ 'content' ] == '<ol>') {
$alreadyHandledIndexes[] = $i;
continue;
}
if (array_key_exists($i, $alreadyHandledIndexes)) {
continue;
}
if(isset($data[ $i ]['content']) && $data[ $i ]['content']==='' && count($data[ $i ]['children'])==1){
$data[ $i ] = last($data[ $i ]['children']);
}
$j = $i + 1;
for ($j; $j < count($data); $j++) {
if (array_key_exists($i, $alreadyHandledIndexes)) {
continue;
}
if (! isset($data[ $j ][ 'content' ]) || strpos($data[ $j ][ 'content' ], 'h1') !== false) {
break;
}
if(isset($data[$i]['numbering_row'])){
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandledIndexes[] = $j;
}else {
break;
}
}
//if (isset($data[ $i ][ 'content' ]) && empty($data[ $i ][ 'content' ])) {
// $data[ $i ] = last($data[ $i ][ 'children' ]);
//}
if (is_array($data[ $i ]) && count($data[ $i ]) > 1 && ! isset($data[ $i ][ 'content' ])) {
$result = array_merge($result, $data[ $i ]);
} else {
$result[] = $data[ $i ];
}
$alreadyHandledIndexes[] = $i;
}
return $result;
}
private function handlePossibleChild($parent, $child = [])
{
if($child['content']===''){
dd($parent);
}
if (isset($parent[ 'children' ])) {
if (empty($parent[ 'content' ]) && count($parent[ 'children' ]) === 1) {
$parent = $parent[ 'children' ][ 0 ];
} elseif (empty($parent[ 'content' ]) && count($parent[ 'children' ]) > 1) {
$parent = $this->fixChildrenStructure($parent[ 'children' ]);
}
}
if (isset($child[ 'content' ]) && $child[ 'content' ] == '<ol>') {
for ($i = 0; $i < count($child[ 'children' ]); $i++) {
$newChild = $child[ 'children' ][ $i ];
if ($child[ 'children' ][ $i ][ 'content' ] == '<ol>') {
$lastParentChild = last($parent[ 'children' ]);
$newChild = $this->handlePossibleChild($lastParentChild, $child[ 'children' ][ $i ]);
}
$parent[ 'children' ][] = $newChild;
}
//return $parent;
}
if (isset($parent[ 'clean_content' ]) && strlen($parent[ 'clean_content' ]) && strpbrk(substr($parent[ 'clean_content' ],
-1), '.,;\'"0123456789') === false && ctype_lower(substr($parent[ 'clean_content' ],
-1)) && isset($child[ 'clean_content' ]) && strlen($child[ 'clean_content' ])) {
$parent[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ] = array_merge($parent[ 'children' ], $child[ 'children' ]);
$parent[ 'clean_content' ] .= ' '.$child[ 'clean_content' ];
}
if (is_array($parent) && count($parent) == 1 && ! isset($parent[ 'content' ])) {
$parent = array_shift($parent);
}
return $parent;
}
}

670
app/Parser/ParseHtmlArray.php

@ -0,0 +1,670 @@
<?php
namespace App\Parser;
use Illuminate\Support\Facades\Log;
class ParseHtmlArray
{
public function fromFile($filePath)
{
if (file_exists($filePath)) {
$fileContent = file_get_contents($filePath);
$fileContent = str_replace('},
]', "}
]", $fileContent);
return $this->handle(json_decode($fileContent,true));
} else {
Log::error('The given file dose not exists!');
}
}
public function handle($docxAsHtmlArray)
{
$response=[];
foreach ($docxAsHtmlArray as $i => $array) {
$response = array_merge($response, $this->handleTestHtml($array));
}
return $this->buildTheStructure($response);
}
private function buildTheStructure($data)
{
$response = [];
$alreadyHandled = [];
$numbers = [];
for ($i = 0; $i < count($data); $i++) {
if (array_key_exists($i, $alreadyHandled)) {
continue;
}
$parent = $data[ $i ];
//get numbering from first 10 chars of the string
preg_match('/^([-+]?\d*\.?\d+)(?:[eE]([-+]?\d+))?/', preg_replace('/[^0-9\.)]/', '',
substr(trim(preg_replace('/[^A-Za-z0-9.)]/', '', preg_replace('/\)/', '.',
preg_replace("/\{.+/", "", html_entity_decode($data[ $i ][ 'content' ]))))), 0, 5)),
$parentNumbering);
if ($parentNumbering && count($numbers) == 0 && last($parentNumbering) < 5) {
$numbers[] = $parentNumbering[ 0 ];
$data[ $i ][ 'numbering' ] = rtrim($parentNumbering[ 0 ], '.');
} elseif ($parentNumbering && count($numbers) > 0 && $parentNumbering[ 0 ] >= last($numbers)) {
$numbers[] = $parentNumbering[ 0 ];
$data[ $i ][ 'numbering' ] = rtrim($parentNumbering[ 0 ], '.');
}
//check if string starts with bold
//check if number of bolds equals to 1
//check if not empty html and contains words
if ((strpos($parent[ 'content' ], "<b>") === 0 || (substr_count($parent[ 'content' ],
"<b>") == 1 || $parentNumbering) && strlen(trim(strip_tags($parent[ 'content' ]))) > 0) || (str_word_count(preg_replace('/[A-Za-z]{4,}/',
'', strip_tags($data[ $i ][ 'content' ]))) < 2)) {
$childNumbers = [];
$j = $i + 1;
//check if data exists
if (isset($data[ $j ]) && strlen($data[ $j ][ 'content' ])) {
for ($j; $j < count($data); $j++) {
if ($data[ $j ][ 'content' ] == '\u00a0') {
$alreadyHandled[] = $j;
}
if (array_key_exists($j, $alreadyHandled)) {
continue;
}
$child = $data[ $j ];
preg_match('/^([-+]?\d*\.?\d+)(?:[eE]([-+]?\d+))?/',
substr(trim(urldecode(str_replace(['<b>', '</b>'], '',
strip_tags($data[ $j ][ 'content' ])))), 0, 5), $childNumbering);
if ($childNumbering && ! preg_match("/[a-z]/i", rtrim(trim($childNumbering[ 0 ])))) {
if ($childNumbering && count($childNumbers) == 0 && trim($childNumbering[ 0 ]) < 5) {
$childNumbers[] = trim($childNumbering[ 0 ]);
$data[ $j ][ 'numbering' ] = rtrim(trim($childNumbering[ 0 ]), '.');
} elseif ($childNumbering && count($childNumbers) > 0 && trim($childNumbering[ 0 ]) >= last($childNumbers)) {
$childNumbers[] = trim($childNumbering[ 0 ]);
$data[ $j ][ 'numbering' ] = rtrim(trim($childNumbering[ 0 ]), '.');
} elseif ($childNumbering && trim($childNumbering[ 0 ]) < 100) {
$childNumbers[] = trim($childNumbering[ 0 ]);
$data[ $j ][ 'numbering' ] = rtrim(trim($childNumbering[ 0 ]), '.');
}
}
if (empty(trim($data[ $i ][ 'content' ])) && isset($data[ $j ][ 'numbering' ])) {
break;
}
$breakPoints = array_change_key_case([
'TERMS OF THE {P1_Pros}',
'TERMS AND CONDITIONS',
'BACKGROUND',
'OPERATIVE PROVISIONS',
'Products and/or Services',
'PAYMENT',
'GRANT OF LICENCE',
'TERM OF LICENCE AGREEMENT',
'ROYALTY',
'PAYMENT',
'PERFORMANCE TARGETS',
'STATIONERY',
'QUALITY CONTROL',
'THE DISTRIBUTOR\'S OBLIGATIONS',
'NON SOLICITATION',
'SALE OF BUSINESS',
'TERMINATION OF AGREEMENT',
'CONDITIONS FOLLOWING TERMINATION',
'RESTRAINT',
'TIME OF ESSENCE AND NOTICES',
'INTERPRETATION',
'ARBITRATION',
'DOMICILIUM AND REGISTERED OFFICE',
'USE OF TRADE MARKS, TRADE NAME, GOODWILL AND KNOW-HOW',
'GENERAL',
'DESCRIPTION OF {P2_NAME} INFORMATION',
'PAYMENT OF FEES',
'SUPPLIER\'S STATUS',
'SUPPLIER\’S OBLIGATIONS',
'DEFINITIONS AND INTERPRETATION',
'DEFINITIONS',
'CONFIDENTIALITY',
'TERMINATION',
'RESTRICTIVE COVENANTS AND INTELLECTUAL PROPERTY',
'DETAILS AND IDENTITY OF CONSULTANT',
'ANTI-BRIBERY',
'ASSIGNMENT SCHEDULE',
'SCHEDULE 1',
'{P1_NAME}\'S LIABILITY',
'DURATION OF AGREEMENT AND SUPPLY',
'SUPPLY OF HARDWARE',
'SUPPLY OF SOFTWARE AND DOCUMENTATION',
'SUPPLY OF SUPPORT SERVICES',
'INTELLECTUAL PROPERTY RIGHTS',
'THE CONTRACT',
'{P1_NAME}\U2019S LIABILITY',
'UPDATES',
'TERMS OF THE {P1_NAME} PRODUCTS.',
'CUSTOMER RESPONSIBILITIES',
'EXHIBIT A',
'EXHIBIT A-1',
'EXHIBIT A-2',
'WARRANTIES',
'EXIT, TERMINATION AND SUSPENSION',
'EXHIBIT B',
'EXHIBIT B-1',
'EXHIBIT B-2',
'COUNTERPARTS',
'LICENSE GRANT',
'INDEMNIFICATION BY CUSTOMER',
'TERMS OF THE {P1_NAME} PRODUCTS',
'TERMS OF CLOUD SERVICE',
'INDEMNIFICATION BY CUSTOMER',
'TERMINATION',
'TERMS OF THE {P1_PROS}',
'SUPPORT',
'SUB CONTRACTING AND THIRD PARTY RECOMMENDATIONS',
'LICENCE AND ACCESS TO SOFTWARE AND HARDWARE',
'DECLARATION OF NON-LIAISON AND ANTI-CORRUPTION COMMITMENT',
'{P1_NAME}\'S DUTIES'
], CASE_UPPER);
//$breakPoints = [];
if ($this->paragraphBrake($data[ $j ], $breakPoints)) {
break;
}
if (substr(trim(str_replace(array_merge([')'], $childNumbering), '', $data[ $j ][ 'content' ])),
0, 3) == '<b>' && str_word_count(strip_tags(str_replace(array_merge([')'],
$childNumbering), '',
$data[ $j ][ 'content' ]))) == str_word_count($this->getTextBetweenTags(str_replace(array_merge([')',],
$childNumbering), '', $data[ $j ][ 'content' ]),
'b')) && (isset($data[ $j + 1 ]) && ((ctype_upper(substr($data[ $j + 1 ][ 'content' ],
0,
1)) || (isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ]) && $data[ $j ][ 'numbering' ] - $data[ $i ][ 'numbering' ] == 1))))) {
break;
}
if (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && ! isset($data[ $i ][ 'numbering' ]) && ctype_upper(str_replace(' ',
'', $data[ $j ][ 'content' ])) && str_word_count($data[ $j ][ 'content' ]) >= 1) {
break;
}
if (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && ! isset($data[ $i ][ 'numbering' ]) && ctype_upper(str_replace([
'<b>',
'</b>',
last($childNumbering),
last($childNumbering),
')',
'.'
], '', trim(str_replace(' ', '',
$data[ $j ][ 'content' ])))) && str_word_count($data[ $j ][ 'content' ]) >= 1) {
break;
}
//if(isset($data[$j]['numbering']) && isset($data[$i]['numbering']) && )
if (isset($data[ $i ][ 'children' ]) && isset($data[ $i ][ 'numbering' ]) && count($data[ $i ][ 'children' ]) && isset($data[ $j ][ 'numbering' ]) && isset(last($data[ $i ][ 'children' ])[ 'numbering' ]) && ($data[ $j ][ 'numbering' ] - last($data[ $i ][ 'children' ])[ 'numbering' ] !== 1 && $data[ $i ][ 'numbering' ] < $data[ $j ][ 'numbering' ]) && ! in_array(substr(strip_tags(last($data[ $i ][ 'children' ])[ 'content' ]),
strlen(strip_tags(last($data[ $i ][ 'children' ])[ 'content' ])) - 1),
[':', '-']) && ! strpos($data[ $j ][ 'numbering' ], '.')) {
break;
}
if (in_array(strtoupper(trim(str_replace([
'<b>',
'</b>',
last($parentNumbering),
last($parentNumbering),
')',
'.'
], '', strip_tags($data[ $i ][ 'content' ])))), $breakPoints)) {
if ((! isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ]) && (substr($data[ $i ][ 'content' ],
0,
3) != '<b>') || (str_word_count(strip_tags($data[ $i ][ 'content' ])) != str_word_count($this->getTextBetweenTags($data[ $i ][ 'content' ],
'b'))))) {
if (! in_array($data[ $i ][ 'content' ], $breakPoints)) {
break;
}
}
}
if (in_array(strtoupper(trim(str_replace([
'<b>',
'</b>',
last($childNumbering),
last($childNumbering),
')',
'.'
], '', strip_tags($data[ $j ][ 'content' ])))), $breakPoints)) {
break;
}
if (in_array(substr(strip_tags($data[ $j ][ 'content' ]),
strlen(strip_tags($data[ $j ][ 'content' ])) - 1), [':', '-'])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && ctype_lower(substr(last($data[ $i ][ 'children' ])[ 'content' ],
strlen(last($data[ $i ][ 'children' ])[ 'content' ]) - 1)) && ctype_lower(substr(trim($data[ $j ][ 'content' ]),
0, 1))) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (str_word_count(preg_replace('/[A-Za-z]{4,}/', '',
strip_tags($data[ $j ][ 'content' ]))) < 3 && strlen(strip_tags($data[ $j ][ 'content' ])) && ! isset($data[ $j ][ 'numbering' ]) && ctype_upper(substr($data[ $j ][ 'content' ],
0, 1)) && str_word_count($data[ $j ][ 'content' ]) < 10) {
if (isset($data[ $i ][ 'children' ]) && ! in_array(substr(trim(last($data[ $i ][ 'children' ])[ 'content' ]),
strlen(trim(last($data[ $i ][ 'children' ])[ 'content' ])) - 1),
['!', '.', '?', '_', '}'])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} else {
break;
}
//dd($data[$i]);
} elseif (str_word_count(preg_replace('/[A-Za-z]{4,}/', '',
strip_tags($data[ $i ][ 'content' ]))) < 2 && strlen(strip_tags($data[ $i ][ 'content' ]))) {
if (isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ]) && is_numeric($data[ $j ][ 'numbering' ]) && abs($data[ $j ][ 'numbering' ] - $data[ $i ][ 'numbering' ]) == 1 && str_word_count($data[ $j ]
[ 'content' ]) < 6) {
break;
}
if (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && ((str_word_count($data[ $j ]
[ 'content' ]) < 6) || (substr_count($data[ $j ][ 'content' ],
'<b>') == 1 && substr_count(last($data[ $i ][ 'children' ])[ 'content' ],
'<b>') == 0 && ! isset(last($data[ $i ][ 'children' ])[ 'numbering' ]))) && ctype_upper((substr($data[ $j ][ 'content' ],
0, 1)))) {
break;
}
if (isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ]) && $data[ $j ][ 'numbering' ] + 1 == $data[ $i ][ 'numbering' ] && str_word_count($data[ $j ][ 'content' ]) < 6) {
break;
}
if (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && ! isset($data[ $i ][ 'numbering' ]) && ! isset(last($data[ $i ][ 'children' ])[ 'numbering' ]) && isset($data[ $j ][ 'numbering' ])) {
break;
}
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (! in_array(trim(strtolower(strip_tags($data[ $j ][ 'content' ]))),
['definitions']) && ! ctype_space($data[ $j ][ 'content' ]) && strlen(trim(strip_tags($data[ $j ][ 'content' ]))) && ! isset($data[ $i ][ 'numbering' ]) && ! isset($data[ $j ][ 'numbering' ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ])) {
if (is_numeric($data[ $j ][ 'numbering' ]) && is_numeric($data[ $i ][ 'numbering' ]) && ((float) $data[ $j ][ 'numbering' ] - (float) $data[ $i ][ 'numbering' ]) == 1 && str_word_count($data[ $j ][ 'content' ]) < str_word_count($data[ $i ][ 'content' ])) {
break;
}
if (is_numeric($data[ $j ][ 'numbering' ]) && abs($data[ $j ][ 'numbering' ] - $data[ $i ][ 'numbering' ]) === 1 && (isset($data[ $i ][ 'children' ]) && (! (isset(last($data[ $i ][ 'children' ])[ 'numbering' ])) || (isset(last($data[ $i ][ 'children' ])[ 'numbering' ]) && abs(last($data[ $i ][ 'children' ])[ 'numbering' ] - $data[ $j ][ 'numbering' ]) !== 1))) && str_word_count($data[ $j ][ 'content' ]) < 8) {
break;
}
if (substr_count($data[ $j ][ 'numbering' ], '.') > substr_count($data[ $i ][ 'numbering' ],
'.') && ((float) $data[ $j ][ 'numbering' ] - (float) $data[ $i ][ 'numbering' ]) < 1) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (((float) $data[ $j ][ 'numbering' ] > (float) $data[ $i ][ 'numbering' ] && substr_count($data[ $j ][ 'content' ],
'<b>') == 0 && substr_count($data[ $i ][ 'content' ],
'<b>') == 1) || (substr_count($data[ $i ][ 'content' ],
"<b>") == 1 && (substr_count($data[ $j ][ 'content' ],
'<b>') == 0 || substr_count($data[ $j ][ 'content' ], '<b>')) > 1)) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (substr_count($data[ $i ][ 'content' ],
'<b>') == 1 && str_word_count($data[ $j ][ 'content' ]) > 6 && isset($data[ $j ][ 'numbering' ])) {
if (strpos($data[ $j ][ 'content' ],
'Networking infrastructure (hardware, firmware, software an') !== false) {
dd('aa');
}
if (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ])) {
$lastParentChild = last($data[ $i ][ 'children' ]);
if (isset($lastParentChild[ 'numbering' ]) && abs($lastParentChild[ 'numbering' ] - $data[ $j ][ 'numbering' ]) === 1 && (substr_count($data[ $j ][ 'content' ],
'<b>') == 1)) {
break;
}
}
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($data[ $i ][ 'numbering' ]) && abs($data[ $i ][ 'numbering' ] - $data[ $j ][ 'numbering' ]) === 1 && str_word_count($data[ $j ][ 'content' ]) >= 6) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && isset($data[ $j ][ 'numbering' ]) && isset(last($data[ $i ][ 'children' ])[ 'numbering' ]) && abs((float) $data[ $j ][ 'numbering' ] - (float) last($data[ $i ][ 'children' ])[ 'numbering' ]) == (float) 1) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($data[ $i ][ 'numbering' ]) && abs($data[ $i ][ 'numbering' ] - $data[ $j ][ 'numbering' ]) == 0 && str_word_count($data[ $j ][ 'content' ]) >= 6) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} else {
break;
}
} elseif (isset($data[ $i ][ 'numbering' ]) && ! isset($data[ $j ][ 'numbering' ]) && str_word_count($data[ $j ][ 'content' ]) > 6) {
if (substr_count($data[ $j ][ 'content' ],
"<b>") == 1 && strpos(strtolower($data[ $i ][ 'content' ]),
'definition') === false) {
break;
}
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (empty($data[ $j ][ 'content' ]) && (isset($data[ $j + 1 ]) && isset($data[ $j - 1 ]) && isset($data[ $i ][ 'children' ]))) {
if (isset(last($data[ $i ][ 'children' ])[ 'numbering' ]) && strlen(last($data[ $i ][ 'children' ])[ 'numbering' ]) == strlen(preg_replace('/[^0-9\.)]/',
'', substr(trim(preg_replace('/ +/', ' ', preg_replace('/[^A-Za-z0-9 .]/', ' ',
urldecode(strip_tags($data[ $j + 1 ][ 'content' ]))))), 0,
5))) && ! empty($data[ $j ][ 'content' ])) {
dd('Here', $data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} else {
break;
}
} elseif (isset($data[ $i ][ 'children' ]) && count($data[ $i ][ 'children' ]) && isset($data[ $j ][ 'numbering' ])) {
$lastParentChild = last($data[ $i ][ 'children' ]);
if (isset($lastParentChild[ 'numbering' ]) && isset($child[ 'numbering' ]) && substr_count($lastParentChild[ 'numbering' ],
'.') > substr_count($data[ $j ][ 'numbering' ], '.')) {
dd('111');
} else {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
}
} else {
break;
}
//if(strpos($data[$i]['content'],'<b>2. TERMS OF THE {P1_Pros}.</b>')!==false || strpos($data[$j]['content'],'<b>2. TERMS OF THE {P1_Pros}.</b>')!==false){
// dd($data[$i],$data[$j]);
//}
}
}
}
if (strlen(trim(strip_tags($data[ $i ][ 'content' ])))) {
$response[] = $data[ $i ];
//if ($data[ $i ][ 'content' ] == "Duration of Agreement and Supply") {
// dd(121,$data[$i],$i);
//}
//if($i > 73){
// dd($i,$data[$i],$response);
//}
}
$alreadyHandled[] = $i;
}
return $response;
}
private function handlePossibleChild($parent, $child)
{
if (empty($parent[ 'content' ]) && ! empty($child[ 'content' ])) {
return $child;
}
if (empty($child[ 'content' ])) {
return $parent;
}
// Must iterate through parent children
if (! isset($parent[ 'children' ]) || (isset($parent[ 'children' ]) && count($parent[ 'children' ]) == 0)) {
$parent[ 'children' ] = [];
if (str_word_count(strip_tags($child[ 'content' ])) >= 5 && strpos($child[ 'content' ], '<b>') === false) {
$parent[ 'children' ][] = $child;
} elseif (strpos($parent[ 'content' ], '<b>') !== false && strpos($child[ 'content' ], '<b>') !== false) {
$parent[ 'children' ][] = $child;
} elseif (isset($child[ 'content' ])) {
$parent[ 'children' ][] = $child;
}
return $parent;
}
$lastParentChild = last($parent[ 'children' ]);
if ($lastParentChild && substr($lastParentChild[ 'content' ],
strlen($lastParentChild[ 'content' ]) - 1) === ':' && ((ctype_lower(substr($child[ 'content' ], 0,
1)) || (ctype_digit(substr($child[ 'content' ], 0,
1)) && str_word_count($child[ 'content' ]) > 5)))) {
$lastParentChild = $this->handlePossibleChild($lastParentChild, $child);
if (isset($lastParentChild[ 'numbering' ]) && isset($child[ 'numbering' ]) && $child[ 'numbering' ] - 1 == $lastParentChild[ 'numbering' ]) {
$parent[ 'children' ][] = $child;
} else {
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
}
return $parent;
}
if (isset($lastParentChild[ 'numbering' ]) && isset($child[ 'numbering' ]) && strlen($child[ 'numbering' ]) > strlen($lastParentChild[ 'numbering' ])) {
if (isset($parent[ 'children' ]) && isset(last($parent[ 'children' ])[ 'numbering' ]) && $child[ 'numbering' ]) {
if (is_numeric($child[ 'numbering' ]) && abs($child[ 'numbering' ] - $lastParentChild[ 'numbering' ]) === 1) {
$parent[ 'children' ][] = $child;
return $parent;
}
}
if (isset($child[ 'numbering' ]) && isset($lastParentChild[ 'numbering' ]) && substr_count($lastParentChild[ 'numbering' ],
'.') == substr_count($child[ 'numbering' ], '.')) {
$parent[ 'children' ][] = $child;
return $parent;
}
$lastParentChild = $this->handlePossibleChild($lastParentChild, $child);
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
if (! in_array(substr(trim(str_replace(['and', 'or'], '', $lastParentChild[ 'content' ])),
strlen(trim(str_replace(['and', 'or'], '', $lastParentChild[ 'content' ]))) - 1),
['!', '.', '?', ';', '_', ':']) && (ctype_lower(substr(trim($child[ 'content' ]), 0,
1)) || ((ctype_upper(substr(trim($child[ 'content' ]), 0,
1)) && ! isset($child[ 'numbering' ]))))) {
//dd($lastParentChild,$child);
if (strpos($lastParentChild[ 'content' ],
'e, this Agreement and the {P1_Name} Software Licence Agreement') !== false) {
dd('aa', $lastParentChild, $child);
}
$lastParentChild[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
} elseif (! in_array(substr(trim($parent[ 'content' ]), strlen(trim($parent[ 'content' ])) - 1),
['!', '.', '?', ';']) && ctype_lower(substr(trim($lastParentChild[ 'content' ]),
strlen(trim($lastParentChild[ 'content' ])) - 1)) && ctype_lower(substr(trim($child[ 'content' ]), 0,
1))) {
$parent[ 'children' ][] = $child;
} elseif (! in_array(substr(trim(str_replace(['and', 'or'], '', $lastParentChild[ 'content' ])),
strlen(trim(str_replace(['and', 'or'], '', $lastParentChild[ 'content' ]))) - 1), [
'!',
'.',
'?',
';',
'_',
':'
]) && isset($lastParentChild[ 'numbering' ]) && isset($child[ 'numbering' ]) && $lastParentChild[ 'numbering' ] > $child[ 'numbering' ]) {
$lastParentChild[ 'children' ][] = $child;
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
} else {
$parent[ 'children' ][] = $child;
}
return $parent;
}
public function handleTestHtml($array)
{
$data = [];
foreach ($array as $item) {
if (count($item) == 1 && is_array(last($item))) {
return $this->handleTestHtml($item);
} else {
$html = $this->buildParagraphs($item);
if (! isset($data[ 'content' ]) && count($html) > 1) {
$data = array_merge($data, $html);
} elseif ($html) {
$data = $html;
}
}
}
return $data;
}
private function buildParagraphs($paragraphs)
{
$result = [];
$alreadyHandled = [];
for ($i = 0; $i < count($paragraphs); $i++) {
if (array_key_exists($i, $alreadyHandled)) {
continue;
}
$paragraph = $paragraphs[ $i ];
if (is_array($paragraph)) {
$result = array_merge($result, $this->buildParagraphs($paragraph));
} elseif (strlen($paragraph) && ! ctype_space($paragraph)) {
$cleanHtml = trim(str_replace('<b> </b>', '',
preg_replace('/<([^>\s]+)[^>]*>(?:\s*(?:<br \/>|&nbsp;|&thinsp;|&ensp;|&emsp;|&#8201;|&#8194;|&#8195;)\s*)*<\/\1>/',
'', preg_replace('/(<font[^>]*>)|(<\/font>)/', '', preg_replace('/\s+/S', " ", $paragraph)))));
if (! empty($cleanHtml)) {
$result[] = ['content' => html_entity_decode($cleanHtml, ENT_COMPAT | ENT_HTML401, 'UTF-8')];
}
}
}
return $result;
}
/*
* Get text between html tag
*/
private function getTextBetweenTags($string, $tagname)
{
$pattern = "/<$tagname ?.*>(.*)<\/$tagname>/";
preg_match($pattern, str_replace(['<u>', '</u>'], '', $string), $matches);
if ($matches) {
return last($matches);
}
return '';
}
private function paragraphBrake($paragraph, array $breakPoints)
{
//$paragraph[ 'content' ] = '2) <b>TERMS OF THE {P1_Pros}.</b> Subject to the terms of the Agreement, {P1_Name} grants Customer and/or its Affiliates a non-exclusive, non-transferable (except to a successor in interest as permitted hereunder) license to use the {P1_Pros} listed on the <u>Order Form</u> during the Term. Customer\’s and/or its Affiliates\’ right to use the {P1_Pros} is limited to the volume and other restrictions contained herein and in the Order Form and the Documentation.';
//$paragraph[ 'numbering' ] = '2';
preg_replace('/<b ?.*>(\d+)<\/b>/', $paragraph[ 'content' ], $paragraph[ 'content' ]);
preg_replace('/(\d+)\)/', $paragraph[ 'content' ], $paragraph[ 'content' ]);
if (isset($paragraph[ 'numbering' ])) {
$paragraph[ 'content' ] = str_replace(['.', ')', $paragraph[ 'numbering' ]], '', $paragraph[ 'content' ]);
}
if (substr_count($paragraph[ 'content' ], '</b>') === 1) {
$breakString = explode('</b>', $paragraph[ 'content' ]);
if ($breakString) {
$breakString = trim(str_replace('<b>', '', trim($breakString[ 0 ])));
if (in_array($breakString, $breakPoints)) {
return true;
}
}
}
return false;
}
}

747
app/Parser/ParseTextArray.php

@ -0,0 +1,747 @@
<?php
namespace App\Parser;
use Illuminate\Support\Facades\Log;
class ParseTextArray
{
/**
* @var array
*/
private $breakPoints = [
'TERMS OF THE {P1_Pros}',
'TERMS AND CONDITIONS',
'BACKGROUND',
'OPERATIVE PROVISIONS',
'Products and/or Services',
'PAYMENT',
'GRANT OF LICENCE',
'TERM OF LICENCE AGREEMENT',
'ROYALTY',
'PAYMENT',
'PERFORMANCE TARGETS',
'STATIONERY',
'QUALITY CONTROL',
'THE DISTRIBUTOR\'S OBLIGATIONS',
'NON SOLICITATION',
'SALE OF BUSINESS',
'TERMINATION OF AGREEMENT',
'CONDITIONS FOLLOWING TERMINATION',
'RESTRAINT',
'TIME OF ESSENCE AND NOTICES',
'INTERPRETATION',
'ARBITRATION',
'DOMICILIUM AND REGISTERED OFFICE',
'USE OF TRADE MARKS, TRADE NAME, GOODWILL AND KNOW-HOW',
'GENERAL',
'DESCRIPTION OF {P2_NAME} INFORMATION',
'PAYMENT OF FEES',
'SUPPLIER\'S STATUS',
'SUPPLIER\’S OBLIGATIONS',
'DEFINITIONS AND INTERPRETATION',
'DEFINITIONS',
'CONFIDENTIALITY',
'TERMINATION',
'RESTRICTIVE COVENANTS AND INTELLECTUAL PROPERTY',
'DETAILS AND IDENTITY OF CONSULTANT',
'ANTI-BRIBERY',
'ASSIGNMENT SCHEDULE',
'SCHEDULE 1',
'{P1_NAME}\'S LIABILITY',
'DURATION OF AGREEMENT AND SUPPLY',
'SUPPLY OF HARDWARE',
'SUPPLY OF SOFTWARE AND DOCUMENTATION',
'SUPPLY OF SUPPORT SERVICES',
'INTELLECTUAL PROPERTY RIGHTS',
'THE CONTRACT',
'{P1_NAME}\U2019S LIABILITY',
'UPDATES',
'TERMS OF THE {P1_NAME} PRODUCTS.',
'CUSTOMER RESPONSIBILITIES',
'EXHIBIT A',
'EXHIBIT A-1',
'EXHIBIT A-2',
'WARRANTIES',
'EXIT, TERMINATION AND SUSPENSION',
'EXHIBIT B',
'EXHIBIT B-1',
'EXHIBIT B-2',
'COUNTERPARTS',
'LICENSE GRANT',
'INDEMNIFICATION BY CUSTOMER',
'TERMS OF THE {P1_NAME} PRODUCTS',
'TERMS OF CLOUD SERVICE',
'INDEMNIFICATION BY CUSTOMER',
'TERMINATION',
'TERMS OF THE {P1_PROS}',
'SUPPORT',
'SUB CONTRACTING AND THIRD PARTY RECOMMENDATIONS',
'LICENCE AND ACCESS TO SOFTWARE AND HARDWARE',
'DECLARATION OF NON-LIAISON AND ANTI-CORRUPTION COMMITMENT',
'{P1_NAME}\'S DUTIES',
'ana are mere',
'definitions',
'fees',
'ENGAGEMENT',
'DUTIES',
'TERMINATION',
'STATEMENTS',
'CONFIDENTIALITY',
'Human rights',
'Labour',
'Environment',
'Anti-corruption',
'Services',
'Scope of the Agreement',
'Staffing Levels for Services','Indemnification'
];
/**
* @var bool
*/
private $pdf;
/**
* ParseTextArray constructor.
*
* @param bool $pdf
*/
public function __construct($pdf = false)
{
$this->breakPoints = $this->nestedUppercase($this->breakPoints);
$this->pdf = $pdf;
}
public function fromFile($filePath)
{
if (file_exists($filePath)) {
$fileContent = file_get_contents($filePath);
return $this->buildTheStructure(array_filter(explode(PHP_EOL, $fileContent)));
} else {
Log::error('The given file dose not exists!');
}
}
/**
* Build the child structure and extract relevant data from the text content
*
*
* @param $textAsArray
*
* @return array
*/
private function buildTheStructure($textAsArray)
{
$textAsArray = array_values($textAsArray);
$response = [];
$alreadyHandled = [];
$countData = count($textAsArray);
for ($i = 0; $i < $countData; $i++) {
if (array_key_exists($i, $alreadyHandled)) {
continue;
}
//extract the content and count the number of the empty spaces from the begining
$data[ $i ] = [
'content' => trim($textAsArray[ $i ]),
'spaces' => strlen($textAsArray[ $i ]) - strlen(ltrim($textAsArray[ $i ]))
];
//Remove numbering from the paragraph content
if ($numbering = $this->getNumbering($textAsArray[ $i ])) {
$data[ $i ][ 'numbering' ] = $numbering;
$data[ $i ][ 'content' ] = trim(ltrim(str_replace($numbering, '', $data[ $i ][ 'content' ]), '.'));
}
if ($this->pdf && strpos($textAsArray[ $i ], 'Page') !== false && strpos($textAsArray[ $i ],
'of') !== false) {
$alreadyHandled[] = $i;
break;
}
$j = $i + 1;
if (isset($textAsArray[ $j ])) {
for ($j; $j < $countData; $j++) {
if (array_key_exists($j, $alreadyHandled)) {
continue;
}
if ($this->pdf && isset($textAsArray[ $j ]) && strpos($textAsArray[ $j ],
'Page') !== false && strpos($textAsArray[ $j ], 'of') !== false) {
$alreadyHandled[] = $j;
continue;
}
//extract the content and count the number of the empty spaces from the begining
$data[ $j ] = [
'content' => trim($textAsArray[ $j ]),
'spaces' => strlen($textAsArray[ $j ]) - strlen(ltrim($textAsArray[ $j ]))
];
//Remove numbering from the paragraph content
if ($numbering = $this->getNumbering($textAsArray[ $j ])) {
$data[ $j ][ 'numbering' ] = $numbering;
$data[ $j ][ 'content' ] = trim(ltrim(str_replace($numbering, '', $data[ $j ][ 'content' ]),
'.'));
}
//break if outh have numbering and the space is equal
if ($data[ $j ][ 'spaces' ] == $data[ $i ][ 'spaces' ] && $this->hasNumbering($data[ $j ]) && $this->hasNumbering($data[ $i ]) && substr_count($data[ $i ][ 'numbering' ],
'.') == substr_count($data[ $j ][ 'numbering' ],
'.') && count(array_filter(str_split($data[ $i ][ 'numbering' ]),
'is_numeric')) == count(array_filter(str_split($data[ $j ][ 'numbering' ]),
'is_numeric'))) {
break;
}
if ($this->hasNumbering($data[ $j ]) && ! $this->hasNumbering($data[ $i ]) && ! $data[ $i ][ 'spaces' ] && $data[ $j ][ 'spaces' ] > $data[ $i ][ 'spaces' ] && ! in_array(substr($data[ $i ][ 'content' ],
-1), [':'])) {
break;
}
if ($this->hasNumbering($data[ $j ]) && $this->hasNumbering($data[ $i ]) && ((float) $data[ $j ][ 'numbering' ] - (float) $data[ $i ][ 'numbering' ]) >= 1) {
break;
}
if ($this->hasNumbering($data[ $j ]) && $this->hasNumbering($data[ $i ]) && ((float) $data[ $j ][ 'numbering' ] - (float) $data[ $i ][ 'numbering' ]) >= 1) {
break;
}
//Hardcoded breakpoints
if ($this->hasNumbering($data[ $j ]) && in_array(strtoupper(str_replace(['.', "\t", ""], '',
$data[ $j ][ 'content' ])), $this->breakPoints)) {
break;
}
//Hardcoded "Schedule break"
if (! $this->hasNumbering($data[ $j ]) && strpos(substr(trim(strtolower(utf8_encode($data[ $j ][ 'content' ]))),
0, 10), 'schedule') !== false) {
break;
}
if (! $this->hasNumbering($data[ $j ]) && strpos(substr(trim($data[ $j ][ 'content' ]), 0, 15),
'Exhibit') !== false && ! in_array(substr(trim($data[ $j ][ 'content' ]), -1), [
'.'
])) {
break;
}
if (strpos(substr(trim(strtolower($data[ $j ][ 'content' ])), 0, 15), 'attachment') !== false) {
break;
}
if ($this->hasNumbering($data[ $j ]) && $this->hasChild($data[ $i ])) {
if ($this->hasNumbering(last($data[ $i ][ 'children' ])) && (is_numeric(last($data[ $i ][ 'children' ])[ 'numbering' ]) && strpos(last($data[ $i ][ 'children' ])[ 'numbering' ],
".") !== false) && (is_numeric($data[ $j ][ 'numbering' ]) && strpos($data[ $j ][ 'numbering' ],
".") === false)) {
break;
}
}
if ($data[ $j ][ 'spaces' ] > $data[ $i ][ 'spaces' ] && strlen($data[ $i ][ 'content' ]) && strlen($data[ $j ][ 'content' ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (isset($textAsArray[ $j + 1 ]) && $this->paragraphBetweenClauses($data[ $i ], $data[ $j ],
array_slice($textAsArray, $j + 1))) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif ($this->hasChild($data[ $i ]) && $this->lastChildIsList($data[ $i ]) && ($data[ $i ][ 'spaces' ] == 0 || $data[ $i ][ 'spaces' ] > $data[ $j ][ 'spaces' ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif ($data[ $j ][ 'spaces' ] == $data[ $i ][ 'spaces' ] && isset($data[ $i ][ 'numbering' ]) && isset($data[ $j ][ 'numbering' ]) && (substr_count($data[ $i ][ 'numbering' ],
'.') < substr_count($data[ $j ][ 'numbering' ],
'.') || count(array_filter(str_split($data[ $i ][ 'numbering' ]),
'is_numeric')) < count(array_filter(str_split($data[ $j ][ 'numbering' ]),
'is_numeric')))) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} else {
if ($this->paragraphIsList($data[ $i ]) && (ctype_lower(substr($data[ $j ][ 'content' ], 0,
1)) || in_array(substr($data[ $j ][ 'content' ], 0, 1), ['{', '•']))) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif ($this->hasNumbering($data[ $i ]) && $this->hasNumbering($data[ $j ]) && is_numeric($data[ $j ][ 'numbering' ]) && strpos($data[ $j ][ 'numbering' ],
".") !== false && strpos($data[ $i ][ 'numbering' ],
".") === false && ! is_int($data[ $j ][ 'numbering' ] - $data[ $i ][ 'numbering' ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif ($this->hasChild($data[ $i ]) && ($data[ $j ][ 'spaces' ] == $this->getLastChildForParagraph($data[ $i ])[ 'spaces' ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (strpos(strtolower($data[ $i ][ 'content' ]),
'definitions and') !== false && in_array(utf8_encode(substr($data[ $j ][ 'content' ], 0,
1)), ['â', '"'])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif ($this->hasChild($data[ $i ]) && $this->paragraphIsList($this->getLastChildFromParagraph($data[ $i ]))) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} elseif (($this->hasChild($data[ $i ]) || $data[ $i ][ 'spaces' ] == $data[ $j ][ 'spaces' ]) && ! $this->hasNumbering($this->getLastChildForParagraph($data[ $i ])) && ! $this->hasNumbering($data[ $j ])) {
$data[ $i ] = $this->handlePossibleChild($data[ $i ], $data[ $j ]);
$alreadyHandled[] = $j;
} else {
break;
}
}
}
}
if (strlen($data[ $i ][ 'content' ])) {
$response[] = $data[ $i ];
}
$alreadyHandled[] = $i;
}
return $this->recheckClauses($response);
}
/**
* Recheck missed clauses and assign them to a parent if is the case
*
* @param $clauses
*
* @return array
*/
private function recheckClauses($clauses)
{
$checkedClauses = [];
$alreadyManaged = [];
for ($i = 0; $i < count($clauses); $i++) {
if (array_key_exists($i, $alreadyManaged)) {
continue;
}
$data [ $i ] = $clauses[ $i ];
$j = $i + 1;
if (isset($clauses[ $j ]) && $clauses[ $j ][ 'content' ] && $this->hasNumbering($data[ $i ]) && ((! $this->hasNumbering($clauses[ $j ])) || (($this->hasNumbering($clauses[ $j ]) && is_numeric($clauses[ $j ][ 'numbering' ]) && count(array_filter(explode('.',
$clauses[ $j ][ 'numbering' ]))) > 1 && is_numeric($clauses[ $i ][ 'numbering' ]) && count(array_filter(explode('.',
$clauses[ $i ][ 'numbering' ]))) <= 1)))) {
for ($j; $j < count($clauses); $j++) {
if (isset($clauses[ $j ][ 'numbering' ]) && is_numeric($clauses[ $j ][ 'numbering' ]) && count(array_filter(explode('.',
$clauses[ $j ][ 'numbering' ]))) == 1) {
break;
}
$data[ $i ][ 'children' ][] = $clauses[ $j ];
$alreadyManaged[] = $j;
}
}
$alreadyManaged[] = $i;
if ($data[ $i ][ 'content' ]) {
$checkedClauses[] = $data[ $i ];
}
}
return $checkedClauses;
}
/**
* Build the child structure based on the spaces before the text
*
* @param $parent
* @param $child
*
*
* @return mixed
*/
private function handlePossibleChild($parent, $child)
{
if (empty($child[ 'content' ])) {
return $parent;
}
if ($this->pdf && ! isset($parent[ 'children' ]) && (ctype_lower(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), 0, 1),
['}', ')']) || is_numeric(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), -1),
['.', ',', ':']) || (! in_array(substr(trim($child[ 'content' ]), -1), [
'.',
',',
':'
])) && $child[ 'spaces' ] > $parent[ 'spaces' ]) && ((in_array(substr(trim($parent[ 'content' ]),
-1), ['}', ')', ',', '"']) || ! in_array(substr(trim($parent[ 'content' ]), -1),
['.', ':', '!']) || ctype_lower(substr(trim($parent[ 'content' ]), -1))))) {
//dd($parent,$child);
$parent[ 'content' ] .= ' '.$child[ 'content' ];
return $parent;
} elseif ($this->pdf && isset($parent[ 'children' ]) && (ctype_lower(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), 0, 1),
['}', ')']) || is_numeric(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), -1),
['.', ',', ':'])) && ((in_array(substr(trim($this->getLastChildForParagraph($parent)[ 'content' ]),
-1), [
'}',
')',
',',
'"'
]) || ! in_array(substr(trim($this->getLastChildForParagraph($parent)[ 'content' ]), -1),
['.', ':', '!']) || ctype_lower(substr(trim($this->getLastChildForParagraph($parent)[ 'content' ]),
-1))))) {
if (strpos($child[ 'content' ], 'thirty') !== false && $parent[ 'numbering' ] !== '1.') {
$lastParentChild = last($parent[ 'children' ]);
$lastParentChild[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
} elseif ($this->pdf && ! isset($parent[ 'children' ]) && $child[ 'spaces' ] >= $parent[ 'spaces' ] && ! $this->hasNumbering($child)) {
if ($this->hasChild($parent)) {
$lastParentChild = $this->getLastChildForParagraph($parent);
$lastParentChild[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
} else {
$parent[ 'content' ] .= ' '.$child[ 'content' ];
}
return $parent;
}
if (! isset($parent[ 'children' ])) {
$parent[ 'children' ][] = $child;
return $parent;
}
$lastParentChild = last($parent[ 'children' ]);
if ($this->lastChildIsList($parent) && (ctype_lower(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), -1), [';']) || strpos($child[ 'content' ],
':') !== false || in_array(trim(substr(trim($child[ 'content' ]), 0, 1)),
['{', '('])) && ! $this->hasNumbering($child)) {
if (! isset($lastParentChild[ 'children' ])) {
$lastParentChild[ 'children' ][] = $child;
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
if (isset($lastParentChild[ 'children' ]) && ! in_array(substr(last($lastParentChild[ 'children' ])[ 'content' ],
-1), ['.', ';', ',']) && ! in_array(substr(trim($child[ 'content' ]), 0, 1),
['(', '{', ':']) && ! $this->hasNumbering($child)) {
$lastParentChild[ 'children' ][ count($lastParentChild[ 'children' ]) - 1 ][ 'content' ] .= ' '.trim($child[ 'content' ]);
} else {
$lastParentChild[ 'children' ][] = $child;
}
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
if ($this->hasNumbering($lastParentChild) && $this->hasNumbering($child) && substr(trim($lastParentChild[ 'content' ]),
-1) == ':' && count(array_filter(str_split($lastParentChild[ 'numbering' ]),
'is_numeric')) < count(array_filter(str_split($child[ 'numbering' ]), 'is_numeric'))) {
$lastParentChild[ 'children' ][] = $child;
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
if ($lastParentChild[ 'spaces' ] == $child[ 'spaces' ]) {
if ($this->hasNumbering($lastParentChild) && $this->hasNumbering($child) && (in_array(substr(trim($lastParentChild[ 'content' ]),
-1), ['.', ';']) || $this->hasNumbering($child))) {
if (($this->hasNumbering($lastParentChild) && $this->hasNumbering($child) && ((int) substr($child[ 'numbering' ],
strrpos($child[ 'numbering' ], '.') + 1) - (int) substr($lastParentChild[ 'numbering' ],
strrpos($lastParentChild[ 'numbering' ],
'.') + 1) == 1)) || (in_array(utf8_encode(substr($lastParentChild[ 'content' ], 0,
1)), ['â', '"', '{']) && in_array(utf8_encode(substr($child[ 'content' ], 0, 1)),
['â', '"', '{', '•']))) {
$parent[ 'children' ][] = $child;
} else {
$lastParentChild[ 'children' ][] = $child;
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
}
} else {
$lastParentChild[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
}
} elseif (! $this->hasNumbering($child) && ! in_array(substr(trim($lastParentChild[ 'content' ]), 0, 1),
['.', ';', '}']) && (ctype_lower(substr(trim($lastParentChild[ 'content' ]),
-1))) || in_array(substr(trim($lastParentChild[ 'content' ]), -1),
[',']) && (ctype_lower(substr(trim($child[ 'content' ]), 0,
1)) || in_array(substr(trim($child[ 'content' ]), 0, 1), ['{', '(', ')']))) {
$lastParentChild[ 'content' ] .= ' '.$child[ 'content' ];
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
} else {
if ($this->hasChild($parent) && in_array(substr(trim($this->getLastChildForParagraph($parent)[ 'content' ]),
-1), ['.', ';', '}'])) {
$lastParentChild[ 'children' ][] = $child;
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
$lastParentChild = $this->handlePossibleChild($lastParentChild, $child);
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
}
return $parent;
}
/**
* Check if paragraph is a list
*
* @param $paragraph
*
* @return bool
*/
private function paragraphIsList($paragraph)
{
if (substr(trim($paragraph[ 'content' ]), -1) == ':') {
return true;
}
return false;
}
/**
* Check if last child from the paragraph is a list
*
* @param $paragraph
*
* @return bool
*/
private function lastChildIsList($paragraph)
{
if ($this->hasChild($paragraph)) {
$lastParentChild = last($paragraph[ 'children' ]);
if (substr(trim($lastParentChild[ 'content' ]), -1) == ':') {
return true;
}
}
return false;
}
private function getLastChildForParagraph($paragraph)
{
if ($this->hasChild($paragraph)) {
$lastParentChild = last($paragraph[ 'children' ]);
return $this->getLastChildFromParagraph($lastParentChild);
}
return $paragraph;
}
/**
* Check if a paragraph has any child
*
* @param $paragraph
*
* @return bool
*/
private function hasChild($paragraph)
{
if (isset($paragraph[ 'children' ])) {
return true;
}
return false;
}
/**
* Extract numbering from a given paragraph
*
* return false if has no numbering
*
* @param $paragraph
*
* @return false|mixed
*/
private function getNumbering($paragraph)
{
if (isset($paragraph)) {
$paragraphContent = trim($paragraph);
if (in_array(substr($paragraphContent, 0, 1), ['(', '{'])) {
return false;
}
if ($this->pdf && isset($paragraph) && strpos($paragraphContent,
'Page') !== false && strpos($paragraphContent, 'of') !== false) {
return false;
}
preg_match('/^([-+]?\d*\.?\d+?\d*\.?\d+|\d+(\.?)*)(?:[eE]([-+]?\d+))?/', preg_replace('/[^0-9\.)]/', '',
substr(trim(preg_replace('/[^A-Za-z0-9.)]/', '',
preg_replace('/\)/', '.', preg_replace("/\{.+/", "", trim($paragraphContent))))), 0, 6)),
$paragraphNumbering);
if (count($paragraphNumbering) && (in_array(substr($paragraphContent, strlen($paragraphNumbering[ 0 ]), 1),
[' ', "\t", '.', ')']) || in_array(substr($paragraphNumbering[ 0 ], -1),
[' ', "\t", '.', ')']) || is_numeric($paragraphNumbering[ 0 ]))) {
$locationOfNumbering = strpos($paragraphContent,$paragraphNumbering[0]);
if(substr($paragraphContent,$locationOfNumbering-1,1)=='(' &&substr($paragraphContent,$locationOfNumbering+1,1)==')'){
return false;
}
return str_replace('..', '.', $paragraphNumbering[ 0 ]);
}
return false;
}
return false;
}
/**
* Check if a paragraph is between clauses
*
* @param $first
* @param $paragraph
* @param $list
*
* @return bool
*/
private function paragraphBetweenClauses($first, $paragraph, $list)
{
if ($this->hasNumbering($first) && ! isset($paragraph[ 'numbering' ])) {
$firstNumberingString = $this->getLastChildFromParagraph($first);
if (isset($firstNumberingString[ 'numbering' ])) {
$firstNumbering = last(array_filter(explode('.', $firstNumberingString[ 'numbering' ])));
foreach ($list as $lastParagraph) {
if ($lastParagraphNumberingString = $this->getNumbering($lastParagraph)) {
$lastParagraphNumbering = last(array_filter(explode('.', $lastParagraphNumberingString)));
if ($lastParagraphNumbering - $firstNumbering == 1 && substr_count($firstNumberingString[ 'numbering' ],
'.') == substr_count($lastParagraphNumberingString, '.')) {
return true;
} elseif (substr_count($firstNumberingString[ 'numbering' ],
'.') > substr_count($lastParagraphNumberingString, '.')) {
return true;
}
return false;
}
}
}
return false;
}
return false;
}
private function getLastChildFromParagraph($paragraph)
{
if (isset($paragraph[ 'children' ])) {
return $this->getLastChildFromParagraph(last($paragraph[ 'children' ]));
}
return $paragraph;
}
private function appendToLastChildFromParagraph($paragraph, $append)
{
if (isset($paragraph[ 'children' ])) {
return $this->getLastChildFromParagraph(last($paragraph[ 'children' ]));
}
$paragraph[ 'content' ] .= ' '.$append[ 'content' ];
return $paragraph;
}
/**
* Check if a paragraph has numbering
*
* @param $paragraph
*
* @return bool
*/
private function hasNumbering($paragraph)
{
if (isset($paragraph[ 'numbering' ]) && $paragraph[ 'numbering' ]) {
return true;
}
return false;
}
/**
* Uppercase all values in the array
*
* @param $value
*
* @return array|string
*/
private function nestedUppercase($value)
{
if (is_array($value)) {
return array_map([$this, 'nestedUppercase'], $value);
}
//remove unwanted chars
return strtoupper(str_replace(['.'], '', $value));
}
}

406
app/Parser/ParseXml.php

@ -0,0 +1,406 @@
<?php
namespace App\Parser;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use SimpleXMLElement;
class ParseXml
{
/**
* @var int
*/
private $titleFontThreshold;
/**
* @var int
*/
private $headerFontFooterThreshold;
/**
* ParseXml constructor.
*/
public function __construct()
{
$this->headerFontFooterThreshold = null;
$this->titleFontThreshold = null;
}
/**
* Handle xml files
*
* @param $xmlFile
*
* @return mixed
*/
public function handle($xmlFile)
{
if (is_string($xmlFile)) {
try {
$storageDisk = Storage::disk('contracts');
while (! $storageDisk->exists($xmlFile)) {
//Sleep if file not yet written
sleep(1);
}
$file = $storageDisk->get($xmlFile);
} catch (\Exception $exception) {
Log::error('Failed to load the xml file '.$exception->getMessage());
}
} else {
$file = file_get_contents($xmlFile);
}
//foreach (simplexml_load_string($file) as $key =>$xmlElementPage){
// dd($xmlElementPage);
//}
return $this->buildChildStructure($this->handleElements(simplexml_load_string($file)->xpath('//text')));
}
/**
* @param $element
*
* @return mixed
*/
private function handleElements($element)
{
if (is_array($element)) {
$elements = $element;
} else {
$elements = (array) $element;
}
//dd(!in_array(trim(last(explode(' ', strip_tags('modify or make additions to the {P1_Name} Software, except to the extent permitted by law; or')))),['and','or']),trim(last(explode(' ', strip_tags('modify or make additions to the {P1_Name} Software, except to the extent permitted by law; or')))));
$this->setTitleThreshold($elements);
$numberOfNodes = count($elements);
$rows = [];
for ($i = 0; $i < $numberOfNodes; $i++) {
$current = $elements[ $i ];
$listContent = [];
if ($current instanceof SimpleXMLElement) {
$content = $this->getNodeContent($current);
//if(strpos($content,'Provided that the Customer has continued to pay ')!==false){
// dd(($i + 1 <= $numberOfNodes && isset($elements[ $i + 1 ]) && (((int) $elements[ $i + 1 ][ 'top' ] === (int) $current[ 'top' ]) || (int) $elements[ $i + 1 ][ 'top' ] <= ((int) $current[ 'top' ] + (int) $current[ 'height' ] + 3)) && (int) $current[ 'top' ] <= (int) $elements[ $i + 1 ][ 'top' ])
// || (isset($elements[ $i + 1 ]) && ctype_lower(substr(trim(strip_tags($this->getNodeContent($elements[ $i + 1 ]))),0,1))), substr(trim(strip_tags($this->getNodeContent($elements[ $i + 1 ]))),0,1))));
//}
$parentNumbering = [];
while ($i + 1 <= $numberOfNodes && isset($elements[ $i + 1 ]) &&
(((((((int) $elements[ $i + 1 ][ 'top' ] === (int) $current[ 'top' ]) || (int) $elements[ $i + 1 ][ 'top' ] <= ((int) $current[ 'top' ] + (int) $current[ 'height' ] + 3)) && (int) $current[ 'top' ] <= (int) $elements[ $i + 1 ][ 'top' ])
|| (ctype_lower(substr(trim(strip_tags($this->getNodeContent($elements[ $i + 1 ]))),0,1)))
|| (! in_array(substr(trim(strip_tags($this->getNodeContent($elements[ $i + 1 ]))),0, 1), [',']))
|| (ctype_lower(substr(trim(strip_tags($content)),strlen(trim(strip_tags($content))) - 1))))
&& ! in_array(substr(trim(str_replace(['and','or'], '', $content)), strlen(trim(str_replace(['and', 'or'], '', $content))) - 1),['!', '.', '?', ';', '_', ':', ')'])
&& ! preg_match('/^.*?\-[^\d]*(\d+)[^\d]*\-.*$/',$content)
&& (substr(trim($this->getNodeContent($elements[ $i + 1 ])), 0,strlen('<b>')) !== '<b>'
&& ctype_lower((substr(trim(strip_tags($content)),strlen(trim(strip_tags($content))) - 1)))))
|| ((int) $elements[ $i ][ 'top' ] === (int) $elements[ $i + 1 ][ 'top' ]))
|| (isset($elements[ $i + 1 ]) && trim(strip_tags($this->getNodeContent($elements[ $i+1])))=='[')
) {
//if($parentNumbering){
// dd($parentNumbering,$content);
//}
preg_match('/^([-+]?\d*\.?\d+)(?:[-+]?\d*\.?\d+)(?:[eE]([-+]?\d+))?/',
preg_replace('/[^0-9\.)]/', '', substr(trim(preg_replace('/[^A-Za-z0-9.)]/', '',
preg_replace('/\)/', '.', preg_replace("/\{.+/", "", html_entity_decode($content))))),
0, 5)), $childNumbering);
if (! $childNumbering) {
preg_match('/^([-+]?\d*\.?\d+)(?:[eE]([-+]?\d+))?/', preg_replace('/[^0-9\.)]/', '',
substr(trim(preg_replace('/[^A-Za-z0-9.)]/', '',
preg_replace('/\)/', '.', preg_replace("/\{.+/", "", html_entity_decode($content))))),
0, 5)), $parentNumbering);
}
//if($childNumbering && strpos($childNumbering[0],"2.1.5")!==false){
// dd(11,$content,$elements[$i],$i,$i+1);
//}
$nextElement = $elements[ $i + 1 ];
$nextElementContent = $this->getNodeContent($nextElement);
$content .= ' '.$nextElementContent;
$current[ 'top' ] = $nextElement[ 'top' ];
$current[ 'height' ] = $nextElement[ 'height' ];
if (count($parentNumbering)) {
$current[ 'row_numbering' ] = $parentNumbering[ 0 ];
$content = str_replace($current[ 'row_numbering' ], '', $content);
$i++;
break;
} elseif ($childNumbering) {
$current[ 'row_numbering' ] = $childNumbering[ 0 ];
$content = str_replace($current[ 'row_numbering' ], '', $content);
if (strlen(trim(strip_tags($content))) && ! in_array(substr(trim(strip_tags($content)),
strlen(trim(strip_tags($content))) - 1),
['.', ':', '!', '?','[',',']) && !ctype_lower(substr(trim(strip_tags($content)),
strlen(trim(strip_tags($content)))-1)) && (!ctype_lower(substr(trim(strip_tags($this->getNodeContent($elements[$i+1]))),
0, 1)) || !in_array(substr(trim(strip_tags($this->getNodeContent($elements[$i+1]))), 0, 1),
['[', '{']))) {
$i++;
break;
}
}
if( ! empty($current[ 'row_numbering' ]) && ctype_digit(trim(preg_replace("/[^0-9a-zA-Z]/",
"", strip_tags($this->getNodeContent($elements[$i])))))){
$i++;
break;
}
//$current[ 'font' ] = $nextElement[ 'font' ];
$i++;
continue;
}
$data = $this->extractNumbering($content);
$content = [
'type' => (int) $current[ 'font' ] === $this->titleFontThreshold ? 'title' : null,
'content' => $data[ 'content' ],
'numbering' => (! empty($current[ 'row_numbering' ])) ? (int)$current[ 'row_numbering' ] : $data[ 'numbering' ],
'top' => (int) $current[ 'top' ],
'height' => (int) $current[ 'height' ],
'left' => (int) $current[ 'left' ],
'font' => (int) $current[ 'font' ],
'children' => $listContent
];
$rows[] = $content;
}
}
return $rows;
}
/**
* Returns the xml node content
*
* @param $node
*
* @return string|string[]|null
*/
private function getNodeContent($node)
{
return preg_replace('!\s+!', ' ', preg_match_all("/<text.*?>(.*?)<\/text>/", $node->asXML(),
$matches) ? $matches[ 1 ] ? $matches[ 1 ][ 0 ] : '' : '');
}
/**
* Extract the numbering if exists from the string
*
* @param $content
*
* @return array
*/
private function extractNumbering($content)
{
$regexOne = '/^(([a-zA-Z0-9]+[.\)])+)([ ]|[a-z]|[A-Z])/';
$regexTwo = '/^(([\d\.]+)\d)/';
if (preg_match($regexOne, $content, $n)) {
$numbering = trim(last($n));
} else {
if (preg_match($regexTwo, $content, $n)) {
$numbering = trim(last($n));
} else {
$numbering = '';
}
}
if (strlen($numbering) > 1) {
return [
'content' => '<p>'.trim(str_replace($numbering, '', $content)).'</p>',
'numbering' => $numbering
];
}
return [
'content' => '<p>'.trim($content).'</p>',
'numbering' => ''
];
}
/**
* Build the structure as required by the editor and the gamification module
*
* @param $elements
*
* @return array
*/
private function buildChildStructure($elements)
{
$alreadyHandledIndexes = [];
$build = [];
// 0 1 2 3 4 5 6
// 1 1.1 1.1.1 1.2 1.2.1 1.3 1.3.1 2 3 4 4.1 4.2 5 6
for ($i = 0; $i < count($elements) - 1; $i++) {
if (! isset($elements[ $i ][ 'type' ])) {
if ($elements[ $i ][ 'top' ] < 100) {
$elements[ $i ][ 'type' ] = 'header';
} elseif ($elements[ $i ][ 'top' ] > 1150) {
$elements[ $i ][ 'type' ] = 'footer';
}
}
if (in_array($i, $alreadyHandledIndexes)) {
continue;
}
if (isset($elements[ $i ][ 'type' ]) && in_array($elements[ $i ][ 'type' ], ['footer', 'header'])) {
continue;
}
for ($j = $i + 1; $j < count($elements); $j++) {
if (! isset($elements[ $j ][ 'type' ])) {
if ($elements[ $j ][ 'top' ] < 100) {
$elements[ $j ][ 'type' ] = 'header';
} elseif ($elements[ $j ][ 'top' ] > 1150) {
$elements[ $j ][ 'type' ] = 'footer';
}
}
if (in_array($j, $alreadyHandledIndexes)) {
continue;
}
if (isset($elements[ $j ][ 'type' ]) && in_array($elements[ $j ][ 'type' ], ['footer', 'header'])) {
continue;
}
if ($elements[ $j ][ 'type' ] === 'title' && $elements[ $i ][ 'top' ] !== $elements[ $j ][ 'top' ] && ! ctype_digit(trim(preg_replace("/[^0-9a-zA-Z]/",
"", strip_tags($elements[ $i ][ 'content' ]))))) {
break;
}
if ($elements[ $i ][ 'left' ] < $elements[ $j ][ 'left' ] || ($elements[ $i ][ 'type' ] == 'title' && is_null($elements[ $j ][ 'type' ]))) {
$elements[ $i ] = $this->handlePossibleChild($elements[ $i ], $elements[ $j ]);
$alreadyHandledIndexes[] = $j;
} else {
break;
}
}
if (! in_array($elements[ $i ][ 'type' ], ['header', 'footer'])) {
$build[] = $elements[ $i ];
}
$alreadyHandledIndexes[] = $i;
}
return $build;
}
/**
* Handle each node child's
*
* @param $parent
* @param $child
*
* @return mixed
*/
protected function handlePossibleChild($parent, $child)
{
// 1
// 1.1
// 1.1.1
// 2
// Must iterate through parent children
if (count($parent[ 'children' ]) === 0) {
$parent[ 'children' ][] = $child;
return $parent;
}
$lastParentChild = last($parent[ 'children' ]);
// Possible to be either child or grandchild
if ($child[ 'left' ] > $lastParentChild[ 'left' ]) {
$lastParentChild = $this->handlePossibleChild($lastParentChild, $child);
} elseif ($child[ 'left' ] === $parent[ 'left' ] && $parent[ 'type' ] == 'title' && is_null($child[ 'type' ])) {
$parent[ 'children' ][] = $child;
return $parent;
} else {
if ($child[ 'left' ] === $lastParentChild[ 'left' ]) {
$parent[ 'children' ][] = $child;
return $parent;
}
}
$parent[ 'children' ][ count($parent[ 'children' ]) - 1 ] = $lastParentChild;
return $parent;
}
/**
* Set's the title threshold
*
* @param $elements
*/
protected function setTitleThreshold($elements)
{
$nextElement = null;
foreach ($elements as $index => $element) {
if ($index + 1 < count($elements) && ! isset($this->titleFontThreshold)) {
$nextElement = $elements[ $index + 1 ];
if ((isset($current->b) || $index == 0 || (! is_null($nextElement) && (int) $element[ 'font' ] < (int) $nextElement[ 'font' ]))) {
$this->titleFontThreshold = (int) $element[ 'font' ];
}
} else {
continue;
}
}
}
/**
* Set's the header and footer threshold
*
* @param $elements
*/
protected function setHeaderFooterThreshold($elements)
{
foreach ($elements as $index => $element) {
if (isset($elements[ $index + 1 ]) && ! isset($this->headerFontFooterThreshold)) {
$nextElement = $elements[ $index + 1 ];
if (! isset($nextElement[ 'type' ]) && $element[ 'top' ] > $nextElement[ 'top' ]) {
$this->headerFontFooterThreshold = $nextElement[ 'font' ];
}
} else {
continue;
}
}
}
}

28
app/Providers/AppServiceProvider.php

@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
}

30
app/Providers/AuthServiceProvider.php

@ -0,0 +1,30 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

21
app/Providers/BroadcastServiceProvider.php

@ -0,0 +1,21 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

41
app/Providers/EventServiceProvider.php

@ -0,0 +1,41 @@
<?php
namespace App\Providers;
use App\Listeners\WebhookFailedToSendToCoreListener;
use App\Listeners\WebhookSuccessfullySentToCoreListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Spatie\WebhookServer\Events\WebhookCallFailedEvent;
use Spatie\WebhookServer\Events\WebhookCallSucceededEvent;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
WebhookCallSucceededEvent::class => [
WebhookSuccessfullySentToCoreListener::class
],
WebhookCallFailedEvent::class => [
WebhookFailedToSendToCoreListener::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}

80
app/Providers/RouteServiceProvider.php

@ -0,0 +1,80 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* The path to the "home" route for your application.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}

53
artisan

@ -0,0 +1,53 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

55
bootstrap/app.php

@ -0,0 +1,55 @@
<?php
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

2
bootstrap/cache/.gitignore

@ -0,0 +1,2 @@
*
!.gitignore

69
composer.json

@ -0,0 +1,69 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The Laravel Framework.",
"keywords": [
"framework",
"laravel"
],
"license": "MIT",
"require": {
"php": "^7.2",
"cebe/markdown": "^1.2",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.2",
"laravel/tinker": "^2.0",
"phpoffice/phpword": "^0.17.0",
"predis/predis": "^1.1",
"spatie/laravel-webhook-server": "^1.13",
"spatie/pdf-to-text": "^1.3"
},
"require-dev": {
"facade/ignition": "^1.4",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^8.0"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeds",
"database/factories"
],
"files": [
"app/Helpers/array.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
}
}

5876
composer.lock
File diff suppressed because it is too large
View File

231
config/app.php

@ -0,0 +1,231 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL', null),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
],
];

117
config/auth.php

@ -0,0 +1,117 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,
];

59
config/broadcasting.php

@ -0,0 +1,59 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

103
config/cache.php

@ -0,0 +1,103 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
| Supported: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb"
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
],
'database' => [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
];

147
config/database.php

@ -0,0 +1,147 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

79
config/filesystems.php

@ -0,0 +1,79 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Default Cloud Filesystem Disk
|--------------------------------------------------------------------------
|
| Many applications store files both locally and in the cloud. For this
| reason, you may specify a default "cloud" driver here. This driver
| will be bound as the Cloud disk implementation in the container.
|
*/
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'visibility' => 'public',
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
],
'contracts' => [
'driver' => 'local',
'root' => storage_path('app/contracts'),
],
'supervisor' => [
'driver' => 'local',
'root' => '/etc/supervisor/conf.d/',
],
],
];

52
config/hashing.php

@ -0,0 +1,52 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 10),
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [
'memory' => 1024,
'threads' => 2,
'time' => 2,
],
];

104
config/logging.php

@ -0,0 +1,104 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'papertrail' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

136
config/mail.php

@ -0,0 +1,136 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Mail Driver
|--------------------------------------------------------------------------
|
| Laravel supports both SMTP and PHP's "mail" function as drivers for the
| sending of e-mail. You may specify which one you're using throughout
| your application here. By default, Laravel is setup for SMTP mail.
|
| Supported: "smtp", "sendmail", "mailgun", "ses",
| "postmark", "log", "array"
|
*/
'driver' => env('MAIL_DRIVER', 'smtp'),
/*
|--------------------------------------------------------------------------
| SMTP Host Address
|--------------------------------------------------------------------------
|
| Here you may provide the host address of the SMTP server used by your
| applications. A default option is provided that is compatible with
| the Mailgun mail service which will provide reliable deliveries.
|
*/
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
/*
|--------------------------------------------------------------------------
| SMTP Host Port
|--------------------------------------------------------------------------
|
| This is the SMTP port used by your application to deliver e-mails to
| users of the application. Like the host we have set this value to
| stay compatible with the Mailgun e-mail application by default.
|
*/
'port' => env('MAIL_PORT', 587),
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
/*
|--------------------------------------------------------------------------
| E-Mail Encryption Protocol
|--------------------------------------------------------------------------
|
| Here you may specify the encryption protocol that should be used when
| the application send e-mail messages. A sensible default using the
| transport layer security protocol should provide great security.
|
*/
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
/*
|--------------------------------------------------------------------------
| SMTP Server Username
|--------------------------------------------------------------------------
|
| If your SMTP server requires a username for authentication, you should
| set it here. This will get used to authenticate with your server on
| connection. You may also set the "password" value below this one.
|
*/
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Sendmail System Path
|--------------------------------------------------------------------------
|
| When using the "sendmail" driver to send e-mails, we will need to know
| the path to where Sendmail lives on this server. A default path has
| been provided here, which will work well on most of your systems.
|
*/
'sendmail' => '/usr/sbin/sendmail -bs',
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
*/
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
/*
|--------------------------------------------------------------------------
| Log Channel
|--------------------------------------------------------------------------
|
| If you are using the "log" driver, you may specify the logging channel
| if you prefer to keep mail messages separate from other log entries
| for simpler reading. Otherwise, the default channel will be used.
|
*/
'log_channel' => env('MAIL_LOG_CHANNEL'),
];

88
config/queue.php

@ -0,0 +1,88 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Laravel. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'your-queue-name'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => null,
],
];

33
config/services.php

@ -0,0 +1,33 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
];

199
config/session.php

@ -0,0 +1,199 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION', null),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using the "apc", "memcached", or "dynamodb" session drivers you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
*/
'store' => env('SESSION_STORE', null),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN', null),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE', false),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| do not enable this as other CSRF protection services are in place.
|
| Supported: "lax", "strict", "none"
|
*/
'same_site' => null,
];

36
config/view.php

@ -0,0 +1,36 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
*/
'paths' => [
resource_path('views'),
],
/*
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
*/
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];

61
config/webhook-server.php

@ -0,0 +1,61 @@
<?php
return [
/*
* The default queue that should be used to send webhook requests.
*/
'queue' => 'default',
/*
* The default http verb to use.
*/
'http_verb' => 'post',
/*
* This class is responsible for calculating the signature that will be added to
* the headers of the webhook request. A webhook client can use the signature
* to verify the request hasn't been tampered with.
*/
'signer' => \Spatie\WebhookServer\Signer\DefaultSigner::class,
/*
* This is the name of the header where the signature will be added.
*/
'signature_header_name' => 'Signature',
/*
* These are the headers that will be added to all webhook requests.
*/
'headers' => [
'Content-Type' => 'application/json',
],
/*
* If a call to a webhook takes longer that this amount of seconds
* the attempt will be considered failed.
*/
'timeout_in_seconds' => 3,
/*
* The amount of times the webhook should be called before we give up.
*/
'tries' => 3,
/*
* This class determines how many seconds there should be between attempts.
*/
'backoff_strategy' => \Spatie\WebhookServer\BackoffStrategy\ExponentialBackoffStrategy::class,
/*
* By default we will verify that the ssl certificate of the destination
* of the webhook is valid.
*/
'verify_ssl' => true,
/*
* When using Laravel Horizon you can specify tags that should be used on the
* underlying job that performs the webhook request.
*/
'tags' => [],
];

2
database/.gitignore

@ -0,0 +1,2 @@
*.sqlite
*.sqlite-journal

35
database/migrations/2019_08_19_000000_create_failed_jobs_table.php

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFailedJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
}
}

16
database/seeds/DatabaseSeeder.php

@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
// $this->call(UsersTableSeeder::class);
}
}

24306
get-pip.py
File diff suppressed because it is too large
View File

21
package.json

@ -0,0 +1,21 @@
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.19",
"cross-env": "^5.1",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.13",
"resolve-url-loader": "^2.3.1",
"sass": "^1.15.2",
"sass-loader": "^8.0.0"
}
}

37
phpunit.xml

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>

22
public/.htaccess

@ -0,0 +1,22 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

BIN
public/favicon.ico

60
public/index.php

@ -0,0 +1,60 @@
<?php
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

2
public/robots.txt

@ -0,0 +1,2 @@
User-agent: *
Disallow:

28
public/web.config

@ -0,0 +1,28 @@
<!--
Rewrites requires Microsoft URL Rewrite Module for IIS
Download: https://www.microsoft.com/en-us/download/details.aspx?id=47337
Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules
-->
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Imported Rule 1" stopProcessing="true">
<match url="^(.*)/$" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Redirect" redirectType="Permanent" url="/{R:1}" />
</rule>
<rule name="Imported Rule 2" stopProcessing="true">
<match url="^" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

1
resources/js/app.js

@ -0,0 +1 @@
require('./bootstrap');

28
resources/js/bootstrap.js

@ -0,0 +1,28 @@
window._ = require('lodash');
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });

19
resources/lang/en/auth.php

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'These credentials do not match our records.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

19
resources/lang/en/pagination.php

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

22
resources/lang/en/passwords.php

@ -0,0 +1,22 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'reset' => 'Your password has been reset!',
'sent' => 'We have e-mailed your password reset link!',
'throttled' => 'Please wait before retrying.',
'token' => 'This password reset token is invalid.',
'user' => "We can't find a user with that e-mail address.",
];

151
resources/lang/en/validation.php

@ -0,0 +1,151 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'The :attribute must be accepted.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',
'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
'alpha' => 'The :attribute may only contain letters.',
'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
'alpha_num' => 'The :attribute may only contain letters and numbers.',
'array' => 'The :attribute must be an array.',
'before' => 'The :attribute must be a date before :date.',
'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
'between' => [
'numeric' => 'The :attribute must be between :min and :max.',
'file' => 'The :attribute must be between :min and :max kilobytes.',
'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
'different' => 'The :attribute and :other must be different.',
'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.',
'ends_with' => 'The :attribute must end with one of the following: :values.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field must have a value.',
'gt' => [
'numeric' => 'The :attribute must be greater than :value.',
'file' => 'The :attribute must be greater than :value kilobytes.',
'string' => 'The :attribute must be greater than :value characters.',
'array' => 'The :attribute must have more than :value items.',
],
'gte' => [
'numeric' => 'The :attribute must be greater than or equal :value.',
'file' => 'The :attribute must be greater than or equal :value kilobytes.',
'string' => 'The :attribute must be greater than or equal :value characters.',
'array' => 'The :attribute must have :value items or more.',
],
'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'numeric' => 'The :attribute must be less than :value.',
'file' => 'The :attribute must be less than :value kilobytes.',
'string' => 'The :attribute must be less than :value characters.',
'array' => 'The :attribute must have less than :value items.',
],
'lte' => [
'numeric' => 'The :attribute must be less than or equal :value.',
'file' => 'The :attribute must be less than or equal :value kilobytes.',
'string' => 'The :attribute must be less than or equal :value characters.',
'array' => 'The :attribute must not have more than :value items.',
],
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'mimetypes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => 'The :attribute must be at least :min.',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'password' => 'The password is incorrect.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values are present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'starts_with' => 'The :attribute must start with one of the following: :values.',
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
'uuid' => 'The :attribute must be a valid UUID.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [],
];

1
resources/sass/app.scss

@ -0,0 +1 @@
//

5
resources/views/errors/401.blade.php

@ -0,0 +1,5 @@
@extends(' errors.minimal')
@section('title', __('Unauthorized'))
@section('code', '401')
@section('message', __('Unauthorized'))

5
resources/views/errors/403.blade.php

@ -0,0 +1,5 @@
@extends(' errors.minimal')
@section('title', __('Forbidden'))
@section('code', '403')
@section('message', __($exception->getMessage() ?: 'Forbidden'))

4
resources/views/errors/404.blade.php

@ -0,0 +1,4 @@
@extends('errors.minimal')
@section('title', __('Not Found'))
@section('code', '404')
@section('message', __('The page you are looking for might have been removed had its name changed or is temporarily unavailable.'))

4
resources/views/errors/405.blade.php

@ -0,0 +1,4 @@
@extends('errors.minimal')
@section('title', __('405 Error'))
@section('code', '405')
@section('message', __('The page you are looking for might have been removed had its name changed or is temporarily unavailable.'))

5
resources/views/errors/419.blade.php

@ -0,0 +1,5 @@
@extends(' errors.minimal')
@section('title', __('Page Expired'))
@section('code', '419')
@section('message', __('Page Expired'))

6
resources/views/errors/429.blade.php

@ -0,0 +1,6 @@
@extends(' errors.minimal')
@section('title', __('Too Many Requests'))
@section('code', '429')
@section('message', __('Too Many Requests'))

5
resources/views/errors/500.blade.php

@ -0,0 +1,5 @@
@extends(' errors.minimal')
@section('title', __('Server Error'))
@section('code', '500')
@section('message', __('Server Error'))

5
resources/views/errors/503.blade.php

@ -0,0 +1,5 @@
@extends(' errors.minimal')
@section('title', __('Service Unavailable'))
@section('code', '503')
@section('message', __($exception->getMessage() ?: 'Service Unavailable'))

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save