Compare commits

...

251 Commits

Author SHA1 Message Date
Bernd Bestel
ddfe33fab6 Update dependencies for next release 2018-09-24 13:57:20 +02:00
Bernd Bestel
2a0ec30bb0 Auto reload the current page when the database has changed and when idling (closes #59) 2018-09-24 13:53:18 +02:00
Bernd Bestel
8540fc44f3 Added option to stay logged in permanently 2018-09-24 13:16:57 +02:00
Bernd Bestel
66095738e3 Added product groups (this closes #55) 2018-09-24 13:02:52 +02:00
Bernd Bestel
e472711d23 Fixed strange (and still kind of unknown) problem in productpicker (fixes #56) 2018-09-24 09:51:55 +02:00
Bernd Bestel
8e054a4981 Fix scrolling to top of page when dynamically removing a table row (fixes #57) 2018-09-24 09:30:26 +02:00
Bernd Bestel
feb28211d8 Slightly reordered the main menu 2018-09-24 09:16:53 +02:00
Bernd Bestel
06f25b7006 Finish first version of tasks feature 2018-09-23 19:26:13 +02:00
Bernd Bestel
f85a67a1ff Continue working on tasks feature 2018-09-23 09:22:54 +02:00
Bernd Bestel
6fe0100927 Start working on tasks feature 2018-09-22 22:01:32 +02:00
Bernd Bestel
bcb359e317 Fixed custom JS/CSS was not included on API doc page 2018-09-22 13:28:49 +02:00
Bernd Bestel
4075067a10 Renamed habits to chores as this is more what it is about 2018-09-22 13:26:58 +02:00
Bernd Bestel
bd3c63218b Fixed missing translation in productpicker 2018-09-22 10:58:17 +02:00
Bernd Bestel
27daf384da Respect X-Forwarded-Proto header in UrlManager (closes #54) 2018-09-21 12:49:01 +02:00
Bernd Bestel
905fc0f357 Hotfix (will be include in v1.18.1 release): Price input on purchase page was not optional 2018-09-08 14:31:42 +02:00
Bernd Bestel
9cd0e4ab2d Update dependencies for next release 2018-09-08 14:14:23 +02:00
Bernd Bestel
6b38cd450f Finalized latest changes 2018-09-08 14:06:19 +02:00
Bernd Bestel
bb60f5f043 Typo... 2018-09-08 12:05:44 +02:00
Bernd Bestel
e777be4d3b Replaced the default number input arrow buttons with own ones to better support touch input (references #44) 2018-09-08 12:04:31 +02:00
Bernd Bestel
8a71d55f0f Added missing German translation for last changes 2018-09-08 09:27:50 +02:00
Bernd Bestel
b01b49d10c Show generic error message on saving master data (this closes #45) 2018-09-08 09:26:12 +02:00
Bernd Bestel
496594d898 Don't save filters across page reloads for all data tables (fixes #52) 2018-09-08 08:56:32 +02:00
Bernd Bestel
1d5e82c341 Fixed tooltip flickering problems (this closes #51) 2018-09-08 08:49:09 +02:00
Bernd Bestel
a9b696f41c Fixed datetimepicker (this closes #43) 2018-09-08 08:36:45 +02:00
Marius Boro
e50b1eb359 Update no.php (#50)
* Update no.php

* Update no.php

* Update no.php

* Update no.php

* Update no.php

* Update no.php
2018-09-04 17:25:52 +02:00
Bernd Bestel
92e0245387 Merge pull request #49 from BlizzWave/patch-14
Update it.php
2018-09-04 08:54:04 +02:00
Bernd Bestel
67d0d3c3d6 Merge pull request #48 from BlizzWave/patch-13
Update de.php
2018-09-04 08:53:54 +02:00
Bernd Bestel
23bcbc23e9 Merge pull request #47 from BlizzWave/patch-12
Update batteriesoverview.js
2018-09-04 08:53:44 +02:00
Marius Boro
085d9a0bc7 Update no.php (#46)
* Update no.php

Updated for version 1.18.0 some typos fixed and more fluent translations.

* Update no.php

* Update no.php
2018-09-04 08:53:28 +02:00
Marius Boro
368df142cf Update it.php
typo
2018-09-03 22:09:37 +02:00
Marius Boro
d38edabb14 Update de.php
typo
2018-09-03 22:09:02 +02:00
Marius Boro
4426a10e2e Update batteriesoverview.js
typo
2018-09-03 22:07:05 +02:00
Bernd Bestel
931dc9d243 Reset the date shortcut checkbox on value changes when it's not the shortcut value 2018-08-18 08:14:26 +02:00
Bernd Bestel
c5b8893008 Update dependencies for next release 2018-08-11 14:55:27 +02:00
Bernd Bestel
c27f41aee4 Don't use buttons in tables with full row select as this is confusing when clicking a button of a not selected row 2018-08-11 14:38:17 +02:00
Bernd Bestel
ef043b38ce Use normal color for successfully validate checkbox inputs 2018-08-11 14:29:08 +02:00
Bernd Bestel
bb261f99c4 Use tooltips where appropriate 2018-08-11 14:23:36 +02:00
Bernd Bestel
48ca0f2ac7 Also clear the shopping list without reloading the whole page 2018-08-11 14:16:11 +02:00
Bernd Bestel
b7f0b06684 Remove items from shopping list without reloading the whole page 2018-08-11 14:07:44 +02:00
Bernd Bestel
324487d395 Add a "consume all ingredients of this recipe" button (this now closes #32) 2018-08-11 11:48:25 +02:00
Bernd Bestel
9a8c61497b Fixed typo 2018-08-09 17:32:21 +02:00
Bernd Bestel
bc7afe4bdd Auto "click" the shortcut checkbox when manually entering the shortcut date (references #40) 2018-08-09 17:25:27 +02:00
Bernd Bestel
bb5dcb2434 Fixed a warning on embedded and demo installations 2018-08-09 17:24:37 +02:00
Bernd Bestel
71b9d11ff5 Implement that recipe ingredients can have arbitrary quantity units (references #32) 2018-08-09 17:24:04 +02:00
Bernd Bestel
3e73a44576 Merge pull request #41 from BlizzWave/patch-10
Update no.php
2018-08-07 21:00:06 +02:00
Marius Boro
dedfe3a854 Update no.php
updated to follow closed issues
2018-08-07 20:46:27 +02:00
Bernd Bestel
c4b0ef4d49 Refresh the complete row on all overview pages on changes, including the background color (closes #39) 2018-08-07 20:11:08 +02:00
Bernd Bestel
339d81318f Add a checkbox to set the "never expires date" (closes #40) 2018-08-06 22:41:35 +02:00
Bernd Bestel
282ee0885b Hotfix - syntax error in norwegian localization file (this will be included in the v1.17.0 release) 2018-08-04 17:46:40 +02:00
Bernd Bestel
5833364e51 Add pluralization of demo and default quantity units 2018-08-04 17:37:43 +02:00
Bernd Bestel
525f1705d1 Update dependencies for next release 2018-08-04 17:22:15 +02:00
Bernd Bestel
5a13cb5ffe Fix jquery timeago update did not really work 2018-08-04 16:54:46 +02:00
Bernd Bestel
e830805443 Refresh also habit/battery statistics on changes on overview pages (references #26) 2018-08-04 15:44:58 +02:00
Bernd Bestel
ca3f28b615 Refresh stock statistics on consume on stock overview page (references #26) 2018-08-04 14:25:32 +02:00
Bernd Bestel
6081b8ee67 Fix some form validation problems (closes #36) 2018-08-04 07:45:24 +02:00
Bernd Bestel
7eef4acd81 Use the same info-bar to explain table colors in shopping list as in stock overview (fixes #27) 2018-08-03 09:06:11 +02:00
Bernd Bestel
678579e933 Don't use ORDER BY in VIEWS (as this is invalid SQL, why does this even work sometimes in SQLite) (fixes #33) 2018-08-03 08:26:59 +02:00
Bernd Bestel
4cc2d39063 Merge remote-tracking branch 'remotes/origin/fix-issue-33' 2018-08-03 08:18:59 +02:00
Bernd Bestel
14cc153422 Removed unused dependency 2018-08-03 08:16:33 +02:00
Bernd Bestel
f5b5c4c7e1 Add default quantity units and locations to reduce confusion (only for new installations) (references #38) 2018-08-03 08:14:23 +02:00
Bernd Bestel
88b76a52a5 Merge pull request #37 from BlizzWave/patch-9
Update no.php
2018-08-02 07:48:33 +02:00
Marius Boro
a4a25af460 Update no.php
typos
2018-08-01 22:44:23 +02:00
Bernd Bestel
41a72d11da Remove unneeded ORDER BY as this maybe lead to problems like in #33 2018-07-31 17:42:07 +02:00
Bernd Bestel
c8236b101b Fix redefine of constant GROCY_DATAPATH when it's not an embedded AND not a demo installation 2018-07-31 17:31:03 +02:00
Bernd Bestel
ef1df0a446 Unify path references and only use relative ones 2018-07-30 19:16:36 +02:00
Bernd Bestel
5c4953b9b2 Merge pull request #28 from BlizzWave/patch-5
Update README.md
2018-07-30 19:12:06 +02:00
Bernd Bestel
ccaf2411fe Merge pull request #30 from BlizzWave/patch-7
Update de.php
2018-07-30 19:03:41 +02:00
Bernd Bestel
bce8bd6b35 Merge pull request #31 from BlizzWave/patch-8
Update batteriesoverview.blade.php
2018-07-30 19:03:28 +02:00
Marius Boro
66c07887cb Update no.php (#29)
* Update no.php

Keeping it updated

* Update no.php
2018-07-30 19:02:02 +02:00
Marius Boro
be99880ce4 Update batteriesoverview.blade.php
Some german slipped in there too.
2018-07-29 23:49:35 +02:00
Marius Boro
e026609972 Update de.php 2018-07-29 23:46:16 +02:00
Marius Boro
3474f55866 Update README.md
added useful information for new users
2018-07-29 23:26:34 +02:00
Bernd Bestel
f583810d5c Properly pluralize everything (closes #19) 2018-07-27 19:39:34 +02:00
Bernd Bestel
419445f5ae Don't add a dummy data point on chart initialization (not needed, will lead to that the current price is always 0 - references #22) 2018-07-26 21:23:37 +02:00
Bernd Bestel
c64eb27ca1 Add something for product price tracking (references #22) 2018-07-26 20:27:38 +02:00
Bernd Bestel
f4eb5196f7 Merge pull request #24 from BlizzWave/patch-4
Updated for Version 1.16.0
2018-07-26 17:30:51 +02:00
Bernd Bestel
9e493430d8 Battery charge interval was not editable and not shown anywhere 2018-07-26 17:28:30 +02:00
Marius Boro
7690eedd70 Update no.php
Updated for Version 1.16.0
2018-07-25 23:40:57 +02:00
Bernd Bestel
aaa270a52f Hotfix for old user to database migration (this will be included in the v1.16.0 release) 2018-07-25 20:26:23 +02:00
Bernd Bestel
6f47a5415c Added a rudimentary habit analysis possibility 2018-07-25 20:01:58 +02:00
Bernd Bestel
42c1709633 Optimize and refactor latest changes 2018-07-25 19:28:15 +02:00
Bernd Bestel
4685ff4145 Add an update script for Linux 2018-07-25 08:22:27 +02:00
Bernd Bestel
249b01d7a8 Added possibility to track who did a habit (this implements and closes #21) 2018-07-24 20:45:14 +02:00
Bernd Bestel
bcbdf58376 Prefix all global vars 2018-07-24 19:41:35 +02:00
Bernd Bestel
7f8540ff4e Replace the single user (defined in /data/config.php) with a multi user management thing 2018-07-24 19:31:43 +02:00
Marius Boro
b52ab91606 Update no.php (#23)
* Update no.php

Better translation and minor typos

* Update no.php
2018-07-23 21:23:55 +02:00
Bernd Bestel
7246ac55b6 Hide scrollbar in sidebar for now (workaround for #15, found no better solution, this closes #15)
This also references BlackrockDigital/startbootstrap-sb-admin#73
2018-07-23 21:21:27 +02:00
Bernd Bestel
848931da21 Mention grocy-desktop in README 2018-07-22 13:59:53 +02:00
Bernd Bestel
bf4092e746 Changed latest release download link 2018-07-22 13:26:46 +02:00
Bernd Bestel
7cee18c926 This is version 1.15.0 2018-07-22 13:10:12 +02:00
Bernd Bestel
e9a4b43268 Improve date picker MMDD shorthand 2018-07-22 13:09:23 +02:00
Bernd Bestel
b1522742cc Add new date input shorthand 2018-07-22 13:03:08 +02:00
Bernd Bestel
ecdaaab789 Update README.md 2018-07-22 12:41:11 +02:00
Bernd Bestel
3379942086 Document settingoverrides for embedded mode 2018-07-22 11:06:01 +02:00
Bernd Bestel
12eaa8c074 Fixed barcode lookup disabled hint was not localized 2018-07-22 10:18:03 +02:00
Bernd Bestel
c6310d636d Always save the recipe edit form when adding, editing or deleting ingredients (fixes #17) 2018-07-22 10:14:06 +02:00
Bernd Bestel
9bedc6a138 Fixed habit- and batterytracking dropdowns - selection did not work 2018-07-22 09:54:06 +02:00
Bernd Bestel
70dbc6018f Fix selected product in productpicker was not highlighted 2018-07-22 09:33:01 +02:00
Bernd Bestel
3afeb44b1d Prepare for embedded mode 2018-07-18 19:07:00 +02:00
Bernd Bestel
3131b8965e Prepare for embedded mode 2018-07-16 21:23:13 +02:00
Bernd Bestel
bbc2fc9e42 Merge pull request #18 from BlizzWave/patch-2
Update no.php
2018-07-16 21:18:23 +02:00
Bernd Bestel
3b4141eb4d Prepare for embedded mode 2018-07-16 21:17:32 +02:00
BlizzWave
5f826be82c Update no.php
typos
2018-07-16 19:17:47 +02:00
BlizzWave
db9ee93d2b Norwegian localization (#16)
* Create no.php

* Update no.php
2018-07-15 21:46:16 +02:00
Bernd Bestel
1eabd29105 Describe new API function 2018-07-15 13:57:27 +02:00
Bernd Bestel
dc05c56440 Do not expand card body automatically 2018-07-15 13:39:48 +02:00
Bernd Bestel
cb88ab2080 Changed file extension of custom CSS and JS files to clarify that the content is HTML and not directly CSS/JS 2018-07-15 13:35:54 +02:00
Bernd Bestel
254e1a9bc1 Fix all the little things for the next release 2018-07-15 13:33:59 +02:00
Bernd Bestel
0fc7c297bf Fixed a problem about recipe fulfillment wrong when there is no stock of a given product 2018-07-15 11:25:12 +02:00
Bernd Bestel
82bfb6a3c3 Improve demo data 2018-07-15 11:24:36 +02:00
Bernd Bestel
277c622475 Add missing German translations 2018-07-15 10:40:21 +02:00
Bernd Bestel
091a0f3efe Removed not needed Italian technical translation item 2018-07-15 10:32:50 +02:00
Bernd Bestel
823c76aa08 Add missing German translations 2018-07-15 10:30:27 +02:00
Bernd Bestel
37dee2a50b Display first recipe by default on recipes page 2018-07-15 10:16:36 +02:00
Bernd Bestel
ea0f5101ec Finalize recipes feature 2018-07-15 09:56:10 +02:00
Bernd Bestel
be650d093d Add a button to clear the whole shopping list 2018-07-15 08:29:26 +02:00
Bernd Bestel
734814d96b More or less finalize recipes feature 2018-07-14 22:49:42 +02:00
Bernd Bestel
d9246b9b42 Start working on recipes feature 2018-07-14 18:23:41 +02:00
Bernd Bestel
70e7e630c3 Refresh screenshots 2018-07-14 14:52:18 +02:00
Bernd Bestel
71fc49252f Modularize product picker 2018-07-14 14:43:57 +02:00
Bernd Bestel
aa0771877f Remember sidebar collapsed state 2018-07-14 11:25:19 +02:00
Bernd Bestel
e5a4d11c0b Remove arrows on tooltips (only needed for sidebar, but found now way to limit this now) 2018-07-14 11:08:10 +02:00
Bernd Bestel
909949a9e1 Expand and highlight parent menu item when active page sidebar navigation item is a sub menu 2018-07-14 10:28:33 +02:00
Bernd Bestel
f018696219 Save data tables state (client side for now) 2018-07-14 10:17:12 +02:00
Bernd Bestel
347a47d0d2 Add info about how to maintain own localizations 2018-07-14 08:51:48 +02:00
Bernd Bestel
c3de4b86b0 Enable column reordering for all data tables 2018-07-14 08:48:14 +02:00
Bernd Bestel
594e77ca41 Only changed default width of datetimepicker for date-only inputs, as this does not work for side-by-side with time picker (references #14) 2018-07-14 08:38:03 +02:00
Bernd Bestel
31ce7a13ea Show a calendar on the shopping list page (just for info purposes) 2018-07-13 22:38:31 +02:00
Bernd Bestel
5d762001c8 Fix too small border in datetime picker (references #14) 2018-07-13 21:37:49 +02:00
Bernd Bestel
bc3d339d9c Small UI adjustments 2018-07-13 21:10:58 +02:00
Bernd Bestel
33e5ed9ddc Fix non-string settings were not correctly recognized 2018-07-12 22:23:00 +02:00
Bernd Bestel
2d712b0ef7 Update comment to reflect changed config "style" 2018-07-12 21:24:37 +02:00
Bernd Bestel
13d81a4e4b Fixed wrong icon 2018-07-12 21:23:47 +02:00
Bernd Bestel
8d917aee12 Corrected typo 2018-07-12 21:21:51 +02:00
Bernd Bestel
09b2cfc46a Fixed loading of a JS when the webserver is case sensitive 2018-07-12 19:48:59 +02:00
Bernd Bestel
789e475207 Fix missing comma 2018-07-12 19:30:33 +02:00
Bernd Bestel
eec5105e5b Merge pull request #13 from davidoskky/master
Italian localization
2018-07-12 19:27:38 +02:00
Bernd Bestel
82f7b2109c Changed how custom JS/CSS can be added 2018-07-12 19:25:45 +02:00
Bernd Bestel
840dd58c03 Add some more info 2018-07-12 19:22:05 +02:00
Bernd Bestel
37d1377f99 Fixed the rest of the broken stuff after the dependency upgrade party 2018-07-12 19:12:31 +02:00
davidoskky
882a3545e5 Add files via upload 2018-07-12 01:04:42 +02:00
Bernd Bestel
778191fd11 Fixed all (or most of) the broken stuff after the dependency upgrade party 2018-07-11 19:43:05 +02:00
Bernd Bestel
71701804ea (More or less) finish upgrading to Bootstrap 4 2018-07-10 20:37:13 +02:00
Bernd Bestel
306c404362 Continue upgrading to Bootstrap 4 2018-07-10 00:07:38 +02:00
Bernd Bestel
4fab4f87d3 Start upgrading top Bootstrap 4 2018-07-09 21:33:23 +02:00
Bernd Bestel
54717a81b1 Streamline data tables 2018-07-09 19:27:22 +02:00
Bernd Bestel
eca299454b Migrate to use Yarn instead of Bower for frontend dependencies 2018-07-08 21:36:07 +02:00
Bernd Bestel
c58083f84a Better approach for stock overview filtering by location (references #11) 2018-07-08 16:54:37 +02:00
Bernd Bestel
ecf96252b9 Add possibility to filter products by location (references #10) 2018-07-08 15:16:24 +02:00
Bernd Bestel
92e648490a Sort all dropdowns 2018-07-08 13:59:42 +02:00
Bernd Bestel
6dd3c26ddd Fix Bower (for now, need to switch to Yarn soon) 2018-07-08 13:51:29 +02:00
Bernd Bestel
02ea26b090 Disable pagination for data tables 2018-07-08 13:50:52 +02:00
Bernd Bestel
0954b5a741 Add option to not use URL rewriting 2018-06-15 20:50:40 +02:00
Bernd Bestel
02b6c3b721 Make it also possible to directly execute/track battery charge cycles and habits from overview pages 2018-05-13 09:38:22 +02:00
Bernd Bestel
6fa4e13ba2 Fix API version display 2018-05-13 09:00:14 +02:00
Bernd Bestel
9837f79f9c Make it also possible to consume the whole stock of a product from stock overview 2018-05-13 08:42:45 +02:00
Bernd Bestel
6e4cd22118 Make big buttons on overview pages responsive (references #9) 2018-05-12 16:38:21 +02:00
Bernd Bestel
ca00dd8e2d Improved table layout 2018-05-12 16:35:14 +02:00
Bernd Bestel
5455ec7bde Added missing translations 2018-05-12 16:30:10 +02:00
Bernd Bestel
2e7af1b050 Added possibility to consume products directly from stock overview 2018-05-12 16:15:28 +02:00
Bernd Bestel
89bae8d25e Changed how version information is stored and displayed 2018-05-12 15:49:21 +02:00
Bernd Bestel
5b5c272909 Correct and complete documentation 2018-05-12 15:36:23 +02:00
Bernd Bestel
3e394a3840 Also show due/overdue on bateries- and habitoverview 2018-05-12 15:30:13 +02:00
Bernd Bestel
ab8094e1c0 Don't expose username when not logged in 2018-05-12 14:56:51 +02:00
Bernd Bestel
bbb5f1c7c7 Rework general page layout and improve responsiveness (references #9) 2018-05-12 14:25:21 +02:00
Bernd Bestel
b607f188af Correct PHP dependency information (closes #8) 2018-05-11 09:51:30 +02:00
Bernd Bestel
9ab1a674fe Merge pull request #7 from d-Rickyy-b/patch-1
FIX: Add missing translation of "Add" button
2018-05-07 22:38:32 +02:00
Rico
2f0a1391b7 FIX: Add missing translation of "Add" button
The "Add" button was not translated in the 'Quantity units' form.
2018-05-07 22:30:17 +02:00
Bernd Bestel
a9a1358b08 Added a plugin system for looking up products against external services by barcode (references #6) 2018-04-22 19:50:24 +02:00
Bernd Bestel
4853174d03 Validate all API request as the API is now open for third parties (references #5) 2018-04-22 14:25:08 +02:00
Bernd Bestel
538d789366 Don't expose stock entity directly via API 2018-04-21 21:33:03 +02:00
Bernd Bestel
0751919b82 Add API & data model documentation hint 2018-04-21 20:13:47 +02:00
Bernd Bestel
99b2a84667 Finish API documentation and token auth (references #5) 2018-04-21 19:18:00 +02:00
Bernd Bestel
9bd6aac09c Start working on API documentation and token auth (references #5) 2018-04-20 23:09:18 +02:00
Bernd Bestel
7be35a90c1 Small style changes 2018-04-20 15:21:29 +02:00
Bernd Bestel
eae5b8bad9 Revise session handling to prepare API authentication via token 2018-04-20 11:34:53 +02:00
Bernd Bestel
0c85342404 Updated Composer dependencies 2018-04-20 09:48:06 +02:00
Bernd Bestel
9ddcdb3ab2 Fixed login form didn't respect the configured BASE_URL 2018-04-18 22:38:05 +02:00
Bernd Bestel
1c537cf5da Added some missing translations 2018-04-18 19:37:36 +02:00
Bernd Bestel
607a90cccc Use absolute URLs everywhere, this should fix #3 2018-04-18 19:03:39 +02:00
Bernd Bestel
3d1c6fc5f0 Added update instructions 2018-04-18 07:59:13 +02:00
Bernd Bestel
df1d3677e8 Fixed wrong format info 2018-04-17 20:10:19 +02:00
Bernd Bestel
4da2ac9b35 Typo... 2018-04-17 20:02:09 +02:00
Bernd Bestel
b4ae7d8538 Added some notable things to README 2018-04-17 20:00:00 +02:00
Bernd Bestel
870b679e0e Updated screenshots 2018-04-16 19:31:31 +02:00
Bernd Bestel
4656a85732 Added localization support 2018-04-16 19:11:32 +02:00
Bernd Bestel
4949913ccb Fix usings 2018-04-15 16:02:17 +02:00
Bernd Bestel
580bd5ac0c This is 1.7.0 2018-04-15 15:41:59 +02:00
Bernd Bestel
2bf3448d18 Separate app bootstrapping and routes 2018-04-15 14:51:31 +02:00
Bernd Bestel
e9bc51ca3d Fix session table missing on start with empty database 2018-04-15 14:49:00 +02:00
Bernd Bestel
5ddae116e0 Some style changes 2018-04-15 14:38:42 +02:00
Bernd Bestel
13566bc6fd Modularize more components 2018-04-15 09:41:53 +02:00
Bernd Bestel
642f95a3f8 Finalize project reorganization 2018-04-14 11:10:38 +02:00
Bernd Bestel
5a1d21ef31 Reorganize project part 3 2018-04-12 21:13:38 +02:00
Bernd Bestel
7dcd39f82f Move public stuff into subdirectory 2018-04-11 20:47:03 +02:00
Bernd Bestel
655aa89bd6 Merge branch 'master' of https://github.com/berrnd/grocy 2018-04-11 19:51:22 +02:00
Bernd Bestel
feb88ab685 Reorganize project part 2 2018-04-11 19:51:05 +02:00
Bernd Bestel
79b4bad014 Reorganize project part 2 2018-04-11 19:49:35 +02:00
Bernd Bestel
bcd5092427 Reorganize project part 1 2018-04-10 20:30:11 +02:00
Bernd Bestel
554a83fa01 Show auto added products with blue background 2018-01-04 22:23:24 +01:00
Bernd Bestel
57acb62520 Show note below product name 2018-01-04 21:55:12 +01:00
Bernd Bestel
52ed5f2285 Show relative date in purchase form 2018-01-04 13:02:28 +01:00
Bernd Bestel
dd1d253ea5 Fix form label naming 2018-01-04 12:53:15 +01:00
Bernd Bestel
e40979a874 Allow arbitrary text in shopping list 2018-01-04 12:51:36 +01:00
Bernd Bestel
2ddbc2656b Added >PHP 7.0 requirement (this closes #2) 2017-11-26 10:27:09 +01:00
Bernd Bestel
7351fce395 Form productivity improvements 2017-11-10 22:45:53 +01:00
Bernd Bestel
e4b0bbf7f7 This is 1.6.1 2017-11-09 21:37:33 +01:00
Bernd Bestel
f71121e6b2 Improved sidebar responsiveness 2017-11-09 21:36:44 +01:00
Bernd Bestel
9114dec695 This is 1.6 2017-11-06 15:21:12 +01:00
Bernd Bestel
2d5ed67ae1 Added possibility to manage rechargeable batteries 2017-11-06 12:41:43 +01:00
Bernd Bestel
dad6322bac Made some forms more convenient (input handling) 2017-11-06 11:29:20 +01:00
Bernd Bestel
05a9406a32 Changed IDE to Visual Studio code and minor style changes 2017-11-06 11:02:05 +01:00
Bernd Bestel
2a3822e781 Updated grocy.png 2017-08-04 22:19:46 +02:00
Bernd Bestel
c69b3a2936 Demo URL has changed 2017-08-02 22:43:00 +02:00
Bernd Bestel
6d4b86a730 Added demo recreation handling 2017-07-27 19:11:07 +02:00
Bernd Bestel
9c2ee58433 This is 1.5 2017-07-25 20:09:41 +02:00
Bernd Bestel
f84593882d Reference most recent major versions instead of specific ones 2017-07-25 20:08:59 +02:00
Bernd Bestel
1241261ca4 Added habit tracking 2017-07-25 20:03:31 +02:00
Bernd Bestel
ebe92335a6 Fix SQLite PDO issue on unix systems 2017-07-25 19:23:08 +02:00
Bernd Bestel
35f2f33ae3 Fix build includes 2017-06-04 18:39:05 +02:00
Bernd Bestel
f0f84b304b Added config/instructions for nginx/Apache URL rewriting - fixes #1 2017-06-04 18:32:34 +02:00
Bernd Bestel
23146417e6 Use session/cookie based authentication with login form instead of basic auth 2017-06-04 18:28:08 +02:00
Bernd Bestel
bd3155d39b Added screenshots 2017-04-23 11:11:13 +02:00
Bernd Bestel
b5fe0a642b Load also last purchased date from stock_log instead of stock 2017-04-22 21:51:07 +02:00
Bernd Bestel
b4b29878db Change FIFO to "First expiring first, then first in first out" 2017-04-22 21:40:22 +02:00
Bernd Bestel
9e68d38df8 Resolve X in date inputs to 2999-12-31 (which is used as "best before date infinite") 2017-04-22 18:04:39 +02:00
Bernd Bestel
574d363d7c Allow date input in form of MMDD and auto append current year 2017-04-22 17:47:27 +02:00
Bernd Bestel
69a011bc86 Little wording changes 2017-04-22 15:47:55 +02:00
Bernd Bestel
fe969c57c4 Changed debug PHP version to 7.1 2017-04-22 12:29:14 +02:00
Bernd Bestel
88d8b72c57 Reorganized sidebar menu 2017-04-22 12:28:43 +02:00
Bernd Bestel
5639797c8d Add note about barcode readers 2017-04-22 11:41:44 +02:00
Bernd Bestel
049a9cee06 Also show quantity unit on dashboard next to amount 2017-04-22 11:38:43 +02:00
Bernd Bestel
14faf57a9e Small productivity improvement (noticed on first own production use :D) 2017-04-22 11:36:05 +02:00
Bernd Bestel
e19b548eff Improved favicon 2017-04-22 09:59:26 +02:00
Bernd Bestel
e3d84c40f7 Added a favicon 2017-04-22 09:37:38 +02:00
Bernd Bestel
50d49219a5 Renamed shopping list item route 2017-04-22 09:27:30 +02:00
Bernd Bestel
96209c852c Add flow to add a new product with prefilled barcode 2017-04-21 22:50:16 +02:00
Bernd Bestel
8e40c50cc1 Validate that best before date is min. today 2017-04-21 19:15:03 +02:00
Bernd Bestel
4b0f0141c9 Fix date input arrow key behavior 2017-04-21 19:10:39 +02:00
Bernd Bestel
d1bd21a601 Finished shopping list feature 2017-04-21 19:02:00 +02:00
Bernd Bestel
c6925ba4c3 Started working on shopping list feature 2017-04-21 15:36:04 +02:00
Bernd Bestel
52e311d847 Show missing products on dashboard 2017-04-21 13:21:09 +02:00
Bernd Bestel
f2f18d260d Show how much is in stock on dashboard 2017-04-21 12:50:53 +02:00
Bernd Bestel
1d293741ba Code review 2017-04-21 12:30:08 +02:00
Bernd Bestel
5db288fc3c Add hint when barcode lookup is disabled 2017-04-21 12:03:56 +02:00
Bernd Bestel
d628f9b3ca Make DB migrations fully automatic 2017-04-21 11:52:24 +02:00
Bernd Bestel
fe8a6d96e4 Added keyboard shortcuts for add product/barcode dialogs 2017-04-20 23:42:06 +02:00
Bernd Bestel
bd16b8c851 Added flow to directly add articles and barcodes form purchase and inventory view 2017-04-20 22:01:14 +02:00
Bernd Bestel
c4a22c18f7 This is 1.0 2017-04-20 17:10:21 +02:00
Bernd Bestel
e38c24f9ed Going straight to 1.0... 2017-04-19 21:09:28 +02:00
Bernd Bestel
83a7534a74 Hide barcode in select dropdown but search in it 2017-04-18 23:04:26 +02:00
251 changed files with 16900 additions and 2122 deletions

203
.gitignore vendored
View File

@@ -1,203 +1,4 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
*.[Cc]ache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
/bower_components
/public/node_modules
/vendor
/.release
/config.php
/composer.phar
/composer.lock
embedded.txt

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"phpserver.relativePath": "public"
}

4
.yarnrc Normal file
View File

@@ -0,0 +1,4 @@
--modules-folder public/node_modules
--install.production true
--install.ignore-scripts true
--install.ignore-optional true

View File

@@ -1,83 +0,0 @@
<?php
class GrocyDbMigrator
{
public static function MigrateDb(PDO $pdo)
{
self::ExecuteMigrationWhenNeeded($pdo, 1, "
CREATE TABLE products (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
location_id INTEGER NOT NULL,
qu_id_purchase INTEGER NOT NULL,
qu_id_stock INTEGER NOT NULL,
qu_factor_purchase_to_stock REAL NOT NULL,
barcode TEXT UNIQUE,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)"
);
self::ExecuteMigrationWhenNeeded($pdo, 2, "
CREATE TABLE locations (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)"
);
self::ExecuteMigrationWhenNeeded($pdo, 3, "
CREATE TABLE quantity_units (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)"
);
self::ExecuteMigrationWhenNeeded($pdo, 4, "
CREATE TABLE stock (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL,
best_before_date DATE,
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
stock_id TEXT NOT NULL
)"
);
self::ExecuteMigrationWhenNeeded($pdo, 5, "
CREATE TABLE consumptions (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL,
best_before_date DATE,
purchased_date DATE,
used_date DATE DEFAULT (datetime('now', 'localtime')),
spoiled INTEGER NOT NULL DEFAULT 0,
stock_id TEXT NOT NULL
)"
);
self::ExecuteMigrationWhenNeeded($pdo, 6, "
INSERT INTO locations (name, description) VALUES ('DefaultLocation', 'This is the first default location, edit or delete it');
INSERT INTO quantity_units (name, description) VALUES ('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);"
);
}
private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql)
{
if ($pdo->query("SELECT COUNT(*) FROM migrations WHERE migration = $migrationId")->fetchColumn() == 0)
{
if ($pdo->exec(utf8_encode($sql)) === false)
{
throw new Exception($pdo->errorInfo());
}
$pdo->exec('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
}
}
}

View File

@@ -1,29 +0,0 @@
<?php
class GrocyDemoDataGenerator
{
public static function PopulateDemoData(PDO $pdo)
{
$sql = "
UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1;
INSERT INTO locations (name) VALUES ('S<><53>igkeitenschrank');
INSERT INTO locations (name) VALUES ('Konvervenschrank');
UPDATE quantity_units SET name = 'St<53>ck' WHERE id = 1;
INSERT INTO quantity_units (name) VALUES ('Packung');
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gummib<69>rchen', 2, 2, 2, 1);
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Chips', 2, 2, 2, 1);
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10);
INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (3, 5, date('now', '+180 day'), '".uniqid()."');
INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (4, 5, date('now', '+180 day'), '".uniqid()."');
INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (5, 5, date('now', '+25 day'), '".uniqid()."');
";
if ($pdo->exec(utf8_encode($sql)) === false)
{
throw new Exception($pdo->errorInfo());
}
}
}

View File

@@ -1,89 +0,0 @@
<?php
class GrocyLogicStock
{
public static function GetCurrentStock()
{
$db = Grocy::GetDbConnectionRaw();
return $db->query('SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date from stock GROUP BY product_id ORDER BY MIN(best_before_date) ASC')->fetchAll(PDO::FETCH_OBJ);
}
public static function GetProductDetails(int $productId)
{
$db = Grocy::GetDbConnection();
$product = $db->products($productId);
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
$productLastPurchased = $db->stock()->where('product_id', $productId)->max('purchased_date');
$productLastUsed = $db->consumptions()->where('product_id', $productId)->max('used_date');
$quPurchase = $db->quantity_units($product->qu_id_purchase);
$quStock = $db->quantity_units($product->qu_id_stock);
return array(
'product' => $product,
'last_purchased' => $productLastPurchased,
'last_used' => $productLastUsed,
'stock_amount' => $productStockAmount,
'quantity_unit_purchase' => $quPurchase,
'quantity_unit_stock' => $quStock
);
}
public static function ConsumeProduct(int $productId, int $amount, bool $spoiled)
{
$db = Grocy::GetDbConnection();
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
$potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('purchased_date', 'ASC')->fetchAll(); //FIFO
if ($amount > $productStockAmount)
{
return false;
}
foreach ($potentialStockEntries as $stockEntry)
{
if ($amount == 0)
{
break;
}
if ($amount >= $stockEntry->amount) //Take the whole stock entry
{
$consumptionRow = $db->consumptions()->createRow(array(
'product_id' => $stockEntry->product_id,
'amount' => $stockEntry->amount,
'best_before_date' => $stockEntry->best_before_date,
'purchased_date' => $stockEntry->purchased_date,
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id
));
$consumptionRow->save();
$amount -= $stockEntry->amount;
$stockEntry->delete();
}
else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
{
$consumptionRow = $db->consumptions()->createRow(array(
'product_id' => $stockEntry->product_id,
'amount' => $amount,
'best_before_date' => $stockEntry->best_before_date,
'purchased_date' => $stockEntry->purchased_date,
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id
));
$consumptionRow->save();
$restStockAmount = $stockEntry->amount - $amount;
$amount = 0;
$stockEntry->update(array(
'amount' => $restStockAmount
));
}
}
return true;
}
}

View File

@@ -1,17 +0,0 @@
<?php
class GrocyPhpHelper
{
public static function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
{
foreach($array as $object)
{
if($object->{$propertyName} == $propertyValue)
{
return $object;
}
}
return null;
}
}

View File

@@ -1,20 +1,92 @@
# grocy
ERP beyond your fridge
## Give it a try
Public demo of the latest version &rarr; [https://demo.grocy.info](https://demo.grocy.info)
## Motivation
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing.
## What it is about
For now my main focus is on stock management, ERP your fridge!
# Give it a try
Public demo of the latest version &rarr; [https://grocy.projectdemos.berrnd.org](https://grocy.projectdemos.berrnd.org)
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing. ERP your fridge!
## How to install
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP enabled webserver, copy `config-dist.php` to `config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually.
> **NEW**
>
> There is now grocy-desktop if you want to run grocy without a webserver just like a normal (windows) desktop application.
>
> See https://github.com/berrnd/grocy-desktop or directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next"...
## Todo
A lot...
Just unpack the [latest release](https://releases.grocy.info/latest) on your PHP (SQLite extension required, currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go, (to make writable `chown -R www-data:www-data data/`). Default login is user `admin` with password `admin`, please change the password immediately (see user menu).
Alternatively clone this repository and install Composer and Yarn dependencies manually.
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`).
## How to update
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.
If you run grocy on Linux, there is also `update.sh` (remember to make the script executable, `chmod +x update.sh` and ensure that you have `unzip` installed) which does exactly this and additionally creates a backup (`.tgz` archive) of the current installation in `data/backups` (backups older than 60 days will be deleted during the update).
## Localization
grocy is fully localizable - the default language is English (integrated into code), a German localization is always maintained by me. There is one file per language in the `localization` directory, if you want to create a translation, it's best to copy `localization/de.php` to a new one (e. g. `localization/it.php`) and translating all strings there. (Language can be changed in `data/config.php`, e. g. `Setting('CULTURE', 'it');`)
### Maintaining your own localization
As the German translation will always be the most complete one, for maintaining your localization it would be easiest when you compare your localization with the German one with a diff tool of your choice.
## Things worth to know
### REST API & data model documentation
See the integrated Swagger UI instance on [/api](https://demo-en.grocy.info/api).
### Barcode readers
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter which is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
### Input shorthands for date fields
For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization.
The following shorthands are available:
- `MMDD` gets expanded to the given day on the current year, if > today, or to the given day next year, if < today, in proper notation
- Example: `0517` will be converted to `2018-05-17`
- `YYYYMMDD` gets expanded to the proper ISO-8601 notation
- Example: `20190417` will be converted to `2019-04-17`
- `YYYYMMe` or `YYYYMM+` gets expanded to the end of the given month in the given year in proper notation
- Example: `201807e` will be converted to `2018-07-31`
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
- Down/up arrow keys will increase/decrease the date by one day
- Right/left arrow keys will increase/decrease the date by 1 week
### Keyboard shorthands for buttons
Wherever a button contains a bold highlighted letter, this is a shortcut key.
Example: Button "Add as new **p**roduct" can be "pressed" by using the `P` key on your keyboard.
### Barcode lookup via external services
Products can be directly added to the database via looking them up against external services by a barcode.
This is currently only possible through the REST API.
There is no plugin included for any service, see the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php`.
### Database migrations
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
### Adding your own CSS or JS without to have to modify the application itself
- When the file `data/custom_js.html` exists, the contents of the file will be added just before `</body>` (end of body) on every page
- When the file `data/custom_css.html` exists, the contents of the file will be added just before `</head>` (end of head) on every page
### Demo mode
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
### Embedded mode
When the file `embedded.txt` exists, it must contain a valid and writable path which will be used as the data directory instead of `data` and authentication will be disabled (used in [grocy-desktop](https://github.com/berrnd/grocy-desktop)).
In embedded mode, settings can be overridden by text files in `data/settingoverrides`, the file name must be `<SettingName>.txt` (e. g. `BASE_URL.txt`) and the content must be the setting value (normally one single line).
## Screenshots
#### Dashboard
![Dashboard](https://github.com/berrnd/grocy/raw/master/publication_assets/dashboard.png "Dashboard")
#### Purchase - with barcode scan
![Purchase - with barcode scan](https://github.com/berrnd/grocy/raw/master/publication_assets/purchase-with-barcode.gif "purchase-with-barcode")
#### Consume - with manual search
![Consume - with manual search](https://github.com/berrnd/grocy/raw/master/publication_assets/consume.gif "consume")
## License
The MIT License (MIT)

71
app.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \Grocy\Helpers\UrlManager;
use \Grocy\Controllers\LoginController;
// Definitions for embedded mode
if (file_exists(__DIR__ . '/embedded.txt'))
{
define('GROCY_IS_EMBEDDED_INSTALL', true);
define('GROCY_DATAPATH', file_get_contents(__DIR__ . '/embedded.txt'));
define('GROCY_USER_ID', 1);
}
else
{
define('GROCY_IS_EMBEDDED_INSTALL', false);
define('GROCY_DATAPATH', __DIR__ . '/data');
}
// Definitions for demo mode
if (file_exists(GROCY_DATAPATH . '/demo.txt'))
{
define('GROCY_IS_DEMO_INSTALL', true);
if (!defined('GROCY_USER_ID'))
{
define('GROCY_USER_ID', 1);
}
}
else
{
define('GROCY_IS_DEMO_INSTALL', false);
}
// Load composer dependencies
require_once __DIR__ . '/vendor/autoload.php';
// Load config files
require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones
// Setup base application
$appContainer = new \Slim\Container([
'settings' => [
'displayErrorDetails' => true,
'determineRouteBeforeAppMiddleware' => true
],
'view' => function($container)
{
return new \Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache');
},
'LoginControllerInstance' => function($container)
{
return new LoginController($container, 'grocy_session');
},
'UrlManager' => function($container)
{
return new UrlManager(GROCY_BASE_URL);
},
'ApiKeyHeaderName' => function($container)
{
return 'GROCY-API-KEY';
}
]);
$app = new \Slim\App($appContainer);
// Load routes from separate file
require_once __DIR__ . '/routes.php';
$app->run();

View File

@@ -1,21 +0,0 @@
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.7",
"font-awesome": "4.7.0",
"bootbox": "4.4.0",
"jquery.serializeJSON": "2.7.2",
"bootstrap-validator": "0.11.9",
"bootstrap-datepicker": "1.6.4",
"moment": "2.18.1",
"bootstrap-combobox": "1.1.8",
"datatables.net": "1.10.13",
"datatables.net-bs": "2.1.1",
"datatables.net-responsive": "2.1.1",
"datatables.net-responsive-bs": "2.1.1",
"jquery-timeago": "1.5.4",
"toastr": "2.1.3",
"tagmanager": "3.0.2"
}
}

View File

@@ -4,8 +4,10 @@ if %projectPath:~-1%==\ set projectPath=%projectPath:~0,-1%
set releasePath=%projectPath%\.release
mkdir "%releasePath%"
for /f "tokens=*" %%a in ('type version.txt') do set version=%%a
for /f "tokens=*" %%a in ('build_tools\jq.exe .Version version.json --raw-output') do set version=%%a
del "%releasePath%\grocy_%version%.zip"
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!composer.phar -xr!grocy.phpproj -xr!grocy.phpproj.user -xr!grocy.sln
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\add_before_end_body.html data\demo.txt data\grocy.db data\.gitignore config.php bower.json
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!publication_assets
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions data\viewcache\*

BIN
build_tools/jq.exe Normal file

Binary file not shown.

View File

@@ -1,8 +1,20 @@
{
"require": {
"slim/slim": "^3.8",
"slim/php-view": "^2.2",
"morris/lessql": "^0.3.4",
"tuupola/slim-basic-auth": "^2.2"
}
"require": {
"php": ">=7.2",
"slim/slim": "^3.8",
"morris/lessql": "^0.3.4",
"rubellum/slim-blade-view": "^0.1.1",
"tuupola/cors-middleware": "^0.7.0"
},
"autoload": {
"psr-4": {
"Grocy\\Services\\": "services/",
"Grocy\\Controllers\\": "controllers/",
"Grocy\\Middleware\\": "middleware/",
"Grocy\\Helpers\\": "helpers/"
},
"files": [
"helpers/extensions.php"
]
}
}

1572
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,27 @@
<?php
define('HTTP_USER', 'admin');
define('HTTP_PASSWORD', 'admin');
# Either "production" or "dev"
Setting('MODE', 'production');
# Either "en" or "de" or the filename (without extension) of
# one of the other available localization files in the "/localization" directory
Setting('CULTURE', 'en');
# To keep it simpel, grocy does not handle any currency conversions,
# this here is used to format all money values,
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
Setting('CURRENCY', '$');
# The base url of your installation,
# should be just "/" when running directly under the root of a (sub)domain
# or for example "https:/example.com/grocy" when using a subdirectory
Setting('BASE_URL', '/');
# The plugin to use for external barcode lookups,
# must be the filename without .php extension and must be located in /data/plugins,
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
# If, however, your webserver does not support URL rewriting,
# set this to true
Setting('DISABLE_URL_REWRITING', false);

View File

@@ -0,0 +1,28 @@
<?php
namespace Grocy\Controllers;
class BaseApiController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json'));
}
protected $OpenApiSpec;
protected function ApiResponse($data)
{
return json_encode($data);
}
protected function VoidApiActionResponse($response, $success = true, $status = 200, $errorMessage = '')
{
return $response->withStatus($status)->withJson(array(
'success' => $success,
'error_message' => $errorMessage
));
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\DatabaseService;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\LocalizationService;
class BaseController
{
public function __construct(\Slim\Container $container) {
$databaseService = new DatabaseService();
$this->Database = $databaseService->GetDbConnection();
$localizationService = new LocalizationService(GROCY_CULTURE);
$this->LocalizationService = $localizationService;
$applicationService = new ApplicationService();
$versionInfo = $applicationService->GetInstalledVersion();
$container->view->set('version', $versionInfo->Version);
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
{
return $localizationService->Localize($text, ...$placeholderValues);
});
$container->view->set('U', function($relativePath, $isResource = false) use($container)
{
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
});
$this->AppContainer = $container;
}
protected $AppContainer;
protected $Database;
protected $LocalizationService;
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\BatteriesService;
class BatteriesApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->BatteriesService = new BatteriesService();
}
protected $BatteriesService;
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$trackedTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
{
$trackedTime = $request->getQueryParams()['tracked_time'];
}
try
{
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->BatteriesService->GetCurrent());
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\BatteriesService;
class BatteriesController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->BatteriesService = new BatteriesService();
}
protected $BatteriesService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteriesoverview', [
'batteries' => $this->Database->batteries()->orderBy('name'),
'current' => $this->BatteriesService->GetCurrent(),
'nextXDays' => 5
]);
}
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batterytracking', [
'batteries' => $this->Database->batteries()->orderBy('name')
]);
}
public function BatteriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'batteries', [
'batteries' => $this->Database->batteries()->orderBy('name')
]);
}
public function BatteryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['batteryId'] == 'new')
{
return $this->AppContainer->view->render($response, 'batteryform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'batteryform', [
'battery' => $this->Database->batteries($args['batteryId']),
'mode' => 'edit'
]);
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ChoresService;
class ChoresApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->ChoresService = new ChoresService();
}
protected $ChoresService;
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$trackedTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
{
$trackedTime = $request->getQueryParams()['tracked_time'];
}
$doneBy = GROCY_USER_ID;
if (isset($request->getQueryParams()['done_by']) && !empty($request->getQueryParams()['done_by']))
{
$doneBy = $request->getQueryParams()['done_by'];
}
try
{
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function ChoreDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId']));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->ChoresService->GetCurrent());
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ChoresService;
class ChoresController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->ChoresService = new ChoresService();
}
protected $ChoresService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresoverview', [
'chores' => $this->Database->chores()->orderBy('name'),
'currentChores' => $this->ChoresService->GetCurrent(),
'nextXDays' => 5
]);
}
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choretracking', [
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'chores', [
'chores' => $this->Database->chores()->orderBy('name')
]);
}
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'choresanalysis', [
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
'chores' => $this->Database->chores()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['choredId'] == 'new')
{
return $this->AppContainer->view->render($response, 'choreform', [
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'choreform', [
'chore' => $this->Database->chores($args['choreId']),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
'mode' => 'edit'
]);
}
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Grocy\Controllers;
class GenericEntityApiController extends BaseApiController
{
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
{
return $this->ApiResponse($this->Database->{$args['entity']}());
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
}
}
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
{
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
}
}
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
{
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
$newRow->save();
$success = $newRow->isClean();
return $this->ApiResponse(array('success' => $success));
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
}
}
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
{
$row = $this->Database->{$args['entity']}($args['objectId']);
$row->update($request->getParsedBody());
$success = $row->isClean();
return $this->ApiResponse(array('success' => $success));
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
}
}
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($this->IsValidEntity($args['entity']))
{
$row = $this->Database->{$args['entity']}($args['objectId']);
$row->delete();
$success = $row->isClean();
return $this->ApiResponse(array('success' => $success));
}
else
{
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
}
}
private function IsValidEntity($entity)
{
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\SessionService;
use \Grocy\Services\DatabaseMigrationService;
use \Grocy\Services\DemoDataGeneratorService;
class LoginController extends BaseController
{
public function __construct(\Slim\Container $container, string $sessionCookieName)
{
parent::__construct($container);
$this->SessionService = new SessionService();
$this->SessionCookieName = $sessionCookieName;
}
protected $SessionService;
protected $SessionCookieName;
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$postParams = $request->getParsedBody();
if (isset($postParams['username']) && isset($postParams['password']))
{
$user = $this->Database->users()->where('username', $postParams['username'])->fetch();
$inputPassword = $postParams['password'];
$stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on';
if ($user !== null && password_verify($inputPassword, $user->password))
{
$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently);
setcookie($this->SessionCookieName, $sessionKey, time() + 31220640000); // Cookie expires in 999 years, but session validity is up to SessionService
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
{
$user->update(array(
'password' => password_hash($inputPassword, PASSWORD_DEFAULT)
));
}
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
}
else
{
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true'));
}
}
else
{
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true'));
}
}
public function LoginPage(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'login');
}
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->SessionService->RemoveSession($_COOKIE[$this->SessionCookieName]);
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
}
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
// Schema migration is done here
$databaseMigrationService = new DatabaseMigrationService();
$databaseMigrationService->MigrateDatabase();
if (GROCY_IS_DEMO_INSTALL)
{
$demoDataGeneratorService = new DemoDataGeneratorService();
$demoDataGeneratorService->PopulateDemoData();
}
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
}
public function GetSessionCookieName()
{
return $this->SessionCookieName;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\ApiKeyService;
class OpenApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->ApiKeyService = new ApiKeyService();
}
protected $ApiKeyService;
public function DocumentationUi(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'openapiui');
}
public function DocumentationSpec(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$applicationService = new ApplicationService();
$versionInfo = $applicationService->GetInstalledVersion();
$this->OpenApiSpec->info->version = $versionInfo->Version;
$this->OpenApiSpec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->UrlManager->ConstructUrl('/manageapikeys'), $this->OpenApiSpec->info->description);
$this->OpenApiSpec->servers[0]->url = $this->AppContainer->UrlManager->ConstructUrl('/api');
return $this->ApiResponse($this->OpenApiSpec);
}
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'manageapikeys', [
'apiKeys' => $this->Database->api_keys(),
'users' => $this->Database->users()
]);
}
public function CreateNewApiKey(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$newApiKey = $this->ApiKeyService->CreateApiKey();
$newApiKeyId = $this->ApiKeyService->GetApiKeyId($newApiKey);
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId"));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
class RecipesApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
}
protected $RecipesService;
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']);
return $this->VoidApiActionResponse($response);
}
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->RecipesService->ConsumeRecipe($args['recipeId']);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\RecipesService;
class RecipesController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->RecipesService = new RecipesService();
}
protected $RecipesService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$recipes = $this->Database->recipes()->orderBy('name');
$selectedRecipe = null;
$selectedRecipePositions = null;
if (isset($request->getQueryParams()['recipe']))
{
$selectedRecipe = $this->Database->recipes($request->getQueryParams()['recipe']);
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $request->getQueryParams()['recipe']);
}
else
{
foreach ($recipes as $recipe)
{
$selectedRecipe = $recipe;
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $recipe->id);
break;
}
}
return $this->AppContainer->view->render($response, 'recipes', [
'recipes' => $recipes,
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
'selectedRecipe' => $selectedRecipe,
'selectedRecipePositions' => $selectedRecipePositions,
'products' => $this->Database->products(),
'quantityunits' => $this->Database->quantity_units()
]);
}
public function RecipeEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$recipeId = $args['recipeId'];
if ($recipeId == 'new')
{
$newRecipe = $this->Database->recipes()->createRow(array(
'name' => $this->LocalizationService->Localize('New recipe')
));
$newRecipe->save();
$recipeId = $this->Database->lastInsertId();
}
return $this->AppContainer->view->render($response, 'recipeform', [
'recipe' => $this->Database->recipes($recipeId),
'recipePositions' => $this->Database->recipes_pos()->where('recipe_id', $recipeId),
'mode' => 'edit',
'products' => $this->Database->products(),
'quantityunits' => $this->Database->quantity_units(),
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment()
]);
}
public function RecipePosEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['recipePosId'] == 'new')
{
return $this->AppContainer->view->render($response, 'recipeposform', [
'mode' => 'create',
'recipe' => $this->Database->recipes($args['recipeId']),
'products' => $this->Database->products()->orderBy('name'),
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
]);
}
else
{
return $this->AppContainer->view->render($response, 'recipeposform', [
'mode' => 'edit',
'recipe' => $this->Database->recipes($args['recipeId']),
'recipePos' => $this->Database->recipes_pos($args['recipePosId']),
'products' => $this->Database->products()->orderBy('name'),
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
]);
}
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\StockService;
class StockApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->StockService = new StockService();
}
protected $StockService;
public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function ProductPriceHistory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->StockService->GetProductPriceHistory($args['productId']));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$bestBeforeDate = date('Y-m-d');
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
{
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
$price = null;
if (isset($request->getQueryParams()['price']) && !empty($request->getQueryParams()['price']) && is_numeric($request->getQueryParams()['price']))
{
$price = $request->getQueryParams()['price'];
}
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
$transactionType = $request->getQueryParams()['transactiontype'];
}
try
{
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$spoiled = false;
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
{
$spoiled = true;
}
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
$transactionType = $request->getQueryParams()['transactiontype'];
}
try
{
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$bestBeforeDate = date('Y-m-d');
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
{
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
try
{
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->StockService->GetCurrentStock());
}
public function CurrentVolatilStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$nextXDays = 5;
if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days']))
{
$nextXDays = $request->getQueryParams()['expiring_days'];
}
$expiringProducts = $this->StockService->GetExpiringProducts($nextXDays);
$expiredProducts = $this->StockService->GetExpiringProducts(-1);
$missingProducts = $this->StockService->GetMissingProducts();
return $this->ApiResponse(array(
'expiring_products' => $expiringProducts,
'expired_products' => $expiredProducts,
'missing_products' => $missingProducts
));
}
public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->StockService->AddMissingProductsToShoppingList();
return $this->VoidApiActionResponse($response);
}
public function ClearShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$this->StockService->ClearShoppingList();
return $this->VoidApiActionResponse($response);
}
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$addFoundProduct = false;
if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1))
{
$addFoundProduct = true;
}
return $this->ApiResponse($this->StockService->ExternalBarcodeLookup($args['barcode'], $addFoundProduct));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\StockService;
class StockController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->StockService = new StockService();
}
protected $StockService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'stockoverview', [
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name'),
'currentStock' => $this->StockService->GetCurrentStock(),
'missingProducts' => $this->StockService->GetMissingProducts(),
'nextXDays' => 5
]);
}
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'purchase', [
'products' => $this->Database->products()->orderBy('name')
]);
}
public function Consume(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'consume', [
'products' => $this->Database->products()->orderBy('name')
]);
}
public function Inventory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'inventory', [
'products' => $this->Database->products()->orderBy('name')
]);
}
public function ShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'shoppinglist', [
'listItems' => $this->Database->shopping_list(),
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'missingProducts' => $this->StockService->GetMissingProducts(),
'productGroups' => $this->Database->product_groups()->orderBy('name')
]);
}
public function ProductsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'products', [
'products' => $this->Database->products()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productGroups' => $this->Database->product_groups()->orderBy('name')
]);
}
public function LocationsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'locations', [
'locations' => $this->Database->locations()->orderBy('name')
]);
}
public function ProductGroupsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'productgroups', [
'productGroups' => $this->Database->product_groups()->orderBy('name')
]);
}
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'quantityunits', [
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
]);
}
public function ProductEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['productId'] == 'new')
{
return $this->AppContainer->view->render($response, 'productform', [
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productgroups' => $this->Database->product_groups()->orderBy('name'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'productform', [
'product' => $this->Database->products($args['productId']),
'locations' => $this->Database->locations()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'productgroups' => $this->Database->product_groups()->orderBy('name'),
'mode' => 'edit'
]);
}
}
public function LocationEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['locationId'] == 'new')
{
return $this->AppContainer->view->render($response, 'locationform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'locationform', [
'location' => $this->Database->locations($args['locationId']),
'mode' => 'edit'
]);
}
}
public function ProductGroupEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['productGroupId'] == 'new')
{
return $this->AppContainer->view->render($response, 'productgroupform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'productgroupform', [
'group' => $this->Database->product_groups($args['productGroupId']),
'mode' => 'edit'
]);
}
}
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['quantityunitId'] == 'new')
{
return $this->AppContainer->view->render($response, 'quantityunitform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'quantityunitform', [
'quantityunit' => $this->Database->quantity_units($args['quantityunitId']),
'mode' => 'edit'
]);
}
}
public function ShoppingListItemEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['itemId'] == 'new')
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
'products' => $this->Database->products()->orderBy('name'),
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'shoppinglistform', [
'listItem' => $this->Database->shopping_list($args['itemId']),
'products' => $this->Database->products()->orderBy('name'),
'mode' => 'edit'
]);
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\DatabaseService;
class SystemApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->DatabaseService = new DatabaseService();
}
protected $DatabaseService;
public function GetDbChangedTime(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse(array(
'changed_time' => $this->DatabaseService->GetDbChangedTime()
));
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\TasksService;
class TasksApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->TasksService = new TasksService();
}
protected $TasksService;
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->TasksService->GetCurrent());
}
public function MarkTaskAsCompleted(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$doneTime = date('Y-m-d H:i:s');
if (isset($request->getQueryParams()['done_time']) && !empty($request->getQueryParams()['done_time']) && IsIsoDateTime($request->getQueryParams()['done_time']))
{
$doneTime = $request->getQueryParams()['done_time'];
}
try
{
$this->TasksService->MarkTaskAsCompleted($args['taskId'], $doneTime);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\TasksService;
class TasksController extends BaseController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->TasksService = new TasksService();
}
protected $TasksService;
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if (isset($request->getQueryParams()['include_done']))
{
$tasks = $this->Database->tasks()->orderBy('name');
}
else
{
$tasks = $this->TasksService->GetCurrent();
}
return $this->AppContainer->view->render($response, 'tasks', [
'tasks' => $tasks,
'nextXDays' => 5,
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()
]);
}
public function TaskEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['taskId'] == 'new')
{
return $this->AppContainer->view->render($response, 'taskform', [
'mode' => 'create',
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
else
{
return $this->AppContainer->view->render($response, 'taskform', [
'task' => $this->Database->tasks($args['taskId']),
'mode' => 'edit',
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
'users' => $this->Database->users()->orderBy('username')
]);
}
}
public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'taskcategories', [
'taskCategories' => $this->Database->task_categories()->orderBy('name')
]);
}
public function TaskCategoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['categoryId'] == 'new')
{
return $this->AppContainer->view->render($response, 'taskcategoryform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'taskcategoryform', [
'category' => $this->Database->task_categories($args['categoryId']),
'mode' => 'edit'
]);
}
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Grocy\Controllers;
use \Grocy\Services\UsersService;
class UsersApiController extends BaseApiController
{
public function __construct(\Slim\Container $container)
{
parent::__construct($container);
$this->UsersService = new UsersService();
}
protected $UsersService;
public function GetUsers(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->UsersService->GetUsersAsDto());
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function CreateUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
try
{
$this->UsersService->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
return $this->ApiResponse(array('success' => true));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function DeleteUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$this->UsersService->DeleteUser($args['userId']);
return $this->ApiResponse(array('success' => true));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function EditUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
try
{
$this->UsersService->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
return $this->ApiResponse(array('success' => true));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Grocy\Controllers;
class UsersController extends BaseController
{
public function UsersList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->AppContainer->view->render($response, 'users', [
'users' => $this->Database->users()->orderBy('username')
]);
}
public function UserEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if ($args['userId'] == 'new')
{
return $this->AppContainer->view->render($response, 'userform', [
'mode' => 'create'
]);
}
else
{
return $this->AppContainer->view->render($response, 'userform', [
'user' => $this->Database->users($args['userId']),
'mode' => 'edit'
]);
}
}
}

2
data/.gitignore vendored
View File

@@ -1,2 +1,4 @@
*
!.gitignore
!viewcache
!plugins

3
data/plugins/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!.gitignore
!DemoBarcodeLookupPlugin.php

View File

@@ -0,0 +1,78 @@
<?php
use \Grocy\Helpers\BaseBarcodeLookupPlugin;
/*
This class must extend BaseBarcodeLookupPlugin (in namespace \Grocy\Helpers)
*/
class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
{
/*
To use this plugin, configure it in data/config.php like this:
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
*/
/*
To try it:
Call the API function at /api/stock/external-barcode-lookup/{barcode}
When you also add ?add=true as a query parameter to the API call,
on a successful lookup the product is added to the database and in the output
the new product id is included (automatically, nothing to do here in the plugin)
*/
/*
Provided references:
$this->Locations contains all locations
$this->QuantityUnits contains all quantity units
*/
/*
Useful hints:
Get a quantity unit by name:
$quantityUnit = FindObjectInArrayByPropertyValue($this->QuantityUnits, 'name', 'Piece');
Get a location by name:
$location = FindObjectInArrayByPropertyValue($this->Locations, 'name', 'Fridge');
*/
/*
This class must implement the protected abstract function ExecuteLookup($barcode),
which is called with the barcode that needs to be looked up and must return an
associative array of the product model or null, when nothing was found for the barcode.
The returned array must contain at least these properties:
array(
'name' => '',
'location_id' => 1, // A valid id of a location object, check against $this->Locations
'qu_id_purchase' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
'qu_id_stock' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same
'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary
)
*/
protected function ExecuteLookup($barcode)
{
if ($barcode === 'x') // Demonstration when nothing is found
{
return null;
}
elseif ($barcode === 'e') // Demonstration when an error occurred
{
throw new \Exception('This is the error message from the plugin...');
}
else
{
return array(
'name' => 'LookedUpProduct_' . RandomString(5),
'location_id' => $this->Locations[0]->id,
'qu_id_purchase' => $this->QuantityUnits[0]->id,
'qu_id_stock' => $this->QuantityUnits[0]->id,
'qu_factor_purchase_to_stock' => 1,
'barcode' => $barcode
);
}
}
}

2
data/viewcache/.gitignore vendored Normal file
View File

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

View File

@@ -1,77 +0,0 @@
var Grocy = {};
$(function()
{
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ContentPage + "']");
menuItem.addClass('active');
$.timeago.settings.allowFuture = true;
$('time.timeago').timeago();
});
Grocy.FetchJson = function(url, success, error)
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
if (xhr.readyState === XMLHttpRequest.DONE)
{
if (xhr.status === 200)
{
if (success)
{
success(JSON.parse(xhr.responseText));
}
}
else
{
if (error)
{
error(xhr);
}
}
}
};
xhr.open('GET', url, true);
xhr.send();
};
Grocy.PostJson = function(url, jsonData, success, error)
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
if (xhr.readyState === XMLHttpRequest.DONE)
{
if (xhr.status === 200)
{
if (success)
{
success(JSON.parse(xhr.responseText));
}
}
else
{
if (error)
{
error(xhr);
}
}
}
};
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(jsonData));
};
Grocy.EmptyElementWhenMatches = function(selector, text)
{
if ($(selector).text() === text)
{
$(selector).text('');
}
};

2064
grocy.openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +0,0 @@
<?php
class Grocy
{
private static $DbConnectionRaw;
/**
* @return PDO
*/
public static function GetDbConnectionRaw()
{
if (self::$DbConnectionRaw == null)
{
$newDb = !file_exists(__DIR__ . '/data/grocy.db');
$pdo = new PDO('sqlite:' . __DIR__ . '/data/grocy.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($newDb)
{
$pdo->exec("CREATE TABLE migrations (migration INTEGER NOT NULL UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')), PRIMARY KEY(migration)) WITHOUT ROWID");
GrocyDbMigrator::MigrateDb($pdo);
if (self::IsDemoInstallation())
{
GrocyDemoDataGenerator::PopulateDemoData($pdo);
}
}
self::$DbConnectionRaw = $pdo;
}
return self::$DbConnectionRaw;
}
private static $DbConnection;
/**
* @return LessQL\Database
*/
public static function GetDbConnection()
{
if (self::$DbConnection == null)
{
self::$DbConnection = new LessQL\Database(self::GetDbConnectionRaw());
}
return self::$DbConnection;
}
public static function IsDemoInstallation()
{
return file_exists(__DIR__ . '/data/demo.txt');
}
private static $InstalledVersion;
public static function GetInstalledVersion()
{
if (self::$InstalledVersion == null)
{
self::$InstalledVersion = file_get_contents(__DIR__ . '/version.txt');
}
return self::$InstalledVersion;
}
}

View File

@@ -1,63 +0,0 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Name>grocy</Name>
<ProjectGuid>edb77631-5196-4860-baeb-bca8900a4b6d</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>
</RootNamespace>
<ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids>
<AssemblyName>grocy</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<IncludeDebugInformation>true</IncludeDebugInformation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<IncludeDebugInformation>false</IncludeDebugInformation>
</PropertyGroup>
<ItemGroup>
<Compile Include="config-dist.php" />
<Compile Include="Grocy.php" />
<Compile Include="GrocyLogicStock.php" />
<Compile Include="GrocyDemoDataGenerator.php" />
<Compile Include="GrocyPhpHelper.php" />
<Compile Include="GrocyDbMigrator.php" />
<Compile Include="index.php" />
<Compile Include="views\consumption.php" />
<Compile Include="views\purchase.php" />
<Compile Include="views\quantityunitform.php" />
<Compile Include="views\locationform.php" />
<Compile Include="views\productform.php" />
<Compile Include="views\locations.php" />
<Compile Include="views\quantityunits.php" />
<Compile Include="views\products.php" />
<Compile Include="views\dashboard.php" />
<Compile Include="views\layout.php" />
</ItemGroup>
<ItemGroup>
<Content Include="bower.json" />
<None Include="build.bat" />
<Content Include="composer.json" />
<Content Include="grocy.js" />
<None Include="README.md" />
<Content Include="README.html">
<SubType>Content</SubType>
<DependentUpon>README.md</DependentUpon>
</Content>
<Content Include="robots.txt" />
<Content Include="style.css" />
<Content Include="version.txt" />
<Content Include="views\consumption.js" />
<Content Include="views\dashboard.js" />
<Content Include="views\purchase.js" />
<Content Include="views\quantityunitform.js" />
<Content Include="views\locationform.js" />
<Content Include="views\productform.js" />
<Content Include="views\locations.js" />
<Content Include="views\quantityunits.js" />
<Content Include="views\products.js" />
</ItemGroup>
<ItemGroup>
<Folder Include="views\" />
</ItemGroup>
</Project>

View File

@@ -1,20 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{A0786B88-2ADB-4C21-ABE8-AA2D79766269}") = "grocy", "grocy.phpproj", "{EDB77631-5196-4860-BAEB-BCA8900A4B6D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EDB77631-5196-4860-BAEB-BCA8900A4B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDB77631-5196-4860-BAEB-BCA8900A4B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,80 @@
<?php
namespace Grocy\Helpers;
abstract class BaseBarcodeLookupPlugin
{
final public function __construct($locations, $quantityUnits)
{
$this->Locations = $locations;
$this->QuantityUnits = $quantityUnits;
}
protected $Locations;
protected $QuantityUnits;
abstract protected function ExecuteLookup($barcode);
final public function Lookup($barcode)
{
$pluginOutput = $this->ExecuteLookup($barcode);
if ($pluginOutput === null)
{
return $pluginOutput;
}
// Plugin must return an associative array
if (!is_array($pluginOutput))
{
throw new \Exception('Plugin output must be an associative array');
}
if (!IsAssociativeArray($pluginOutput)) // $pluginOutput is at least an indexed array here
{
throw new \Exception('Plugin output must be an associative array');
}
// Check for minimum needed properties
$minimunNeededProperties = array(
'name',
'location_id',
'qu_id_purchase',
'qu_id_stock',
'qu_factor_purchase_to_stock',
'barcode'
);
foreach ($minimunNeededProperties as $prop)
{
if (!array_key_exists($prop, $pluginOutput))
{
throw new \Exception("Plugin output does not provide needed property $prop");
}
}
// $pluginOutput contains all needed properties here
// Check referenced entity ids are valid
$locationId = $pluginOutput['location_id'];
if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null)
{
throw new \Exception("Location $locationId is not a valid location id");
}
$quIdPurchase = $pluginOutput['qu_id_purchase'];
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null)
{
throw new \Exception("Location $quIdPurchase is not a valid quantity unit id");
}
$quIdStock = $pluginOutput['qu_id_stock'];
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null)
{
throw new \Exception("Location $quIdStock is not a valid quantity unit id");
}
$quFactor = $pluginOutput['qu_factor_purchase_to_stock'];
if (empty($quFactor) || !is_numeric($quFactor))
{
throw new \Exception('Quantity unit factor is empty or not a number');
}
return $pluginOutput;
}
}

42
helpers/UrlManager.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace Grocy\Helpers;
class UrlManager
{
public function __construct(string $basePath)
{
if ($basePath === '/')
{
$this->BasePath = $this->GetBaseUrl();
}
else
{
$this->BasePath = $basePath;
}
}
protected $BasePath;
public function ConstructUrl($relativePath, $isResource = false)
{
if (GROCY_DISABLE_URL_REWRITING === false || $isResource === true)
{
return rtrim($this->BasePath, '/') . $relativePath;
}
else // Is not a resource and URL rewriting is disabled
{
return rtrim($this->BasePath, '/') . '/index.php' . $relativePath;
}
}
private function GetBaseUrl()
{
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
{
$_SERVER['HTTPS'] = 'on';
}
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
}
}

180
helpers/extensions.php Normal file
View File

@@ -0,0 +1,180 @@
<?php
function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
{
foreach($array as $object)
{
if($object->{$propertyName} == $propertyValue)
{
return $object;
}
}
return null;
}
function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==')
{
$returnArray = array();
foreach($array as $object)
{
switch($operator)
{
case '==':
if($object->{$propertyName} == $propertyValue)
{
$returnArray[] = $object;
}
break;
case '>':
if($object->{$propertyName} > $propertyValue)
{
$returnArray[] = $object;
}
break;
case '<':
if($object->{$propertyName} < $propertyValue)
{
$returnArray[] = $object;
}
break;
}
}
return $returnArray;
}
function FindAllItemsInArrayByValue($array, $value, $operator = '==')
{
$returnArray = array();
foreach($array as $item)
{
switch($operator)
{
case '==':
if($item == $value)
{
$returnArray[] = $item;
}
break;
case '>':
if($item > $value)
{
$returnArray[] = $item;
}
break;
case '<':
if($item < $value)
{
$returnArray[] = $item;
}
break;
}
}
return $returnArray;
}
function SumArrayValue($array, $propertyName)
{
$sum = 0;
foreach($array as $object)
{
$sum += $object->{$propertyName};
}
return $sum;
}
function GetClassConstants($className)
{
$r = new ReflectionClass($className);
return $r->getConstants();
}
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$randomString = '';
for ($i = 0; $i < $length; $i++)
{
$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)];
}
return $randomString;
}
function IsAssociativeArray(array $array)
{
$keys = array_keys($array);
return array_keys($keys) !== $keys;
}
function IsIsoDate($dateString)
{
$d = DateTime::createFromFormat('Y-m-d', $dateString);
return $d && $d->format('Y-m-d') === $dateString;
}
function IsIsoDateTime($dateTimeString)
{
$d = DateTime::createFromFormat('Y-m-d H:i:s', $dateTimeString);
return $d && $d->format('Y-m-d H:i:s') === $dateTimeString;
}
function BoolToString(bool $bool)
{
return $bool ? 'true' : 'false';
}
function Setting(string $name, $value)
{
if (!defined('GROCY_' . $name))
{
// The content of a $name.txt file in /data/settingoverrides can overwrite the given setting (for embedded mode)
$settingOverrideFile = GROCY_DATAPATH . '/settingoverrides/' . $name . '.txt';
if (file_exists($settingOverrideFile))
{
define('GROCY_' . $name, file_get_contents($settingOverrideFile));
}
else
{
define('GROCY_' . $name, $value);
}
}
}
function GetUserDisplayName($user)
{
$displayName = '';
if (empty($user->first_name) && !empty($user->last_name))
{
$displayName = $user->last_name;
}
elseif (empty($user->last_name) && !empty($user->first_name))
{
$displayName = $user->first_name;
}
elseif (!empty($user->last_name) && !empty($user->first_name))
{
$displayName = $user->first_name . ' ' . $user->last_name;
}
else
{
$displayName = $user->username;
}
return $displayName;
}
function Pluralize($number, $singularForm, $pluralForm)
{
$text = $singularForm;
if ($number != 1 && $pluralForm !== null && !empty($pluralForm))
{
$text = $pluralForm;
}
return $text;
}

259
index.php
View File

@@ -1,259 +0,0 @@
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Slim\Views\PhpRenderer;
require_once 'vendor/autoload.php';
require_once 'config.php';
require_once 'Grocy.php';
require_once 'GrocyDbMigrator.php';
require_once 'GrocyDemoDataGenerator.php';
require_once 'GrocyLogicStock.php';
require_once 'GrocyPhpHelper.php';
$app = new \Slim\App(new \Slim\Container([
'settings' => [
'displayErrorDetails' => true,
],
]));
$container = $app->getContainer();
$container['renderer'] = new PhpRenderer('./views');
if (!Grocy::IsDemoInstallation())
{
$isHttpsReverseProxied = !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https';
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
'realm' => 'grocy',
'secure' => !$isHttpsReverseProxied,
'users' => [
HTTP_USER => HTTP_PASSWORD
]
]));
}
$app->get('/', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Dashboard',
'contentPage' => 'dashboard.php',
'products' => $db->products(),
'currentStock' => GrocyLogicStock::GetCurrentStock()
]);
});
$app->get('/purchase', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Purchase',
'contentPage' => 'purchase.php',
'products' => $db->products()
]);
});
$app->get('/consumption', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Consumption',
'contentPage' => 'consumption.php',
'products' => $db->products()
]);
});
$app->get('/products', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Products',
'contentPage' => 'products.php',
'products' => $db->products(),
'locations' => $db->locations(),
'quantityunits' => $db->quantity_units()
]);
});
$app->get('/locations', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Locations',
'contentPage' => 'locations.php',
'locations' => $db->locations()
]);
});
$app->get('/quantityunits', function(Request $request, Response $response)
{
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [
'title' => 'Quantity units',
'contentPage' => 'quantityunits.php',
'quantityunits' => $db->quantity_units()
]);
});
$app->get('/product/{productId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
if ($args['productId'] == 'new')
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Create product',
'contentPage' => 'productform.php',
'locations' => $db->locations(),
'quantityunits' => $db->quantity_units(),
'mode' => 'create'
]);
}
else
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Edit product',
'contentPage' => 'productform.php',
'product' => $db->products($args['productId']),
'locations' => $db->locations(),
'quantityunits' => $db->quantity_units(),
'mode' => 'edit'
]);
}
});
$app->get('/location/{locationId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
if ($args['locationId'] == 'new')
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Create location',
'contentPage' => 'locationform.php',
'mode' => 'create'
]);
}
else
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Edit location',
'contentPage' => 'locationform.php',
'location' => $db->locations($args['locationId']),
'mode' => 'edit'
]);
}
});
$app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
if ($args['quantityunitId'] == 'new')
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Create quantity unit',
'contentPage' => 'quantityunitform.php',
'mode' => 'create'
]);
}
else
{
return $this->renderer->render($response, '/layout.php', [
'title' => 'Edit quantity unit',
'contentPage' => 'quantityunitform.php',
'quantityunit' => $db->quantity_units($args['quantityunitId']),
'mode' => 'edit'
]);
}
});
$app->group('/api', function()
{
$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
echo json_encode($db->{$args['entity']}());
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
echo json_encode($db->{$args['entity']}($args['objectId']));
return $response->withHeader('Content-Type', 'application/json');
});
$this->post('/add-object/{entity}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
$newRow = $db->{$args['entity']}()->createRow($request->getParsedBody());
$newRow->save();
$success = $newRow->isClean();
echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
});
$this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
$row = $db->{$args['entity']}($args['objectId']);
$row->update($request->getParsedBody());
$success = $row->isClean();
echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args)
{
$db = Grocy::GetDbConnection();
$row = $db->{$args['entity']}($args['objectId']);
$row->delete();
$success = $row->isClean();
echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args)
{
echo json_encode(GrocyLogicStock::GetProductDetails($args['productId']));
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/stock/get-current-stock', function(Request $request, Response $response)
{
echo json_encode(GrocyLogicStock::GetCurrentStock());
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/stock/consume-product/{productId}/{amount}', function(Request $request, Response $response, $args)
{
$spoiled = false;
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
{
$spoiled = true;
}
echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled)));
return $response->withHeader('Content-Type', 'application/json');
});
$this->get('/helper/uniqid', function(Request $request, Response $response)
{
echo json_encode(array('uniqid' => uniqid()));
return $response->withHeader('Content-Type', 'application/json');
});
});
$app->run();

328
localization/de.php Normal file
View File

@@ -0,0 +1,328 @@
<?php
return array(
'Stock overview' => 'Bestand',
'#1 products expiring within the next #2 days' => '#1 Produkte laufen innerhalb der nächsten #2 Tage ab',
'#1 products are already expired' => '#1 Produkte sind bereits abgelaufen',
'#1 products are below defined min. stock amount' => '#1 Produkte sind unter Mindestbestand',
'Product' => 'Produkt',
'Amount' => 'Menge',
'Next best before date' => 'Nächstes MHD',
'Logout' => 'Abmelden',
'Chores overview' => 'Hausarbeiten',
'Batteries overview' => 'Batterien',
'Purchase' => 'Einkauf',
'Consume' => 'Verbrauch',
'Inventory' => 'Inventur',
'Shopping list' => 'Einkaufszettel',
'Chore tracking' => 'Hausarbeiten-Ausführung',
'Battery tracking' => 'Batterie-Ladzyklus',
'Products' => 'Produkte',
'Locations' => 'Standorte',
'Quantity units' => 'Mengeneinheiten',
'Chores' => 'Hausarbeiten',
'Batteries' => 'Batterien',
'Chore' => 'Hausarbeit',
'Next estimated tracking' => 'Nächste geplante Ausführung',
'Last tracked' => 'Zuletzt ausgeführt',
'Battery' => 'Batterie',
'Last charged' => 'Zuletzt geladen',
'Next planned charge cycle' => 'Nächster geplanter Ladezyklus',
'Best before' => 'MHD',
'OK' => 'OK',
'Product overview' => 'Produktübersicht',
'Stock quantity unit' => 'Mengeneinheit Bestand',
'Stock amount' => 'Bestand',
'Last purchased' => 'Zuletzt gekauft',
'Last used' => 'Zuletzt benutzt',
'Spoiled' => 'Verdorben',
'Barcode lookup is disabled' => 'Barcode-Suche ist deaktiviert',
'will be added to the list of barcodes for the selected product on submit' => 'wird der Liste der Barcodes für das ausgewählte Produkt beim Speichern hinzugefügt',
'New amount' => 'Neue Menge',
'Note' => 'Notiz',
'Tracked time' => 'Ausführungszeit',
'Chore overview' => 'Hausarbeit Übersicht',
'Tracked count' => 'Ausführungsanzahl',
'Battery overview' => 'Batterie Übersicht',
'Charge cycles count' => 'Ladezyklen',
'Create shopping list item' => 'Einkaufszettel Eintrag erstellen',
'Edit shopping list item' => 'Einkaufszettel Eintrag bearbeiten',
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 Einheiten wurden automatisch hinzugefügt und gelten zusätzlich der hier eingegebenen Menge',
'Save' => 'Speichern',
'Add' => 'Hinzufügen',
'Name' => 'Name',
'Location' => 'Standort',
'Min. stock amount' => 'Mindestbestand',
'QU purchase' => 'ME Einkauf',
'QU stock' => 'ME Bestand',
'QU factor' => 'ME-Faktor',
'Description' => 'Beschreibung',
'Create product' => 'Produkt erstellen',
'Barcode(s)' => 'Barcode(s)',
'Minimum stock amount' => 'Mindestbestand',
'Default best before days' => 'Standard Haltbarkeit in Tagen',
'Quantity unit purchase' => 'Mengeneinheit Einkauf',
'Quantity unit stock' => 'Mengeneinheit Bestand',
'Factor purchase to stock quantity unit' => 'Faktor Mengeneinheit Einkauf zu Mengeneinheit Bestand',
'Create location' => 'Standort erstellen',
'Create quantity unit' => 'Mengeneinheit erstellen',
'Period type' => 'Periodentyp',
'Period days' => 'Tage/Periode',
'Create chore' => 'Hausarbeit erstellen',
'Used in' => 'Benutzt in',
'Create battery' => 'Batterie erstellen',
'Edit battery' => 'Batterie bearbeiten',
'Edit chore' => 'Hausarbeit bearbeiten',
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
'Edit product' => 'Produkt bearbeiten',
'Edit location' => 'Standort bearbeiten',
'Record data' => 'Daten erfassen',
'Manage master data' => 'Stammdaten verwalten',
'This will apply to added products' => 'Dies gilt für hinzugefügte Produkte',
'never' => 'nie',
'Add products that are below defined min. stock amount' => 'Produkte unter Mindestbestand hinzufügen',
'For purchases this amount of days will be added to today for the best before date suggestion' => 'Bei Einkäufen wird hierauf basierend das MHD vorausgefüllt',
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Das bedeutet 1 #1 im Einkauf entsprechen #2 #3 im Bestand',
'Login' => 'Anmelden',
'Username' => 'Benutzername',
'Password' => 'Passwort',
'Invalid credentials, please try again' => 'Ungültige Zugangsdaten, bitte versuche es erneut',
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
'Yes' => 'Ja',
'No' => 'Nein',
'Are you sure to delete chore "#1"?' => 'Hausarbeit "#1" wirklich löschen?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
'Cancel' => 'Abbrechen',
'Add as new product' => 'Als neues Produkt hinzufügen',
'Add as barcode to existing product' => 'Barcode vorhandenem Produkt zuweisen',
'Add as new product and prefill barcode' => 'Neues Produkt erstellen und Barcode vorbelegen',
'Are you sure to delete quantity unit "#1"?' => 'Mengeneinheit "#1" wirklich löschen?',
'Are you sure to delete product "#1"?' => 'Produkt "#1" wirklich löschen?',
'Are you sure to delete location "#1"?' => 'Standort "#1" wirklich löschen?',
'Manage API keys' => 'API-Keys verwalten',
'REST API & data model documentation' => 'REST-API & Datenmodell Dokumentation',
'API keys' => 'API-Keys',
'Create new API key' => 'Neuen API-Key erstellen',
'API key' => 'API-Key',
'Expires' => 'Läuft ab',
'Created' => 'Erstellt',
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Hausarbeit #1 Tage nach der letzten Ausführung geplant wird',
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
'About grocy' => 'Über grocy',
'Close' => 'Schließen',
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
'#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an',
'#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig',
'Released on' => 'Veröffentlicht am',
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
'Tracked execution of chore #1 on #2' => 'Ausführung von #1 am #2 erfasst',
'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
'All' => 'Alle',
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
'Track execution of chore #1' => 'Erfasse eine Ausführung von #1',
'Filter by location' => 'Nach Standort filtern',
'Search' => 'Suche',
'Not logged in' => 'Nicht angemeldet',
'You have to select a product' => 'Ein Produkt muss ausgewählt werden',
'You have to select a chore' => 'Eine Hausarbeit muss ausgewählt werden',
'You have to select a battery' => 'Eine Batterie muss ausgewählt werden',
'A name is required' => 'Ein Name ist erforderlich',
'A location is required' => 'Ein Standort ist erforderlich',
'The amount cannot be lower than #1' => 'Die Menge darf nicht kleiner als #1 sein',
'This cannot be negative' => 'Dies darf nicht negativ sein',
'A quantity unit is required' => 'Eine Mengeneinheit muss ausgewählt werden',
'A period type is required' => 'Eine Periodentyp muss ausgewählt werden',
'A best before date is required and must be later than today' => 'Ein Mindesthaltbarkeitsdatum ist erforderlich und muss später als heute sein',
'Settings' => 'Einstellungen',
'This can only be before now' => 'Dies kann nur vor jetzt sein',
'Calendar' => 'Kalender',
'Recipes' => 'Rezepte',
'Edit recipe' => 'Rezept bearbeiten',
'New recipe' => 'Neues Rezept',
'Ingredients list' => 'Zutatenliste',
'Add recipe ingredient' => 'Rezeptzutat hinzufügen',
'Edit recipe ingredient' => 'Rezeptzutat bearbeiten',
'Are you sure to delete recipe "#1"?' => 'Rezept "#1" wirklich löschen?',
'Are you sure to delete recipe ingredient "#1"?' => 'Rezeptzutat "#1" wirklich löschen?',
'Are you sure to empty the shopping list?' => 'Sicher, dass den Einkaufszettel geleert werden soll?',
'Clear list' => 'Liste leeren',
'Requirements fulfilled' => 'Bedarf im Bestand',
'Put missing products on shopping list' => 'Fehlende Produkte auf den Einkaufszettel setzen',
'Not enough in stock, #1 ingredients missing' => 'Nicht ausreichend im Bestand, #1 Zutaten fehlen',
'Enough in stock' => 'Bestand reicht aus',
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Bestand nicht ausreichend, #1 Zutaten fehlen, stehen aber bereits auf dem Einkaufszettel',
'Expand to fullscreen' => 'Auf ganzen Bildschirm vergrößern',
'Ingredients' => 'Zutaten',
'Preparation' => 'Zubereitung',
'Recipe' => 'Rezept',
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Nicht ausreichend im Bestand, #1 fehlen, #2 stehen bereits auf dem Einkaufszettel',
'Show notes' => 'Notizen anzeigen',
'Put missing amount on shopping list' => 'Fehlende Menge auf den Einkaufszettel setzen',
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Sicher alle fehlenden Zutaten für Rezept "#1" auf die Einkaufsliste zu setzen?',
'Added for recipe #1' => 'Hinzugefügt für Rezept #1',
'Manage users' => 'Benutzer verwalten',
'User' => 'Benutzer',
'Users' => 'Benutzer',
'Are you sure to delete user "#1"?' => 'Benutzer "#1" wirklich löschen?',
'Create user' => 'Benutzer erstellen',
'Edit user' => 'Benutzer bearbeiten',
'First name' => 'Vorname',
'Last name' => 'Nachname',
'A username is required' => 'Ein Benutzername ist erforderlich',
'Confirm password' => 'Passwort bestätigen',
'Passwords do not match' => 'Passwörter stimmen nicht überein',
'Change password' => 'Passwort ändern',
'Done by' => 'Ausgeführt von',
'Last done by' => 'Zuletzt ausgeführt von',
'Unknown' => 'Unbekannt',
'Filter by chore' => 'Nach Hausarbeit filtern',
'Chores analysis' => 'Hausarbeiten Analyse',
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
'Last price' => 'Letzter Preis',
'Price history' => 'Preisentwicklung',
'No price history available' => 'Keine Preisdaten verfügbar',
'Price' => 'Preis',
'in #1 per purchase quantity unit' => 'in #1 pro Einkaufsmengeneinheit',
'The price cannot be lower than #1' => 'Der Preis darf nicht niedriger als #1 sein',
'#1 product expires within the next #2 days' => '#1 Produkt läuft innerhalb der nächsten #2 Tage ab',
'#1 product is already expired' => '#1 Produkt ist bereits abgelaufen',
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
'Unit' => 'Einheit',
'Units' => 'Einheiten',
'#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an',
'#1 chore is overdue to be done' => '#1 Hausarbeit ist überfällig',
'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden',
'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig',
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge',
'in singular form' => 'in der Einzahl',
'in plural form' => 'in der Mehrzahl',
'Never expires' => 'Läuft nie ab',
'This cannot be lower than #1' => 'Dies darf nicht kleiner als #1 sein',
'-1 means that this product never expires' => '-1 bedeuet, dass dieses Produkt niemals abläuft',
'Quantity unit' => 'Mengeneinheit',
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Nur prüfen, ob eine einzelne Einheit vorrätig ist (eine abweichende Mengeneinheit kann dann oben verwendet werden)',
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Sicher, dass alle Zutaten die vom Rezept "#1" benötigt werden aus dem Bestand entfernt werden sollen (Zutaten markiert mit "nur prüfen, ob eine einzelne Einheit vorrätig ist" werden ignoriert)?',
'Removed all ingredients of recipe "#1" from stock' => 'Alle Zutaten, die vom Rezept "#1" benötigt werden, wurdem aus dem Bestand entfernt',
'Consume all ingredients needed by this recipe' => 'Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand enternen',
'Click to show technical details' => 'Klick um technische Details anzuzeigen',
'Error while saving, probably this item already exists' => 'Fehler beim Speichern, möglicherweise existiert das Element bereits',
'Error details' => 'Fehlerdetails',
'Tasks' => 'Aufgaben',
'Show done tasks' => 'Erledigte Aufgaben anzeigen',
'Task' => 'Aufgabe',
'Due' => 'Fällig',
'Assigned to' => 'Zugewiesen an',
'Mark task "#1" as completed' => 'Aufgabe "#1" als erledigt markieren',
'Uncategorized' => 'Nicht kategorisiert',
'Task categories' => 'Aufgabenkategorien',
'Create task' => 'Aufgabe erstellen',
'A due date is required' => 'Ein Fälligkeitsdatum ist erforderlich',
'Category' => 'Kategorie',
'Edit task' => 'Aufgabe bearbeiten',
'Are you sure to delete task "#1"?' => 'Aufgabe "#1" wirklich löschen?',
'#1 task is due to be done within the next #2 days' => '#1 Aufgabe steht in den nächsten #2 Tagen an',
'#1 tasks are due to be done within the next #2 days' => '#1 Aufgaben stehen in den nächsten #2 Tagen an',
'#1 task is overdue to be done' => '#1 Aufgabe ist überfällig',
'#1 tasks are overdue to be done' => '#1 Aufgaben sind überfällig',
'Edit task category' => 'Aufgabenkategorie bearbeiten',
'Create task category' => 'Aufgabenkategorie erstellen',
'Product groups' => 'Produktgruppen',
'Ungrouped' => 'Ungruppiert',
'Create product group' => 'Produktgruppe erstellen',
'Edit product group' => 'Produktgruppe bearbeiten',
'Product group' => 'Produktgruppe',
'Are you sure to delete product group "#1"?' => 'Produktgruppe "#1" wirklich löschen?',
'Stay logged in permanently' => 'Dauerhaft angemeldet bleiben',
'When not set, you will get logged out at latest after 30 days' => 'Wenn nicht gesetzt, wirst du spätestens nach 30 Tagen automatisch abgemeldet',
//Constants
'manually' => 'Manuell',
'dynamic-regular' => 'Dynamisch regelmäßig',
//Technical component translations
'timeago_locale' => 'de',
'timeago_nan' => 'vor NaN Jahren',
'moment_locale' => 'de',
'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}',
//Demo data
'Cookies' => 'Cookies',
'Chocolate' => 'Schokolade',
'Pantry' => 'Vorratskammer',
'Candy cupboard' => 'Süßigkeitenschrank',
'Tinned food cupboard' => 'Konservenschrank',
'Fridge' => 'Kühlschrank',
'Piece' => 'Stück',
'Pieces' => 'Stücke',
'Pack' => 'Packung',
'Packs' => 'Packungen',
'Glass' => 'Glas',
'Glasses' => 'Gläser',
'Tin' => 'Dose',
'Tins' => 'Dosen',
'Can' => 'Becher',
'Cans' => 'Becher',
'Bunch' => 'Bund',
'Bunches' => 'Bunde',
'Gummy bears' => 'Gummibärchen',
'Crisps' => 'Chips',
'Eggs' => 'Eier',
'Noodles' => 'Nudeln',
'Pickles' => 'Essiggurken',
'Gulash soup' => 'Gulaschsuppe',
'Yogurt' => 'Joghurt',
'Cheese' => 'Käse',
'Cold cuts' => 'Aufschnitt',
'Paprika' => 'Paprika',
'Cucumber' => 'Gurke',
'Radish' => 'Radieschen',
'Tomato' => 'Tomaten',
'Changed towels in the bathroom' => 'Handtücher im Bad gewechselt',
'Cleaned the kitchen floor' => 'Küchenboden gewischt',
'Warranty ends' => 'Garantie endet',
'TV remote control' => 'TV Fernbedienung',
'Alarm clock' => 'Wecker',
'Heat remote control' => 'Fernbedienung Heizung',
'Lawn mowed in the garden' => 'Rasen im Garten gemäht',
'Some good snacks' => 'Paar gute Snacks',
'Pizza dough' => 'Pizzateig',
'Sieved tomatoes' => 'Passierte Tomaten',
'Salami' => 'Salami',
'Toast' => 'Toast',
'Minced meat' => 'Hackfleisch',
'Pizza' => 'Pizza',
'Spaghetti bolognese' => 'Spaghetti Bolognese',
'Sandwiches' => 'Belegte Toasts',
'English' => 'Englisch',
'German' => 'Deutsch',
'Italian' => 'Italienisch',
'Demo in different language' => 'Demo in anderer Sprache',
'This is the note content of the recipe ingredient' => 'Dies ist der Inhalt der Notiz der Zutat',
'Demo User' => 'Demo Benutzer',
'Gram' => 'Gramm',
'Grams' => 'Gramm',
'Flour' => 'Mehl',
'Pancakes' => 'Pfannkuchen',
'Sugar' => 'Zucker',
'Home' => 'Zuhause',
'Life' => 'Leben',
'Projects' => 'Projekte',
'Repair the garage door' => 'Garagentor reparieren',
'Fork and improve grocy' => 'grocy forken und verbessern',
'Find a solution for what to do when I forget the door keys' => 'Eine Lösung für "Haustürschlüssel vergessen" finden',
'Sweets' => 'Süßigkeiten',
'Bakery products' => 'Bäckerei Produkte',
'Tinned food' => 'Konservern',
'Butchery products' => 'Metzgerei',
'Vegetables/Fruits' => 'Obst/Gemüse',
'Refrigerated products' => 'Kühlregal'
);

13
localization/en.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
return array(
//Constants
'manually' => 'Manually',
'dynamic-regular' => 'Dynamic regular',
//Technical component translations
'timeago_locale' => 'en',
'timeago_nan' => 'NaN years ago',
'moment_locale' => '',
'datatables_localization' => '{"sEmptyTable":"No data available in table","sInfo":"Showing _START_ to _END_ of _TOTAL_ entries","sInfoEmpty":"Showing 0 to 0 of 0 entries","sInfoFiltered":"(filtered from _MAX_ total entries)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Show _MENU_ entries","sLoadingRecords":"Loading...","sProcessing":"Processing...","sSearch":"Search:","sZeroRecords":"No matching records found","oPaginate":{"sFirst":"First","sLast":"Last","sNext":"Next","sPrevious":"Previous"},"oAria":{"sSortAscending":": activate to sort column ascending","sSortDescending":": activate to sort column descending"}}'
);

192
localization/it.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
return array(
'Stock overview' => 'Dispensa',
'#1 products expiring within the next #2 days' => '#1 prodotti scadranno tra #2 giorni',
'#1 products are already expired' => '#1 prodotti scaduti',
'#1 products are below defined min. stock amount' => '#1 prodotti sotto il limite minimo',
'Product' => 'prodotto',
'Amount' => 'quantità',
'Next best before date' => 'Prossima data di scadenza',
'Logout' => 'Logout',
'Chores overview' => 'Riepilogo delle abitudini',
'Batteries overview' => 'Riepilogo delle batterie',
'Purchase' => 'Acquisti',
'Consume' => 'Consumi',
'Inventory' => 'Inventario',
'Shopping list' => 'Lista della spesa',
'Chore tracking' => 'Dati abitudini',
'Battery tracking' => 'Dati batterie',
'Products' => 'Prodotti',
'Locations' => 'Posizioni',
'Quantity units' => 'Unità di misura',
'Chores' => 'Abitudini',
'Batteries' => 'Batterie',
'Chore' => 'Abitudine',
'Next estimated tracking' => 'Prossima esecuzione',
'Last tracked' => 'Ultima esecuzione',
'Battery' => 'Batterie',
'Last charged' => 'Ultima ricarica',
'Next planned charge cycle' => 'Prossima ricarica',
'Best before' => 'Data di scadenza',
'OK' => 'OK',
'Product overview' => 'Riepilogo dei prodotti',
'Stock quantity unit' => 'Unità di misura',
'Stock amount' => 'Quantità',
'Last purchased' => 'Ultimo acquisto',
'Last used' => 'Ultimo utilizzo',
'Spoiled' => 'Scaduto',
'Barcode lookup is disabled' => 'I codici a barre sono disabilitati',
'will be added to the list of barcodes for the selected product on submit' => 'sarà aggiunto alla lista dei codici a barre per questo prodotto',
'New amount' => 'Nuova quantità',
'Note' => 'Nota',
'Tracked time' => 'Ora di esecuzione',
'Chore overview' => 'Riepilogo dell\'abitudine',
'Tracked count' => 'Numero di esecuzioni',
'Battery overview' => 'Riepilogo della batteria',
'Charge cycles count' => 'Numero di ricariche',
'Create shopping list item' => 'Aggiungi un prodotto alla lista della spesa',
'Edit shopping list item' => 'Modifica un\'entrata della lista della spesa',
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 sono state aggiunte automaticamente',
'Save' => 'Salva',
'Add' => 'Aggiungi',
'Name' => 'Nome',
'Location' => 'Posizione',
'Min. stock amount' => 'Quantità minima',
'QU purchase' => 'Unità di acquisto',
'QU stock' => 'Unità di dispensa',
'QU factor' => 'Fattore di conversione',
'Description' => 'Descrizione',
'Create product' => 'Aggiungi prodotto',
'Barcode(s)' => 'Codice a barre',
'Minimum stock amount' => 'Quantità minima',
'Default best before days' => 'Data di scadenza standard in giorni',
'Quantity unit purchase' => 'Unità di acquisto',
'Quantity unit stock' => 'Unità di dispensa',
'Factor purchase to stock quantity unit' => 'Fattore di conversione tra quantità di acquisto e di dispensa',
'Create location' => 'Aggiungi posizione',
'Create quantity unit' => 'Aggiungi unità di misura',
'Period type' => 'Tipo di ripetizione',
'Period days' => 'Periodo in giorni',
'Create chore' => 'Aggiungi abitudine',
'Used in' => 'Usato in',
'Create battery' => 'Aggiungi batteria',
'Edit battery' => 'Modifica batteria',
'Edit chore' => 'Modifica abitudine',
'Edit quantity unit' => 'Modifica unità di misura',
'Edit product' => 'Modifica prodotto',
'Edit location' => 'Modifica posizione',
'Record data' => 'Registra dati',
'Manage master data' => 'Gestisci dati',
'This will apply to added products' => 'Verrà applicato ai prodotti aggiunti',
'never' => 'mai',
'Add products that are below defined min. stock amount' => 'Aggiungi prodotti sotti il limite minimo',
'For purchases this amount of days will be added to today for the best before date suggestion' => 'Questo numero di giorni verrà aggiunto alla data di acquisto per la data di scadenza',
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Questo significa che 1 #1 acquistato diventerà #2 #3 in dispensa',
'Login' => 'Login',
'Username' => 'Username',
'Password' => 'Password',
'Invalid credentials, please try again' => 'Credenziali non valide, per favore riprova',
'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?',
'Yes' => 'Si',
'No' => 'No',
'Are you sure to delete chore "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?',
'Create or assign product' => 'Aggiungi o assegna prodotto',
'Cancel' => 'Annulla',
'Add as new product' => 'Aggiungi come nuovo prodotto',
'Add as barcode to existing product' => 'Assegna il codice a barre ad un prodotto',
'Add as new product and prefill barcode' => 'Aggiungi come nuovo prodotto ed assegna il codice a barre',
'Are you sure to delete quantity unit "#1"?' => 'Sei sicuro di voler eliminare l\'unità di misura "#1"?',
'Are you sure to delete product "#1"?' => 'Sei sicuro di voler eliminare il prodotto "#1"?',
'Are you sure to delete location "#1"?' => 'Sei sicuro di voler eliminare la posizione "#1"?',
'Manage API keys' => 'Gestisci le chiavi API',
'REST API & data model documentation' => 'REST API & Documentazione del modello di dati',
'API keys' => 'Chiavi API',
'Create new API key' => 'Crea nuova chiave API',
'API key' => 'Chiave API',
'Expires' => 'Scade il',
'Created' => 'Creata il',
'This product is not in stock' => 'Questo prodotto non è in dispensa',
'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa',
'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa',
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente',
'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa',
'About grocy' => 'Riguardo grocy',
'Close' => 'Chiudi',
'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni',
'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate',
'#1 chores are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
'#1 chores are overdue to be done' => '#1 abitudini da eseguire',
'Released on' => 'Rilasciato il',
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3',
'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3',
'Tracked execution of chore #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2',
'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2',
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
'All' => 'Tutto',
'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1',
'Track execution of chore #1' => 'Registra l\'esecuzione dell\'abitudine #1',
'Filter by location' => 'Filtra per posizione',
'Search' => 'Cerca',
'Not logged in' => 'Non autenticato',
'You have to select a product' => 'Devi selezionare un prodotto',
'You have to select a chore' => 'Devi selezionare un\'abitudine',
'You have to select a battery' => 'Devi selezionare una batteria',
'A name is required' => 'Inserisci un nome',
'A location is required' => 'Inserisci la posizione',
'The amount cannot be lower than #1' => 'La quantità non può essere minore di #1',
'This cannot be negative' => 'Il numero non può essere negativo',
'A quantity unit is required' => 'Inserisci un\'unità di misura',
'A period type is required' => 'Seleziona un tipo di ripetizione',
//Constants
'manually' => 'Manualmente',
'dynamic-regular' => 'Regolatore dinamico',
//Technical component translations
'timeago_locale' => 'it',
'timeago_nan' => 'NaN anni fa',
'moment_locale' => 'it',
'datatables_localization' => '{"sEmptyTable":"Nessun dato disponibile","sInfo":"Mostrando da _START_ a _END_ di _TOTAL_ voci","sInfoEmpty":"Mostrando da 0 a 0 di 0 voci","sInfoFiltered":"(Filtrato da _MAX_ voci totali)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Mostra _MENU_ voci","sLoadingRecords":"Caricando...","sProcessing":"Calcolando...","sSearch":"Cerca:","sZeroRecords":"Nessun risultato trovato","oPaginate":{"sFirst":"Prima","sLast":"Ultima","sNext":"Prossima","sPrevious":"Precedente"},"oAria":{"sSortAscending":": ordine crescente","sSortDescending":": ordine decrescente"}}',
//Demo data
'Cookies' => 'Biscotti',
'Chocolate' => 'Cioccolato',
'Pantry' => 'Vorratskammer',
'Candy cupboard' => 'Süßigkeitenschrank',
'Tinned food cupboard' => 'Konservenschrank',
'Fridge' => 'Kühlschrank',
'Piece' => 'Pezzo',
'Pieces' => 'Pezzi',
'Pack' => 'Pacco',
'Packs' => 'Pacchi',
'Glass' => 'Bicchiere',
'Glasses' => 'Bicchieri',
'Tin' => 'Scatola',
'Tins' => 'Scatole',
'Can' => 'Lattina',
'Cans' => 'Lattine',
'Bunch' => 'Cespo',
'Bunches' => 'Cespi',
'Gummy bears' => 'Caramelle',
'Crisps' => 'Patatine',
'Eggs' => 'Uova',
'Noodles' => 'Spaghetti',
'Pickles' => 'Marmellata',
'Gulash soup' => 'Dado',
'Yogurt' => 'Yogurt',
'Cheese' => 'Parmigiano',
'Cold cuts' => 'Pancetta',
'Paprika' => 'Pepe',
'Cucumber' => 'Zucchine',
'Radish' => 'Radicchio',
'Tomato' => 'Pomodori',
'Changed towels in the bathroom' => 'Cambiare asciugamani in bagno',
'Cleaned the kitchen floor' => 'Pulire la cucina',
'Warranty ends' => 'Scadenza dalla garanzia',
'TV remote control' => 'Telecomando',
'Alarm clock' => 'Sveglia',
'Heat remote control' => 'Termostato'
);

286
localization/no.php Normal file
View File

@@ -0,0 +1,286 @@
<?php
return array(
'Stock overview' => 'Husholdning',
'#1 products expiring within the next #2 days' => '#1 Produkt som går ut på dato innen de neste #2 dagene',
'#1 products are already expired' => '#1 Produkt som har gått ut på dato',
'#1 products are below defined min. stock amount' => '#1 Produkt under minimum husholdningsnivå',
'Product' => 'Produkt',
'Amount' => 'Antall',
'Next best before date' => 'Kommende best før dato',
'Logout' => 'Logg ut',
'Chores overview' => 'Oversikt Husoppgaver',
'Batteries overview' => 'Oversikt Batteri',
'Purchase' => 'Innkjøp',
'Consume' => 'Forbrukt',
'Inventory' => 'Endre Husholdning',
'Shopping list' => 'Handleliste',
'Chore tracking' => 'Logge Husoppgaver',
'Battery tracking' => 'Batteri Ladesyklus',
'Products' => 'Produkter',
'Locations' => 'Lokasjoner',
'Quantity units' => 'Forpakning',
'Chores' => 'Husoppgaver',
'Batteries' => 'Batterier',
'Chore' => 'Husoppgave',
'Next estimated tracking' => 'Neste handling',
'Last tracked' => 'Sist logget',
'Battery' => 'Batteri',
'Last charged' => 'Sist ladet',
'Next planned charge cycle' => 'Neste planlagte ladesyklus',
'Best before' => 'Best før',
'OK' => 'OK',
'Product overview' => 'Produkt oversikt',
'Stock quantity unit' => 'Forpakningstype i husholdningen',
'Stock amount' => 'Husholdning',
'Last purchased' => 'Sist kjøpt',
'Last used' => 'Sist brukt',
'Spoiled' => 'Produkt har gått ut på dato',
'Barcode lookup is disabled' => 'Strekkodesøk deaktivert',
'will be added to the list of barcodes for the selected product on submit' => 'Blir lagt til liste over strekkoder når produkt blir lagt inn.',
'New amount' => 'Nytt antall',
'Note' => 'Info',
'Tracked time' => 'Tid utført/ ladet',
'Chore overview' => 'Oversikt Husoppgave',
'Tracked count' => 'Antall utførelser/ ladninger',
'Battery overview' => 'Batteri Oversikt',
'Charge cycles count' => 'Antall ladesykluser',
'Create shopping list item' => 'Opprett handelisteoppføring',
'Edit shopping list item' => 'Endre på handlelistoppføring',
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 enheter ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
'Save' => 'Lagre',
'Add' => 'Legg til',
'Name' => 'Navn',
'Location' => 'Lokasjon',
'Min. stock amount' => 'Minimums antall for husholdingen',
'QU purchase' => 'FPK innkjøp',
'QU stock' => 'FPK husholdning',
'QU factor' => 'FPK faktor',
'Description' => 'Beskrivelse',
'Create product' => 'Opprett produkt',
'Barcode(s)' => 'Strekkode(r)',
'Minimum stock amount' => 'Minimums antall for husholdningen',
'Default best before days' => 'Standard antall dager best før',
'Quantity unit purchase' => 'Forpakning kjøpt',
'Quantity unit stock' => 'Forpakning husholdning',
'Factor purchase to stock quantity unit' => 'Innkjøpsfaktor for forpakning',
'Create location' => 'Opprett lokasjon',
'Create quantity unit' => 'Opprett forpakning',
'Period type' => 'Gjentakelse',
'Period days' => 'Antall dager for gjentakelse',
'Create chore' => 'Opprett husoppgave',
'Used in' => 'Brukt',
'Create battery' => 'Opprett batteri',
'Edit battery' => 'Endre batteri',
'Edit chore' => 'Endre husoppgave',
'Edit quantity unit' => 'Endre forpakning',
'Edit product' => 'Endre produkt',
'Edit location' => 'Endre lokasjon',
'Record data' => 'Logg handlinger',
'Manage master data' => 'Administrer masterdata',
'This will apply to added products' => 'Dette vil gjelde for produkt som blir lagt til',
'never' => 'aldri',
'Add products that are below defined min. stock amount' => 'Legg til produkt som er under minimumsnivå for husholdningen',
'For purchases this amount of days will be added to today for the best before date suggestion' => 'For innkjøp vil dette antallet dager legges til bestfør forslaget',
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Dette betyr at 1 #1 innkjøp vil bli omgjort til #2 #3 husholdning',
'Login' => 'Logg inn',
'Username' => 'Brukernavn',
'Password' => 'Passord',
'Invalid credentials, please try again' => 'Feil brukernavn og/eller passord, prøv igjen',
'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette Batteri "#1"?',
'Yes' => 'Ja',
'No' => 'Nei',
'Are you sure to delete chore "#1"?' => 'Er du sikker på du ønsker å slette husoppgave "#1"?',
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?',
'Create or assign product' => 'Opprett eller tildel til produkt',
'Cancel' => 'Avbryt',
'Add as new product' => 'Legg til som nytt produkt',
'Add as barcode to existing product' => 'Legg til strekkode til allerede eksisterende produkt',
'Add as new product and prefill barcode' => 'Legg til som nytt produkt med forhåndsutfylt strekkode',
'Are you sure to delete quantity unit "#1"?' => 'Er du sikker du ønsker å slette forpakning "#1"?',
'Are you sure to delete product "#1"?' => 'Er du sikker du ønsker å slette produkt "#1"?',
'Are you sure to delete location "#1"?' => 'Er du sikker du ønsker å slette lokasjon "#1"?',
'Manage API keys' => 'Administrer API-Keys',
'REST API & data model documentation' => 'REST-API & Datamodell Dokumentasjon',
'API keys' => 'API-Keys',
'Create new API key' => 'Opprett ny API-Key',
'API key' => 'API-Key',
'Expires' => 'Går ut',
'Created' => 'Opprettet',
'This product is not in stock' => 'Dette produktet er ikke i husholdningen',
'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen',
'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen',
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husoppgaven er logget #1 dag etter den sist var logget',
'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen',
'About grocy' => 'Om Grocy',
'Close' => 'Lukk',
'#1 batteries are due to be charged within the next #2 days' => '#1 Batteri må lades innen de #2 neste dagene',
'#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp',
'#1 chores are due to be done within the next #2 days' => '#1 husoppgaver skal gjøres inne de #2 neste dagene',
'#1 chores are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse',
'Released on' => 'Utgitt',
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen',
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
'Tracked execution of chore #1 on #2' => 'Utførte husoppgave "#1" den #2',
'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2',
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
'All' => 'Alle',
'Track charge cycle of battery #1' => '#1 ladet',
'Track execution of chore #1' => 'Utfør husoppgave #1',
'Filter by location' => 'Filtrér etter lokasjon',
'Search' => 'Søk',
'Not logged in' => 'Ikke logget inn',
'You have to select a product' => 'Du må velge et produkt',
'You have to select a chore' => 'Du må velge en husoppgaven',
'You have to select a battery' => 'Du må velge et batteri',
'A name is required' => 'Vennligst fyll inn et navn',
'A location is required' => 'En lokasjon kreves',
'The amount cannot be lower than #1' => 'Antallet kan ikke være lavere enn #1',
'This cannot be negative' => 'Dette kan ikke være negativt',
'A quantity unit is required' => 'Forpakning antall/størrelse kreves',
'A period type is required' => 'En periodetype kreves',
'A best before date is required and must be later than today' => 'En best før dato kreves, denne må være senere enn i dag',
'Settings' => 'Innstillinger',
'This can only be before now' => 'Dette kan kun være før nå',
'Calendar' => 'Kalender',
'Recipes' => 'Oppskrifter',
'Edit recipe' => 'Endre oppskrift',
'New recipe' => 'Ny oppskrift',
'Ingredients list' => 'Liste over ingredienser',
'Add recipe ingredient' => 'Legg ingrediens til oppskrift',
'Edit recipe ingredient' => 'Endre ingrediens i oppskrift',
'Are you sure to delete recipe "#1"?' => 'Er du sikker du ønsker å slette oppskrift "#1"?',
'Are you sure to delete recipe ingredient "#1"?' => 'Er du sikker du ønsker å slette ingrediens "#1" fra oppskriften?',
'Are you sure to empty the shopping list?' => 'Er du sikker du ønsker å slette handlelisten?',
'Clear list' => 'Tøm liste',
'Requirements fulfilled' => 'Har jeg alt jeg trenger for denne oppskriften?',
'Put missing products on shopping list' => 'Legg manglende produkter til handlelisten',
'Not enough in stock, #1 ingredients missing' => 'Ikke nok i husholdningen, #1 ingredienser mangler',
'Enough in stock' => 'Nok i husholdningen',
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Ikke nok i husholdningen, #1 ingrediens mangler, men denne er på handelisten',
'Expand to fullscreen' => 'Full skjerm',
'Ingredients' => 'Ingredienser',
'Preparation' => 'Forberedelse / Slik gjør du',
'Recipe' => 'Oppskrift',
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Ikke nok i husholdningen, mangler #1, er #2 på handlelisten',
'Show notes' => 'Vis notater',
'Put missing amount on shopping list' => 'Legg manglende til handlelisten',
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Er du sikker du ønsker å legge alle manglende ingredienser til oppskrift "#1"?',
'Added for recipe #1' => 'Lagt til fra oppskrift "#1"',
'Manage users' => 'Administrer brukere',
'User' => 'Bruker',
'Users' => 'Brukere',
'Are you sure to delete user "#1"?' => 'Er du sikker på du ønsker å slette bruker, "#1"?',
'Create user' => 'Legg til bruker',
'Edit user' => 'Endre på bruker',
'First name' => 'Fornavn',
'Last name' => 'Etternavn',
'A username is required' => 'Et brukernavn er nødvendig',
'Confirm password' => 'Bekreft passord',
'Passwords do not match' => 'Passord er ikke like',
'Change password' => 'Endre passord',
'Done by' => 'Utført av',
'Last done by' => 'Sist utført av',
'Unknown' => 'Ukjent',
'Filter by chore' => 'Filtrér husoppave',
'Chores analysis' => 'Statistikk husoppgaver',
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)',
'Last price' => 'Siste pris',
'Price history' => 'Prishistorikk',
'No price history available' => 'Ingen prishistorikk tilgjengelig',
'Price' => 'Pris',
'in #1 per purchase quantity unit' => 'I #1 per kjøpt forpakning ',
'The price cannot be lower than #1' => 'Prisen kan ikke være lavere enn #1',
'#1 product expires within the next #2 days' => '#1 Produkt går ut på dato innen de #2 neste dagene',
'#1 product is already expired' => '#1 Produkt er allerede gått ut på dato',
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
'Unit' => 'Enhet',
'Units' => 'Enheter',
'#1 chore is due to be done within the next #2 days' => '#1 husoppgave skal gjøres inne de #2 neste dagene',
'#1 chore is overdue to be done' => '#1 husoppgave har gått over fristen for utførelse',
'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager',
'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades',
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
'in singular form' => 'I entall',
'in plural form' => 'I flertall',
'Never expires' => 'Går ikke ut på dato',
'This cannot be lower than #1' => 'Dette kan ikke være lavere enn #1',
'-1 means that this product never expires' => '-1 Betyr at dette produktet aldri går ut på dato',
'Quantity unit' => 'Forpakning',
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Huk av hvis du ønsker å bruke mindre enn forpakningsstørrelse i husholdningen',
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Er du sikker du ønsker å forbruke alle ingredienser for "#1" oppskriften? (Ingredienser merket med "bruke mindre enn forpakningsstørrelse i husholdningen" blir ignorert',
'Removed all ingredients of recipe "#1" from stock' => 'Fjern alle ingredienser for "#1" oppskriften fra husholdningen.',
'Consume all ingredients needed by this recipe' => 'Konsumer alle ingredienser for denne oppskriften',
//Constants
'manually' => 'Manuel',
'dynamic-regular' => 'Automatisk',
//Technical component translations
'timeago_locale' => 'no',
'timeago_nan' => 'for NaN År',
'moment_locale' => 'nb',
'datatables_localization' => '{"sEmptyTable":"Det finnes ingen data i tabellen","sInfo":"_START_ fra _END_ til _TOTAL_ skriv","sInfoEmpty":"Ingen data tilgjengelign","sInfoFiltered":"(filtrert fra _MAX_ skriv)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ registrer deg","sLoadingRecords":"Laster ..","sProcessing":"Vennligst vent ..","sSearch":"Søk","sZeroRecords":"Ingen oppføringer tilgjengelig","oPaginate":{"sFirst":"Første","sPrevious":"Bakover","sNext":"Neste","sLast":"Siste"},"oAria":{"sSortAscending":": Sortér stigende","sSortDescending":": Sortér synkende"},"select":{"rows":{"0":"klikk på en linje for å velge","1":"1 linje valgt","_":"%d linger valgt"}},"buttons":{"print":"Print","colvis":"Søyle","copy":"Kopi","copyTitle":"Kopier til utklippstavlen","copyKeys":"Trykk <i>ctrl</i> eller <i>⌘</i> + <i>C</i> for å kopiere tabell<br> til utklipptavlen.<br><br>For å avbryte, klikke på meldingen eller trykk på ESC.","copySuccess":{"1":"1 Kolonne kopiert","_":"%d kolonne kopiert"}}}',
//Demo data
'Cookies' => 'Cookies',
'Chocolate' => 'Sjokolade',
'Pantry' => 'Spiskammers',
'Candy cupboard' => 'Godteriskapet',
'Tinned food cupboard' => 'Boksematskapet',
'Fridge' => 'Kjøleskapet',
'Piece' => 'Ett',
'Pieces' => 'Flere',
'Pack' => 'Pakke',
'Packs' => 'Pakker',
'Glass' => 'Glass',
'Glasses' => 'Glass',
'Tin' => 'Hermetikkboks',
'Tins' => 'Hermetikkbokser',
'Can' => 'Boks',
'Cans' => 'Bokser',
'Bunch' => 'Klase',
'Bunches' => 'Klaser',
'Gummy bears' => 'Vingummibjørner',
'Crisps' => 'Chips',
'Eggs' => 'Egg',
'Noodles' => 'Nuddler',
'Pickles' => 'Sur agurk',
'Gulash soup' => 'Gulasj suppe',
'Yogurt' => 'Yoghurt',
'Cheese' => 'Ost',
'Cold cuts' => 'Kjøttpålegg',
'Paprika' => 'Paprika',
'Cucumber' => 'Agurk',
'Radish' => 'Reddik',
'Tomato' => 'Tomat',
'Changed towels in the bathroom' => 'Bytt handklær på badet',
'Cleaned the kitchen floor' => 'Vasket kjøkkengulvet',
'Warranty ends' => 'Garanti utgår',
'TV remote control' => 'Fjernkontroll for TV',
'Alarm clock' => 'Alarmklokke',
'Heat remote control' => 'Fjernkontroll for termostat',
'Lawn mowed in the garden' => 'Kuttet gresset i hagen',
'Some good snacks' => 'Noen gode snacks',
'Pizza dough' => 'Pizzadeig',
'Sieved tomatoes' => 'Tomatpuré',
'Salami' => 'Salami',
'Toast' => 'Ristet brød',
'Minced meat' => 'Kjøttdeig',
'Pizza' => 'Pizza',
'Spaghetti bolognese' => 'Spaghetti Bolognese',
'Sandwiches' => 'Smørbrød',
'English' => 'Engelsk',
'German' => 'Tysk',
'Italian' => 'Italiensk',
'Demo in different language' => 'Demo i annet språk',
'This is the note content of the recipe ingredient' => 'Dette er notisen for ingrediensen i oppskriften',
'Demo User' => 'Demo Bruker',
'Gram' => 'Gram',
'Grams' => 'Gram',
'Flour' => 'Mel',
'Pancakes' => 'Pannekaker',
'Sugar' => 'Sukker'
);

View File

@@ -0,0 +1,72 @@
<?php
namespace Grocy\Middleware;
use \Grocy\Services\SessionService;
use \Grocy\Services\ApiKeyService;
class ApiKeyAuthMiddleware extends BaseMiddleware
{
public function __construct(\Slim\Container $container, string $sessionCookieName, string $apiKeyHeaderName)
{
parent::__construct($container);
$this->SessionCookieName = $sessionCookieName;
$this->ApiKeyHeaderName = $apiKeyHeaderName;
}
protected $SessionCookieName;
protected $ApiKeyHeaderName;
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{
$route = $request->getAttribute('route');
$routeName = $route->getName();
if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
{
define('GROCY_AUTHENTICATED', true);
$response = $next($request, $response);
}
else
{
$validSession = true;
$validApiKey = true;
$sessionService = new SessionService();
if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName]))
{
$validSession = false;
}
$apiKeyService = new ApiKeyService();
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName)))
{
$validApiKey = false;
}
if (!$validSession && !$validApiKey)
{
define('GROCY_AUTHENTICATED', false);
$response = $response->withStatus(401);
}
elseif ($validApiKey)
{
$user = $apiKeyService->GetUserByApiKey($request->getHeaderLine($this->ApiKeyHeaderName));
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
$response = $next($request, $response);
}
elseif ($validSession)
{
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
$response = $next($request, $response);
}
}
return $response;
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Grocy\Middleware;
use \Grocy\Services\ApplicationService;
class BaseMiddleware
{
public function __construct(\Slim\Container $container)
{
$this->AppContainer = $container;
$this->ApplicationService = new ApplicationService();
}
protected $AppContainer;
protected $ApplicationService;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Grocy\Middleware;
class JsonMiddleware extends BaseMiddleware
{
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{
$response = $next($request, $response);
return $response->withHeader('Content-Type', 'application/json');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Grocy\Middleware;
use \Grocy\Services\SessionService;
use \Grocy\Services\LocalizationService;
class SessionAuthMiddleware extends BaseMiddleware
{
public function __construct(\Slim\Container $container, string $sessionCookieName)
{
parent::__construct($container);
$this->SessionCookieName = $sessionCookieName;
}
protected $SessionCookieName;
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{
$route = $request->getAttribute('route');
$routeName = $route->getName();
$sessionService = new SessionService();
if ($routeName === 'root')
{
$response = $next($request, $response);
}
elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
{
$user = $sessionService->GetDefaultUser();
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_USERNAME', $user->username);
$response = $next($request, $response);
}
else
{
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
{
define('GROCY_AUTHENTICATED', false);
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
}
else
{
if ($routeName !== 'login')
{
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_USERNAME', $user->username);
define('GROCY_USER_ID', $user->id);
}
else
{
define('GROCY_AUTHENTICATED', false);
}
$response = $next($request, $response);
}
}
return $response;
}
}

13
migrations/0001.sql Normal file
View File

@@ -0,0 +1,13 @@
CREATE TABLE products (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
location_id INTEGER NOT NULL,
qu_id_purchase INTEGER NOT NULL,
qu_id_stock INTEGER NOT NULL,
qu_factor_purchase_to_stock REAL NOT NULL,
barcode TEXT,
min_stock_amount INTEGER NOT NULL DEFAULT 0,
default_best_before_days INTEGER NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

6
migrations/0002.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE locations (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

6
migrations/0003.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE quantity_units (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

9
migrations/0004.sql Normal file
View File

@@ -0,0 +1,9 @@
CREATE TABLE stock (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL,
best_before_date DATE,
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
stock_id TEXT NOT NULL,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

12
migrations/0005.sql Normal file
View File

@@ -0,0 +1,12 @@
CREATE TABLE stock_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL,
best_before_date DATE,
purchased_date DATE,
used_date DATE,
spoiled INTEGER NOT NULL DEFAULT 0,
stock_id TEXT NOT NULL,
transaction_type TEXT NOT NULL,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

19
migrations/0006.sql Normal file
View File

@@ -0,0 +1,19 @@
INSERT INTO locations
(name, description)
VALUES
('DefaultLocation', 'This is the first default location, edit or delete it');
INSERT INTO quantity_units
(name, description)
VALUES
('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
INSERT INTO products
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
VALUES
('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
INSERT INTO products
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
VALUES
('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);

9
migrations/0007.sql Normal file
View File

@@ -0,0 +1,9 @@
CREATE VIEW stock_missing_products
AS
SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing
FROM products p
LEFT JOIN stock s
ON p.id = s.product_id
WHERE p.min_stock_amount != 0
GROUP BY p.id
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount

5
migrations/0008.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE VIEW stock_current
AS
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
FROM stock
GROUP BY product_id

7
migrations/0009.sql Normal file
View File

@@ -0,0 +1,7 @@
CREATE TABLE shopping_list (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL UNIQUE,
amount INTEGER NOT NULL DEFAULT 0,
amount_autoadded INTEGER NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

8
migrations/0010.sql Normal file
View File

@@ -0,0 +1,8 @@
CREATE TABLE habits (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
period_type TEXT NOT NULL,
period_days INTEGER,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

6
migrations/0011.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE habits_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
habit_id INTEGER NOT NULL,
tracked_time DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

5
migrations/0012.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE VIEW habits_current
AS
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
FROM habits_log
GROUP BY habit_id

8
migrations/0013.sql Normal file
View File

@@ -0,0 +1,8 @@
CREATE TABLE batteries (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
used_in TEXT,
charge_interval_days INTEGER NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

6
migrations/0014.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE battery_charge_cycles (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
battery_id TEXT NOT NULL,
tracked_time DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

5
migrations/0015.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE VIEW batteries_current
AS
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
FROM battery_charge_cycles
GROUP BY battery_id

1
migrations/0016.sql Normal file
View File

@@ -0,0 +1 @@
ALTER TABLE shopping_list RENAME TO shopping_list_old

8
migrations/0017.sql Normal file
View File

@@ -0,0 +1,8 @@
CREATE TABLE shopping_list (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER,
note TEXT,
amount INTEGER NOT NULL DEFAULT 0,
amount_autoadded INTEGER NOT NULL DEFAULT 0,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

4
migrations/0018.sql Normal file
View File

@@ -0,0 +1,4 @@
INSERT INTO shopping_list
(product_id, amount, amount_autoadded, row_created_timestamp)
SELECT product_id, amount, amount_autoadded, row_created_timestamp
FROM shopping_list_old

1
migrations/0019.sql Normal file
View File

@@ -0,0 +1 @@
DROP TABLE shopping_list_old

6
migrations/0020.sql Normal file
View File

@@ -0,0 +1,6 @@
CREATE TABLE sessions (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
session_key TEXT NOT NULL UNIQUE,
expires DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

11
migrations/0021.sql Normal file
View File

@@ -0,0 +1,11 @@
DELETE FROM locations
WHERE name = 'DefaultLocation';
DELETE FROM quantity_units
WHERE name = 'DefaultQuantityUnit';
DELETE FROM products
WHERE name = 'DefaultProduct1';
DELETE FROM products
WHERE name = 'DefaultProduct2';

7
migrations/0022.sql Normal file
View File

@@ -0,0 +1,7 @@
CREATE TABLE api_keys (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
api_key TEXT NOT NULL UNIQUE,
expires DATETIME,
last_used DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

1
migrations/0023.sql Normal file
View File

@@ -0,0 +1 @@
DELETE FROM sessions

2
migrations/0024.sql Normal file
View File

@@ -0,0 +1,2 @@
ALTER TABLE sessions
ADD COLUMN last_used DATETIME

50
migrations/0025.sql Normal file
View File

@@ -0,0 +1,50 @@
CREATE TABLE recipes (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE TABLE recipes_pos (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
recipe_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
amount INTEGER NOT NULL DEFAULT 0,
note TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE VIEW recipes_fulfillment
AS
SELECT
r.id AS recipe_id,
rp.id AS recipe_pos_id,
rp.product_id AS product_id,
rp.amount AS recipe_amount,
IFNULL(sc.amount, 0) AS stock_amount,
CASE WHEN IFNULL(sc.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled,
CASE WHEN IFNULL(sc.amount, 0) - IFNULL(rp.amount, 0) < 0 THEN ABS(IFNULL(sc.amount, 0) - IFNULL(rp.amount, 0)) ELSE 0 END AS missing_amount,
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list
FROM recipes r
JOIN recipes_pos rp
ON r.id = rp.recipe_id
LEFT JOIN (
SELECT product_id, SUM(amount + amount_autoadded) AS amount
FROM shopping_list
GROUP BY product_id) sl
ON rp.product_id = sl.product_id
LEFT JOIN stock_current sc
ON rp.product_id = sc.product_id;
CREATE VIEW recipes_fulfillment_sum
AS
SELECT
r.id AS recipe_id,
IFNULL(MIN(rf.need_fulfilled), 1) AS need_fulfilled,
IFNULL(MIN(rf.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
(SELECT COUNT(*) FROM recipes_fulfillment WHERE recipe_id = rf.recipe_id AND need_fulfilled = 0 AND recipe_pos_id IS NOT NULL) AS missing_products_count
FROM recipes r
LEFT JOIN recipes_fulfillment rf
ON rf.recipe_id = r.id
GROUP BY r.id;

20
migrations/0026.sql Normal file
View File

@@ -0,0 +1,20 @@
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
username TEXT NOT NULL UNIQUE,
first_name TEXT,
last_name TEXT,
password TEXT NOT NULL,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
DROP TABLE sessions;
CREATE TABLE sessions (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
session_key TEXT NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
expires DATETIME,
last_used DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)

24
migrations/0027.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
// This is executed inside DatabaseMigrationService class/context
$db = $this->DatabaseService->GetDbConnection();
if (defined('GROCY_HTTP_USER'))
{
// Migrate old user defined in config file to database
$newUserRow = $db->users()->createRow(array(
'username' => GROCY_HTTP_USER,
'password' => password_hash(GROCY_HTTP_PASSWORD, PASSWORD_DEFAULT)
));
$newUserRow->save();
}
else
{
// Create default user "admin" with password "admin"
$newUserRow = $db->users()->createRow(array(
'username' => 'admin',
'password' => password_hash('admin', PASSWORD_DEFAULT)
));
$newUserRow->save();
}

13
migrations/0028.sql Normal file
View File

@@ -0,0 +1,13 @@
ALTER TABLE habits_log
ADD done_by_user_id INTEGER;
DROP TABLE api_keys;
CREATE TABLE api_keys (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
api_key TEXT NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
expires DATETIME,
last_used DATETIME,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);

5
migrations/0029.sql Normal file
View File

@@ -0,0 +1,5 @@
ALTER TABLE stock
ADD price DECIMAL(15, 2);
ALTER TABLE stock_log
ADD price DECIMAL(15, 2);

2
migrations/0030.sql Normal file
View File

@@ -0,0 +1,2 @@
ALTER TABLE quantity_units
ADD name_plural TEXT;

32
migrations/0031.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
// This is executed inside DatabaseMigrationService class/context
use \Grocy\Services\LocalizationService;
$localizationService = new LocalizationService(GROCY_CULTURE);
$db = $this->DatabaseService->GetDbConnection();
if ($db->quantity_units()->count() === 0)
{
// Create 2 default quantity units
$newRow = $db->quantity_units()->createRow(array(
'name' => $localizationService->Localize('Piece'),
'name_plural' => $localizationService->Localize('Pieces')
));
$newRow->save();
$newRow = $db->quantity_units()->createRow(array(
'name' => $localizationService->Localize('Pack'),
'name_plural' => $localizationService->Localize('Packs')
));
$newRow->save();
}
if ($db->locations()->count() === 0)
{
// Create a default location
$newRow = $db->locations()->createRow(array(
'name' => $localizationService->Localize('Fridge')
));
$newRow->save();
}

20
migrations/0032.sql Normal file
View File

@@ -0,0 +1,20 @@
DROP VIEW stock_current;
CREATE VIEW stock_current
AS
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
FROM stock
GROUP BY product_id;
DROP VIEW habits_current;
CREATE VIEW habits_current
AS
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
FROM habits_log
GROUP BY habit_id;
DROP VIEW batteries_current;
CREATE VIEW batteries_current
AS
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
FROM battery_charge_cycles
GROUP BY battery_id;

29
migrations/0033.sql Normal file
View File

@@ -0,0 +1,29 @@
DROP VIEW habits_current;
CREATE VIEW habits_current
AS
SELECT
h.id AS habit_id,
MAX(l.tracked_time) AS last_tracked_time,
CASE h.period_type
WHEN 'manually' THEN '2999-12-31 23:59:59'
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
END AS next_estimated_execution_time
FROM habits h
LEFT JOIN habits_log l
ON h.id = l.habit_id
GROUP BY h.id, h.period_days;
DROP VIEW batteries_current;
CREATE VIEW batteries_current
AS
SELECT
b.id AS battery_id,
MAX(l.tracked_time) AS last_tracked_time,
CASE WHEN b.charge_interval_days = 0
THEN '2999-12-31 23:59:59'
ELSE datetime(MAX(l.tracked_time), '+' || CAST(b.charge_interval_days AS TEXT) || ' day')
END AS next_estimated_charge_time
FROM batteries b
LEFT JOIN battery_charge_cycles l
ON b.id = l.battery_id
GROUP BY b.id, b.charge_interval_days;

41
migrations/0034.sql Normal file
View File

@@ -0,0 +1,41 @@
ALTER TABLE recipes_pos
ADD qu_id INTEGER;
UPDATE recipes_pos
SET qu_id = (SELECT qu_id_stock FROM products where id = product_id);
CREATE TRIGGER recipes_pos_qu_id_default AFTER INSERT ON recipes_pos
BEGIN
UPDATE recipes_pos
SET qu_id = (SELECT qu_id_stock FROM products where id = product_id)
WHERE qu_id IS NULL
AND id = NEW.id;
END;
ALTER TABLE recipes_pos
ADD only_check_single_unit_in_stock TINYINT NOT NULL DEFAULT 0;
DROP VIEW recipes_fulfillment;
CREATE VIEW recipes_fulfillment
AS
SELECT
r.id AS recipe_id,
rp.id AS recipe_pos_id,
rp.product_id AS product_id,
rp.amount AS recipe_amount,
IFNULL(sc.amount, 0) AS stock_amount,
CASE WHEN IFNULL(sc.amount, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END THEN 1 ELSE 0 END AS need_fulfilled,
CASE WHEN IFNULL(sc.amount, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END < 0 THEN ABS(IFNULL(sc.amount, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END) ELSE 0 END AS missing_amount,
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
rp.qu_id
FROM recipes r
JOIN recipes_pos rp
ON r.id = rp.recipe_id
LEFT JOIN (
SELECT product_id, SUM(amount + amount_autoadded) AS amount
FROM shopping_list
GROUP BY product_id) sl
ON rp.product_id = sl.product_id
LEFT JOIN stock_current sc
ON rp.product_id = sc.product_id;

31
migrations/0035.sql Normal file
View File

@@ -0,0 +1,31 @@
ALTER TABLE habits RENAME TO chores;
CREATE TABLE chores_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
chore_id INTEGER NOT NULL,
tracked_time DATETIME,
done_by_user_id INTEGER,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
INSERT INTO chores_log
(chore_id, tracked_time, done_by_user_id, row_created_timestamp)
SELECT habit_id, tracked_time, done_by_user_id, row_created_timestamp
FROM habits_log;
DROP TABLE habits_log;
DROP VIEW habits_current;
CREATE VIEW chores_current
AS
SELECT
h.id AS chore_id,
MAX(l.tracked_time) AS last_tracked_time,
CASE h.period_type
WHEN 'manually' THEN '2999-12-31 23:59:59'
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
END AS next_estimated_execution_time
FROM chores h
LEFT JOIN chores_log l
ON h.id = l.chore_id
GROUP BY h.id, h.period_days;

24
migrations/0036.sql Normal file
View File

@@ -0,0 +1,24 @@
CREATE TABLE tasks (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
due_date DATETIME,
done TINYINT NOT NULL DEFAULT 0 CHECK(done IN (0, 1)),
done_timestamp DATETIME,
category_id INTEGER,
assigned_to_user_id INTEGER,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE TABLE task_categories (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);
CREATE VIEW tasks_current
AS
SELECT *
FROM tasks
WHERE done = 0;

9
migrations/0037.sql Normal file
View File

@@ -0,0 +1,9 @@
ALTER TABLE products
ADD product_group_id INTEGER;
CREATE TABLE product_groups (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE,
description TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
);

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "grocy",
"private": true,
"dependencies": {
"@danielfarrell/bootstrap-combobox": "https://github.com/pallidus-fintech/bootstrap-combobox.git#enhance/boostrap_4",
"@fortawesome/fontawesome-free": "^5.1.0",
"TagManager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
"bootbox": "https://github.com/makeusabrew/bootbox.git#v5.x",
"bootstrap": "^4.1.1",
"chart.js": "^2.7.2",
"datatables.net": "^1.10.19",
"datatables.net-bs4": "^1.10.19",
"datatables.net-colreorder": "^1.5.1",
"datatables.net-colreorder-bs4": "^1.5.1",
"datatables.net-responsive": "^2.2.3",
"datatables.net-responsive-bs4": "^2.2.3",
"datatables.net-rowgroup": "^1.0.4",
"datatables.net-rowgroup-bs4": "^1.0.4",
"datatables.net-select": "^1.2.7",
"datatables.net-select-bs4": "^1.2.7",
"jquery": "^3.3.1",
"jquery-serializejson": "^2.8.1",
"jquery-ui-dist": "^1.12.1",
"moment": "^2.22.2",
"startbootstrap-sb-admin": "^4.0.0",
"swagger-ui-dist": "^3.17.3",
"tagmanager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
"tempusdominus-bootstrap-4": "^5.0.1",
"timeago": "^1.6.3",
"toastr": "^2.1.4"
}
}

4
public/.htaccess Normal file
View File

@@ -0,0 +1,4 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

View File

@@ -0,0 +1,336 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="NotoSans" horiz-adv-x="1254" ><font-face
font-family="Noto Sans"
units-per-em="2048"
panose-1="2 11 8 2 4 5 4 2 2 4"
ascent="2189"
descent="-600"
alphabetic="0" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="825" d="M570 485H326L275 1462H621L570 485ZM271 143Q271 190 284 222T322 275T378 304T447 313Q482 313 513 304T569 275T607 223T621 143Q621 98 607 66T569 13T514 -17T447 -27Q410 -27 378 -18T322 13T285 66T271 143Z" />
<glyph unicode="&quot;" horiz-adv-x="1118" d="M502 1494L461 966H264L223 1494H502ZM924 1494L883 966H686L645 1494H924Z" />
<glyph unicode="#" horiz-adv-x="1363" d="M1019 767L972 535H1217V329H933L871 0H651L714 329H520L459 0H244L303 329H79V535H342L389 767H151V976H426L486 1295H705L645 976H843L904 1295H1119L1058 976H1285V767H1019ZM559 535H755L802 767H606L559 535Z" />
<glyph unicode="$" horiz-adv-x="1171" d="M1092 457Q1092 298 977 202T655 86V-119H518V82Q274 87 90 168V432Q177 389 299 356T518 317V627L451 653Q253 731 171 822T88 1049Q88 1194 201 1287T518 1401V1554H655V1405Q884 1395 1069 1313L975 1079Q819 1143
655 1157V862Q850 787 932 732T1053 611T1092 457ZM791 442Q791 484 757 513T655 573V324Q791 347 791 442ZM389 1049Q389 1005 419 977T518 918V1153Q389 1134 389 1049Z" />
<glyph unicode="%" horiz-adv-x="1842" d="M324 924Q324 842 349 802T429 761Q483 761 509 801T535 924Q535 1086 429 1086Q374 1086 349 1046T324 924ZM777 926Q777 839 757 770T693 653T584 580T427 554Q342 554 278 579T170 652T104 769T82 926Q82 1013 102
1082T164 1198T272 1270T427 1295Q513 1295 578 1270T688 1198T754 1082T777 926ZM1434 1274L633 0H404L1205 1274H1434ZM1307 351Q1307 269 1332 229T1412 188Q1466 188 1492 228T1518 351Q1518 513 1412 513Q1357 513 1332 473T1307 351ZM1760 353Q1760 266 1740
197T1676 81T1567 8T1410 -18Q1325 -18 1261 7T1154 80T1088 197T1066 353Q1066 440 1086 509T1148 625T1255 697T1410 722Q1496 722 1561 697T1671 625T1737 509T1760 353Z" />
<glyph unicode="&amp;" horiz-adv-x="1536" d="M1536 0H1159L1044 113Q853 -20 612 -20Q368 -20 225 92T82 395Q82 532 142 628T350 809Q275 895 241 973T207 1145Q207 1297 323 1390T635 1483Q821 1483 932 1397T1044 1165Q1044 1046 975 948T752 760L1036 483Q1107
600 1159 784H1477Q1441 649 1378 521T1235 293L1536 0ZM403 424Q403 338 467 287T633 236Q759 236 860 297L528 627Q470 583 437 535T403 424ZM762 1133Q762 1186 726 1216T633 1247Q566 1247 528 1215T489 1124Q489 1036 584 930Q670 978 716 1024T762 1133Z"
/>
<glyph unicode="&apos;" horiz-adv-x="696" d="M502 1493L461 965H264L223 1493H502Z" />
<glyph unicode="(" horiz-adv-x="791" d="M135 673Q135 816 153 955T208 1225T301 1476T436 1703H686Q545 1482 473 1218T401 676Q401 540 419 405T473 141T562 -109T684 -338H436Q358 -235 302 -118T208 128T153 394T135 673Z" />
<glyph unicode=")" horiz-adv-x="791" d="M656 692Q656 549 638 410T583 140T490 -111T355 -338H105Q246 -117 318 147T390 689Q390 825 372 960T318 1224T229 1474T107 1703H355Q433 1600 489 1483T583 1237T638 971T656 692Z" />
<glyph unicode="*" horiz-adv-x="1243" d="M761 1525L720 1157L1093 1261L1126 1009L786 985L1009 688L782 567L626 880L489 569L253 688L474 985L136 1011L175 1261L540 1157L499 1525H761Z" />
<glyph unicode="+" horiz-adv-x="1168" d="M475 552H108V771H475V1140H694V771H1060V552H694V188H475V552Z" />
<glyph unicode="," horiz-adv-x="594" d="M444 288L459 264Q445 206 426 142T383 12T334 -119T283 -244H63Q78 -179 92 -109T120 30T145 165T163 288H444Z" />
<glyph unicode="-" horiz-adv-x="749" d="M106 484V734H643V484H106Z" />
<glyph unicode="." horiz-adv-x="584" d="M117 143Q117 190 130 222T168 275T224 304T293 313Q328 313 359 304T415 275T453 223T467 143Q467 98 453 66T415 13T360 -17T293 -27Q256 -27 224 -18T168 13T131 66T117 143Z" />
<glyph unicode="/" horiz-adv-x="966" d="M894 1705L334 -339H72L632 1705H894Z" />
<glyph unicode="0" horiz-adv-x="1128" d="M1065 731Q1065 554 1038 415T950 179T794 31T563 -20Q436 -20 342 31T186 179T94 415T63 731Q63 908 90 1048T178 1285T333 1433T563 1485Q689 1485 783 1434T940 1286T1034 1049T1065 731ZM371 731Q371 481 414 355T563
229Q667 229 712 354T758 731Q758 982 713 1108T563 1235Q510 1235 474 1203T414 1108T381 951T371 731Z" />
<glyph unicode="1" horiz-adv-x="1128" d="M817 0H508V846Q508 872 508 908T510 984T513 1064T516 1137Q511 1131 499 1119T472 1093T441 1063T410 1036L242 901L92 1087L563 1462H817V0Z" />
<glyph unicode="2" horiz-adv-x="1128" d="M1063 0H82V215L426 586Q491 656 544 715T635 830T694 944T715 1069Q715 1143 671 1184T551 1225Q472 1225 399 1186T246 1075L78 1274Q123 1315 172 1352T280 1419T410 1465T569 1483Q674 1483 757 1454T900 1372T990
1242T1022 1071Q1022 985 992 907T910 753T790 603T643 451L467 274V260H1063V0Z" />
<glyph unicode="3" horiz-adv-x="1128" d="M1006 1135Q1006 1059 982 999T915 893T815 817T690 770V764Q867 742 958 657T1049 426Q1049 330 1015 249T909 107T729 14T473 -20Q355 -20 251 -1T57 59V322Q102 298 152 280T252 250T350 231T442 225Q528 225 585
241T676 286T724 355T739 444Q739 489 721 525T661 587T552 627T387 641H283V858H385Q477 858 538 874T635 919T687 986T702 1067Q702 1145 654 1189T500 1233Q452 1233 411 1224T334 1200T269 1168T215 1133L59 1339Q101 1370 150 1396T258 1441T383 1472T526
1483Q634 1483 722 1460T874 1392T971 1283T1006 1135Z" />
<glyph unicode="4" horiz-adv-x="1128" d="M1085 303H909V0H608V303H4V518L625 1462H909V543H1085V303ZM608 543V791Q608 804 608 828T610 884T612 948T615 1011T618 1063T621 1096H612Q594 1054 572 1007T520 913L276 543H608Z" />
<glyph unicode="5" horiz-adv-x="1128" d="M598 934Q692 934 773 905T914 820T1008 681T1042 489Q1042 370 1005 276T896 116T718 15T473 -20Q418 -20 364 -15T261 -1T167 24T86 59V326Q121 306 167 289T262 259T362 239T457 231Q591 231 661 286T731 463Q731
571 663 627T451 684Q425 684 396 681T338 673T283 663T238 651L115 717L170 1462H942V1200H438L414 913Q446 920 488 927T598 934Z" />
<glyph unicode="6" horiz-adv-x="1128" d="M76 621Q76 726 87 830T128 1029T208 1207T336 1349T522 1444T776 1479Q797 1479 822 1478T872 1476T922 1471T965 1464V1217Q927 1226 885 1231T799 1237Q664 1237 577 1204T439 1110T367 966T340 780H352Q372 816 400
847T467 901T552 937T659 950Q754 950 830 919T958 829T1039 684T1067 487Q1067 368 1034 274T938 115T788 15T590 -20Q482 -20 388 18T225 136T116 335T76 621ZM584 227Q625 227 658 242T716 289T754 369T768 483Q768 590 724 651T588 713Q542 713 504 695T439
648T398 583T383 510Q383 459 395 409T433 318T496 252T584 227Z" />
<glyph unicode="7" horiz-adv-x="1128" d="M207 0L727 1200H55V1460H1063V1266L530 0H207Z" />
<glyph unicode="8" horiz-adv-x="1128" d="M565 1481Q656 1481 737 1459T879 1393T976 1283T1012 1128Q1012 1062 992 1009T937 912T854 834T750 772Q808 741 863 703T962 618T1031 511T1057 379Q1057 288 1021 214T920 88T765 8T565 -20Q447 -20 355 7T200 84T105
207T72 371Q72 446 94 506T154 614T243 699T352 764Q303 795 260 831T186 912T136 1011T117 1130Q117 1217 153 1282T252 1392T395 1459T565 1481ZM358 389Q358 349 371 316T409 258T473 221T561 207Q666 207 718 256T770 387Q770 429 753 462T708 524T645 577T575
623L553 637Q509 615 473 590T412 534T372 467T358 389ZM563 1255Q530 1255 502 1245T453 1216T420 1169T408 1106Q408 1064 420 1034T454 980T504 938T565 901Q596 917 624 936T673 979T708 1035T721 1106Q721 1141 709 1169T676 1216T626 1245T563 1255Z" />
<glyph unicode="9" horiz-adv-x="1128" d="M1055 838Q1055 733 1044 629T1003 429T923 252T795 109T609 15T354 -20Q333 -20 308 -19T258 -17T208 -13T166 -6V242Q203 232 245 227T332 221Q467 221 554 254T692 348T764 493T791 678H778Q758 642 730 611T664 557T578
521T471 508Q376 508 300 539T172 629T91 774T63 971Q63 1090 96 1184T192 1343T342 1444T541 1479Q649 1479 743 1441T906 1323T1015 1123T1055 838ZM547 1231Q506 1231 472 1216T414 1170T376 1090T362 975Q362 869 407 807T543 745Q589 745 627 763T692 810T733
875T748 948Q748 999 736 1049T698 1140T635 1206T547 1231Z" />
<glyph unicode=":" horiz-adv-x="663" d="M156 143Q156 190 169 222T207 275T263 304T332 313Q367 313 398 304T454 275T492 223T506 143Q506 98 492 66T454 13T399 -17T332 -27Q295 -27 263 -18T207 13T170 66T156 143ZM156 969Q156 1016 169 1048T207 1101T263
1130T332 1139Q367 1139 398 1130T454 1101T492 1049T506 969Q506 924 492 892T454 839T399 809T332 799Q295 799 263 808T207 838T170 891T156 969Z" />
<glyph unicode=";" horiz-adv-x="663" d="M483 288L498 264Q484 206 465 142T422 12T373 -119T322 -244H102Q117 -179 131 -109T159 30T184 165T203 288H483ZM156 969Q156 1016 169 1048T207 1101T263 1130T332 1139Q367 1139 398 1130T454 1101T492 1049T506
969Q506 924 492 892T454 839T399 809T332 799Q295 799 263 808T207 838T170 891T156 969Z" />
<glyph unicode="&lt;" horiz-adv-x="1168" d="M1060 143L108 581V724L1060 1220V980L417 663L1060 382V143Z" />
<glyph unicode="=" horiz-adv-x="1168" d="M108 747V964H1060V747H108ZM108 358V577H1060V358H108Z" />
<glyph unicode="&gt;" horiz-adv-x="1168" d="M108 382L751 663L108 980V1220L1060 724V581L108 143V382Z" />
<glyph unicode="?" horiz-adv-x="1114" d="M366 485V559Q366 610 376 651T408 730T464 803T546 877Q588 910 617 936T664 987T690 1041T698 1106Q698 1163 660 1200T542 1237Q473 1237 394 1208T229 1137L127 1358Q170 1383 220 1405T325 1445T436 1473T546 1483Q648
1483 730 1459T869 1387T956 1273T987 1120Q987 1057 973 1008T932 916T863 834T766 750Q724 717 698 693T656 646T636 601T630 545V485H366ZM333 143Q333 190 346 222T384 275T440 304T510 313Q545 313 576 304T632 275T670 223T684 143Q684 98 670 66T632 13T577
-17T510 -27Q473 -27 441 -18T384 13T347 66T333 143Z" />
<glyph unicode="@" horiz-adv-x="1837" d="M1735 733Q1735 589 1689 469T1559 282T1364 215Q1290 215 1233 250T1151 344H1135Q1086 284 1016 250T860 215Q683 215 581 321T479 612Q479 821 611 951T963 1081Q1044 1081 1150 1066T1323 1026L1300 537Q1300 397
1376 397Q1440 397 1478 490T1516 735Q1516 896 1450 1019T1262 1208T983 1274Q782 1274 632 1190T403 950T324 590Q324 313 471 166T899 18Q1011 18 1139 43T1382 111V-82Q1170 -172 907 -172Q527 -172 315 28T102 584Q102 831 211 1033T522 1348T981 1462Q1201
1462 1374 1372T1641 1116T1735 733ZM711 608Q711 397 883 397Q972 397 1019 460T1077 668L1090 889Q1044 899 975 899Q850 899 781 821T711 608Z" />
<glyph unicode="A" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899Z" />
<glyph unicode="B" horiz-adv-x="1376" d="M184 1462H639Q950 1462 1090 1374T1231 1092Q1231 961 1170 877T1006 776V766Q1145 735 1206 650T1268 424Q1268 224 1124 112T731 0H184V1462ZM494 883H674Q800 883 856 922T913 1051Q913 1135 852 1171T657 1208H494V883ZM494
637V256H696Q824 256 885 305T946 455Q946 637 686 637H494Z" />
<glyph unicode="C" horiz-adv-x="1305" d="M805 1225Q630 1225 534 1094T438 727Q438 238 805 238Q959 238 1178 315V55Q998 -20 776 -20Q457 -20 288 173T119 729Q119 957 202 1128T440 1391T805 1483Q1018 1483 1233 1380L1133 1128Q1051 1167 968 1196T805 1225Z" />
<glyph unicode="D" horiz-adv-x="1516" d="M1397 745Q1397 384 1192 192T598 0H184V1462H643Q1001 1462 1199 1273T1397 745ZM1075 737Q1075 1208 659 1208H494V256H627Q1075 256 1075 737Z" />
<glyph unicode="E" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0Z" />
<glyph unicode="F" horiz-adv-x="1124" d="M489 0H184V1462H1022V1208H489V831H985V578H489V0Z" />
<glyph unicode="G" horiz-adv-x="1483" d="M739 821H1319V63Q1178 17 1054 -1T799 -20Q468 -20 294 174T119 733Q119 1087 321 1285T883 1483Q1108 1483 1317 1393L1214 1145Q1054 1225 881 1225Q680 1225 559 1090T438 727Q438 489 535 364T819 238Q916 238 1016
258V563H739V821Z" />
<glyph unicode="H" horiz-adv-x="1567" d="M1382 0H1073V631H494V0H184V1462H494V889H1073V1462H1382V0Z" />
<glyph unicode="I" horiz-adv-x="797" d="M731 0H66V176L244 258V1204L66 1286V1462H731V1286L553 1204V258L731 176V0Z" />
<glyph unicode="J" horiz-adv-x="678" d="M31 -430Q-74 -430 -152 -408V-150Q-72 -170 -6 -170Q96 -170 140 -107T184 92V1462H494V94Q494 -162 377 -296T31 -430Z" />
<glyph unicode="K" horiz-adv-x="1360" d="M1360 0H1008L625 616L494 522V0H184V1462H494V793L616 965L1012 1462H1356L846 815L1360 0Z" />
<glyph unicode="L" horiz-adv-x="1157" d="M184 0V1462H494V256H1087V0H184Z" />
<glyph unicode="M" horiz-adv-x="1931" d="M803 0L451 1147H442Q461 797 461 680V0H184V1462H606L952 344H958L1325 1462H1747V0H1458V692Q1458 741 1459 805T1473 1145H1464L1087 0H803Z" />
<glyph unicode="N" horiz-adv-x="1665" d="M1481 0H1087L451 1106H442Q461 813 461 688V0H184V1462H575L1210 367H1217Q1202 652 1202 770V1462H1481V0Z" />
<glyph unicode="O" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227 538 1103T444
733Z" />
<glyph unicode="P" horiz-adv-x="1286" d="M494 774H596Q739 774 810 830T881 995Q881 1104 822 1156T635 1208H494V774ZM1194 1006Q1194 770 1047 645T627 520H494V0H184V1462H651Q917 1462 1055 1348T1194 1006Z" />
<glyph unicode="Q" horiz-adv-x="1630" d="M1511 733Q1511 475 1420 301T1151 45L1503 -348H1106L838 -20H815Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227
817 1227Q632 1227 538 1103T444 733Z" />
<glyph unicode="R" horiz-adv-x="1352" d="M494 813H594Q741 813 811 862T881 1016Q881 1120 810 1164T588 1208H494V813ZM494 561V0H184V1462H610Q908 1462 1051 1354T1194 1024Q1194 895 1123 795T922 637L1352 0H1008L659 561H494Z" />
<glyph unicode="S" horiz-adv-x="1128" d="M1047 406Q1047 208 905 94T508 -20Q274 -20 94 68V356Q242 290 344 263T532 236Q634 236 688 275T743 391Q743 434 719 467T649 532T459 631Q325 694 258 752T151 887T111 1067Q111 1261 242 1372T606 1483Q720 1483
823 1456T1040 1380L940 1139Q823 1187 747 1206T596 1225Q508 1225 461 1184T414 1077Q414 1036 433 1006T493 947T690 844Q895 746 971 648T1047 406Z" />
<glyph unicode="T" horiz-adv-x="1186" d="M748 0H438V1204H41V1462H1145V1204H748V0Z" />
<glyph unicode="U" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374Z" />
<glyph unicode="V" horiz-adv-x="1331" d="M1018 1462H1331L834 0H496L0 1462H313L588 592Q611 515 635 413T666 270Q677 362 741 592L1018 1462Z" />
<glyph unicode="W" horiz-adv-x="1980" d="M1608 0H1255L1057 768Q1046 809 1020 937T989 1110Q983 1056 959 937T922 766L725 0H373L0 1462H305L492 664Q541 443 563 281Q569 338 590 457T631 643L844 1462H1137L1350 643Q1364 588 1385 475T1417 281Q1427 359
1449 475T1489 664L1675 1462H1980L1608 0Z" />
<glyph unicode="X" horiz-adv-x="1366" d="M1366 0H1012L672 553L332 0H0L485 754L31 1462H373L688 936L997 1462H1331L872 737L1366 0Z" />
<glyph unicode="Y" horiz-adv-x="1278" d="M639 860L944 1462H1278L793 569V0H485V559L0 1462H336L639 860Z" />
<glyph unicode="Z" horiz-adv-x="1186" d="M1137 0H49V201L750 1206H68V1462H1118V1262L418 256H1137V0Z" />
<glyph unicode="[" horiz-adv-x="798" d="M698 -339H214V1704H698V1493H474V-128H698V-339Z" />
<glyph unicode="\" horiz-adv-x="966" d="M334 1705L894 -339H632L72 1705H334Z" />
<glyph unicode="]" horiz-adv-x="798" d="M100 1704H584V-339H100V-128H324V1493H100V1704Z" />
<glyph unicode="^" horiz-adv-x="1168" d="M45 520L483 1470H627L1122 520H883L561 1163Q492 1002 421 839T281 520H45Z" />
<glyph unicode="_" horiz-adv-x="842" d="M846 -324H-4V-184H846V-324Z" />
<glyph unicode="`" horiz-adv-x="1243" d="M707 1241Q644 1285 522 1383T332 1548V1569H674Q737 1468 909 1268V1241H707Z" />
<glyph unicode="a" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780 518L662
514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518Z" />
<glyph unicode="b" horiz-adv-x="1296" d="M782 1139Q980 1139 1092 985T1204 561Q1204 284 1089 132T774 -20Q577 -20 465 123H444L393 0H160V1556H465V1194Q465 1125 453 973H465Q572 1139 782 1139ZM684 895Q571 895 519 826T465 596V563Q465 383 518 305T688
227Q782 227 837 313T893 565Q893 730 837 812T684 895Z" />
<glyph unicode="c" horiz-adv-x="1053" d="M614 -20Q92 -20 92 553Q92 838 234 988T641 1139Q835 1139 989 1063L899 827Q827 856 765 874T641 893Q403 893 403 555Q403 227 641 227Q729 227 804 250T954 324V63Q880 16 805 -2T614 -20Z" />
<glyph unicode="d" horiz-adv-x="1296" d="M514 -20Q317 -20 205 133T92 557Q92 832 206 985T522 1139Q733 1139 844 975H854Q831 1100 831 1198V1556H1137V0H903L844 145H831Q727 -20 514 -20ZM621 223Q738 223 792 291T852 522V555Q852 735 797 813T616 891Q514
891 458 805T401 553Q401 388 458 306T621 223Z" />
<glyph unicode="e" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871 228T1059 295V59Q979
19 888 0T666 -20Z" />
<glyph unicode="f" horiz-adv-x="793" d="M778 889H514V0H209V889H41V1036L209 1118V1200Q209 1391 303 1479T604 1567Q762 1567 885 1520L807 1296Q715 1325 637 1325Q572 1325 543 1287T514 1188V1118H778V889Z" />
<glyph unicode="g" horiz-adv-x="1296" d="M623 219Q746 219 799 289T852 518V555Q852 734 797 812T618 891Q403 891 403 553Q403 385 456 302T623 219ZM1137 -2Q1137 -243 997 -367T578 -492Q333 -492 160 -426V-182Q363 -268 596 -268Q831 -268 831 -14V8L840
145H831Q724 -20 514 -20Q313 -20 203 135T92 557Q92 832 206 985T522 1139Q728 1139 846 975H854L879 1118H1137V-2Z" />
<glyph unicode="h" horiz-adv-x="1346" d="M1192 0H887V653Q887 895 707 895Q579 895 522 808T465 526V0H160V1556H465V1239Q465 1202 458 1065L451 975H467Q569 1139 791 1139Q988 1139 1090 1033T1192 729V0Z" />
<glyph unicode="i" horiz-adv-x="625" d="M147 1407Q147 1556 313 1556Q479 1556 479 1407Q479 1336 438 1297T313 1257Q147 1257 147 1407ZM465 0H160V1118H465V0Z" />
<glyph unicode="j" horiz-adv-x="625" d="M70 -492Q-47 -492 -131 -467V-227Q-61 -246 12 -246Q89 -246 124 -203T160 -76V1118H465V-121Q465 -299 362 -395T70 -492ZM147 1407Q147 1556 313 1556Q479 1556 479 1407Q479 1336 438 1297T313 1257Q147 1257 147 1407Z" />
<glyph unicode="k" horiz-adv-x="1270" d="M453 608L586 778L899 1118H1243L799 633L1270 0H918L596 453L465 348V0H160V1556H465V862L449 608H453Z" />
<glyph unicode="l" horiz-adv-x="625" d="M465 0H160V1556H465V0Z" />
<glyph unicode="m" horiz-adv-x="2011" d="M1161 0H856V653Q856 774 816 834T688 895Q571 895 518 809T465 526V0H160V1118H393L434 975H451Q496 1052 581 1095T776 1139Q1027 1139 1116 975H1143Q1188 1053 1275 1096T1473 1139Q1663 1139 1760 1042T1858 729V0H1552V653Q1552
774 1512 834T1384 895Q1272 895 1217 815T1161 561V0Z" />
<glyph unicode="n" horiz-adv-x="1346" d="M1192 0H887V653Q887 774 844 834T707 895Q579 895 522 810T465 526V0H160V1118H393L434 975H451Q502 1056 591 1097T795 1139Q990 1139 1091 1034T1192 729V0Z" />
<glyph unicode="o" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798 1139
921 1069T1110 868T1176 561Z" />
<glyph unicode="p" horiz-adv-x="1296" d="M774 -20Q577 -20 465 123H449Q465 -17 465 -39V-492H160V1118H408L451 973H465Q572 1139 782 1139Q980 1139 1092 986T1204 561Q1204 382 1152 250T1002 49T774 -20ZM684 895Q571 895 519 826T465 596V563Q465 383 518
305T688 227Q893 227 893 565Q893 730 843 812T684 895Z" />
<glyph unicode="q" horiz-adv-x="1296" d="M623 219Q739 219 793 285T852 518V555Q852 735 797 813T618 891Q403 891 403 553Q403 385 456 302T623 219ZM514 -20Q316 -20 204 132T92 557Q92 831 206 985T520 1139Q626 1139 705 1099T844 975H852L879 1118H1137V-492H831V-23Q831
38 844 145H831Q782 64 701 22T514 -20Z" />
<glyph unicode="r" horiz-adv-x="930" d="M784 1139Q846 1139 887 1130L864 844Q827 854 774 854Q628 854 547 779T465 569V0H160V1118H391L436 930H451Q503 1024 591 1081T784 1139Z" />
<glyph unicode="s" horiz-adv-x="1018" d="M940 332Q940 160 821 70T463 -20Q341 -20 255 -4T94 45V297Q179 257 285 230T473 203Q639 203 639 299Q639 335 617 357T541 408T397 475Q268 529 208 575T120 680T92 827Q92 976 207 1057T535 1139Q737 1139 928 1051L836
831Q752 867 679 890T530 913Q395 913 395 840Q395 799 438 769T629 680Q760 627 821 581T911 475T940 332Z" />
<glyph unicode="t" horiz-adv-x="889" d="M631 223Q711 223 823 258V31Q709 -20 543 -20Q360 -20 277 72T193 350V889H47V1018L215 1120L303 1356H498V1118H811V889H498V350Q498 285 534 254T631 223Z" />
<glyph unicode="u" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952Z" />
<glyph unicode="v" horiz-adv-x="1165" d="M426 0L0 1118H319L535 481Q568 377 578 252H586Q592 365 631 481L846 1118H1165L739 0H426Z" />
<glyph unicode="w" horiz-adv-x="1753" d="M1079 0L993 391L879 885H870L666 0H338L20 1118H324L453 623Q475 535 514 256H522Q526 332 557 497L573 582L711 1118H1047L1178 582Q1190 527 1207 421T1227 256H1235Q1246 345 1266 458T1300 623L1434 1118H1733L1411
0H1079Z" />
<glyph unicode="x" horiz-adv-x="1184" d="M389 571L29 1118H375L592 762L811 1118H1157L793 571L1174 0H827L592 383L356 0H10L389 571Z" />
<glyph unicode="y" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444 8L0 1118Z" />
<glyph unicode="z" horiz-adv-x="999" d="M938 0H55V180L573 885H86V1118H920V920L416 233H938V0Z" />
<glyph unicode="{" horiz-adv-x="872" d="M78 562V801Q140 801 188 812T270 848T319 912T334 1008V1379Q334 1465 353 1526T421 1627T556 1684T772 1703V1478Q732 1477 700 1470T645 1444T610 1393T598 1310V953Q592 730 364 688V676Q479 656 540 590T598 412V55Q598
4 610 -28T644 -78T699 -104T772 -113V-339Q641 -339 556 -321T422 -263T353 -162T334 -14V353Q334 466 269 514T78 562Z" />
<glyph unicode="|" horiz-adv-x="1105" d="M443 1703H662V-339H443V1703Z" />
<glyph unicode="}" horiz-adv-x="872" d="M794 563Q732 563 684 552T602 516T553 452T538 356V-15Q538 -101 519 -162T451 -263T316 -320T100 -339V-114Q140 -113 172 -106T227 -80T262 -29T274 54V411Q280 634 508 676V688Q393 708 332 774T274 952V1309Q274
1360 262 1392T228 1442T173 1468T100 1477V1703Q231 1703 316 1685T450 1627T519 1526T538 1378V1011Q538 898 603 850T794 802V563Z" />
<glyph unicode="~" horiz-adv-x="1168" d="M548 556Q511 572 483 583T431 600T386 609T342 612Q313 612 282 603T221 577T163 538T108 491V722Q159 776 222 803T364 831Q394 831 419 829T473 819T537 800T620 767Q658 751 686 741T739 724T784 715T827 712Q856
712 887 721T948 747T1006 785T1060 833V602Q959 493 804 493Q774 493 749 495T695 504T631 523T548 556Z" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="&#xa1;" horiz-adv-x="586" d="M168 606H412L463 -369H117L168 606ZM467 948Q467 864 422 821T291 778Q208 778 163 822T117 948Q117 1029 163 1073T291 1118Q375 1118 421 1074T467 948Z" />
<glyph unicode="&#xa2;" horiz-adv-x="1171" d="M563 176Q143 235 143 741Q143 1002 247 1144T563 1317V1483H741V1325Q907 1316 1040 1251L950 1016Q878 1045 816 1063T692 1081Q571 1081 513 998T455 743Q455 416 692 416Q774 416 840 431T1006 492V238Q879
177 741 168V-20H563V176Z" />
<glyph unicode="&#xa3;" horiz-adv-x="1171" d="M700 1483Q895 1483 1090 1401L997 1171Q840 1235 725 1235Q647 1235 605 1191T563 1063V870H938V651H563V508Q563 338 412 260H1130V0H82V248Q185 292 223 349T262 506V651H84V870H262V1065Q262 1266 376 1374T700 1483Z" />
<glyph unicode="&#xa4;" horiz-adv-x="1171" d="M188 723Q188 825 242 920L113 1047L260 1194L387 1067Q478 1120 584 1120Q689 1120 780 1065L907 1194L1057 1051L928 922Q981 833 981 723Q981 616 928 524L1053 399L907 254L780 379Q685 328 584 328Q469 328
385 379L260 256L115 401L242 526Q188 619 188 723ZM395 723Q395 646 449 591T584 535Q665 535 720 590T776 723Q776 803 720 858T584 913Q506 913 451 857T395 723Z" />
<glyph unicode="&#xa5;" horiz-adv-x="1171" d="M584 860L848 1462H1161L778 715H973V537H727V399H973V221H727V0H440V221H193V399H440V537H193V715H383L6 1462H322L584 860Z" />
<glyph unicode="&#xa6;" horiz-adv-x="1128" d="M455 1550H674V735H455V1550ZM455 350H674V-465H455V350Z" />
<glyph unicode="&#xa7;" horiz-adv-x="995" d="M121 801Q121 955 254 1049Q121 1133 121 1280Q121 1409 232 1488T526 1567Q696 1567 889 1483L807 1292Q701 1342 640 1359T520 1376Q439 1376 402 1354T365 1284Q365 1238 411 1202T578 1118Q746 1049 820 967T895
780Q895 602 770 522Q832 482 863 430T895 303Q895 155 776 68T455 -20Q252 -20 106 59V266Q187 225 286 197T455 168Q649 168 649 285Q649 324 631 348T567 397T442 457Q256 532 189 609T121 801ZM344 823Q344 759 406 709T590 610Q668 667 668 754Q668 823 614
869T434 961Q396 946 370 909T344 823Z" />
<glyph unicode="&#xa8;" horiz-adv-x="1243" d="M279 1405Q279 1470 316 1505T418 1540Q484 1540 521 1503T559 1405Q559 1345 521 1309T418 1272Q354 1272 317 1307T279 1405ZM682 1405Q682 1475 722 1507T823 1540Q888 1540 926 1504T965 1405Q965 1344 926
1308T823 1272Q763 1272 723 1304T682 1405Z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M893 1055Q774 1055 707 970T639 731Q639 574 697 490T891 406Q986 406 1106 453V322Q1046 295 997 284T883 272Q690 272 585 392T479 731Q479 940 590 1064T891 1188Q1021 1188 1143 1126L1083 1001Q977 1055 893
1055ZM100 731Q100 931 200 1106T475 1382T852 1483Q1052 1483 1227 1383T1503 1108T1604 731Q1604 534 1507 361T1235 84T852 -20Q645 -20 470 83T198 360T100 731ZM209 731Q209 559 295 410T530 175T852 88Q1024 88 1173 174T1408 409T1495 731Q1495 903 1409
1052T1174 1287T852 1374Q680 1374 531 1288T296 1053T209 731Z" />
<glyph unicode="&#xaa;" horiz-adv-x="784" d="M561 764L530 874Q487 816 425 784T289 752Q172 752 110 810T47 975Q47 1084 129 1138T397 1202L496 1206Q496 1323 369 1323Q288 1323 152 1262L86 1397Q152 1429 231 1454T410 1479Q547 1479 621 1408T696 1206V764H561ZM252
977Q252 939 275 921T330 903Q407 903 451 944T496 1051V1087L397 1081Q252 1071 252 977Z" />
<glyph unicode="&#xab;" horiz-adv-x="1260" d="M82 573L453 1028L672 909L393 561L672 213L453 94L82 547V573ZM588 573L958 1028L1178 909L899 561L1178 213L958 94L588 547V573Z" />
<glyph unicode="&#xac;" horiz-adv-x="1171" d="M1081 248H862V612H88V831H1081V248Z" />
<glyph unicode="&#xad;" horiz-adv-x="659" d="M61 424V674H598V424H61Z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M727 764H829Q910 764 954 804T999 909Q999 982 958 1014T827 1047H727V764ZM1157 913Q1157 830 1114 770T997 680L1235 283H1059L854 637H727V283H571V1178H834Q1002 1178 1079 1113T1157 913ZM100 731Q100 931
200 1106T475 1382T852 1483Q1052 1483 1227 1383T1503 1108T1604 731Q1604 534 1507 361T1235 84T852 -20Q645 -20 470 83T198 360T100 731ZM209 731Q209 559 295 410T530 175T852 88Q1024 88 1173 174T1408 409T1495 731Q1495 903 1409 1052T1174 1287T852 1374Q680
1374 531 1288T296 1053T209 731Z" />
<glyph unicode="&#xaf;" horiz-adv-x="749" d="M106 484V734H643V484H106Z" />
<glyph unicode="&#xb0;" horiz-adv-x="877" d="M92 1137Q92 1229 138 1309T264 1436T438 1483Q530 1483 610 1437T737 1310T784 1137Q784 1044 738 964T611 838T438 793Q293 793 193 892T92 1137ZM283 1137Q283 1073 327 1028T438 983Q504 983 549 1029T594 1137Q594
1200 549 1247T438 1294Q374 1294 329 1248T283 1137Z" />
<glyph unicode="&#xb1;" horiz-adv-x="1171" d="M475 674H88V893H475V1282H694V893H1081V674H694V289H475V674ZM88 0V219H1081V0H88Z" />
<glyph unicode="&#xb2;" horiz-adv-x="776" d="M702 586H55V754L279 973Q381 1073 409 1117T438 1212Q438 1250 414 1270T350 1290Q269 1290 170 1202L47 1354Q194 1483 383 1483Q520 1483 599 1417T678 1233Q678 1148 631 1073T455 881L350 786H702V586Z" />
<glyph unicode="&#xb3;" horiz-adv-x="776" d="M666 1249Q666 1106 496 1051V1038Q590 1018 642 963T694 829Q694 708 606 639T332 569Q189 569 59 639V829Q207 739 330 739Q473 739 473 846Q473 899 429 925T307 952H195V1112H287Q370 1112 410 1138T451 1221Q451
1259 426 1284T350 1309Q303 1309 261 1290T162 1231L61 1372Q123 1419 198 1450T377 1481Q504 1481 585 1417T666 1249Z" />
<glyph unicode="&#xb4;" horiz-adv-x="1243" d="M332 1241V1268Q504 1468 567 1569H909V1548Q857 1496 732 1394T535 1241H332Z" />
<glyph unicode="&#xb5;" horiz-adv-x="1352" d="M465 465Q465 344 509 284T647 223Q773 223 830 309T887 592V1118H1192V0H961L918 150H903Q861 65 801 23T653 -20Q591 -20 539 3T455 70L460 -15L465 -172V-492H160V1118H465V465Z" />
<glyph unicode="&#xb6;" horiz-adv-x="1341" d="M1167 -260H1006V1356H840V-260H678V559Q616 541 532 541Q316 541 215 666T113 1042Q113 1302 222 1429T563 1556H1167V-260Z" />
<glyph unicode="&#xb7;" horiz-adv-x="584" d="M117 723Q117 807 162 850T293 893Q376 893 421 849T467 723Q467 642 421 598T293 553Q209 553 163 597T117 723Z" />
<glyph unicode="&#xb8;" horiz-adv-x="420" d="M418 -250Q418 -378 343 -435T109 -492Q31 -492 -37 -471V-303Q-10 -310 35 -317T106 -324Q178 -324 178 -262Q178 -179 12 -154L90 0H283L256 -61Q330 -85 374 -135T418 -250Z" />
<glyph unicode="&#xb9;" horiz-adv-x="776" d="M584 586H346V1032L349 1144L354 1239Q327 1203 279 1161L201 1100L92 1227L393 1462H584V586Z" />
<glyph unicode="&#xba;" horiz-adv-x="795" d="M737 1116Q737 945 646 849T395 752Q242 752 150 850T57 1116Q57 1285 146 1382T399 1479Q551 1479 644 1381T737 1116ZM260 1116Q260 1016 292 966T397 915Q469 915 500 965T532 1116Q532 1216 501 1265T397 1315Q325
1315 293 1266T260 1116Z" />
<glyph unicode="&#xbb;" horiz-adv-x="1260" d="M1178 547L807 94L588 213L866 561L588 909L807 1028L1178 573V547ZM672 547L301 94L82 213L360 561L82 909L301 1028L672 573V547Z" />
<glyph unicode="&#xbc;" horiz-adv-x="1804" d="M794 586H556V1032L559 1144L564 1239Q537 1203 489 1161L411 1100L302 1227L603 1462H794V586ZM1370 1462L559 0H320L1131 1462H1370ZM1682 152H1557V1H1319V152H936V306L1321 883H1557V320H1682V152ZM1319 320V484Q1319
570 1325 668Q1316 642 1290 588T1248 511L1121 320H1319Z" />
<glyph unicode="&#xbd;" horiz-adv-x="1804" d="M794 586H556V1032L559 1144L564 1239Q537 1203 489 1161L411 1100L302 1227L603 1462H794V586ZM1370 1462L559 0H320L1131 1462H1370ZM1716 1H1069V169L1293 388Q1395 488 1423 532T1452 627Q1452 665 1428 685T1364
705Q1283 705 1184 617L1061 769Q1208 898 1397 898Q1534 898 1613 832T1692 648Q1692 563 1645 488T1469 296L1364 201H1716V1Z" />
<glyph unicode="&#xbe;" horiz-adv-x="1804" d="M697 1249Q697 1106 527 1051V1038Q621 1018 673 963T725 829Q725 708 637 639T363 569Q220 569 90 639V829Q238 739 361 739Q504 739 504 846Q504 899 460 925T338 952H226V1112H318Q401 1112 441 1138T482 1221Q482
1259 457 1284T381 1309Q334 1309 292 1290T193 1231L92 1372Q154 1419 229 1450T408 1481Q535 1481 616 1417T697 1249ZM1441 1462L630 0H391L1202 1462H1441ZM1712 152H1587V1H1349V152H966V306L1351 883H1587V320H1712V152ZM1349 320V484Q1349 570 1355 668Q1346
642 1320 588T1278 511L1151 320H1349Z" />
<glyph unicode="&#xbf;" horiz-adv-x="977" d="M713 606V532Q713 434 669 363T516 215Q407 137 379 93T350 -14Q350 -71 393 -108T526 -145Q605 -145 695 -116T881 -45L983 -266Q885 -322 762 -356T532 -391Q312 -391 187 -295T61 -29Q61 79 109 158T301 342Q396
412 422 449T449 547V606H713ZM745 948Q745 864 700 821T569 778Q486 778 441 822T395 948Q395 1029 441 1073T569 1118Q653 1118 699 1074T745 948Z" />
<glyph unicode="&#xc0;" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM713 1579Q650 1623 528 1721T338 1886V1907H680Q743 1806 915 1606V1579H713Z" />
<glyph unicode="&#xc1;" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM541 1579V1606Q713 1806 776 1907H1118V1886Q1066 1834 941 1732T744 1579H541Z" />
<glyph unicode="&#xc2;" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM938 1579Q781 1672 704 1755Q626 1674 475 1579H272V1606Q461 1795 528 1907H885Q916 1855
992 1766T1141 1606V1579H938Z" />
<glyph unicode="&#xc3;" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM543 1684Q512 1684 484 1658T442 1577H293Q304 1722 375 1804T565 1886Q606 1886 645 1870T723
1834T799 1798T872 1782Q903 1782 931 1808T973 1888H1122Q1111 1743 1039 1661T850 1579Q809 1579 770 1595T692 1631T616 1667T543 1684Z" />
<glyph unicode="&#xc4;" horiz-adv-x="1413" d="M1079 0L973 348H440L334 0H0L516 1468H895L1413 0H1079ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM365 1743Q365 1808 402 1843T504 1878Q570 1878 607 1841T645 1743Q645 1683 607 1647T504
1610Q440 1610 403 1645T365 1743ZM768 1743Q768 1813 808 1845T909 1878Q974 1878 1012 1842T1051 1743Q1051 1682 1012 1646T909 1610Q849 1610 809 1642T768 1743Z" />
<glyph unicode="&#xc5;" horiz-adv-x="1413" d="M958 1567Q958 1478 911 1419L1413 0H1079L973 348H440L334 0H0L500 1419Q457 1477 457 1565Q457 1673 524 1737T705 1802Q814 1802 886 1738T958 1567ZM899 608Q752 1081 734 1143T707 1241Q674 1113 518 608H899ZM801
1565Q801 1610 774 1635T705 1661Q663 1661 636 1636T608 1565Q608 1472 705 1468Q747 1468 774 1494T801 1565Z" />
<glyph unicode="&#xc6;" horiz-adv-x="1950" d="M1829 0H956V348H465L315 0H0L655 1462H1829V1208H1266V887H1792V633H1266V256H1829V0ZM578 608H956V1198H829L578 608Z" />
<glyph unicode="&#xc7;" horiz-adv-x="1305" d="M805 1225Q630 1225 534 1094T438 727Q438 238 805 238Q959 238 1178 315V55Q998 -20 776 -20Q457 -20 288 173T119 729Q119 957 202 1128T440 1391T805 1483Q1018 1483 1233 1380L1133 1128Q1051 1167 968 1196T805
1225ZM959 -250Q959 -378 884 -435T650 -492Q572 -492 504 -471V-303Q531 -310 576 -317T647 -324Q719 -324 719 -262Q719 -179 553 -154L631 0H824L797 -61Q871 -85 915 -135T959 -250Z" />
<glyph unicode="&#xc8;" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM634 1579Q571 1623 449 1721T259 1886V1907H601Q664 1806 836 1606V1579H634Z" />
<glyph unicode="&#xc9;" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM424 1579V1606Q596 1806 659 1907H1001V1886Q949 1834 824 1732T627 1579H424Z" />
<glyph unicode="&#xca;" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM841 1579Q684 1672 607 1755Q529 1674 378 1579H175V1606Q364 1795 431 1907H788Q819 1855 895 1766T1044 1606V1579H841Z" />
<glyph unicode="&#xcb;" horiz-adv-x="1147" d="M1026 0H184V1462H1026V1208H494V887H989V633H494V256H1026V0ZM272 1743Q272 1808 309 1843T411 1878Q477 1878 514 1841T552 1743Q552 1683 514 1647T411 1610Q347 1610 310 1645T272 1743ZM675 1743Q675 1813
715 1845T816 1878Q881 1878 919 1842T958 1743Q958 1682 919 1646T816 1610Q756 1610 716 1642T675 1743Z" />
<glyph unicode="&#xcc;" horiz-adv-x="797" d="M731 0H66V176L244 258V1204L66 1286V1462H731V1286L553 1204V258L731 176V0ZM417 1579Q354 1623 232 1721T42 1886V1907H384Q447 1806 619 1606V1579H417Z" />
<glyph unicode="&#xcd;" horiz-adv-x="797" d="M731 0H66V176L244 258V1204L66 1286V1462H731V1286L553 1204V258L731 176V0ZM237 1579V1606Q409 1806 472 1907H814V1886Q762 1834 637 1732T440 1579H237Z" />
<glyph unicode="&#xce;" horiz-adv-x="797" d="M731 0H66V176L244 258V1204L66 1286V1462H731V1286L553 1204V258L731 176V0ZM630 1579Q473 1672 396 1755Q318 1674 167 1579H-36V1606Q153 1795 220 1907H577Q608 1855 684 1766T833 1606V1579H630Z" />
<glyph unicode="&#xcf;" horiz-adv-x="797" d="M731 0H66V176L244 258V1204L66 1286V1462H731V1286L553 1204V258L731 176V0ZM57 1743Q57 1808 94 1843T196 1878Q262 1878 299 1841T337 1743Q337 1683 299 1647T196 1610Q132 1610 95 1645T57 1743ZM460 1743Q460
1813 500 1845T601 1878Q666 1878 704 1842T743 1743Q743 1682 704 1646T601 1610Q541 1610 501 1642T460 1743Z" />
<glyph unicode="&#xd0;" horiz-adv-x="1516" d="M47 850H184V1462H643Q1001 1462 1199 1273T1397 745Q1397 384 1192 192T598 0H184V596H47V850ZM1075 737Q1075 969 971 1088T657 1208H494V850H731V596H494V256H625Q1075 256 1075 737Z" />
<glyph unicode="&#xd1;" horiz-adv-x="1665" d="M1481 0H1087L451 1106H442Q461 813 461 688V0H184V1462H575L1210 367H1217Q1202 652 1202 770V1462H1481V0ZM668 1684Q637 1684 609 1658T567 1577H418Q429 1722 500 1804T690 1886Q731 1886 770 1870T848 1834T924
1798T997 1782Q1028 1782 1056 1808T1098 1888H1247Q1236 1743 1164 1661T975 1579Q934 1579 895 1595T817 1631T741 1667T668 1684Z" />
<glyph unicode="&#xd2;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227
538 1103T444 733ZM824 1579Q761 1623 639 1721T449 1886V1907H791Q854 1806 1026 1606V1579H824Z" />
<glyph unicode="&#xd3;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227
538 1103T444 733ZM658 1579V1606Q830 1806 893 1907H1235V1886Q1183 1834 1058 1732T861 1579H658Z" />
<glyph unicode="&#xd4;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227
538 1103T444 733ZM1047 1579Q890 1672 813 1755Q735 1674 584 1579H381V1606Q570 1795 637 1907H994Q1025 1855 1101 1766T1250 1606V1579H1047Z" />
<glyph unicode="&#xd5;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227
538 1103T444 733ZM652 1684Q621 1684 593 1658T551 1577H402Q413 1722 484 1804T674 1886Q715 1886 754 1870T832 1834T908 1798T981 1782Q1012 1782 1040 1808T1082 1888H1231Q1220 1743 1148 1661T959 1579Q918 1579 879 1595T801 1631T725 1667T652 1684Z"
/>
<glyph unicode="&#xd6;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q479 -20 299 175T119 735Q119 1100 299 1292T817 1485Q1154 1485 1332 1291T1511 733ZM444 733Q444 488 537 364T815 240Q1186 240 1186 733Q1186 1227 817 1227Q632 1227
538 1103T444 733ZM474 1743Q474 1808 511 1843T613 1878Q679 1878 716 1841T754 1743Q754 1683 716 1647T613 1610Q549 1610 512 1645T474 1743ZM877 1743Q877 1813 917 1845T1018 1878Q1083 1878 1121 1842T1160 1743Q1160 1682 1121 1646T1018 1610Q958 1610
918 1642T877 1743Z" />
<glyph unicode="&#xd7;" horiz-adv-x="1168" d="M428 663L129 964L280 1118L581 819L886 1118L1040 968L735 663L1036 360L886 208L581 509L280 210L131 362L428 663Z" />
<glyph unicode="&#xd8;" horiz-adv-x="1630" d="M1511 733Q1511 370 1331 175T815 -20Q618 -20 479 45L389 -90L227 18L317 154Q119 348 119 735Q119 1100 299 1292T817 1485Q1015 1485 1161 1415L1245 1540L1405 1436L1317 1305Q1511 1111 1511 733ZM444 733Q444
542 500 426L1006 1182Q922 1227 817 1227Q632 1227 538 1103T444 733ZM1186 733Q1186 913 1135 1030L635 279Q711 240 815 240Q1186 240 1186 733Z" />
<glyph unicode="&#xd9;" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM750 1579Q687 1623 565 1721T375 1886V1907H717Q780
1806 952 1606V1579H750Z" />
<glyph unicode="&#xda;" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM602 1579V1606Q774 1806 837 1907H1179V1886Q1127 1834
1002 1732T805 1579H602Z" />
<glyph unicode="&#xdb;" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM1006 1579Q849 1672 772 1755Q694 1674 543 1579H340V1606Q529
1795 596 1907H953Q984 1855 1060 1766T1209 1606V1579H1006Z" />
<glyph unicode="&#xdc;" horiz-adv-x="1548" d="M1374 1462V516Q1374 354 1302 232T1092 45T768 -20Q486 -20 330 124T174 520V1462H483V567Q483 398 551 319T776 240Q928 240 996 319T1065 569V1462H1374ZM433 1743Q433 1808 470 1843T572 1878Q638 1878 675
1841T713 1743Q713 1683 675 1647T572 1610Q508 1610 471 1645T433 1743ZM836 1743Q836 1813 876 1845T977 1878Q1042 1878 1080 1842T1119 1743Q1119 1682 1080 1646T977 1610Q917 1610 877 1642T836 1743Z" />
<glyph unicode="&#xdd;" horiz-adv-x="1278" d="M639 860L944 1462H1278L793 569V0H485V559L0 1462H336L639 860ZM461 1579V1606Q633 1806 696 1907H1038V1886Q986 1834 861 1732T664 1579H461Z" />
<glyph unicode="&#xde;" horiz-adv-x="1286" d="M1194 770Q1194 541 1052 417T647 293H494V0H184V1462H494V1233H672Q926 1233 1060 1114T1194 770ZM494 543H594Q739 543 810 595T881 770Q881 877 818 929T618 981H494V543Z" />
<glyph unicode="&#xdf;" horiz-adv-x="1456" d="M1249 1241Q1249 1177 1228 1129T1175 1042T1106 975T1037 922T984 877T963 834Q963 807 989 781T1082 715Q1228 624 1280 575T1358 465T1384 326Q1384 154 1268 67T924 -20Q825 -20 753 -6T621 43V285Q674 249
756 224T903 199Q1071 199 1071 322Q1071 363 1055 388T998 444T883 516Q757 588 708 647T659 788Q659 852 694 905T799 1007Q876 1062 907 1102T938 1188Q938 1248 875 1288T711 1329Q595 1329 530 1277T465 1128V0H160V1139Q160 1340 306 1453T711 1567Q955 1567
1102 1479T1249 1241Z" />
<glyph unicode="&#xe0;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM870 1241Q807 1285 685 1383T495 1548V1569H837Q900 1468 1072 1268V1241H870Z" />
<glyph unicode="&#xe1;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM441 1241V1268Q613 1468 676 1569H1018V1548Q966 1496 841 1394T644 1241H441Z" />
<glyph unicode="&#xe2;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM1099 1496Q942 1589 865 1672Q787 1591 636 1496H433V1523Q622 1712 689 1824H1046Q1077 1772 1153 1683T1302 1523V1496H1099Z" />
<glyph unicode="&#xe3;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM467 1346Q436 1346 408 1320T366 1239H217Q228 1384 299 1466T489 1548Q530 1548 569 1532T647 1496T723 1460T796 1444Q827 1444 855 1470T897 1550H1046Q1035 1405 963 1323T774
1241Q733 1241 694 1257T616 1293T540 1329T467 1346Z" />
<glyph unicode="&#xe4;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM285 1405Q285 1470 322 1505T424 1540Q490 1540 527 1503T565 1405Q565 1345 527 1309T424 1272Q360 1272 323 1307T285 1405ZM688 1405Q688 1475 728 1507T829 1540Q894 1540
932 1504T971 1405Q971 1344 932 1308T829 1272Q769 1272 729 1304T688 1405Z" />
<glyph unicode="&#xe5;" horiz-adv-x="1237" d="M870 0L811 152H803Q726 55 645 18T432 -20Q271 -20 179 72T86 334Q86 512 210 596T586 690L780 696V745Q780 915 606 915Q472 915 291 834L190 1040Q383 1141 618 1141Q843 1141 963 1043T1083 745V0H870ZM780
518L662 514Q529 510 464 466T399 332Q399 203 547 203Q653 203 716 264T780 426V518ZM883 1479Q883 1371 812 1305T629 1239Q517 1239 449 1303T381 1477Q381 1585 448 1649T629 1714Q739 1714 811 1648T883 1479ZM725 1477Q725 1522 698 1547T629 1573Q587 1573
560 1548T533 1477Q533 1432 557 1406T629 1380Q671 1380 698 1406T725 1477Z" />
<glyph unicode="&#xe6;" horiz-adv-x="1878" d="M1329 -20Q1192 -20 1080 30T895 186Q797 69 699 25T442 -20Q281 -20 184 74T86 334Q86 512 207 596T569 690L760 696V780Q760 849 716 882T594 915Q454 915 289 838L190 1040Q379 1141 612 1141Q839 1141 954 1010Q1020
1074 1106 1106T1313 1139Q1534 1139 1662 1002T1790 631V483H1067Q1072 353 1144 280T1346 207Q1542 207 1726 295V59Q1647 20 1555 0T1329 -20ZM760 518L647 514Q523 510 461 467T399 332Q399 203 539 203Q640 203 700 264T760 426V518ZM1307 922Q1090 922 1073
686H1503Q1501 798 1448 860T1307 922Z" />
<glyph unicode="&#xe7;" horiz-adv-x="1053" d="M614 -20Q92 -20 92 553Q92 838 234 988T641 1139Q835 1139 989 1063L899 827Q827 856 765 874T641 893Q403 893 403 555Q403 227 641 227Q729 227 804 250T954 324V63Q880 16 805 -2T614 -20ZM805 -250Q805 -378
730 -435T496 -492Q418 -492 350 -471V-303Q377 -310 422 -317T493 -324Q565 -324 565 -262Q565 -179 399 -154L477 0H670L643 -61Q717 -85 761 -135T805 -250Z" />
<glyph unicode="&#xe8;" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871 228T1059
295V59Q979 19 888 0T666 -20ZM876 1241Q813 1285 691 1383T501 1548V1569H843Q906 1468 1078 1268V1241H876Z" />
<glyph unicode="&#xe9;" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871 228T1059
295V59Q979 19 888 0T666 -20ZM447 1241V1268Q619 1468 682 1569H1024V1548Q972 1496 847 1394T650 1241H447Z" />
<glyph unicode="&#xea;" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871 228T1059
295V59Q979 19 888 0T666 -20ZM860 1241Q703 1334 626 1417Q548 1336 397 1241H194V1268Q383 1457 450 1569H807Q838 1517 914 1428T1063 1268V1241H860Z" />
<glyph unicode="&#xeb;" horiz-adv-x="1210" d="M623 922Q526 922 471 861T408 686H836Q834 799 777 860T623 922ZM666 -20Q396 -20 244 129T92 551Q92 832 232 985T621 1139Q858 1139 990 1004T1122 631V483H401Q406 353 478 280T680 207Q781 207 871 228T1059
295V59Q979 19 888 0T666 -20ZM297 1405Q297 1470 334 1505T436 1540Q502 1540 539 1503T577 1405Q577 1345 539 1309T436 1272Q372 1272 335 1307T297 1405ZM700 1405Q700 1475 740 1507T841 1540Q906 1540 944 1504T983 1405Q983 1344 944 1308T841 1272Q781
1272 741 1304T700 1405Z" />
<glyph unicode="&#xec;" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM274 1241Q211 1285 89 1383T-101 1548V1569H241Q304 1468 476 1268V1241H274Z" />
<glyph unicode="&#xed;" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM145 1241V1268Q317 1468 380 1569H722V1548Q670 1496 545 1394T348 1241H145Z" />
<glyph unicode="&#xee;" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM544 1241Q387 1334 310 1417Q232 1336 81 1241H-122V1268Q67 1457 134 1569H491Q522 1517 598 1428T747 1268V1241H544Z" />
<glyph unicode="&#xef;" horiz-adv-x="625" d="M465 0H160V1118H465V0ZM-29 1405Q-29 1470 8 1505T110 1540Q176 1540 213 1503T251 1405Q251 1345 213 1309T110 1272Q46 1272 9 1307T-29 1405ZM374 1405Q374 1475 414 1507T515 1540Q580 1540 618 1504T657 1405Q657
1344 618 1308T515 1272Q455 1272 415 1304T374 1405Z" />
<glyph unicode="&#xf0;" horiz-adv-x="1268" d="M510 1298Q421 1357 358 1391L459 1567Q607 1500 717 1425L942 1565L1042 1411L872 1307Q1028 1164 1102 985T1176 573Q1176 293 1031 137T631 -20Q386 -20 239 117T92 489Q92 722 222 858T573 995Q778 995 848
897L856 901Q792 1056 664 1178L434 1036L334 1192L510 1298ZM864 532Q864 640 803 705T635 770Q514 770 459 702T403 487Q403 347 463 276T635 205Q758 205 811 287T864 532Z" />
<glyph unicode="&#xf1;" horiz-adv-x="1346" d="M1192 0H887V653Q887 774 844 834T707 895Q579 895 522 810T465 526V0H160V1118H393L434 975H451Q502 1056 591 1097T795 1139Q990 1139 1091 1034T1192 729V0ZM508 1346Q477 1346 449 1320T407 1239H258Q269 1384
340 1466T530 1548Q571 1548 610 1532T688 1496T764 1460T837 1444Q868 1444 896 1470T938 1550H1087Q1076 1405 1004 1323T815 1241Q774 1241 735 1257T657 1293T581 1329T508 1346Z" />
<glyph unicode="&#xf2;" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798
1139 921 1069T1110 868T1176 561ZM868 1241Q805 1285 683 1383T493 1548V1569H835Q898 1468 1070 1268V1241H868Z" />
<glyph unicode="&#xf3;" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798
1139 921 1069T1110 868T1176 561ZM467 1241V1268Q639 1468 702 1569H1044V1548Q992 1496 867 1394T670 1241H467Z" />
<glyph unicode="&#xf4;" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798
1139 921 1069T1110 868T1176 561ZM864 1241Q707 1334 630 1417Q552 1336 401 1241H198V1268Q387 1457 454 1569H811Q842 1517 918 1428T1067 1268V1241H864Z" />
<glyph unicode="&#xf5;" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798
1139 921 1069T1110 868T1176 561ZM469 1346Q438 1346 410 1320T368 1239H219Q230 1384 301 1466T491 1548Q532 1548 571 1532T649 1496T725 1460T798 1444Q829 1444 857 1470T899 1550H1048Q1037 1405 965 1323T776 1241Q735 1241 696 1257T618 1293T542 1329T469
1346Z" />
<glyph unicode="&#xf6;" horiz-adv-x="1268" d="M403 561Q403 395 457 310T635 225Q757 225 810 309T864 561Q864 727 810 810T633 893Q511 893 457 811T403 561ZM1176 561Q1176 288 1032 134T631 -20Q470 -20 347 50T158 253T92 561Q92 835 235 987T637 1139Q798
1139 921 1069T1110 868T1176 561ZM291 1405Q291 1470 328 1505T430 1540Q496 1540 533 1503T571 1405Q571 1345 533 1309T430 1272Q366 1272 329 1307T291 1405ZM694 1405Q694 1475 734 1507T835 1540Q900 1540 938 1504T977 1405Q977 1344 938 1308T835 1272Q775
1272 735 1304T694 1405Z" />
<glyph unicode="&#xf7;" horiz-adv-x="1168" d="M108 552V771H1060V552H108ZM444 313Q444 355 455 384T485 430T529 456T583 464Q611 464 636 456T680 431T710 384T722 313Q722 273 711 244T680 197T636 170T583 161Q555 161 530 169T485 196T455 244T444 313ZM444
1011Q444 1053 455 1082T485 1129T529 1155T583 1163Q611 1163 636 1155T680 1129T710 1082T722 1011Q722 971 711 943T680 896T636 869T583 860Q555 860 530 868T485 895T455 942T444 1011Z" />
<glyph unicode="&#xf8;" horiz-adv-x="1268" d="M1176 561Q1176 288 1032 134T631 -20Q505 -20 397 25L330 -76L176 29L244 129Q92 285 92 561Q92 835 235 987T637 1139Q769 1139 885 1087L940 1169L1092 1061L1034 977Q1176 822 1176 561ZM403 561Q403 467 422
395L739 870Q696 893 633 893Q511 893 457 811T403 561ZM864 561Q864 642 852 702L543 240Q581 225 635 225Q757 225 810 309T864 561Z" />
<glyph unicode="&#xf9;" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM876 1241Q813 1285 691 1383T501 1548V1569H843Q906 1468
1078 1268V1241H876Z" />
<glyph unicode="&#xfa;" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM498 1241V1268Q670 1468 733 1569H1075V1548Q1023 1496 898
1394T701 1241H498Z" />
<glyph unicode="&#xfb;" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM901 1241Q744 1334 667 1417Q589 1336 438 1241H235V1268Q424
1457 491 1569H848Q879 1517 955 1428T1104 1268V1241H901Z" />
<glyph unicode="&#xfc;" horiz-adv-x="1346" d="M952 0L911 143H895Q846 65 756 23T551 -20Q354 -20 254 85T154 389V1118H459V465Q459 344 502 284T639 223Q767 223 824 308T881 592V1118H1186V0H952ZM326 1405Q326 1470 363 1505T465 1540Q531 1540 568 1503T606
1405Q606 1345 568 1309T465 1272Q401 1272 364 1307T326 1405ZM729 1405Q729 1475 769 1507T870 1540Q935 1540 973 1504T1012 1405Q1012 1344 973 1308T870 1272Q810 1272 770 1304T729 1405Z" />
<glyph unicode="&#xfd;" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444 8L0 1118ZM393 1241V1268Q565
1468 628 1569H970V1548Q918 1496 793 1394T596 1241H393Z" />
<glyph unicode="&#xfe;" horiz-adv-x="1296" d="M465 973Q515 1054 596 1096T782 1139Q980 1139 1092 985T1204 561Q1204 288 1093 134T782 -20Q569 -20 465 117H451L458 55L465 -39V-492H160V1556H465V1165L458 1045L451 973H465ZM684 895Q571 895 519 826T465
596V563Q465 383 518 305T688 227Q893 227 893 565Q893 730 843 812T684 895Z" />
<glyph unicode="&#xff;" horiz-adv-x="1165" d="M0 1118H334L545 489Q572 407 582 295H588Q599 398 631 489L838 1118H1165L692 -143Q627 -318 507 -405T225 -492Q146 -492 70 -475V-233Q125 -246 190 -246Q271 -246 331 -197T426 -47L444 8L0 1118ZM499 1405Q499
1470 536 1505T638 1540Q704 1540 741 1503T779 1405Q779 1345 741 1309T638 1272Q574 1272 537 1307T499 1405ZM902 1405Q902 1475 942 1507T1043 1540Q1108 1540 1146 1504T1185 1405Q1185 1344 1146 1308T1043 1272Q983 1272 943 1304T902 1405Z" />
<glyph unicode="&#x2013;" horiz-adv-x="1024" d="M82 496V726H942V496H82Z" />
<glyph unicode="&#x2014;" horiz-adv-x="2048" d="M82 496V726H1966V496H82Z" />
<glyph unicode="&#x2018;" horiz-adv-x="687" d="M159 1014Q173 1069 192 1129T235 1252T284 1376T335 1493H555Q540 1433 526 1367T498 1235T473 1107T454 992H174L159 1014Z" />
<glyph unicode="&#x2019;" horiz-adv-x="686" d="M576 1471Q562 1416 543 1356T500 1233T451 1109T400 992H181Q195 1052 209 1118T237 1250T262 1378T281 1493H561L576 1471Z" />
<glyph unicode="&#x201a;" horiz-adv-x="584" d="M459 215Q407 13 283 -264H63Q128 2 164 238H444L459 215Z" />
<glyph unicode="&#x201c;" horiz-adv-x="1153" d="M626 1014Q640 1069 659 1129T702 1252T751 1376T802 1493H1021Q1007 1433 993 1367T965 1235T940 1107T921 992H641L626 1014ZM159 1014Q173 1069 192 1129T235 1252T284 1376T335 1493H555Q540 1433 526 1367T498
1235T473 1107T454 992H174L159 1014Z" />
<glyph unicode="&#x201d;" horiz-adv-x="1153" d="M576 1471Q562 1416 543 1356T500 1233T451 1109T400 992H181Q195 1052 209 1118T237 1250T262 1378T281 1493H561L576 1471ZM1043 1471Q1029 1416 1010 1356T967 1233T918 1109T867 992H647Q662 1052 676 1118T704
1250T729 1378T748 1493H1028L1043 1471Z" />
<glyph unicode="&#x201e;" horiz-adv-x="1051" d="M459 215Q407 13 283 -264H63Q128 2 164 238H444L459 215ZM926 215Q874 13 750 -264H530Q595 2 631 238H911L926 215Z" />
<glyph unicode="&#x2022;" horiz-adv-x="770" d="M98 748Q98 902 172 983T385 1065Q522 1065 597 983T672 748Q672 596 597 513T385 430Q247 430 173 513T98 748Z" />
<glyph unicode="&#x2039;" horiz-adv-x="754" d="M82 573L453 1028L672 909L393 561L672 213L453 94L82 547V573Z" />
<glyph unicode="&#x203a;" horiz-adv-x="754" d="M672 547L301 94L82 213L360 561L82 909L301 1028L672 573V547Z" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,334 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="NotoSans" horiz-adv-x="1222" ><font-face
font-family="Noto Sans"
units-per-em="2048"
panose-1="2 11 8 2 4 5 4 9 2 4"
ascent="2189"
descent="-600"
alphabetic="0" />
<glyph unicode=" " horiz-adv-x="532" />
<glyph unicode="!" horiz-adv-x="586" d="M391 485H150L307 1462H647L391 485ZM25 115Q25 205 78 259T229 313Q297 313 338 275T379 168Q379 81 324 27T180 -27Q107 -27 66 10T25 115Z" />
<glyph unicode="&quot;" horiz-adv-x="928" d="M549 1462L397 934H201L272 1462H549ZM954 1462L803 934H604L678 1462H954Z" />
<glyph unicode="#" horiz-adv-x="1323" d="M1036 846L967 614H1225L1206 408H909L793 0H573L690 408H496L381 0H166L279 408H41L59 614H336L406 846H154L172 1055H461L580 1462H797L680 1055H879L995 1462H1210L1094 1055H1333L1315 846H1036ZM553 614H750L819
846H623L553 614Z" />
<glyph unicode="$" horiz-adv-x="1128" d="M1034 496Q1034 312 909 205T541 80L502 -119H362L406 82Q197 94 51 168V434Q249 327 455 317L526 639Q363 700 285 790T207 1004Q207 1177 334 1283T684 1405L719 1556H858L825 1405Q991 1383 1120 1315L1014 1083Q882
1148 772 1157L709 858Q840 807 904 759T1001 646T1034 496ZM594 322Q657 331 696 367T735 465Q735 511 711 540T651 584L594 322ZM633 1157Q571 1150 537 1116T502 1022Q502 943 582 911L633 1157Z" />
<glyph unicode="%" horiz-adv-x="1753" d="M518 1274Q455 1274 408 1146T360 868Q360 772 416 772Q481 772 528 903T575 1178Q575 1274 518 1274ZM821 1165Q821 999 765 855T614 638T397 565Q258 565 187 648T115 885Q115 1054 170 1196T319 1411T535 1483Q672
1483 746 1403T821 1165ZM1554 1462L471 0H231L1319 1462H1554ZM1376 690Q1337 690 1301 634T1242 478T1219 285Q1219 188 1274 188Q1315 188 1351 243T1410 397T1434 594Q1434 690 1376 690ZM1679 590Q1679 423 1625 277T1477 56T1262 -18Q1118 -18 1046 60T973
283Q973 460 1026 605T1173 824T1393 899Q1530 899 1604 821T1679 590Z" />
<glyph unicode="&amp;" horiz-adv-x="1450" d="M1325 0H975L903 98Q728 -20 500 -20Q291 -20 180 77T68 358Q68 503 146 606T420 807Q344 937 344 1065Q344 1260 461 1372T778 1485Q947 1485 1044 1403T1141 1178Q1141 898 776 752L971 489Q1015 546 1051 610T1130
784H1430Q1297 471 1120 287L1325 0ZM541 623Q453 572 418 519T383 387Q383 322 428 279T545 236Q660 236 766 295L541 623ZM662 920Q775 979 817 1031T860 1143Q860 1200 830 1225T760 1251Q694 1251 658 1205T621 1085Q621 1039 633 993T662 920Z" />
<glyph unicode="&apos;" horiz-adv-x="522" d="M549 1462L397 934H201L272 1462H549Z" />
<glyph unicode="(" horiz-adv-x="694" d="M74 281Q74 620 196 907T578 1462H840Q585 1184 463 889T340 270Q340 -38 457 -324H223Q74 -58 74 281Z" />
<glyph unicode=")" horiz-adv-x="694" d="M618 858Q618 516 494 228T115 -324H-147Q352 221 352 868Q352 1175 236 1462H469Q618 1198 618 858Z" />
<glyph unicode="*" horiz-adv-x="1116" d="M885 1522L772 1169L1159 1198L1141 944L803 987L963 651L717 578L627 915L430 637L223 801L498 1049L172 1141L258 1378L596 1204L629 1573L885 1522Z" />
<glyph unicode="+" horiz-adv-x="1128" d="M475 612H109V831H475V1200H694V831H1061V612H694V248H475V612Z" />
<glyph unicode="," horiz-adv-x="584" d="M377 238L385 215Q267 -40 123 -264H-102Q-28 -97 92 238H377Z" />
<glyph unicode="-" horiz-adv-x="659" d="M41 424L94 674H618L565 424H41Z" />
<glyph unicode="." horiz-adv-x="584" d="M25 115Q25 205 78 259T229 313Q297 313 338 275T379 168Q379 81 324 27T180 -27Q107 -27 66 10T25 115Z" />
<glyph unicode="/" horiz-adv-x="862" d="M1014 1462L205 0H-90L719 1462H1014Z" />
<glyph unicode="0" horiz-adv-x="1128" d="M1110 1012Q1110 704 1029 464T803 102T469 -20Q271 -20 169 102T66 467Q66 758 148 994T376 1357T711 1485Q1110 1485 1110 1012ZM684 1235Q604 1235 535 1131T417 829T369 461Q369 346 396 288T494 229Q576 229 645
335T761 637T807 1022Q807 1133 777 1184T684 1235Z" />
<glyph unicode="1" horiz-adv-x="1128" d="M688 0H383L563 829Q598 981 639 1116Q630 1108 578 1069T315 899L182 1114L748 1462H997L688 0Z" />
<glyph unicode="2" horiz-adv-x="1128" d="M913 0H-49L-6 213L471 637Q651 796 719 891T788 1071Q788 1146 747 1185T637 1225Q571 1225 502 1192T330 1073L184 1276Q316 1388 436 1435T686 1483Q876 1483 987 1385T1098 1126Q1098 1019 1057 925T935 737T668
492L399 270V260H967L913 0Z" />
<glyph unicode="3" horiz-adv-x="1128" d="M1104 1149Q1104 995 1010 890T748 756V748Q879 722 946 642T1014 440Q1014 307 940 202T728 39T401 -20Q162 -20 14 59V326Q98 276 196 251T387 225Q545 225 630 288T715 465Q715 637 457 637H319L365 858H438Q605 858
701 920T797 1092Q797 1159 754 1196T633 1233Q499 1233 346 1133L219 1337Q343 1418 451 1450T698 1483Q888 1483 996 1393T1104 1149Z" />
<glyph unicode="4" horiz-adv-x="1128" d="M1028 303H858L795 0H502L565 303H-25L23 537L793 1462H1104L909 543H1079L1028 303ZM616 543L674 791Q686 849 714 955T756 1096H745Q699 1014 618 915L305 543H616Z" />
<glyph unicode="5" horiz-adv-x="1128" d="M623 922Q806 922 912 819T1018 532Q1018 365 947 240T738 48T408 -20Q291 -20 190 3T27 61V330Q201 231 379 231Q533 231 620 302T707 496Q707 590 650 637T483 684Q381 684 270 651L166 729L373 1462H1128L1073 1200H584L496
907Q568 922 623 922Z" />
<glyph unicode="6" horiz-adv-x="1128" d="M88 469Q88 671 149 864T316 1199T573 1413T930 1485Q1055 1485 1153 1458L1102 1212Q1018 1237 911 1237Q713 1237 592 1126T408 784H416Q531 950 727 950Q884 950 969 853T1055 580Q1055 411 984 267T794 51T516 -20Q304
-20 196 107T88 469ZM530 227Q629 227 691 321T754 557Q754 628 721 670T618 713Q558 713 504 678T416 582T383 422Q383 331 423 279T530 227Z" />
<glyph unicode="7" horiz-adv-x="1128" d="M78 0L815 1202H186L242 1462H1217L1176 1268L424 0H78Z" />
<glyph unicode="8" horiz-adv-x="1128" d="M721 1485Q844 1485 936 1443T1077 1325T1126 1151Q1126 1017 1046 918T815 766Q1032 625 1032 401Q1032 279 969 183T788 33T514 -20Q300 -20 178 80T55 350Q55 648 403 776Q238 908 238 1075Q238 1194 296 1287T464
1433T721 1485ZM582 643Q466 598 409 536T352 383Q352 302 402 255T537 207Q630 207 684 260T739 399Q739 472 703 530T582 643ZM694 1260Q618 1260 573 1214T528 1094Q528 962 651 893Q836 965 836 1114Q836 1182 797 1221T694 1260Z" />
<glyph unicode="9" horiz-adv-x="1128" d="M1092 1001Q1092 721 993 468T729 98T326 -20Q198 -20 86 12V268Q197 227 313 227Q487 227 596 329T768 672H760Q649 514 465 514Q302 514 213 617T123 903Q123 1069 196 1208T392 1416T678 1485Q881 1485 986 1362T1092
1001ZM645 1237Q580 1237 530 1195T452 1081T424 928Q424 841 461 797T567 752Q627 752 678 788T760 888T791 1047Q791 1131 756 1184T645 1237Z" />
<glyph unicode=":" horiz-adv-x="584" d="M25 115Q25 205 78 259T229 313Q297 313 338 275T379 168Q379 81 324 27T180 -27Q107 -27 66 10T25 115ZM207 940Q207 1032 262 1085T412 1139Q480 1139 520 1101T561 993Q561 907 507 853T362 799Q290 799 249 835T207 940Z" />
<glyph unicode=";" horiz-adv-x="584" d="M385 215Q267 -40 123 -264H-102Q-28 -97 92 238H377L385 215ZM207 940Q207 1032 262 1085T412 1139Q480 1139 520 1101T561 993Q561 907 507 853T362 799Q290 799 249 835T207 940Z" />
<glyph unicode="&lt;" horiz-adv-x="1128" d="M1061 203L109 641V784L1061 1280V1040L418 723L1061 442V203Z" />
<glyph unicode="=" horiz-adv-x="1128" d="M109 418V637H1061V418H109ZM109 807V1024H1061V807H109Z" />
<glyph unicode="&gt;" horiz-adv-x="1128" d="M109 442L752 723L109 1040V1280L1061 784V641L109 203V442Z" />
<glyph unicode="?" horiz-adv-x="940" d="M260 485Q287 631 345 723T520 895Q644 979 677 1022T711 1118Q711 1237 578 1237Q528 1237 472 1221T270 1137L178 1358Q408 1483 623 1483Q800 1483 903 1396T1006 1151Q1006 1068 978 1002T895 879T705 731Q611 668
575 620T518 485H260ZM166 115Q166 206 221 259T371 313Q439 313 479 275T520 168Q520 81 465 27T322 -27Q248 -27 207 11T166 115Z" />
<glyph unicode="@" horiz-adv-x="1753" d="M1733 840Q1733 667 1669 519T1492 288T1237 205Q1149 205 1093 243T1020 352H1010Q958 272 896 239T754 205Q627 205 556 284T485 514Q485 661 552 790T740 995T1008 1071Q1193 1071 1335 1016L1229 596Q1218 552 1210
520T1202 455Q1202 387 1260 387Q1326 387 1384 451T1476 622T1511 836Q1511 1049 1388 1161T1028 1274Q825 1274 662 1180T407 914T315 522Q315 279 449 142T825 4Q942 4 1044 24T1266 90V-96Q1036 -186 801 -186Q584 -186 423 -101T177 141T92 500Q92 779 212
997T555 1338T1053 1462Q1371 1462 1552 1299T1733 840ZM995 889Q913 889 850 838T750 701T713 526Q713 461 737 424T807 387Q948 387 1020 657L1077 879Q1041 889 995 889Z" />
<glyph unicode="A" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827Z" />
<glyph unicode="B" horiz-adv-x="1270" d="M788 1462Q1017 1462 1134 1381T1251 1137Q1251 987 1168 891T932 762V754Q1032 728 1091 658T1151 477Q1151 248 998 124T575 0H53L362 1462H788ZM545 883H694Q815 883 875 931T936 1071Q936 1208 766 1208H614L545
883ZM412 256H592Q709 256 775 314T842 475Q842 637 659 637H494L412 256Z" />
<glyph unicode="C" horiz-adv-x="1253" d="M905 1227Q773 1227 668 1146T498 907T434 569Q434 402 502 321T721 240Q867 240 1059 317V57Q860 -20 659 -20Q405 -20 264 129T123 553Q123 815 227 1035T505 1370T905 1485Q1030 1485 1127 1463T1335 1380L1217 1130Q1111
1189 1042 1208T905 1227Z" />
<glyph unicode="D" horiz-adv-x="1386" d="M1323 909Q1323 629 1225 423T942 108T504 0H53L362 1462H758Q1028 1462 1175 1319T1323 909ZM518 256Q666 256 776 332T948 555T1010 893Q1010 1047 938 1127T729 1208H614L412 256H518Z" />
<glyph unicode="E" horiz-adv-x="1110" d="M870 0H53L362 1462H1180L1126 1208H614L547 887H1024L969 633H492L412 256H924L870 0Z" />
<glyph unicode="F" horiz-adv-x="1087" d="M358 0H53L362 1462H1176L1122 1208H614L535 831H1008L952 578H479L358 0Z" />
<glyph unicode="G" horiz-adv-x="1413" d="M754 821H1317L1155 59Q1021 13 907 -3T664 -20Q405 -20 264 127T123 549Q123 815 228 1031T527 1366T979 1485Q1197 1485 1389 1386L1274 1135Q1200 1175 1126 1199T965 1223Q812 1223 692 1140T503 904T434 573Q434
401 506 321T729 240Q805 240 899 264L965 563H698L754 821Z" />
<glyph unicode="H" horiz-adv-x="1434" d="M1135 0H829L963 631H492L358 0H53L362 1462H668L547 889H1018L1139 1462H1444L1135 0Z" />
<glyph unicode="I" horiz-adv-x="784" d="M588 0H-59L-23 176L164 258L365 1204L213 1286L250 1462H897L860 1286L670 1204L469 258L625 176L588 0Z" />
<glyph unicode="J" horiz-adv-x="678" d="M-135 -430Q-229 -430 -322 -403V-150Q-234 -170 -158 -170Q-59 -170 2 -110T92 82L385 1462H690L387 39Q335 -206 212 -318T-135 -430Z" />
<glyph unicode="K" horiz-adv-x="1255" d="M1141 0H803L592 592L467 522L358 0H53L362 1462H668L514 756L670 965L1083 1462H1444L850 762L1141 0Z" />
<glyph unicode="L" horiz-adv-x="1061" d="M53 0L362 1462H668L412 256H924L870 0H53Z" />
<glyph unicode="M" horiz-adv-x="1802" d="M840 369L1389 1462H1812L1503 0H1223L1368 692Q1404 861 1477 1133H1468L899 0H618L557 1133H549Q527 951 471 680L328 0H53L362 1462H766L831 369H840Z" />
<glyph unicode="N" horiz-adv-x="1546" d="M1247 0H905L549 1106H539L531 1047Q504 833 473 688L328 0H53L362 1462H719L1059 385H1067Q1102 608 1135 770L1282 1462H1556L1247 0Z" />
<glyph unicode="O" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688 240Q809
240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227Z" />
<glyph unicode="P" horiz-adv-x="1249" d="M1260 1036Q1260 795 1091 658T623 520H467L358 0H53L362 1462H770Q1012 1462 1136 1355T1260 1036ZM522 774H647Q789 774 870 843T952 1028Q952 1121 904 1164T758 1208H614L522 774Z" />
<glyph unicode="Q" horiz-adv-x="1495" d="M1432 938Q1432 622 1310 383T975 45L1229 -348H870L692 -20H666Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390
500 315T688 240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227Z" />
<glyph unicode="R" horiz-adv-x="1247" d="M530 813H608Q739 813 812 870T885 1044Q885 1126 838 1167T688 1208H614L530 813ZM477 561L358 0H53L362 1462H721Q958 1462 1077 1360T1196 1061Q1196 903 1113 790T874 621L1135 0H803L596 561H477Z" />
<glyph unicode="S" horiz-adv-x="1085" d="M946 432Q946 223 798 102T397 -20Q176 -20 41 70V344Q234 236 399 236Q511 236 574 278T637 395Q637 438 624 470T585 531T461 633Q323 732 267 829T211 1038Q211 1167 273 1268T449 1426T713 1483Q930 1483 1110 1384L1001
1151Q845 1225 713 1225Q630 1225 577 1180T524 1061Q524 1000 557 955T705 834Q826 754 886 658T946 432Z" />
<glyph unicode="T" horiz-adv-x="1087" d="M571 0H266L520 1204H168L223 1462H1233L1178 1204H825L571 0Z" />
<glyph unicode="U" horiz-adv-x="1415" d="M1434 1462L1233 516Q1176 250 1015 115T596 -20Q384 -20 263 93T141 401Q141 473 156 539L352 1462H657L463 543Q446 469 446 418Q446 240 635 240Q758 240 830 316T934 545L1128 1462H1434Z" />
<glyph unicode="V" horiz-adv-x="1208" d="M537 299L564 377Q615 525 645 582L1077 1462H1393L645 0H311L184 1462H479L530 582L532 529V465Q532 350 528 299H537Z" />
<glyph unicode="W" horiz-adv-x="1831" d="M1004 1018Q952 855 891 719L567 0H229L184 1462H471L477 664Q477 612 473 491T463 317H471Q504 410 544 515T596 643L965 1462H1235L1256 589Q1256 443 1247 317H1255Q1308 476 1384 666L1714 1462H2023L1376 0H1030L1008
721L1006 834Q1006 928 1012 1018H1004Z" />
<glyph unicode="X" horiz-adv-x="1241" d="M1124 0H793L621 543L225 0H-117L459 764L221 1462H541L694 944L1057 1462H1401L856 737L1124 0Z" />
<glyph unicode="Y" horiz-adv-x="1155" d="M627 870L1001 1462H1343L725 559L606 0H303L422 559L186 1462H498L627 870Z" />
<glyph unicode="Z" horiz-adv-x="1098" d="M920 0H-61L-23 201L754 1206H211L264 1462H1200L1159 1260L377 256H973L920 0Z" />
<glyph unicode="[" horiz-adv-x="678" d="M436 -324H-37L344 1462H817L772 1251H557L266 -113H481L436 -324Z" />
<glyph unicode="\" horiz-adv-x="862" d="M481 1462L705 0H438L221 1462H481Z" />
<glyph unicode="]" horiz-adv-x="678" d="M-92 -113H121L412 1251H197L242 1462H715L334 -324H-137L-92 -113Z" />
<glyph unicode="^" horiz-adv-x="1128" d="M35 520L653 1470H801L1077 520H854L680 1153L279 520H35Z" />
<glyph unicode="_" horiz-adv-x="819" d="M635 -324H-186L-156 -184H666L635 -324Z" />
<glyph unicode="`" horiz-adv-x="1135" d="M934 1241H750Q679 1310 612 1394T508 1548V1569H819Q855 1421 934 1266V1241Z" />
<glyph unicode="a" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802 761 848T659
895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223Z" />
<glyph unicode="b" horiz-adv-x="1219" d="M610 -20Q416 -20 334 143H326L268 0H37L367 1556H668L606 1268Q565 1086 522 969H530Q608 1067 672 1103T813 1139Q959 1139 1043 1031T1128 733Q1128 543 1060 366T873 84T610 -20ZM692 895Q618 895 558 830T459 641T420
399Q420 319 457 271T559 223Q626 223 687 292T785 481T823 719Q823 895 692 895Z" />
<glyph unicode="c" horiz-adv-x="989" d="M506 -20Q305 -20 198 87T90 391Q90 603 164 776T374 1044T682 1139Q864 1139 1010 1067L918 838Q864 861 812 878T694 895Q609 895 541 831T434 656T395 416Q395 320 440 272T567 223Q643 223 708 246T842 305V59Q690
-20 506 -20Z" />
<glyph unicode="d" horiz-adv-x="1217" d="M406 -20Q259 -20 175 87T90 387Q90 583 161 761T350 1039T608 1139Q690 1139 749 1102T862 975H870L872 1003Q878 1113 897 1198L973 1556H1274L944 0H715L733 145H725Q654 58 577 19T406 -20ZM532 223Q602 223 661
286T759 476T799 719Q799 799 762 847T659 895Q591 895 530 823T432 633T395 399Q395 223 532 223Z" />
<glyph unicode="e" horiz-adv-x="1141" d="M696 922Q608 922 530 842T428 647H473Q628 647 714 695T801 827Q801 922 696 922ZM532 -20Q322 -20 206 93T90 412Q90 619 172 789T396 1049T715 1139Q892 1139 991 1058T1090 834Q1090 647 923 546T446 444H395L393
423V403Q393 312 444 260T592 207Q679 207 750 226T922 293V66Q750 -20 532 -20Z" />
<glyph unicode="f" horiz-adv-x="764" d="M-45 -492Q-149 -492 -219 -467V-225Q-158 -246 -104 -246Q-43 -246 3 -206T68 -76L272 889H109L139 1034L322 1118L340 1202Q381 1392 478 1479T752 1567Q883 1567 987 1518L907 1294Q838 1325 774 1325Q717 1325 682
1285T635 1180L623 1118H842L793 889H573L358 -121Q281 -492 -45 -492Z" />
<glyph unicode="g" horiz-adv-x="1217" d="M950 1118H1182L938 -27Q886 -265 756 -378T391 -492Q287 -492 203 -477T27 -420V-158Q187 -244 375 -244Q479 -244 544 -197T638 -48T696 150H688Q616 56 545 18T391 -20Q252 -20 171 87T90 385Q90 581 160 758T348
1037T602 1139Q692 1139 756 1105T885 975H893L950 1118ZM537 223Q605 223 663 287T760 472T799 719Q799 799 762 847T662 895Q591 895 529 823T431 635T395 399Q395 311 433 267T537 223Z" />
<glyph unicode="h" horiz-adv-x="1237" d="M977 0H676L813 653Q829 721 829 772Q829 895 721 895Q629 895 554 781T436 463L338 0H37L367 1556H668Q629 1375 608 1278T522 969H530Q592 1046 668 1092T844 1139Q982 1139 1057 1056T1133 817Q1133 744 1110 637L977 0Z" />
<glyph unicode="i" horiz-adv-x="608" d="M338 0H37L274 1118H575L338 0ZM322 1380Q322 1467 369 1511T504 1556Q577 1556 615 1525T653 1436Q653 1356 609 1307T473 1257Q322 1257 322 1380Z" />
<glyph unicode="j" horiz-adv-x="608" d="M-90 -492Q-194 -492 -264 -467V-225Q-203 -246 -150 -246Q-13 -246 23 -76L276 1118H578L313 -121Q236 -492 -90 -492ZM324 1380Q324 1467 371 1511T506 1556Q579 1556 617 1525T655 1436Q655 1356 611 1307T475 1257Q324
1257 324 1380Z" />
<glyph unicode="k" horiz-adv-x="1163" d="M920 1118H1264L766 614L1051 0H715L532 420L412 348L338 0H37L367 1556H668L520 862Q509 805 472 687L459 643H467L920 1118Z" />
<glyph unicode="l" horiz-adv-x="608" d="M338 0H37L367 1556H668L338 0Z" />
<glyph unicode="m" horiz-adv-x="1853" d="M844 1139Q1063 1139 1106 911H1114Q1184 1023 1275 1081T1470 1139Q1606 1139 1677 1054T1749 817Q1749 741 1726 637L1593 0H1292L1430 653Q1446 721 1446 772Q1446 895 1348 895Q1254 895 1179 780T1063 465L967 0H666L803
653Q819 721 819 772Q819 895 721 895Q629 895 554 781T436 463L338 0H37L274 1118H504L483 911H492Q638 1139 844 1139Z" />
<glyph unicode="n" horiz-adv-x="1237" d="M977 0H676L813 653Q829 721 829 772Q829 895 721 895Q629 895 554 781T436 463L338 0H37L274 1118H504L483 911H492Q638 1139 844 1139Q982 1139 1057 1056T1133 817Q1133 744 1110 637L977 0Z" />
<glyph unicode="o" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684 1139Q880
1139 994 1021T1108 696Z" />
<glyph unicode="p" horiz-adv-x="1219" d="M813 1139Q959 1139 1043 1032T1128 731Q1128 540 1060 364T872 84T610 -20Q527 -20 467 17T356 143H348Q336 -16 305 -152L233 -492H-68L274 1118H504L487 948H496Q634 1139 813 1139ZM692 895Q618 895 558 830T459
642T420 399Q420 319 457 271T559 223Q626 223 687 292T785 481T823 719Q823 895 692 895Z" />
<glyph unicode="q" horiz-adv-x="1217" d="M391 -20Q303 -20 235 27T129 166T90 385Q90 583 162 762T351 1040T608 1139Q694 1139 760 1102T885 975H893L950 1118H1182L840 -492H539Q586 -274 612 -155T696 150H688Q616 56 545 18T391 -20ZM535 223Q605 223 663
287T760 475T799 719Q799 799 762 847T659 895Q591 895 530 823T432 633T395 399Q395 311 431 267T535 223Z" />
<glyph unicode="r" horiz-adv-x="862" d="M842 1139Q901 1139 938 1128L872 838Q827 854 772 854Q656 854 569 763T444 500L338 0H37L274 1118H504L483 911H494Q639 1139 842 1139Z" />
<glyph unicode="s" horiz-adv-x="969" d="M829 369Q829 181 705 81T358 -20Q251 -20 172 -5T23 45V293Q180 203 342 203Q422 203 473 235T524 324Q524 367 487 401T356 487Q235 555 187 622T139 782Q139 952 249 1045T565 1139Q766 1139 928 1044L829 829Q689
913 571 913Q514 913 479 888T444 819Q444 780 476 751T596 676Q719 613 774 539T829 369Z" />
<glyph unicode="t" horiz-adv-x="840" d="M514 223Q579 223 676 258V33Q565 -20 410 -20Q260 -20 190 43T119 238Q119 288 131 350L246 889H94L123 1036L319 1120L451 1356H645L596 1118H879L829 889H547L432 350Q426 320 426 297Q426 223 514 223Z" />
<glyph unicode="u" horiz-adv-x="1237" d="M262 1118H563L426 465Q410 397 410 346Q410 223 518 223Q610 223 685 337T803 655L901 1118H1202L965 0H735L756 207H745Q600 -20 395 -20Q257 -20 184 62T111 301Q111 394 135 514L262 1118Z" />
<glyph unicode="v" horiz-adv-x="1049" d="M459 301Q523 478 547 524L844 1118H1167L563 0H240L102 1118H397L442 532Q449 399 449 301H459Z" />
<glyph unicode="w" horiz-adv-x="1614" d="M850 860Q760 605 733 541L500 0H176L125 1118H406L410 623Q410 414 399 285H408Q420 329 454 425T514 582L745 1118H1073V582Q1073 389 1063 285H1073Q1154 535 1192 623L1411 1118H1718L1188 0H858L852 520Q852 675
862 860H850Z" />
<glyph unicode="x" horiz-adv-x="1087" d="M379 573L154 1118H475L590 784L834 1118H1188L721 557L965 0H639L514 342L250 0H-100L379 573Z" />
<glyph unicode="y" horiz-adv-x="1063" d="M102 1118H397L453 600Q465 495 465 307H473Q493 358 517 426T582 580L842 1118H1169L489 -160Q312 -492 6 -492Q-84 -492 -141 -473V-233Q-73 -246 -25 -246Q59 -246 122 -198T240 -49L266 0L102 1118Z" />
<glyph unicode="z" horiz-adv-x="932" d="M748 0H-47L-12 180L563 885H166L217 1118H967L924 918L358 233H797L748 0Z" />
<glyph unicode="{" horiz-adv-x="727" d="M201 319Q201 459 -8 459L37 688Q159 688 229 729T322 868L383 1153Q421 1323 514 1392T784 1462H868L819 1237Q729 1235 689 1203T633 1096L567 799Q522 592 291 563V555Q376 529 417 473T459 338Q459 294 444 225L408
47Q401 19 401 -4Q401 -58 434 -78T526 -98V-324H473Q306 -324 220 -261T133 -76Q133 -19 147 49L186 233Q201 302 201 319Z" />
<glyph unicode="|" horiz-adv-x="1128" d="M455 1550H674V-465H455V1550Z" />
<glyph unicode="}" horiz-adv-x="727" d="M256 1462Q596 1462 596 1214Q596 1158 582 1090L543 905Q528 836 528 819Q528 680 737 680L692 451Q570 451 500 409T408 270L346 -14Q309 -184 216 -254T-55 -324H-100V-98Q-7 -95 37 -63T96 43L162 340Q187 451 257
506T438 575V584Q270 635 270 801Q270 844 285 913L322 1092Q328 1122 328 1143Q328 1197 292 1217T182 1237L223 1462H256Z" />
<glyph unicode="~" horiz-adv-x="1128" d="M342 672Q288 672 226 639T109 551V782Q210 891 365 891Q429 891 482 877T621 827Q685 800 732 786T827 772Q878 772 939 802T1061 893V662Q958 553 805 553Q746 553 696 564T549 616Q460 654 422 663T342 672Z" />
<glyph unicode="&#xa0;" horiz-adv-x="532" />
<glyph unicode="&#xa1;" horiz-adv-x="586" d="M182 606H424L266 -371H-74L182 606ZM549 977Q549 885 494 832T344 778Q276 778 236 816T195 924Q195 1009 249 1063T393 1118Q466 1118 507 1081T549 977Z" />
<glyph unicode="&#xa2;" horiz-adv-x="1128" d="M575 -20H387L436 190Q302 226 233 326T164 584Q164 777 226 939T404 1201T672 1325L705 1483H893L858 1325Q976 1311 1083 1260L991 1030Q938 1053 886 1070T768 1087Q685 1087 619 1028T511 855T469 608Q469 512
514 464T641 416Q716 416 781 439T915 498V252Q779 181 616 172L575 -20Z" />
<glyph unicode="&#xa3;" horiz-adv-x="1128" d="M872 1485Q1067 1485 1241 1399L1128 1167Q987 1235 891 1235Q816 1235 768 1196T700 1063L653 834H952L907 614H608L590 530Q548 335 381 260H1036L981 0H-12L37 246Q233 294 281 510L303 614H111L156 834H348L397
1081Q438 1278 559 1381T872 1485Z" />
<glyph unicode="&#xa4;" horiz-adv-x="1128" d="M190 723Q190 825 244 920L115 1047L262 1194L389 1067Q480 1120 586 1120Q691 1120 782 1065L909 1194L1059 1051L930 922Q983 833 983 723Q983 616 930 524L1055 399L909 254L782 379Q687 328 586 328Q471 328
387 379L262 256L117 401L244 526Q190 619 190 723ZM397 723Q397 646 451 591T586 535Q667 535 722 590T778 723Q778 803 722 858T586 913Q508 913 453 857T397 723Z" />
<glyph unicode="&#xa5;" horiz-adv-x="1128" d="M608 872L979 1462H1290L784 715H987L948 537H696L668 399H920L883 221H631L584 0H293L340 221H88L125 399H377L406 537H154L193 715H389L197 1462H494L608 872Z" />
<glyph unicode="&#xa6;" horiz-adv-x="1128" d="M455 1550H674V735H455V1550ZM455 350H674V-465H455V350Z" />
<glyph unicode="&#xa7;" horiz-adv-x="995" d="M150 756Q150 841 196 914T352 1057Q310 1091 282 1141T254 1249Q254 1396 370 1481T684 1567Q856 1567 1028 1479L946 1286Q799 1370 664 1370Q601 1370 561 1345T520 1264Q520 1222 560 1189T688 1118Q810 1064
870 991T930 819Q930 635 737 520Q775 485 801 435T827 326Q827 165 701 73T356 -20Q152 -20 20 55V279Q192 174 365 174Q464 174 509 209T555 301Q555 340 522 373T395 453Q278 510 214 583T150 756ZM506 958Q455 934 424 890T393 793Q393 742 435 700T580 612Q629
643 655 689T682 782Q682 888 506 958Z" />
<glyph unicode="&#xa8;" horiz-adv-x="1135" d="M416 1382Q416 1460 455 1500T567 1540Q692 1540 692 1432Q692 1359 656 1316T543 1272Q416 1272 416 1382ZM799 1382Q799 1460 838 1500T950 1540Q1075 1540 1075 1432Q1075 1359 1039 1316T926 1272Q799 1272 799 1382Z" />
<glyph unicode="&#xa9;" horiz-adv-x="1704" d="M928 1055Q806 1055 740 971T674 731Q674 406 928 406Q1021 406 1141 451V322Q1076 294 1027 283T920 272Q730 272 623 392T516 731Q516 940 626 1064T930 1188Q1056 1188 1182 1126L1120 1001Q1012 1055 928 1055ZM137
731Q137 931 237 1106T512 1382T889 1483Q1086 1483 1259 1386T1536 1114T1640 731Q1640 527 1540 355T1267 81T889 -20Q682 -20 507 83T235 360T137 731ZM246 731Q246 559 332 410T567 175T889 88Q1062 88 1210 173T1446 407T1534 731Q1534 906 1447 1054T1211
1288T889 1374Q717 1374 568 1288T333 1053T246 731Z" />
<glyph unicode="&#xaa;" horiz-adv-x="772" d="M369 748Q266 748 209 818T152 1016Q152 1133 198 1244T321 1415T498 1475Q563 1475 605 1449T676 1372H684L723 1462H877L719 760H571L580 852H571Q491 748 369 748ZM442 899Q487 899 526 939T591 1060T618 1214Q618
1321 530 1321Q457 1321 407 1225T356 1010Q356 899 442 899Z" />
<glyph unicode="&#xab;" horiz-adv-x="1151" d="M72 569L473 1032L664 877L385 543L520 193L274 90L72 551V569ZM559 569L961 1032L1151 877L872 543L1008 193L762 90L559 551V569Z" />
<glyph unicode="&#xac;" horiz-adv-x="1128" d="M1061 248H842V612H109V831H1061V248Z" />
<glyph unicode="&#xad;" horiz-adv-x="659" d="M41 424L94 674H618L565 424H41Z" />
<glyph unicode="&#xae;" horiz-adv-x="1704" d="M137 731Q137 931 237 1106T512 1382T889 1483Q1086 1483 1259 1386T1536 1114T1640 731Q1640 527 1540 355T1267 81T889 -20Q682 -20 507 83T235 360T137 731ZM246 731Q246 559 332 410T567 175T889 88Q1062 88
1210 173T1446 407T1534 731Q1534 906 1447 1054T1211 1288T889 1374Q717 1374 568 1288T333 1053T246 731ZM1198 913Q1198 836 1151 773T1018 674L1243 291H1083L883 639H774V291H639V1171H874Q1039 1171 1118 1107T1198 913ZM774 762H862Q1055 762 1055 909Q1055
984 1007 1014T862 1044H774V762Z" />
<glyph unicode="&#xaf;" horiz-adv-x="1024" d="M1214 1556H178L223 1757H1260L1214 1556Z" />
<glyph unicode="&#xb0;" horiz-adv-x="877" d="M164 1137Q164 1230 210 1310T338 1437T510 1483Q603 1483 683 1436T810 1309T856 1137Q856 1044 810 964T684 839T510 793Q417 793 337 838T211 963T164 1137ZM354 1137Q354 1074 399 1029T510 983Q576 983 621
1029T666 1137Q666 1200 621 1247T510 1294Q445 1294 400 1247T354 1137Z" />
<glyph unicode="&#xb1;" horiz-adv-x="1128" d="M475 674H109V893H475V1262H694V893H1061V674H694V309H475V674ZM109 0V219H1061V0H109Z" />
<glyph unicode="&#xb2;" horiz-adv-x="776" d="M707 586H59L94 752L367 971Q478 1062 508 1093T552 1152T567 1208Q567 1250 542 1270T481 1290Q395 1290 293 1208L193 1366Q267 1423 349 1453T541 1483Q664 1483 737 1420T811 1260Q811 1190 789 1137T719 1034T530
881L401 786H748L707 586Z" />
<glyph unicode="&#xb3;" horiz-adv-x="776" d="M813 1270Q813 1184 762 1127T596 1038V1030Q750 997 750 856Q750 725 643 647T358 569Q283 569 213 584T92 625V817Q217 745 346 745Q422 745 471 775T520 864Q520 901 494 926T406 952H279L313 1112H403Q487 1112
535 1140T584 1225Q584 1265 558 1285T487 1305Q401 1305 299 1239L217 1389Q359 1481 530 1481Q660 1481 736 1426T813 1270Z" />
<glyph unicode="&#xb4;" horiz-adv-x="1135" d="M483 1266Q562 1354 705 1569H1040V1552Q994 1496 886 1400T692 1241H483V1266Z" />
<glyph unicode="&#xb5;" horiz-adv-x="1249" d="M424 348Q424 288 455 256T535 223Q625 223 697 329T815 649L913 1118H1214L977 0H752L770 176H760Q643 -20 494 -20Q443 -20 405 -1T346 47H336Q323 -58 316 -101T236 -492H-68L274 1118H575L440 473Q424 403 424 348Z" />
<glyph unicode="&#xb6;" horiz-adv-x="1341" d="M1202 -260H1040V1356H874V-260H713V559Q651 541 567 541Q351 541 249 666T147 1042Q147 1298 254 1427T598 1556H1202V-260Z" />
<glyph unicode="&#xb7;" horiz-adv-x="584" d="M131 695Q131 785 184 839T335 893Q403 893 444 855T485 748Q485 661 430 607T286 553Q213 553 172 590T131 695Z" />
<glyph unicode="&#xb8;" horiz-adv-x="420" d="M262 -250Q262 -366 179 -429T-55 -492Q-141 -492 -207 -469V-301Q-144 -324 -82 -324Q20 -324 20 -242Q20 -208 -11 -186T-121 -154L-25 0H160L121 -72Q262 -121 262 -250Z" />
<glyph unicode="&#xb9;" horiz-adv-x="776" d="M528 1462H735L549 586H303L387 983Q411 1092 442 1190Q426 1175 362 1130L231 1049L129 1214L528 1462Z" />
<glyph unicode="&#xba;" horiz-adv-x="754" d="M809 1190Q809 1062 761 958T628 801T432 748Q298 748 230 823T162 1034Q162 1231 266 1353T543 1475Q672 1475 740 1402T809 1190ZM522 1311Q458 1311 415 1222T371 1022Q371 911 451 911Q514 911 556 996T598 1204Q598
1311 522 1311Z" />
<glyph unicode="&#xbb;" horiz-adv-x="1151" d="M1079 553L678 90L487 245L766 579L631 929L877 1032L1079 571V553ZM592 553L190 90L0 245L279 579L143 929L389 1032L592 571V553Z" />
<glyph unicode="&#xbc;" horiz-adv-x="1804" d="M1500 1462L416 0H177L1264 1462H1500ZM752 1462H959L773 586H527L611 983Q635 1092 666 1190Q650 1175 586 1130L455 1049L353 1214L752 1462ZM1573 152H1454L1422 1H1184L1217 152H844L875 326L1350 883H1610L1489
320H1608L1573 152ZM1252 320L1310 551L1332 625Q1319 605 1289 567T1078 320H1252Z" />
<glyph unicode="&#xbd;" horiz-adv-x="1804" d="M1500 1462L416 0H177L1264 1462H1500ZM752 1462H959L773 586H527L611 983Q635 1092 666 1190Q650 1175 586 1130L455 1049L353 1214L752 1462ZM1588 1H940L975 167L1248 386Q1359 477 1389 508T1433 567T1448 623Q1448
665 1423 685T1362 705Q1276 705 1174 623L1074 781Q1148 838 1230 868T1422 898Q1545 898 1618 835T1692 675Q1692 605 1670 552T1600 449T1411 296L1282 201H1629L1588 1Z" />
<glyph unicode="&#xbe;" horiz-adv-x="1804" d="M854 1270Q854 1184 803 1127T637 1038V1030Q791 997 791 856Q791 725 684 647T399 569Q324 569 254 584T133 625V817Q258 745 387 745Q463 745 512 775T561 864Q561 901 535 926T447 952H320L354 1112H444Q528
1112 576 1140T625 1225Q625 1265 599 1285T528 1305Q442 1305 340 1239L258 1389Q400 1481 571 1481Q701 1481 777 1426T854 1270ZM1631 1462L547 0H308L1395 1462H1631ZM1634 152H1515L1483 1H1245L1278 152H905L936 326L1411 883H1671L1550 320H1669L1634 152ZM1313
320L1371 551L1393 625Q1380 605 1350 567T1139 320H1313Z" />
<glyph unicode="&#xbf;" horiz-adv-x="940" d="M678 606Q652 461 598 372T418 197Q296 114 262 71T227 -27Q227 -145 360 -145Q410 -145 466 -129T668 -45L760 -266Q539 -391 315 -391Q138 -391 35 -304T-68 -59Q-68 23 -40 89T44 213T233 360Q327 423 363 471T420
606H678ZM772 977Q772 885 717 832T567 778Q499 778 459 816T418 924Q418 1010 472 1064T616 1118Q689 1118 730 1081T772 977Z" />
<glyph unicode="&#xc0;" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827ZM965 1579H781Q710 1648 643 1732T539 1886V1907H850Q886 1759 965 1604V1579Z" />
<glyph unicode="&#xc1;" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827ZM735 1604Q814 1692 957 1907H1292V1890Q1246 1834 1138 1738T944 1579H735V1604Z" />
<glyph unicode="&#xc2;" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827ZM1235 1579H1037Q974 1632 875 1747Q770 1659 643 1579H426V1604Q489 1661
579 1751T721 1907H1059Q1081 1853 1133 1765T1235 1604V1579Z" />
<glyph unicode="&#xc3;" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827ZM999 1579Q950 1579 913 1595T843 1631T782 1667T719 1684Q688 1684 664 1656T625
1577H448Q507 1886 729 1886Q778 1886 816 1870T888 1834T950 1798T1010 1782Q1044 1782 1068 1807T1114 1888H1286Q1220 1579 999 1579Z" />
<glyph unicode="&#xc4;" horiz-adv-x="1286" d="M842 348H369L197 0H-123L643 1468H1016L1163 0H866L842 348ZM827 608L801 958L796 1091L793 1247H788Q734 1100 682 993L494 608H827ZM535 1720Q535 1798 574 1838T686 1878Q811 1878 811 1770Q811 1697 775 1654T662
1610Q535 1610 535 1720ZM918 1720Q918 1798 957 1838T1069 1878Q1194 1878 1194 1770Q1194 1697 1158 1654T1045 1610Q918 1610 918 1720Z" />
<glyph unicode="&#xc5;" horiz-adv-x="1286" d="M1087 1571Q1087 1468 1022 1403L1163 0H866L842 348H369L197 0H-123L623 1432Q586 1485 586 1569Q586 1677 653 1741T834 1806Q943 1806 1015 1742T1087 1571ZM827 608L801 958L796 1093L793 1247H788Q734 1100
682 993L494 608H827ZM930 1569Q930 1614 903 1639T834 1665Q792 1665 765 1640T737 1569Q737 1524 761 1499T834 1473Q876 1473 903 1498T930 1569Z" />
<glyph unicode="&#xc6;" horiz-adv-x="1833" d="M1593 0H776L850 348H424L205 0H-123L799 1462H1903L1849 1208H1337L1270 887H1747L1692 633H1214L1135 256H1647L1593 0ZM905 608L1032 1208H952L588 608H905Z" />
<glyph unicode="&#xc7;" horiz-adv-x="1253" d="M905 1227Q773 1227 668 1146T498 907T434 569Q434 402 502 321T721 240Q867 240 1059 317V57Q860 -20 659 -20Q405 -20 264 129T123 553Q123 815 227 1035T505 1370T905 1485Q1030 1485 1127 1463T1335 1380L1217
1130Q1111 1189 1042 1208T905 1227ZM825 -250Q825 -366 742 -429T508 -492Q422 -492 356 -469V-301Q419 -324 481 -324Q583 -324 583 -242Q583 -208 552 -186T442 -154L538 0H723L684 -72Q825 -121 825 -250Z" />
<glyph unicode="&#xc8;" horiz-adv-x="1110" d="M870 0H53L362 1462H1180L1126 1208H614L547 887H1024L969 633H492L412 256H924L870 0ZM906 1579H722Q651 1648 584 1732T480 1886V1907H791Q827 1759 906 1604V1579Z" />
<glyph unicode="&#xc9;" horiz-adv-x="1110" d="M870 0H53L362 1462H1180L1126 1208H614L547 887H1024L969 633H492L412 256H924L870 0ZM608 1604Q687 1692 830 1907H1165V1890Q1119 1834 1011 1738T817 1579H608V1604Z" />
<glyph unicode="&#xca;" horiz-adv-x="1110" d="M870 0H53L362 1462H1180L1126 1208H614L547 887H1024L969 633H492L412 256H924L870 0ZM1177 1579H979Q916 1632 817 1747Q712 1659 585 1579H368V1604Q431 1661 521 1751T663 1907H1001Q1023 1853 1075 1765T1177
1604V1579Z" />
<glyph unicode="&#xcb;" horiz-adv-x="1110" d="M870 0H53L362 1462H1180L1126 1208H614L547 887H1024L969 633H492L412 256H924L870 0ZM457 1720Q457 1798 496 1838T608 1878Q733 1878 733 1770Q733 1697 697 1654T584 1610Q457 1610 457 1720ZM840 1720Q840
1798 879 1838T991 1878Q1116 1878 1116 1770Q1116 1697 1080 1654T967 1610Q840 1610 840 1720Z" />
<glyph unicode="&#xcc;" horiz-adv-x="784" d="M588 0H-59L-23 176L164 258L365 1204L213 1286L250 1462H897L860 1286L670 1204L469 258L625 176L588 0ZM708 1579H524Q453 1648 386 1732T282 1886V1907H593Q629 1759 708 1604V1579Z" />
<glyph unicode="&#xcd;" horiz-adv-x="784" d="M588 0H-59L-23 176L164 258L365 1204L213 1286L250 1462H897L860 1286L670 1204L469 258L625 176L588 0ZM455 1604Q534 1692 677 1907H1012V1890Q966 1834 858 1738T664 1579H455V1604Z" />
<glyph unicode="&#xce;" horiz-adv-x="784" d="M588 0H-59L-23 176L164 258L365 1204L213 1286L250 1462H897L860 1286L670 1204L469 258L625 176L588 0ZM978 1579H780Q717 1632 618 1747Q513 1659 386 1579H169V1604Q232 1661 322 1751T464 1907H802Q824 1853
876 1765T978 1604V1579Z" />
<glyph unicode="&#xcf;" horiz-adv-x="784" d="M588 0H-59L-23 176L164 258L365 1204L213 1286L250 1462H897L860 1286L670 1204L469 258L625 176L588 0ZM282 1720Q282 1798 321 1838T433 1878Q558 1878 558 1770Q558 1697 522 1654T409 1610Q282 1610 282 1720ZM665
1720Q665 1798 704 1838T816 1878Q941 1878 941 1770Q941 1697 905 1654T792 1610Q665 1610 665 1720Z" />
<glyph unicode="&#xd0;" horiz-adv-x="1386" d="M1323 909Q1323 629 1225 423T942 108T504 0H53L178 596H37L92 850H231L362 1462H758Q1028 1462 1175 1319T1323 909ZM518 256Q666 256 776 332T948 555T1010 893Q1010 1047 938 1127T729 1208H614L539 850H776L721
596H483L412 256H518Z" />
<glyph unicode="&#xd1;" horiz-adv-x="1546" d="M1247 0H905L549 1106H539L531 1047Q504 833 473 688L328 0H53L362 1462H719L1059 385H1067Q1102 608 1135 770L1282 1462H1556L1247 0ZM1114 1579Q1065 1579 1028 1595T958 1631T897 1667T834 1684Q803 1684 779
1656T740 1577H563Q622 1886 844 1886Q893 1886 931 1870T1003 1834T1065 1798T1125 1782Q1159 1782 1183 1807T1229 1888H1401Q1335 1579 1114 1579Z" />
<glyph unicode="&#xd2;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688
240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227ZM1053 1579H869Q798 1648 731 1732T627 1886V1907H938Q974 1759 1053 1604V1579Z" />
<glyph unicode="&#xd3;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688
240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227ZM753 1604Q832 1692 975 1907H1310V1890Q1264 1834 1156 1738T962 1579H753V1604Z" />
<glyph unicode="&#xd4;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688
240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227ZM1308 1579H1110Q1047 1632 948 1747Q843 1659 716 1579H499V1604Q562 1661 652 1751T794 1907H1132Q1154 1853 1206 1765T1308 1604V1579Z" />
<glyph unicode="&#xd5;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688
240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227ZM1071 1579Q1022 1579 985 1595T915 1631T854 1667T791 1684Q760 1684 736 1656T697 1577H520Q579 1886 801 1886Q850 1886 888 1870T960 1834T1022 1798T1082 1782Q1116 1782 1140 1807T1186
1888H1358Q1292 1579 1071 1579Z" />
<glyph unicode="&#xd6;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q410 -20 267 127T123 537Q123 802 222 1024T495 1366T897 1485Q1152 1485 1292 1341T1432 938ZM872 1227Q751 1227 650 1136T492 884T434 537Q434 390 500 315T688
240Q809 240 908 327T1064 573T1120 930Q1120 1072 1055 1149T872 1227ZM604 1720Q604 1798 643 1838T755 1878Q880 1878 880 1770Q880 1697 844 1654T731 1610Q604 1610 604 1720ZM987 1720Q987 1798 1026 1838T1138 1878Q1263 1878 1263 1770Q1263 1697 1227
1654T1114 1610Q987 1610 987 1720Z" />
<glyph unicode="&#xd7;" horiz-adv-x="1128" d="M428 723L129 1024L281 1178L582 879L887 1178L1040 1028L735 723L1036 420L887 268L582 569L281 270L131 422L428 723Z" />
<glyph unicode="&#xd8;" horiz-adv-x="1495" d="M1432 938Q1432 655 1333 432T1062 94T666 -20Q486 -20 362 51L254 -86L100 29L221 180Q123 318 123 537Q123 802 222 1024T495 1366T897 1485Q1079 1485 1202 1409L1307 1540L1458 1423L1341 1278Q1432 1144 1432
938ZM870 1233Q744 1233 641 1142T481 889T424 537Q424 505 432 436L1028 1190Q959 1233 870 1233ZM1133 930L1128 1010L539 270Q598 233 692 233Q816 233 918 322T1076 569T1133 930Z" />
<glyph unicode="&#xd9;" horiz-adv-x="1415" d="M1434 1462L1233 516Q1176 250 1015 115T596 -20Q384 -20 263 93T141 401Q141 473 156 539L352 1462H657L463 543Q446 469 446 418Q446 240 635 240Q758 240 830 316T934 545L1128 1462H1434ZM1002 1579H818Q747
1648 680 1732T576 1886V1907H887Q923 1759 1002 1604V1579Z" />
<glyph unicode="&#xda;" horiz-adv-x="1415" d="M1434 1462L1233 516Q1176 250 1015 115T596 -20Q384 -20 263 93T141 401Q141 473 156 539L352 1462H657L463 543Q446 469 446 418Q446 240 635 240Q758 240 830 316T934 545L1128 1462H1434ZM757 1604Q836 1692
979 1907H1314V1890Q1268 1834 1160 1738T966 1579H757V1604Z" />
<glyph unicode="&#xdb;" horiz-adv-x="1415" d="M1434 1462L1233 516Q1176 250 1015 115T596 -20Q384 -20 263 93T141 401Q141 473 156 539L352 1462H657L463 543Q446 469 446 418Q446 240 635 240Q758 240 830 316T934 545L1128 1462H1434ZM1284 1579H1086Q1023
1632 924 1747Q819 1659 692 1579H475V1604Q538 1661 628 1751T770 1907H1108Q1130 1853 1182 1765T1284 1604V1579Z" />
<glyph unicode="&#xdc;" horiz-adv-x="1415" d="M1434 1462L1233 516Q1176 250 1015 115T596 -20Q384 -20 263 93T141 401Q141 473 156 539L352 1462H657L463 543Q446 469 446 418Q446 240 635 240Q758 240 830 316T934 545L1128 1462H1434ZM584 1720Q584 1798
623 1838T735 1878Q860 1878 860 1770Q860 1697 824 1654T711 1610Q584 1610 584 1720ZM967 1720Q967 1798 1006 1838T1118 1878Q1243 1878 1243 1770Q1243 1697 1207 1654T1094 1610Q967 1610 967 1720Z" />
<glyph unicode="&#xdd;" horiz-adv-x="1155" d="M627 870L1001 1462H1343L725 559L606 0H303L422 559L186 1462H498L627 870ZM606 1604Q685 1692 828 1907H1163V1890Q1117 1834 1009 1738T815 1579H606V1604Z" />
<glyph unicode="&#xde;" horiz-adv-x="1241" d="M1192 807Q1192 564 1022 429T555 293H420L358 0H53L362 1462H668L618 1233H702Q944 1233 1068 1127T1192 807ZM475 547H580Q720 547 802 614T885 799Q885 979 690 979H567L475 547Z" />
<glyph unicode="&#xdf;" horiz-adv-x="1350" d="M846 1567Q1054 1567 1177 1477T1300 1237Q1300 1123 1251 1045T1073 893Q1000 851 977 825T954 770Q954 747 976 721T1055 651Q1162 568 1199 501T1237 350Q1237 180 1114 80T776 -20Q589 -20 479 41V281Q607 203
737 203Q838 203 885 236T932 322Q932 362 906 397T797 494Q703 566 668 624T633 750Q633 834 678 895T840 1022Q906 1059 944 1098T983 1194Q983 1256 944 1292T819 1329Q723 1329 663 1278T578 1106L324 -113Q281 -311 177 -401T-100 -492Q-190 -492 -260 -467V-225Q-199
-246 -145 -246Q-12 -246 25 -68L279 1139Q326 1363 461 1465T846 1567Z" />
<glyph unicode="&#xe0;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM1125 1241H941Q870 1310 803 1394T699 1548V1569H1010Q1046 1421 1125 1266V1241Z" />
<glyph unicode="&#xe1;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM598 1266Q677 1354 820 1569H1155V1552Q1109 1496 1001 1400T807 1241H598V1266Z" />
<glyph unicode="&#xe2;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM1120 1241H922Q859 1294 760 1409Q655 1321 528 1241H311V1266Q374 1323 464 1413T606 1569H944Q966 1515 1018 1427T1120 1266V1241Z" />
<glyph unicode="&#xe3;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM884 1241Q835 1241 798 1257T728 1293T667 1329T604 1346Q573 1346 549 1318T510 1239H333Q392 1548 614 1548Q663 1548 701 1532T773 1496T835 1460T895 1444Q929 1444 953 1469T999
1550H1171Q1105 1241 884 1241Z" />
<glyph unicode="&#xe4;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM416 1382Q416 1460 455 1500T567 1540Q692 1540 692 1432Q692 1359 656 1316T543 1272Q416 1272 416 1382ZM799 1382Q799 1460 838 1500T950 1540Q1075 1540 1075 1432Q1075 1359 1039
1316T926 1272Q799 1272 799 1382Z" />
<glyph unicode="&#xe5;" horiz-adv-x="1217" d="M406 -20Q259 -20 175 86T90 385Q90 583 162 762T351 1040T608 1139Q705 1139 775 1097T885 975H893L950 1118H1182L944 0H719L733 145H725Q591 -20 406 -20ZM524 223Q593 223 657 290T760 471T799 731Q799 802
761 848T659 895Q591 895 530 823T432 633T395 399Q395 311 428 267T524 223ZM1279 1479Q1279 1372 1209 1306T1025 1239Q915 1239 846 1302T777 1477Q777 1586 845 1650T1025 1714Q1135 1714 1207 1649T1279 1479ZM1122 1477Q1122 1522 1095 1547T1025 1573Q983
1573 956 1548T929 1477Q929 1432 953 1406T1025 1380Q1067 1380 1094 1406T1122 1477Z" />
<glyph unicode="&#xe6;" horiz-adv-x="1786" d="M1206 -20Q1097 -20 1027 7T909 94L893 0H705L719 145H709Q635 53 561 17T399 -20Q253 -20 172 89T90 385Q90 585 158 760T343 1037T602 1139Q695 1139 758 1102T872 975H883L940 1118H1128L1110 1028Q1154 1077
1230 1108T1399 1139Q1556 1139 1645 1056T1735 834Q1735 647 1568 546T1092 444H1040L1038 425V406Q1038 310 1093 259T1253 207Q1319 207 1405 230T1567 293V66Q1388 -20 1206 -20ZM518 223Q590 223 652 292T751 479T788 719Q788 799 755 847T653 895Q585 895
525 826T430 641T395 399Q395 315 427 269T518 223ZM1341 922Q1253 922 1175 842T1073 647H1118Q1273 647 1359 695T1446 827Q1446 922 1341 922Z" />
<glyph unicode="&#xe7;" horiz-adv-x="989" d="M506 -20Q305 -20 198 87T90 391Q90 603 164 776T374 1044T682 1139Q864 1139 1010 1067L918 838Q864 861 812 878T694 895Q609 895 541 831T434 656T395 416Q395 320 440 272T567 223Q643 223 708 246T842 305V59Q690
-20 506 -20ZM653 -250Q653 -366 570 -429T336 -492Q250 -492 184 -469V-301Q247 -324 309 -324Q411 -324 411 -242Q411 -208 380 -186T270 -154L366 0H551L512 -72Q653 -121 653 -250Z" />
<glyph unicode="&#xe8;" horiz-adv-x="1141" d="M696 922Q608 922 530 842T428 647H473Q628 647 714 695T801 827Q801 922 696 922ZM532 -20Q322 -20 206 93T90 412Q90 619 172 789T396 1049T715 1139Q892 1139 991 1058T1090 834Q1090 647 923 546T446 444H395L393
423V403Q393 312 444 260T592 207Q679 207 750 226T922 293V66Q750 -20 532 -20ZM1105 1241H921Q850 1310 783 1394T679 1548V1569H990Q1026 1421 1105 1266V1241Z" />
<glyph unicode="&#xe9;" horiz-adv-x="1141" d="M696 922Q608 922 530 842T428 647H473Q628 647 714 695T801 827Q801 922 696 922ZM532 -20Q322 -20 206 93T90 412Q90 619 172 789T396 1049T715 1139Q892 1139 991 1058T1090 834Q1090 647 923 546T446 444H395L393
423V403Q393 312 444 260T592 207Q679 207 750 226T922 293V66Q750 -20 532 -20ZM528 1266Q607 1354 750 1569H1085V1552Q1039 1496 931 1400T737 1241H528V1266Z" />
<glyph unicode="&#xea;" horiz-adv-x="1141" d="M696 922Q608 922 530 842T428 647H473Q628 647 714 695T801 827Q801 922 696 922ZM532 -20Q322 -20 206 93T90 412Q90 619 172 789T396 1049T715 1139Q892 1139 991 1058T1090 834Q1090 647 923 546T446 444H395L393
423V403Q393 312 444 260T592 207Q679 207 750 226T922 293V66Q750 -20 532 -20ZM1357 1241H1159Q1096 1294 997 1409Q892 1321 765 1241H548V1266Q611 1323 701 1413T843 1569H1181Q1203 1515 1255 1427T1357 1266V1241Z" />
<glyph unicode="&#xeb;" horiz-adv-x="1141" d="M696 922Q608 922 530 842T428 647H473Q628 647 714 695T801 827Q801 922 696 922ZM532 -20Q322 -20 206 93T90 412Q90 619 172 789T396 1049T715 1139Q892 1139 991 1058T1090 834Q1090 647 923 546T446 444H395L393
423V403Q393 312 444 260T592 207Q679 207 750 226T922 293V66Q750 -20 532 -20ZM640 1382Q640 1460 679 1500T791 1540Q916 1540 916 1432Q916 1359 880 1316T767 1272Q640 1272 640 1382ZM1023 1382Q1023 1460 1062 1500T1174 1540Q1299 1540 1299 1432Q1299
1359 1263 1316T1150 1272Q1023 1272 1023 1382Z" />
<glyph unicode="&#xec;" horiz-adv-x="608" d="M338 0H37L274 1118H575L338 0ZM579 1241H395Q324 1310 257 1394T153 1548V1569H464Q500 1421 579 1266V1241Z" />
<glyph unicode="&#xed;" horiz-adv-x="608" d="M338 0H37L274 1118H575L338 0ZM291 1266Q370 1354 513 1569H848V1552Q802 1496 694 1400T500 1241H291V1266Z" />
<glyph unicode="&#xee;" horiz-adv-x="608" d="M338 0H37L274 1118H575L338 0ZM845 1241H647Q584 1294 485 1409Q380 1321 253 1241H36V1266Q99 1323 189 1413T331 1569H669Q691 1515 743 1427T845 1266V1241Z" />
<glyph unicode="&#xef;" horiz-adv-x="608" d="M338 0H37L274 1118H575L338 0ZM145 1382Q145 1460 184 1500T296 1540Q421 1540 421 1432Q421 1359 385 1316T272 1272Q145 1272 145 1382ZM528 1382Q528 1460 567 1500T679 1540Q804 1540 804 1432Q804 1359 768
1316T655 1272Q528 1272 528 1382Z" />
<glyph unicode="&#xf0;" horiz-adv-x="1182" d="M618 1300Q580 1338 494 1380L612 1567Q750 1504 844 1427L1081 1559L1157 1407L965 1300Q1046 1194 1078 1070T1110 795Q1110 546 1041 363T837 80T514 -20Q298 -20 185 90T72 406Q72 571 136 707T317 919T582
995Q754 995 844 870H854Q834 1077 737 1171L506 1040L418 1188L618 1300ZM528 205Q594 205 650 260T739 409T772 602Q772 679 734 724T625 770Q552 770 495 717T407 574T375 377Q375 296 414 251T528 205Z" />
<glyph unicode="&#xf1;" horiz-adv-x="1237" d="M977 0H676L813 653Q829 721 829 772Q829 895 721 895Q629 895 554 781T436 463L338 0H37L274 1118H504L483 911H492Q638 1139 844 1139Q982 1139 1057 1056T1133 817Q1133 744 1110 637L977 0ZM911 1241Q862 1241
825 1257T755 1293T694 1329T631 1346Q600 1346 576 1318T537 1239H360Q419 1548 641 1548Q690 1548 728 1532T800 1496T862 1460T922 1444Q956 1444 980 1469T1026 1550H1198Q1132 1241 911 1241Z" />
<glyph unicode="&#xf2;" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684
1139Q880 1139 994 1021T1108 696ZM1101 1241H917Q846 1310 779 1394T675 1548V1569H986Q1022 1421 1101 1266V1241Z" />
<glyph unicode="&#xf3;" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684
1139Q880 1139 994 1021T1108 696ZM571 1266Q650 1354 793 1569H1128V1552Q1082 1496 974 1400T780 1241H571V1266Z" />
<glyph unicode="&#xf4;" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684
1139Q880 1139 994 1021T1108 696ZM1365 1241H1167Q1104 1294 1005 1409Q900 1321 773 1241H556V1266Q619 1323 709 1413T851 1569H1189Q1211 1515 1263 1427T1365 1266V1241Z" />
<glyph unicode="&#xf5;" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684
1139Q880 1139 994 1021T1108 696ZM1121 1241Q1072 1241 1035 1257T965 1293T904 1329T841 1346Q810 1346 786 1318T747 1239H570Q629 1548 851 1548Q900 1548 938 1532T1010 1496T1072 1460T1132 1444Q1166 1444 1190 1469T1236 1550H1408Q1342 1241 1121 1241Z"
/>
<glyph unicode="&#xf6;" horiz-adv-x="1198" d="M805 696Q805 893 662 893Q587 893 528 832T431 653T393 410Q393 225 543 225Q618 225 678 286T771 457T805 696ZM1108 696Q1108 485 1038 322T834 70T518 -20Q323 -20 207 97T90 410Q90 623 161 789T368 1047T684
1139Q880 1139 994 1021T1108 696ZM661 1382Q661 1460 700 1500T812 1540Q937 1540 937 1432Q937 1359 901 1316T788 1272Q661 1272 661 1382ZM1044 1382Q1044 1460 1083 1500T1195 1540Q1320 1540 1320 1432Q1320 1359 1284 1316T1171 1272Q1044 1272 1044 1382Z"
/>
<glyph unicode="&#xf7;" horiz-adv-x="1128" d="M109 612V831H1061V612H109ZM444 373Q444 449 481 486T584 524Q650 524 686 485T723 373Q723 303 686 262T584 221Q519 221 482 260T444 373ZM444 1071Q444 1146 481 1184T584 1223Q651 1223 687 1183T723 1071Q723
1001 686 961T584 920Q519 920 482 959T444 1071Z" />
<glyph unicode="&#xf8;" horiz-adv-x="1198" d="M1108 696Q1108 485 1038 322T834 70T518 -20Q395 -20 293 33L184 -102L43 6L162 154Q90 261 90 410Q90 623 161 789T368 1047T684 1139Q815 1139 911 1083L981 1171L1126 1061L1042 956Q1108 849 1108 696ZM662
903Q580 903 519 846T421 674T385 426L750 879Q715 903 662 903ZM543 215Q616 215 675 270T773 437T815 682L457 238Q468 230 492 223T543 215Z" />
<glyph unicode="&#xf9;" horiz-adv-x="1237" d="M262 1118H563L426 465Q410 397 410 346Q410 223 518 223Q610 223 685 337T803 655L901 1118H1202L965 0H735L756 207H745Q600 -20 395 -20Q257 -20 184 62T111 301Q111 394 135 514L262 1118ZM1101 1241H917Q846
1310 779 1394T675 1548V1569H986Q1022 1421 1101 1266V1241Z" />
<glyph unicode="&#xfa;" horiz-adv-x="1237" d="M262 1118H563L426 465Q410 397 410 346Q410 223 518 223Q610 223 685 337T803 655L901 1118H1202L965 0H735L756 207H745Q600 -20 395 -20Q257 -20 184 62T111 301Q111 394 135 514L262 1118ZM610 1266Q689 1354
832 1569H1167V1552Q1121 1496 1013 1400T819 1241H610V1266Z" />
<glyph unicode="&#xfb;" horiz-adv-x="1237" d="M262 1118H563L426 465Q410 397 410 346Q410 223 518 223Q610 223 685 337T803 655L901 1118H1202L965 0H735L756 207H745Q600 -20 395 -20Q257 -20 184 62T111 301Q111 394 135 514L262 1118ZM1143 1241H945Q882
1294 783 1409Q678 1321 551 1241H334V1266Q397 1323 487 1413T629 1569H967Q989 1515 1041 1427T1143 1266V1241Z" />
<glyph unicode="&#xfc;" horiz-adv-x="1237" d="M262 1118H563L426 465Q410 397 410 346Q410 223 518 223Q610 223 685 337T803 655L901 1118H1202L965 0H735L756 207H745Q600 -20 395 -20Q257 -20 184 62T111 301Q111 394 135 514L262 1118ZM430 1382Q430 1460
469 1500T581 1540Q706 1540 706 1432Q706 1359 670 1316T557 1272Q430 1272 430 1382ZM813 1382Q813 1460 852 1500T964 1540Q1089 1540 1089 1432Q1089 1359 1053 1316T940 1272Q813 1272 813 1382Z" />
<glyph unicode="&#xfd;" horiz-adv-x="1063" d="M102 1118H397L453 600Q465 495 465 307H473Q493 358 517 426T582 580L842 1118H1169L489 -160Q312 -492 6 -492Q-84 -492 -141 -473V-233Q-73 -246 -25 -246Q59 -246 122 -198T240 -49L266 0L102 1118ZM497 1266Q576
1354 719 1569H1054V1552Q1008 1496 900 1400T706 1241H497V1266Z" />
<glyph unicode="&#xfe;" horiz-adv-x="1219" d="M813 1139Q963 1139 1045 1033T1128 731Q1128 532 1059 350T877 74T627 -20Q449 -20 356 143H348Q336 -16 305 -152L233 -492H-68L367 1556H668L602 1249Q573 1118 522 969H530Q661 1139 813 1139ZM682 895Q611
895 552 830T457 646T420 399Q420 319 453 271T559 223Q630 223 691 292T788 478T823 719Q823 807 786 851T682 895Z" />
<glyph unicode="&#xff;" horiz-adv-x="1063" d="M102 1118H397L453 600Q465 495 465 307H473Q493 358 517 426T582 580L842 1118H1169L489 -160Q312 -492 6 -492Q-84 -492 -141 -473V-233Q-73 -246 -25 -246Q59 -246 122 -198T240 -49L266 0L102 1118ZM585 1382Q585
1460 624 1500T736 1540Q861 1540 861 1432Q861 1359 825 1316T712 1272Q585 1272 585 1382ZM968 1382Q968 1460 1007 1500T1119 1540Q1244 1540 1244 1432Q1244 1359 1208 1316T1095 1272Q968 1272 968 1382Z" />
<glyph unicode="&#x2013;" horiz-adv-x="983" d="M41 436L90 666H942L893 436H41Z" />
<glyph unicode="&#x2014;" horiz-adv-x="1966" d="M41 436L90 666H1925L1876 436H41Z" />
<glyph unicode="&#x2018;" horiz-adv-x="440" d="M123 961L115 983Q218 1210 377 1462H602Q511 1249 408 961H123Z" />
<glyph unicode="&#x2019;" horiz-adv-x="440" d="M586 1462L594 1440Q491 1213 332 961H106Q195 1167 301 1462H586Z" />
<glyph unicode="&#x201a;" horiz-adv-x="569" d="M377 238L385 215Q282 -12 123 -264H-102Q-14 -57 92 238H377Z" />
<glyph unicode="&#x201c;" horiz-adv-x="887" d="M569 961L561 983Q664 1210 823 1462H1049Q952 1235 854 961H569ZM123 961L115 983Q218 1210 377 1462H602Q511 1249 408 961H123Z" />
<glyph unicode="&#x201d;" horiz-adv-x="887" d="M586 1462L594 1440Q491 1213 332 961H106Q195 1167 301 1462H586ZM1032 1462L1040 1440Q937 1213 778 961H553Q576 1014 599 1072T748 1462H1032Z" />
<glyph unicode="&#x201e;" horiz-adv-x="1018" d="M377 238L385 215Q282 -12 123 -264H-102Q-14 -57 92 238H377ZM825 238L834 215Q734 -6 571 -264H346Q370 -207 395 -146T541 238H825Z" />
<glyph unicode="&#x2022;" horiz-adv-x="770" d="M139 748Q139 902 213 983T426 1065Q563 1065 638 983T713 748Q713 596 638 513T426 430Q288 430 214 513T139 748Z" />
<glyph unicode="&#x2039;" horiz-adv-x="664" d="M72 569L473 1032L664 877L385 543L520 193L274 90L72 551V569Z" />
<glyph unicode="&#x203a;" horiz-adv-x="664" d="M592 553L190 90L0 245L279 579L143 929L389 1032L592 571V553Z" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 53 KiB

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