Compare commits

...

295 Commits

Author SHA1 Message Date
Vincent LAURENT c4c2183132 Preserve the last text size for a pdf session 2023-07-22 01:18:40 +02:00
Gabriel Poma c375b43b14 Modal to inform that changes will be lost if the page is reloaded or closed 2023-07-19 14:34:41 +02:00
Vincent LAURENT 06bac144dd Bug js all cases was not planned 2023-07-18 10:36:38 +02:00
Vincent LAURENT d5f4eca0ff Button to display help point 2023-07-18 10:28:00 +02:00
Vincent LAURENT f4bab4ac5f Help message for signing a pdf with several people for the first time, because the interface could suggest that creating the signature is enough. 2023-07-18 01:57:22 +02:00
Vincent LAURENT 26aac0af94 Merge branch 'master' of github.com:24eme/signaturepdf 2023-05-20 02:08:15 +02:00
Vincent LAURENT f9c413d85a Retrieving the current commit number to display it in the footer of each page 2023-05-20 02:05:24 +02:00
Gabriel 00bf081319
Merge pull request #60 from kosssi/master 2023-05-15 14:35:25 +02:00
Gabriel 1023ebd347
Merge pull request #59 from xgaia/fix/docker 2023-05-12 16:36:10 +02:00
Simon C 5f703890ac feat: Update php version on Docker image 2023-05-12 16:01:32 +02:00
xgaia 37a678e9db chore: fix owner of the data dir 2023-05-12 12:22:58 +02:00
Vincent LAURENT 357bffc55f organization: The first upload field allows you to choose several files 2023-05-04 00:45:20 +02:00
Vincent LAURENT fee27a0311 Force js update 2023-05-03 01:49:14 +02:00
Étienne Deparis a1509a554f Message to inform that in private browsing mode (on firefox) the application does not work because opening the cache is impossible 2023-05-03 01:45:37 +02:00
Vincent LAURENT 30e7ffef89 metadata: the variable maxSize was not defined 2023-05-03 01:10:56 +02:00
Vincent LAURENT ada82de930 Force js reload to activate revert commit "prompt rechargement de la
page"
2023-05-03 01:03:46 +02:00
Vincent LAURENT 4646ad35a7 The true value of the PDF_DEMO_LINK option was not working 2023-05-03 01:00:41 +02:00
Vincent LAURENT c19f1af646
metadata: adding pdf-lib dépendance in documentation 2023-05-02 23:54:01 +02:00
Vincent LAURENT d6b6b43ac2 Merge pull request #55 from xgaia/fix/dockerfile
Fix/dockerfile
2023-05-02 23:46:23 +02:00
xgaia 7d4ae2907f style: prettier on README 2023-05-02 23:46:12 +02:00
Gabriel Poma 96b21fb850 Merge branch 'master' of github.com:24eme/signaturepdf 2023-05-02 12:32:09 +02:00
Gabriel Poma 5925d4e4c6 Revert "prompt rechargement de la page"
This reverts commit 818f08d24c.
2023-05-02 12:18:57 +02:00
xgaia c119d8d1cd chore(docker): fix dockerbuild permission issue 2023-04-21 12:32:35 +02:00
Vincent LAURENT de66c05f2a
metadata: Document of configuration defaults fields 2023-04-20 16:36:48 +02:00
Vincent LAURENT 9b36ae02e5 metadata: favicon 2023-04-20 16:29:58 +02:00
Vincent LAURENT e488e20fad metadata: wording 2023-04-20 16:24:23 +02:00
Vincent LAURENT a04e26d2ce metadata: bug loading value 2023-04-20 16:11:53 +02:00
Gabriel Poma c89c8d53dd Merge branch 'metadata' 2023-04-20 15:57:35 +02:00
Gabriel Poma 60044ee371 Merge branch 'master' of github.com:gpoma/signaturepdf 2023-04-20 15:57:30 +02:00
Gabriel Poma c30c89ad60 Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 15:55:58 +02:00
Gabriel Poma 96c663493c filename global + on reprends le filename au dl 2023-04-20 15:55:35 +02:00
Vincent LAURENT 841cc18ed9 metadata: textes 2023-04-20 15:53:50 +02:00
Gabriel Poma 2653effbc8 suppression métadonnées 2023-04-20 15:52:27 +02:00
Vincent LAURENT 9abd4e848b metadata: new tab on each page 2023-04-20 15:47:12 +02:00
Vincent LAURENT 4b0ca82652 metadata: If any field focus on add field 2023-04-20 15:44:59 +02:00
Vincent LAURENT 4965d7878b metadata: autofocus on first input 2023-04-20 15:43:20 +02:00
Vincent LAURENT b30be5b344 metadata: fields list by default define in configuration 2023-04-20 15:37:01 +02:00
Gabriel Poma 8d9f49a3e8 on sort la fct de download 2023-04-20 15:17:49 +02:00
Gabriel Poma 4f4d3a0583 fix enregistrement 2023-04-20 15:11:33 +02:00
Vincent LAURENT 49bf6e50ab Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 14:38:39 +02:00
Vincent LAURENT 6cc0461975 metadata: delete max upload size control 2023-04-20 14:36:09 +02:00
Gabriel Poma 784e15f252 Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-20 13:22:09 +02:00
Gabriel Poma 6bed0fdc1d on sauve les métadonnées et on DL le pdf 2023-04-20 13:21:44 +02:00
Vincent LAURENT 75434aee71 metadata: logo 2023-04-20 13:12:03 +02:00
Vincent LAURENT 6c28618fbe metadata: New formatting of the add metadata form 2023-04-20 00:07:42 +02:00
Vincent LAURENT a2843a329c Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-19 17:27:33 +02:00
Vincent LAURENT 9bdd28c7df metadata: render pdf in right column 2023-04-19 17:26:55 +02:00
Gabriel Poma eab2e0c127 suppression: confirm 2023-04-19 15:42:48 +02:00
Gabriel Poma 011c9094e1 suppression: croix dans input 2023-04-19 15:37:51 +02:00
Gabriel Poma cbea7de840 metadata: suppression metadonnée 2023-04-19 13:00:30 +02:00
Gabriel Poma b037685c57 metadata: nom de la métadonnée requis 2023-04-19 11:03:57 +02:00
Gabriel Poma f9af7f969e Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-19 10:48:47 +02:00
Vincent LAURENT f0eb416ced metadata: adding a new metadata entrie 2023-04-19 09:03:35 +02:00
Vincent LAURENT e2f9637426 metadata: read metadata informations and display it in form input 2023-04-19 02:29:24 +02:00
Vincent LAURENT 186a9cc225 metadata: new tab and page metadata 2023-04-18 23:44:38 +02:00
Gabriel Poma eec274bf0f Merge branch 'metadata' of github.com:24eme/signaturepdf into metadata 2023-04-18 18:59:20 +02:00
Vincent LAURENT 9a63402a2a Documentation precision in the README and in the config.ini file and use of ";" instead of "#" for comments 2023-03-18 00:54:26 +01:00
Vincent LAURENT 68be39ed23 feat: Added option to remove or edit demo pdf link 2023-03-18 00:34:18 +01:00
xgaia c9f0d25027 feat: Add possibility to disable Organization 2023-03-17 11:22:40 +01:00
Vincent LAURENT 7d7e666bc7
Larger version 2022-12-30 16:46:05 +01:00
Vincent LAURENT f27abba511
General logo while waiting for a more personalized version 2022-12-30 16:42:46 +01:00
Gabriel Poma 818f08d24c prompt rechargement de la page
TODO: checker si modification
2022-12-26 18:18:09 +01:00
Vincent LAURENT 5a4309fcf3 [Organization] The download full PDF button is disabled when pages are selected 2022-12-07 22:46:12 +01:00
Vincent LAURENT 1eb0f5101b [Organization] A click on a neutral element of the page deselects the pages 2022-12-07 01:48:24 +01:00
Jonas Chopin-Revel 66cc24abd1 Edit Dockerfile, two changes to reduce the image size and avoid to be as root in the container 2022-12-06 23:04:44 +01:00
Vincent LAURENT 3ee536cff3 Buttons near page dragged not hide 2022-11-20 01:06:28 +01:00
Vincent LAURENT 4f9697d409 Change icon to drag here 2022-11-20 00:54:48 +01:00
Vincent LAURENT e9bd3f9704 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-29 00:21:55 +02:00
Vincent LAURENT b3087129a8 css not use 2022-10-29 00:21:32 +02:00
Gabriel Poma 50ed5b5973 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-21 17:57:47 +02:00
Vincent LAURENT 9cbf0107a2 [Organization] If selection mode it will not active hover action page buttons 2022-10-18 00:02:41 +02:00
Gabriel Poma 4efb3d9ad1 Télécharger plus explicite 2022-10-17 15:50:46 +02:00
Gabriel Poma b979fd3d02 typo 2022-10-17 15:44:48 +02:00
Gabriel Poma ea354ca8cb cursor help 2022-10-17 15:31:43 +02:00
Vincent LAURENT a04123b846 [Organization] Modal to manage docs in mobile 2022-10-17 00:49:32 +02:00
Vincent LAURENT e7dacf528c Formatting home page 2022-10-16 23:38:37 +02:00
Vincent LAURENT 36d0e0df4b [Organization] PDF Document name on mobile truncate 2022-10-15 01:51:00 +02:00
Vincent LAURENT cf418043f2 [Organization] Shorter title 2022-10-14 00:25:26 +02:00
Vincent LAURENT 25fdbbba58 [Organization] Modal to move page in click mode 2022-10-14 00:22:47 +02:00
Vincent LAURENT 9a70988f78 [Organization] Disabled hover style on touch screen 2022-10-11 02:08:34 +02:00
Vincent LAURENT c48ca163d0 Padding adjust 2022-10-11 00:57:47 +02:00
Vincent LAURENT d1431afbae Message below 2022-10-11 00:54:52 +02:00
Vincent LAURENT c66126b865 Message to inform how select page in mobile version and padding top
adapted to select bar menu
2022-10-11 00:52:43 +02:00
Vincent LAURENT 7a91ab5ef5 Button cancel séléction more visible in desktop and mobile mode 2022-10-11 00:42:45 +02:00
Vincent LAURENT bf849a4d30 Text clearer 2022-10-10 23:26:20 +02:00
Vincent LAURENT 4d55c69c91 Footer smaller on mobile 2022-10-10 23:19:41 +02:00
Vincent LAURENT 89a7e24e59 Subtitle more discreet 2022-10-10 23:13:13 +02:00
Vincent LAURENT f3ff57dd00 [Organization] Drag start disabled in dragging mode 2022-10-09 01:51:38 +02:00
Vincent LAURENT e112050623 [Organization] Button global to cancel draging mode 2022-10-09 01:46:17 +02:00
Vincent LAURENT 233c9a8e1e [Organization] Click on drag button active dragging mode 2022-10-09 00:55:13 +02:00
Vincent LAURENT 653036f28d [Organization] Move page on left or right 2022-10-09 00:44:04 +02:00
Vincent LAURENT a848163e2d [Organization] Buttons to drag here near the page to drag are hidden 2022-10-09 00:25:41 +02:00
Vincent LAURENT ccf0afd5e2 [Organization] Button do drag here at left and right of the page 2022-10-09 00:04:44 +02:00
Vincent LAURENT 692245f763 [Organization] Hide top and bottom margin in dragging mode mobile 2022-10-08 11:05:03 +02:00
Vincent LAURENT ed36de8bd6 [Organization] 2 pages per line in mobile and fix scroll in backdrop div 2022-10-08 10:57:39 +02:00
Vincent LAURENT 7ae60e41a8 [Organization] Hide button when a page is selected 2022-10-08 10:34:51 +02:00
Vincent LAURENT 3f9d8f380f [Organization] Display page number when page is selected 2022-10-08 10:28:18 +02:00
Vincent LAURENT 96172423c6 Hover button more visible on pdf page 2022-10-08 01:34:31 +02:00
Vincent LAURENT 1da99c5c29 [Organization] stop propagation when page drop 2022-10-08 01:20:21 +02:00
Vincent LAURENT e45aaa4eb3 [Organization] drag and drop multiple 2022-10-08 01:07:49 +02:00
Vincent LAURENT 2e0044eb64 [Organization] button restore always visible on mobile 2022-10-08 00:32:57 +02:00
Vincent LAURENT 6588ff91b7 [Organization] Blur button after action on mobile 2022-10-08 00:29:16 +02:00
Vincent LAURENT 6c5a1f7006 Disable drag and drop on mobile 2022-10-08 00:09:41 +02:00
Vincent LAURENT e1d6e5bdad Subtitle size smaller on mobile 2022-10-08 00:00:59 +02:00
Vincent LAURENT 3c87f0fb3d Subtitle lighter 2022-10-07 23:55:49 +02:00
Vincent LAURENT 90def30fef [Organization] on mobile mode selection and buttons on top and bottom bar 2022-10-07 23:45:33 +02:00
Vincent LAURENT ad7439a419 Click on page to select it 2022-10-07 19:50:25 +02:00
Vincent LAURENT 3bc1343701 Merge branch 'master' of github.com:24eme/signaturepdf 2022-10-07 17:49:51 +02:00
Vincent LAURENT 8fb39606b6 Subtitle signature 2022-10-07 17:49:30 +02:00
Vincent LAURENT 0a4cce8d56
Encouragement to reference instance 2022-10-07 14:05:07 +02:00
Vincent LAURENT b193b022d7
Add nebulae instance 2022-10-07 13:27:57 +02:00
Vincent LAURENT 09e5985258
Add hostux instance 2022-10-07 11:29:37 +02:00
Vincent LAURENT 7219519f0c Subtitle with explain 2022-10-07 11:04:13 +02:00
Vincent LAURENT eb758a1b9a Drag and drop with only detecting change and direction more easier et
reliable
2022-10-07 10:47:13 +02:00
Vincent LAURENT ebc92a17e5 Fixed drag and drop when page changes line 2022-10-07 01:41:30 +02:00
Vincent LAURENT c300673ab1 Merge branch 'organization' 2022-09-24 09:43:11 +02:00
Vincent LAURENT 6aa3bfe804 Button to cancel move mode smaller 2022-09-24 09:41:26 +02:00
Vincent LAURENT 8216041378 No opacity on sub text in modal like other 2022-09-24 09:37:35 +02:00
Vincent LAURENT e5dd867883 Reorganization of documentation 2022-07-18 21:14:57 +02:00
Vincent LAURENT 8d25b8cafa
Merge pull request #36 from battosai30/patch-1
Installation doc on alpine
2022-07-18 21:08:57 +02:00
battosai30 9cdb4bcf6b
Update README.md 2022-07-08 11:38:39 +02:00
Vincent LAURENT f63d5ee0a5 Fix typo "à main levée" 2022-06-20 09:50:15 +02:00
Vincent LAURENT 91d526ae01 Fix typo "à main levée" 2022-06-20 09:49:42 +02:00
Vincent LAURENT d888334299 Move a page in clique instead of drag and drop for mobile mode 2022-05-31 01:27:13 +02:00
Vincent LAURENT 0739e2e7c3 Right margin on mobile adjust 2022-05-25 01:08:19 +02:00
Vincent LAURENT bc4bded932 Top bar in mobile mode with a button to open sidebar and zoom buttons suitable for mobile 2022-05-24 00:43:34 +02:00
Vincent LAURENT a3236c8368 Upload multiple files at the same time 2022-05-23 23:57:14 +02:00
Vincent LAURENT 9c07ef9355 Button download on mobile at the wrong place 2022-05-21 10:05:17 +02:00
Vincent LAURENT 62c5bbab91 Downoad button in mobile mode 2022-05-21 10:01:17 +02:00
Vincent LAURENT f0e86c61da Checkbox of a file placed at the end 2022-05-21 02:19:09 +02:00
Vincent LAURENT 8b8aa16fa7 The "deselect the selected pages" button become a cross at the top right of the block 2022-05-21 01:43:49 +02:00
Vincent LAURENT 60522a1c18 Checkbox files related to page selection 2022-05-21 01:07:07 +02:00
Vincent LAURENT 02bad421bd Button downloaded the selected pages in the block rather than at the bottom instead of the global downloaded button 2022-05-18 21:28:49 +02:00
Vincent LAURENT ec797574eb Block of actions of selected pages 2022-05-18 10:04:49 +02:00
Vincent LAURENT cfcd34829e In mobile mode the buttons are displayed in click on the page 2022-05-18 01:42:41 +02:00
Vincent LAURENT eb170fcd63 Button download hide by default 2022-05-18 01:39:38 +02:00
Vincent LAURENT 5bb3ef0f64 Centralized hover state 2022-05-18 01:28:00 +02:00
Vincent LAURENT 6ffcd6a346 centralized page status in a function and button restore a page 2022-05-18 01:02:09 +02:00
Vincent LAURENT 8fa6b5e459 Manipulation buttons of the selected pages above and like the add button of pdf 2022-05-17 01:54:45 +02:00
Vincent LAURENT 492e20dda0 Page in size 4/3 in height 2022-05-15 01:48:38 +02:00
Vincent LAURENT 02e8007ec1 Title to the overflying buttons of a page 2022-05-12 00:39:14 +02:00
Vincent LAURENT 3f2b002804 The page number and the name of the document is displayed only at the top and a border and a background also 2022-05-12 00:26:35 +02:00
Vincent LAURENT 17b0b22f8e [organization] Dedicated button for deleting and rotating selected pages 2022-05-11 09:29:46 +02:00
Vincent LAURENT a7ab90c698 [organization] Shadow and background on the div of the pdf and not of the whole block 2022-05-11 08:04:20 +02:00
Vincent LAURENT 9199e27d9d [organization] In selection mode the rotate, download and delete buttons are linked 2022-05-11 00:57:49 +02:00
Vincent LAURENT a694d62be4 Organisation menu hide on mobile mode 2022-05-10 23:57:53 +02:00
Vincent LAURENT abda679784 Navigation between the signature and the organization 2022-05-08 23:07:24 +02:00
Vincent LAURENT 9671751ef3 Uniformity of the upload block 2022-05-07 11:11:40 +02:00
Vincent LAURENT 156859993f Force update js 2022-05-07 02:08:56 +02:00
Vincent LAURENT 9b25ef4c6e Allows to cancel the rendering of a page in case it is not finished and there is a new zoom level 2022-05-07 02:06:30 +02:00
Vincent LAURENT 6198c55d3a Button upload a new pdf more visible 2022-05-07 01:22:18 +02:00
Vincent LAURENT d842917573 The download button of a page allows to download the page 2022-05-07 01:14:52 +02:00
Vincent LAURENT b1e7f02215 PDF download button different in selection mode and a new "Cancel selection" button 2022-05-07 00:42:24 +02:00
Vincent LAURENT 24437112b8 The selection button allows you to select a page and activate the "selection" mode to download only the selected pages 2022-05-06 00:44:15 +02:00
Vincent LAURENT 89a5b58961 The HTML content of a page is divided into several lines for clarity 2022-05-06 00:18:36 +02:00
Vincent LAURENT e6a5c45d92 The selection checkbox becomes a delete button.
2 other new buttons not yet developed : the selection and the download of a page
2022-05-06 00:07:57 +02:00
Vincent LAURENT f48e78642b Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-28 21:28:41 +02:00
Vincent LAURENT 36a692e34b List of loaded pdf 2022-04-28 19:13:45 +02:00
Vincent LAURENT abb63e8f90 Zoom button with a magnifying glass icon in the upper right corner 2022-04-28 19:09:56 +02:00
Vincent LAURENT 14e8765b6e
Formatting 2022-04-27 13:02:26 +02:00
Vincent LAURENT 5b8b4872e9
Code and financial contributors 2022-04-27 13:01:28 +02:00
Vincent LAURENT eca93fb359 PDF storage expiration time for multi-signature mode 2022-04-27 00:41:44 +02:00
Vincent LAURENT b2ca7e81f3 Multi-signature pdf generation uses temporary files with unique names to avoid collisions 2022-04-15 01:11:51 +02:00
Vincent LAURENT b2c7b8986b Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-15 00:50:38 +02:00
Vincent LAURENT ebf7e0e889 Automatically reload the pdf if there is a new signature 2022-04-15 00:47:36 +02:00
Vincent LAURENT 3327a64774
Documentation update to last version 2022-04-14 10:56:48 +02:00
Vincent LAURENT 39380bd641 Force js update 2022-04-14 10:30:57 +02:00
Vincent LAURENT 4964e98a56 Meta description 2022-04-14 10:29:49 +02:00
Vincent LAURENT f703d61f24 FirstPage with white background 2022-04-14 09:09:48 +02:00
Vincent LAURENT 992081a882 let instead of var 2022-04-14 09:09:01 +02:00
Vincent LAURENT 8ca0115391 signature pad size minimum less small 2022-04-14 00:50:17 +02:00
Vincent LAURENT a787b82d45 Modal to start a more responsive and simple text sharing 2022-04-14 00:19:31 +02:00
Vincent LAURENT f8ee51f1e9 Modal for creating a signature more responsive with tab subtitles specifying the type of signature 2022-04-14 00:14:19 +02:00
Vincent LAURENT 62d6a7c0c3 Informative sentence about the exchanges with the server simpler 2022-04-14 00:13:17 +02:00
Vincent LAURENT 8c4adb0216 The signature pad more fluid and with a slightly finer minimum line 2022-04-14 00:10:10 +02:00
Vincent LAURENT a6e9b444ce Clarification of the information text in the modal share 2022-04-13 00:41:31 +02:00
Vincent LAURENT ac8279e43e Modal to inform that the PDF will be stored on the server before enabling multi-signature PDF sharing 2022-04-13 00:40:35 +02:00
Vincent LAURENT 48107c16fd Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-12 23:36:37 +02:00
Vincent LAURENT ad93b7ccef Clarification on not storing the pdf on the server 2022-04-12 23:36:28 +02:00
Vincent LAURENT c7b7a3e2ee
Merge pull request #25 from logilab/docker-multi-signature
feat(docker): update dockerfile to enable pdf storage for multi-signature
2022-04-11 10:40:09 +02:00
Simon Chabot 05550b03c9 feat(docker): update dockerfile to enable pdf storage for multi-signature 2022-04-11 09:34:18 +02:00
Vincent LAURENT 7106b79298 Column on the right for the different actions instead of the bar at the bottom 2022-04-10 01:35:42 +02:00
Vincent LAURENT 152d54b397 The rotation of pdf pages are taken into account on the server side with pdftk 2022-04-08 01:06:35 +02:00
Vincent LAURENT 43992abcff Reduction of the space between pages 2022-04-08 00:18:28 +02:00
Vincent LAURENT 75813eefaa Plus and minus button to change the number of pages displayed per line 2022-04-07 00:13:22 +02:00
Vincent LAURENT 720c0ba34d All the blocks containing the pages have the same size and the pdf fits inside 2022-04-06 23:17:27 +02:00
Vincent LAURENT c7811b437b Page rotation 2022-04-05 01:11:03 +02:00
Vincent LAURENT 5332629032 pdtfk work with additionnal pdf 2022-04-04 00:53:24 +02:00
Vincent LAURENT 4051264f78 Filename with count signature 2022-04-02 01:54:21 +02:00
Vincent LAURENT 4a261db73e Protect 'hash' get parameter 2022-04-02 01:32:16 +02:00
Vincent LAURENT 0e891e1307 Using php function to move and copy instead of shell 2022-04-02 01:07:41 +02:00
Vincent LAURENT 5b6d30a870 Reordering the code 2022-04-02 01:02:40 +02:00
Vincent LAURENT 8ae7d12a63 If there was no signature at the time of sharing, no signature layer is recorded 2022-04-02 00:58:35 +02:00
Vincent LAURENT ebbad9a7e0 Button close pdf and home return 2022-04-02 00:51:52 +02:00
Vincent LAURENT 38b1dcebba Individual or group picto formatting 2022-04-02 00:32:51 +02:00
Vincent LAURENT 7df937668d Sharing mode responsive on mobile 2022-04-01 19:32:37 +02:00
Vincent LAURENT 8a3ea4a4e2 Update bootstrap icons font 2022-04-01 18:49:16 +02:00
Vincent LAURENT d1f41cb099 Merge branch 'master' of github.com:24eme/signaturepdf 2022-04-01 18:33:05 +02:00
Vincent LAURENT 8fb7d1745f Force update javascript 2022-04-01 18:32:50 +02:00
Vincent LAURENT a085b68904
example path easier 2022-04-01 18:07:20 +02:00
Vincent LAURENT 52d8cf5506
cp au lieu de mkdir 2022-04-01 18:06:36 +02:00
Vincent LAURENT 07dd82558a Rename parameter in configuration 2022-04-01 18:01:18 +02:00
Vincent LAURENT e2327a0edc Merge branch 'sharing' 2022-04-01 18:00:18 +02:00
Vincent LAURENT f950dd1cd8 Rename parameter 'STORAGE' in 'PDF_STORAGE_PATH' 2022-04-01 17:59:38 +02:00
Vincent LAURENT 915808243e
Documentation of the sharing mode configuration 2022-04-01 17:56:48 +02:00
Vincent LAURENT 3e4aebac44 Updating bootstrap 2022-04-01 17:39:14 +02:00
Vincent LAURENT beb2313366 Docker configuration file in the config folder 2022-04-01 17:36:53 +02:00
Vincent LAURENT b57bad56e2 Storage path of the pdf in config and disabled in the interface if not defined 2022-04-01 17:34:35 +02:00
Vincent LAURENT 35c31d3d8b Formatting of the signature counter 2022-04-01 16:54:50 +02:00
Jb Lm 487434e8b9 Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-04-01 15:45:24 +02:00
Jb Lm 62615bd649 get and print nb layers 2022-04-01 15:43:47 +02:00
Jb Lm cf41808878 route for getting nb layers 2022-04-01 15:43:24 +02:00
Vincent LAURENT a3d72959f7 Share link visually disabled when pdf edition is begin 2022-04-01 15:09:13 +02:00
Vincent LAURENT 5faf1f2a65 Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-04-01 15:04:03 +02:00
Vincent LAURENT 0c34e442cd Share link in sharing mode 2022-04-01 15:03:50 +02:00
Jb Lm 12ad0806da Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-04-01 14:54:40 +02:00
Jb Lm 0158023433 update btn 2022-04-01 14:54:30 +02:00
Vincent LAURENT 5a7d9f2a6a Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-04-01 14:53:07 +02:00
Vincent LAURENT 621e8b8145 location modal hash remove when modal is hidden 2022-04-01 14:52:30 +02:00
Jb Lm fce2b8e4c3 change wording for more comprehension 2022-04-01 14:49:02 +02:00
Jb Lm 23728f11b7 lowercase 2022-04-01 14:45:25 +02:00
Jb Lm a0505e545a Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-04-01 14:42:03 +02:00
Jb Lm c4bbffd3bf add noscript tag to inform for no fonctionnal website without js 2022-04-01 14:41:58 +02:00
Vincent LAURENT 85d9250d1b Share link build with javascript 2022-04-01 11:26:18 +02:00
Jb Lm 5e0c1abf45 spelling mistake 2022-04-01 10:37:36 +02:00
Jb Lm f82ec19809 bug correction 2022-04-01 10:36:29 +02:00
Jb Lm f884eb73ae sharing folder existance and rights check 2022-04-01 10:35:30 +02:00
Vincent LAURENT 7f2730decb Modal download button normal instead of small 2022-04-01 01:17:10 +02:00
Vincent LAURENT 3169425f4a Icon people fill and in title in sharing mode 2022-04-01 01:11:21 +02:00
Vincent LAURENT 1e925a38a0 Storing and display filename in sharing mode 2022-04-01 00:52:44 +02:00
Vincent LAURENT 0cc5ba6439 Adding document filename in sidebar 2022-04-01 00:32:52 +02:00
Vincent LAURENT c47c7728c8 Removing empty lines and indentation 2022-04-01 00:00:54 +02:00
Vincent LAURENT b598f1f35c The format of the signed pdf took the one from the svg which was not necessarily in the same format 2022-04-01 00:00:05 +02:00
Vincent LAURENT f9e64c0169 Presentation modal share informations 2022-03-31 23:41:09 +02:00
Vincent LAURENT 12e6fd770f Formatting and text of the modal after save 2022-03-31 23:11:30 +02:00
Vincent LAURENT c0108da53a Modal when pdf is signed 2022-03-31 19:29:39 +02:00
Vincent LAURENT 3665f31c11 Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-03-31 18:47:11 +02:00
Vincent LAURENT 5a36382e27 Disabled button download when save is needed 2022-03-31 18:46:53 +02:00
Jb Lm 2f2d0d09aa Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-03-31 18:39:52 +02:00
Jb Lm 8943efaf08 js for btn save for sharing action 2022-03-31 18:38:38 +02:00
Jb Lm 7732ed4447 refactoring 2022-03-31 18:38:12 +02:00
Jb Lm 1b4c730f40 save svg for sharing action 2022-03-31 18:37:30 +02:00
Vincent LAURENT 82e04e5116 Delete html experimentation 2022-03-31 18:25:29 +02:00
Vincent LAURENT 175555a06e Modal share copy to clipboard 2022-03-31 18:24:10 +02:00
Vincent LAURENT 9a225a261a Buttons color 2022-03-31 18:23:59 +02:00
Vincent LAURENT 8d3b494309 Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-03-31 16:35:51 +02:00
Vincent LAURENT 6537d1ae1c Modal share informations on the first open 2022-03-31 16:35:26 +02:00
Jb Lm bb48a9acd8 pdf download button for sharing mode 2022-03-31 16:17:12 +02:00
Jb Lm 2f7c1f5156 save pdf for sharing mode 2022-03-31 16:05:45 +02:00
Jb Lm c1c86fb881 exclusive pdf file for treatment (for debug mode) 2022-03-31 16:05:19 +02:00
Jb Lm 69781d8e73 Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-03-31 15:00:46 +02:00
Jb Lm 6f8c2fb4ed end of forcing 2022-03-31 15:00:42 +02:00
Jb Lm be2d9a794c deleting generated file from server 2022-03-31 14:59:48 +02:00
Jb Lm b432256ccc increase layer on original pdf with pdftk overlay 2022-03-31 14:58:27 +02:00
Jb Lm 0d81740d09 improved files identification 2022-03-31 14:55:45 +02:00
Jb Lm 6c109acdbe duplicate error 2022-03-31 14:54:14 +02:00
Vincent LAURENT 814793f847 Hide button share and download when in sharing mode 2022-03-31 13:46:39 +02:00
Vincent LAURENT b5d3b990fb Fix javascript bug 2022-03-31 13:25:29 +02:00
Jb Lm 0ca295070d Merge branch 'sharing' of github.com:24eme/signaturepdf into sharing 2022-03-31 12:15:48 +02:00
Jb Lm c452c32527 add /signature/@hash/pdf route for concatenation of multiple signature 2022-03-31 12:15:26 +02:00
Vincent LAURENT b6dad1dcf5 Loading pdf from hash 2022-03-31 11:53:55 +02:00
Vincent LAURENT a41155c950 Share upload file original and redirect to a new route with a hash 2022-03-31 11:46:03 +02:00
Vincent LAURENT b3c7c186a8 Form and button to share pdf 2022-03-31 11:08:46 +02:00
Vincent LAURENT eb42a1547f Storage configuration 2022-03-31 10:59:47 +02:00
Vincent LAURENT 7787d46bc7 Upload a new pdf and display pages 2022-03-31 01:46:45 +02:00
Vincent LAURENT db58c63ac3 Drag block more visible 2022-03-31 00:28:48 +02:00
Vincent LAURENT a31eb7852a 5 page per ligne and hide bar when drag and drop 2022-03-30 10:19:12 +02:00
Vincent LAURENT d51f31109f PDF Organisation page for mobile 2022-03-30 09:56:07 +02:00
Vincent LAURENT 2bca27fd42 Dynamic page filename 2022-03-30 02:08:21 +02:00
Vincent LAURENT 6f8f20ea08 Fix alert message pdf file size 2022-03-30 02:05:02 +02:00
Vincent LAURENT b5a1115cd4 Pages Drag an drop, page width adapt to window 2022-03-30 02:00:25 +02:00
Vincent LAURENT 7fe764d915 Using css grid in fixed bottom bar 2022-03-29 01:05:43 +02:00
Vincent LAURENT 933f5b8e42 checkbox block background white 2022-03-28 01:27:54 +02:00
Vincent LAURENT 1996c661b2 let instead of var and dead code 2022-03-28 00:57:30 +02:00
Vincent LAURENT afdcbf952c Start of an interface to rearrange the pages of a pdf 2022-03-28 00:56:00 +02:00
Vincent LAURENT 30aa95f6ce Fatfree core lib only 2022-03-26 23:40:57 +01:00
Vincent LAURENT 2c8c9a7f8e Fatfree 3.8.0 with php 8 compatibility 2022-03-26 23:36:32 +01:00
Vincent LAURENT 253cc31d73 Managing page history and title 2022-03-26 23:10:06 +01:00
Vincent LAURENT 61fc267e93 Bootstrap css map file and update version 2022-03-26 11:11:06 +01:00
Vincent LAURENT 9fc73054ab Default favicon 2022-03-26 11:10:41 +01:00
Vincent LAURENT b8f216d166 The upload and the signature in a single page and the page is no longer reloaded after the upload 2022-03-26 11:02:47 +01:00
Vincent LAURENT 8495642ab8 Allow the use of the project in an iframe 2022-03-02 23:33:51 +01:00
Vincent LAURENT d2f773a91d No longer use php to generate the key of a pdf file and use the filename in javascript instead 2022-02-06 01:14:37 +01:00
Vincent LAURENT 97e994a1be
Merge pull request #23 from chatons-libreon/master
Dockerize signaturepdf
2022-02-02 23:06:01 +01:00
xgaia 6af056f847 docs: fix README 2022-01-30 20:49:44 +01:00
xgaia 3ec94a0ae3 docs: update README 2022-01-28 18:24:01 +01:00
xgaia 2d76302d8b chore: config php.ini with env 2022-01-28 18:23:43 +01:00
xgaia 1f0022b6e1 ci: dockerize and push with gh-actions 2022-01-28 17:23:51 +01:00
Vincent LAURENT 2f834f6ba6
Pas besoin du AllowOverride 2022-01-26 15:21:38 +01:00
Vincent LAURENT da76ec041b
Configuration apache 2022-01-26 15:11:26 +01:00
Vincent LAURENT 4e47ecf18a Redirection vers index.php 2022-01-26 15:05:08 +01:00
Vincent LAURENT f54f3b6c02 Création des événements dans une fonction dédiée et création du pad de
signature
2022-01-05 02:25:39 +01:00
Vincent LAURENT 9b34a76e54 Réorganisation de l'organisation des fonctions 2022-01-05 02:18:55 +01:00
Vincent LAURENT c20b244dba Externalisation du css 2022-01-05 01:23:37 +01:00
Vincent LAURENT f65f77b4aa L'ajout de texte avec accent est possible maintenant 2022-01-04 09:37:53 +01:00
84 changed files with 4075 additions and 4233 deletions

54
.github/workflows/docker.yaml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Docker publish
on:
push:
branches:
- 'master'
tags:
- '*.*.*'
jobs:
docker:
environment: docker-registry
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
# list of Docker images to use as base name for tags
images: registry.hub.docker.com/${{ secrets.DOCKER_REGISTRY_USERNAME }}/signaturepdf
# generate Docker tags based on the following events/attributes
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to registry
uses: docker/login-action@v1
with:
registry: registry.hub.docker.com
username: xgaia
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
secrets: |
GIT_AUTH_TOKEN=${{ secrets.DOCKER_REGISTRY_TOKEN }}

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ package-lock.json
package.json
node_modules
tests/downloads/
config/config.ini

24
Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM php:8.2-apache
ENV SERVERNAME=localhost
ENV UPLOAD_MAX_FILESIZE=24M
ENV POST_MAX_SIZE=24M
ENV MAX_FILE_UPLOADS=201
ENV PDF_STORAGE_PATH=/data
ENV DISABLE_ORGANIZATION=false
ENV PDF_DEMO_LINK=true
RUN apt update && \
apt install -y gettext-base librsvg2-bin pdftk imagemagick potrace && \
rm -rf /var/lib/apt/lists/*
COPY . /usr/local/signaturepdf
RUN envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini && \
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf && \
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini && \
a2enmod rewrite && a2ensite signaturepdf
WORKDIR /usr/local/signaturepdf
CMD /usr/local/signaturepdf/entrypoint.sh

242
README.md
View File

@ -7,6 +7,11 @@ Logiciel web libre permettant de signer un PDF.
Liste des instances permettant d'utiliser ce logiciel :
- [pdf.24eme.fr](https://pdf.24eme.fr)
- [pdf.libreon.fr](https://pdf.libreon.fr)
- [pdf.hostux.net](https://pdf.hostux.net)
- [pdf.nebulae.co](https://pdf.nebulae.co)
_N'hésitez pas à rajouter la votre via une issue ou une pull request_
## License
@ -14,15 +19,17 @@ Logiciel libre sous license AGPL V3
## Installation
### Debian/Ubuntu
Dépendances :
- php >= 5.6
- php >= 5.6
- rsvg-convert
- pdftk
- imagemagick
- potrace
Sur debian :
Installation des dépendances :
```
sudo aptitude install php librsvg2-bin pdftk imagemagick potrace
@ -40,13 +47,232 @@ Pour le lancer :
php -S localhost:8000 -t public
```
### Configuration de PHP
#### Configuration de PHP
```
upload_max_filesize = 24M # Taille maximum du fichier PDF à signer
post_max_size = 24M # Taille maximum du fichier PDF à signer
max_file_uploads = 201 # Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine
```
#### Configuration d'apache
```
DocumentRoot /path/to/signaturepdf/public
<Directory /path/to/signaturepdf/public>
Require all granted
FallbackResource /index.php
php_value max_file_uploads 201
php_value upload_max_filesize 24M
php_value post_max_size 24M
</Directory>
```
### Déployer avec docker
#### Construction de l'image
```bash
docker build -t signaturepdf .
```
#### Lancement d'un conteneur
```bash
docker run -d --name=signaturepdf -p 8080:80 signaturepdf
```
[localhost:8080](http://localhost:8080)
#### Configuration
Les variables suivantes permettent de configurer le déployement :
| Variable | description | exemple | defaut |
| ---------------------- | ------------------------------------------------------------------ | -------------------------------- | --------- |
| `SERVERNAME` | url de déploiement | `pdf.24eme.fr` | localhost |
| `UPLOAD_MAX_FILESIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
| `POST_MAX_SIZE` | Taille maximum du fichier PDF à signer | 48M | 24M |
| `MAX_FILE_UPLOADS` | Nombre de pages maximum du PDF, ici 200 pages + le PDF d'origine | 401 | 201 |
| `PDF_STORAGE_PATH` | chemin vers lequel les fichiers pdf uploadés pourront être stockés | /data | /data |
| `DISABLE_ORGANIZATION` | Desactiver la route Organiser | true | false |
| `PDF_DEMO_LINK` | Afficher, retirer ou changer le lien de PDF de démo | false, `link` or `relative path` | true |
```bash
docker run -d --name=signaturepdf -p 8080:80 -e SERVERNAME=pdf.example.org -e UPLOAD_MAX_FILESIZE=48M -e POST_MAX_SIZE=48M -e MAX_FILE_UPLOADS=401 -e PDF_STORAGE_PATH=/data signaturepdf
```
### Alpine
Voici un script permettant d'installer la solution sous Linux Alpine (testé en version 3.15).
Pensez à éditer la variable "domain" en début de script pour correspondre à l'URL avec laquelle elle sera appelée.
Les composants principaux sont :
- php 8 + php-fpm
- Nginx
- pdftk (installation "manuelle" nécessitant openjdk8)
- imagick
- potrace
- librsvg
Ce que fait le script :
- Installation des dépendances
- Configuration de php et php-fpm
- Configuration d'Nginx
- Configuration du config.ini
- Git clone du repo
```
#!/bin/sh
domain='sign.example.com'
apk update
apk add bash nginx git php8 php8-fpm php8-session php8-gd php8-fileinfo openjdk8 imagemagick potrace librsvg
cd /tmp
wget https://gitlab.com/pdftk-java/pdftk/-/jobs/924565145/artifacts/raw/build/libs/pdftk-all.jar
mv pdftk-all.jar pdftk.jar
cat <<EOF >>pdftk
#!/usr/bin/env bash
/usr/bin/java -jar "\$0.jar" "\$@"
EOF
chmod 775 pdftk*
mv pdftk* /usr/bin
sed -i 's/user = nobody/user = nginx/g' /etc/php8/php-fpm.d/www.conf
sed -i 's/;listen.owner = nginx/listen.owner = nginx/g' /etc/php8/php-fpm.d/www.conf
sed -i 's/post_max_size = 8M/post_max_size = 50M/g' /etc/php8/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 50M/g' /etc/php8/php.ini
sed -i 's/max_file_uploads = 20 /max_file_uploads = 300/g' /etc/php8/php.ini
service php-fpm8 restart
cd /var/www
git clone https://github.com/24eme/signaturepdf.git
cat <<EOF >>/etc/nginx/http.d/signaturepdf.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name ${domain};
client_max_body_size 0;
root /var/www/signaturepdf/public/;
index index.php index.html;
location / {
# URLs to attempt, including pretty ones.
try_files \$uri \$uri/ /index.php?\$query_string;
}
location ~ [^/]\.php(/|$) {
root /var/www/signaturepdf/public/;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_buffer_size 128k;
fastcgi_buffers 128 128k;
fastcgi_param PATH_INFO \$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
}
EOF
rm /etc/nginx/http.d/default.conf
rm -R /var/www/localhost
service nginx restart
rc-update add nginx
rc-update add php-fpm8
mkdir /var/www/signaturepdf/tmp
chown nginx /var/www/signaturepdf/tmp
cat <<EOF >>/var/www/signaturepdf/config/config.ini
PDF_STORAGE_PATH=/var/www/signaturepdf/tmp
EOF
```
## Configuration
### Activation et configuration du mode partage de signature à plusieurs
Ce mode permet de proposer la signature d'un pdf à plusieurs personnes mais il nécessite que les PDF soient stockés sur le serveur, il convient donc de définir un dossier qui contiendra ces PDF.
Il n'est pas obligatoire d'activer ce mode pour que l'application fonctionne c'est une option.
Créer le fichier `config/config.ini`
```
cp config/config.ini{.example,}
```
Dans ce fichier `config/config.ini`, il suffit ce configurer la variable `PDF_STORAGE_PATH` avec le chemin vers lequel les fichiers pdf uploadés pourront être stockés :
```
PDF_STORAGE_PATH=/path/to/folder
```
Créer ce dossier :
```
mkdir /path/to/folder
```
Le serveur web devra avoir les droits en écriture sur ce dossier.
Par exemple pour apache :
```
chown www-data /path/to/folder/to/store/pdf
```
### Desactivation du mode Organiser
Pour desactiver le mode Organiser, ajouter `DISABLE_ORGANIZATION=true` dans le fichier
`config/config.ini`.
### Cacher ou modifier le lien de PDF de démo
Pour cacher le lien de pdf de démo, ajouter `PDF_DEMO_LINK=false` dans le fichier
`config/config.ini`.
### Champs chargés par défaut pour l'édition de métadonnéés
Dans le fichier de configuration `config/config.ini` il est possible de rajouter autant de champs que l'on souhaite avec le type HTML de l'input (text, date, number email, etc ...) qui seront préchargées pour chaque PDF.
```
METADATA_DEFAULT_FIELDS[field1].type = "text"
METADATA_DEFAULT_FIELDS[field2].type = "text"
METADATA_DEFAULT_FIELDS[field3].type = "date"
METADATA_DEFAULT_FIELDS[field4].type = "number"
```
## Mise à jour
La dernière version stable est sur la branche `master`, pour la mise à jour il suffit de récupérer les dernières modifications :
```
git pull -r
```
## Tests
Pour exécuter les tests fonctionnels :
@ -75,11 +301,21 @@ DEBUG=1 make test
- **OpenType.js** outils de transformation d'un texte et sa police en chemin : https://github.com/opentypejs/opentype.js (MIT)
- **ImageMagick** ensemble d'outils de manipulation d'images : https://imagemagick.org/ (Apache-2.0)
- **Caveat** police de caractères style écriture à la main : https://github.com/googlefonts/caveat (OFL-1.1)
- **PDF-LIB** librairie js permettant de manipuler un PDF qui est utilisé pour écrire dans les métadonnées : https://pdf-lib.js.org/ (MIT)
Pour les tests :
- **Jest** Framework de Test Javascript : https://jestjs.io/ (MIT)
- **Puppeteer** librairie Node.js pour contrôler un navigateur : https://github.com/puppeteer/puppeteer (Apache-2.0)
## Contributeurs
- Vincent LAURENT (24ème)
- Le Metayer Jean-Baptiste (24ème)
- Xavier Garnier (Logilab)
- Simon Chabot (Logilab)
- Gabriel POMA (24ème)
Logilab a apporté une contribution financière de 1 365 € TTC à la société 24ème pour développer le mode multi signature.
Le développement du logiciel a principalement été réalisé sur le temps de travail de salariés du 24ème.

322
app.php
View File

@ -1,60 +1,63 @@
<?php
$f3 = require(__DIR__.'/vendor/fatfree/lib/base.php');
$f3 = require(__DIR__.'/vendor/fatfree/base.php');
if(getenv("DEBUG")) {
$f3->set('DEBUG', getenv("DEBUG"));
}
$f3->set('XFRAME', null); // Allow use in an iframe
$f3->set('ROOT', __DIR__);
$f3->set('UI', $f3->get('ROOT')."/templates/");
$f3->set('UPLOADS', sys_get_temp_dir()."/");
$f3->set('COMMIT', getCommit());
function convertPHPSizeToBytes($sSize)
{
//
$sSuffix = strtoupper(substr($sSize, -1));
if (!in_array($sSuffix,array('P','T','G','M','K'))){
return (int)$sSize;
}
$iValue = substr($sSize, 0, -1);
switch ($sSuffix) {
case 'P':
$iValue *= 1024;
// Fallthrough intended
case 'T':
$iValue *= 1024;
// Fallthrough intended
case 'G':
$iValue *= 1024;
// Fallthrough intended
case 'M':
$iValue *= 1024;
// Fallthrough intended
case 'K':
$iValue *= 1024;
break;
}
return (int)$iValue;
$f3->config(__DIR__.'/config/config.ini');
if($f3->get('PDF_STORAGE_PATH') && !preg_match('|/$|', $f3->get('PDF_STORAGE_PATH'))) {
$f3->set('PDF_STORAGE_PATH', $f3->get('PDF_STORAGE_PATH').'/');
}
if($f3->get('PDF_DEMO_LINK') === null || $f3->get('PDF_DEMO_LINK') === true) {
$f3->set('PDF_DEMO_LINK', 'https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf');
}
$f3->set('disableOrganization', false);
if($f3->get('DISABLE_ORGANIZATION')) {
$f3->set('disableOrganization', $f3->get('DISABLE_ORGANIZATION'));
}
$f3->route('GET /',
function($f3) {
$f3->set('key', hash('md5', uniqid().rand()));
$f3->reroute('/signature');
}
);
$f3->route('GET /signature',
function($f3) {
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')))));
$f3->set('maxPage', ini_get('max_file_uploads') - 1);
echo View::instance()->render('index.html.php');
if(!$f3->get('PDF_STORAGE_PATH')) {
$f3->set('noSharingMode', true);
}
echo View::instance()->render('signature.html.php');
}
);
$f3->route('GET /@key',
$f3->route('GET /signature/@hash',
function($f3) {
$f3->set('key', $f3->get('PARAMS.key'));
$f3->set('hash', Web::instance()->slug($f3->get('PARAMS.hash')));
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')))));
$f3->set('maxPage', ini_get('max_file_uploads') - 1);
echo View::instance()->render('pdf.html.php');
if(!is_dir($f3->get('PDF_STORAGE_PATH').$f3->get('hash'))) {
$f3->error(404);
}
echo View::instance()->render('signature.html.php');
}
);
$f3->route('POST /image2svg',
function($f3) {
$files = Web::instance()->receive(function($file,$formFieldName){
@ -102,7 +105,6 @@ $f3->route('POST /sign',
unlink($tmpfile);
$svgFiles = "";
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "pdf" && strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
$f3->error(403);
@ -135,7 +137,7 @@ $f3->route('POST /sign',
}
shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $tmpfile.'.svg.pdf', $svgFiles));
shell_exec(sprintf("pdftk %s multibackground %s output %s", $tmpfile.'.svg.pdf', $tmpfile.".pdf", $tmpfile.'_signe.pdf'));
shell_exec(sprintf("pdftk %s multistamp %s output %s", $tmpfile.".pdf", $tmpfile.'.svg.pdf', $tmpfile.'_signe.pdf'));
Web::instance()->send($tmpfile.'_signe.pdf', null, 0, TRUE, $filename);
@ -146,4 +148,254 @@ $f3->route('POST /sign',
}
);
return $f3;
$f3->route('POST /share',
function($f3) {
$hash = substr(hash('sha512', uniqid().rand()), 0, 20);
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder."/");
if (!is_dir($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder doesn\'t exist');
}
if (!is_writable($f3->get('PDF_STORAGE_PATH'))) {
$f3->error(500, 'Sharing folder is not writable');
}
mkdir($sharingFolder);
$expireFile = $sharingFolder.".expire";
file_put_contents($expireFile, $f3->get('POST.duration'));
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
$filename = "original.pdf";
$tmpfile = tempnam($sharingFolder, date('YmdHis'));
$svgFiles = "";
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "pdf" && strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
$f3->error(403);
}
if($formFieldName == "svg" && strpos(Web::instance()->mime($file['tmp_name'], true), 'image/svg+xml') !== 0) {
$f3->error(403);
}
return true;
}, false, function($fileBaseName, $formFieldName) use ($tmpfile, $filename, $sharingFolder, &$svgFiles) {
if($formFieldName == "pdf") {
file_put_contents($sharingFolder."/filename.txt", $fileBaseName);
return $filename;
}
if($formFieldName == "svg") {
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
return basename($tmpfile."_".$fileBaseName);
}
});
if(!count($files)) {
$f3->error(403);
}
if($svgFiles) {
shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $tmpfile.'.svg.pdf', $svgFiles));
}
if(!$f3->get('DEBUG')) {
array_map('unlink', glob($tmpfile."*.svg"));
}
$f3->reroute('/signature/'.$hash."#informations");
}
);
$f3->route('GET /signature/@hash/pdf',
function($f3) {
$hash = Web::instance()->slug($f3->get('PARAMS.hash'));
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$files = scandir($sharingFolder);
$originalFile = $sharingFolder.'/original.pdf';
$finalFile = $sharingFolder.'/'.$f3->get('PARAMS.hash').uniqid().'.pdf';
$filename = $f3->get('PARAMS.hash').'.pdf';
if(file_exists($sharingFolder."/filename.txt")) {
$filename = file_get_contents($sharingFolder."/filename.txt");
}
$layers = [];
foreach($files as $file) {
if(strpos($file, 'svg.pdf') !== false) {
$layers[] = $sharingFolder.'/'.$file;
}
}
if (!$layers) {
Web::instance()->send($originalFile, null, 0, TRUE, $filename);
}
$filename = str_replace('.pdf', '_signe-'.count($layers).'x.pdf', $filename);
copy($originalFile, $finalFile);
$bufferFile = $finalFile.".tmp";
foreach($layers as $layerFile) {
shell_exec(sprintf("pdftk %s multistamp %s output %s", $finalFile, $layerFile, $bufferFile));
rename($bufferFile, $finalFile);
}
Web::instance()->send($finalFile, null, 0, TRUE, $filename);
if($f3->get('DEBUG')) {
return;
}
array_map('unlink', glob($finalFile."*"));
}
);
$f3->route('POST /signature/@hash/save',
function($f3) {
$hash = Web::instance()->slug($f3->get('PARAMS.hash'));
$sharingFolder = $f3->get('PDF_STORAGE_PATH').$hash;
$f3->set('UPLOADS', $sharingFolder.'/');
$tmpfile = tempnam($sharingFolder, date('YmdHis'));
unlink($tmpfile);
$svgFiles = "";
$expireFile = $sharingFolder.".expire";
touch($expireFile, date_format(date_modify(date_create(), file_get_contents($expireFile)), 'U'));
$files = Web::instance()->receive(function($file,$formFieldName){
if($formFieldName == "svg" && strpos(Web::instance()->mime($file['tmp_name'], true), 'image/svg+xml') !== 0) {
$f3->error(403);
}
return true;
}, false, function($fileBaseName, $formFieldName) use ($f3, $tmpfile, &$svgFiles) {
if($formFieldName == "svg") {
$svgFiles .= " ".$tmpfile."_".$fileBaseName;
return basename($tmpfile."_".$fileBaseName);
}
});
if(!$svgFiles) {
$f3->error(403);
}
shell_exec(sprintf("rsvg-convert -f pdf -o %s %s", $tmpfile.'.svg.pdf', $svgFiles));
if(!$f3->get('DEBUG')) {
array_map('unlink', explode(' ', trim($svgFiles)));
}
$f3->reroute('/signature/'.$f3->get('PARAMS.hash')."#signed");
}
);
$f3->route('GET /signature/@hash/nblayers',
function($f3) {
$hash = Web::instance()->slug($f3->get('PARAMS.hash'));
$files = scandir($f3->get('PDF_STORAGE_PATH').$hash);
$nbLayers = 0;
foreach($files as $file) {
if(strpos($file, 'svg.pdf') !== false) {
$nbLayers++;
}
}
echo $nbLayers;
}
);
$f3->route('GET /cron', function($f3) {
$sharingFolder = $f3->get('PDF_STORAGE_PATH');
foreach(glob($sharingFolder.'*.expire') as $expireFile) {
if(filemtime($expireFile) > time()) {
continue;
}
$expiredFolder = str_replace('.expire', '', $expireFile);
array_map('unlink', glob($expiredFolder."/*"));
rmdir($expiredFolder);
unlink($expireFile);
}
});
if (!$f3->get('disableOrganization')) {
$f3->route('GET /organization',
function($f3) {
$f3->set('maxSize', min(array(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')))));
echo View::instance()->render('organization.html.php');
}
);
$f3->route('POST /organize',
function($f3) {
$filenames = array();
$tmpfile = tempnam($f3->get('UPLOADS'), 'pdfsignature_organize');
unlink($tmpfile);
$pages = explode(',', preg_replace('/[^A-Z0-9a-z,]+/', '', $f3->get('POST.pages')));
$files = Web::instance()->receive(function($file,$formFieldName){
if(strpos(Web::instance()->mime($file['tmp_name'], true), 'application/pdf') !== 0) {
$f3->error(403);
}
return true;
}, false, function($fileBaseName, $formFieldName) use ($tmpfile, &$filenames) {
$filenames[] = str_replace('.pdf', '', $fileBaseName);
return basename($tmpfile).uniqid().".pdf";
});
if(!count($files)) {
$f3->error(403);
}
$pdfs = array();
foreach(array_keys($files) as $i => $file) {
$pdfs[] = chr(65 + $i)."=".$file;
}
shell_exec(sprintf("pdftk %s cat %s output %s", implode(" ", $pdfs), implode(" ", $pages), $tmpfile.'_final.pdf'));
Web::instance()->send($tmpfile."_final.pdf", null, 0, TRUE, implode('_', $filenames));
if($f3->get('DEBUG')) {
return;
}
array_map('unlink', glob($tmpfile."*"));
}
);
}
$f3->route('GET /metadata',
function($f3) {
echo View::instance()->render('metadata.html.php');
}
);
function getCommit() {
if(!file_exists(__DIR__.'/.git/HEAD')) {
return null;
}
$head = str_replace(["ref: ", "\n"], "", file_get_contents(__DIR__.'/.git/HEAD'));
$commit = null;
if(strpos($head, "refs/") !== 0) {
$commit = $head;
}
if(file_exists(__DIR__.'/.git/'.$head)) {
$commit = str_replace("\n", "", file_get_contents(__DIR__.'/.git/'.$head));
}
return substr($commit, 0, 7);
}
function convertPHPSizeToBytes($sSize)
{
$sSuffix = strtoupper(substr($sSize, -1));
if (!in_array($sSuffix,array('P','T','G','M','K'))){
return (int)$sSize;
}
$iValue = substr($sSize, 0, -1);
switch ($sSuffix) {
case 'P': $iValue *= 1024;
case 'T': $iValue *= 1024;
case 'G': $iValue *= 1024;
case 'M': $iValue *= 1024;
case 'K': $iValue *= 1024; break;
}
return (int)$iValue;
}
return $f3;

22
config/apache.conf Normal file
View File

@ -0,0 +1,22 @@
<VirtualHost *:80>
ServerName ${SERVERNAME}
DocumentRoot /usr/local/signaturepdf/public
DirectoryIndex index.php
AddDefaultCharset UTF-8
<Directory /usr/local/signaturepdf/public>
AllowOverride All
<IfVersion >= 2.3>
Require all granted
</IfVersion>
<IfVersion < 2.3>
Order Deny,Allow
Allow from all
</IfVersion>
</Directory>
LogLevel warn
ErrorLog /var/log/apache2/ssp_error.log
CustomLog /var/log/apache2/ssp_access.log combined
</VirtualHost>

14
config/config.ini.example Normal file
View File

@ -0,0 +1,14 @@
[globals]
; Path to which stored pdf to activate the mode of sharing a signature to several.
; To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=/path/to/folder
; Disable organization tab and routes
;DISABLE_ORGANIZATION=false
; Manage demo link pdf : true (by default, show with default link), false (hide), or custom link
;PDF_DEMO_LINK=true
; Metadata default fields
;METADATA_DEFAULT_FIELDS[metadata_key].type = "text"

11
config/config.ini.tpl Normal file
View File

@ -0,0 +1,11 @@
[globals]
; Path to which stored pdf to activate the mode of sharing a signature to several.
; To deactivate this mode, simply do not configure it or leave it empty
PDF_STORAGE_PATH=${PDF_STORAGE_PATH}
; Disable organization tab and routes
DISABLE_ORGANIZATION=${DISABLE_ORGANIZATION}
; Manage demo link pdf : true (by default, show), false (hide), or custom link
PDF_DEMO_LINK=${PDF_DEMO_LINK}

3
config/php.ini Normal file
View File

@ -0,0 +1,3 @@
upload_max_filesize = $UPLOAD_MAX_FILESIZE
post_max_size = $POST_MAX_SIZE
max_file_uploads = $MAX_FILE_UPLOADS

12
entrypoint.sh Executable file
View File

@ -0,0 +1,12 @@
#! /bin/bash
envsubst < /usr/local/signaturepdf/config/apache.conf > /etc/apache2/sites-available/signaturepdf.conf
envsubst < /usr/local/signaturepdf/config/php.ini > /usr/local/etc/php/conf.d/uploads.ini
envsubst < /usr/local/signaturepdf/config/config.ini.tpl > /usr/local/signaturepdf/config/config.ini
if [[ ! -z $PDF_STORAGE_PATH ]] ; then
mkdir -p $PDF_STORAGE_PATH
chown www-data:www-data $PDF_STORAGE_PATH
fi
apache2-foreground

1
public/.htaccess Normal file
View File

@ -0,0 +1 @@
FallbackResource /index.php

84
public/css/app.css Normal file
View File

@ -0,0 +1,84 @@
@font-face {
font-family: 'Caveat';
font-style: normal;
font-weight: 400;
src: url(/vendor/fonts/Caveat-Regular.ttf) format('truetype');
}
#container-pages {
overflow: auto;
}
#sidebarTools {
width: 350px;
}
#sidebarTools .list-item-add label:hover {
background: #e8ebed;
border: 1px solid #505050;
}
#sidebarTools .list-item-add label:active, #sidebarTools .list-item-add label.active, #sidebarTools .list-item-add .btn-check:active + .btn-outline-secondary, #sidebarTools .list-item-add .btn-check:checked + .btn-outline-secondary {
background: #c9d1d8;
border: 1px solid #000;
box-shadow: 0 .25rem .5rem rgba(0,0,0,.075) !important;
}
#input-text-signature {
font-family: Caveat;
font-size: 48px;
}
#img-upload {
max-width: 460px;
max-height: 200px;
}
.canvas-container .btn-drag, .canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
font-size: 30px;
cursor: move;
background: rgb(255,255,255,0.6);
}
.canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right, .canvas-container .btn-drag-here_mobile, .canvas-container .btn-cancel {
cursor: pointer;
z-index: 9999;
}
.canvas-container .btn-rotate, .canvas-container .btn-delete, .canvas-container .btn-select, .canvas-container .btn-download, .canvas-container .btn-restore, .canvas-container .btn-drag-here-left, .canvas-container .btn-drag-here-right {
cursor: pointer;
font-size: 25px;
}
.canvas-container .btn-rotate:hover, .canvas-container .btn-delete:hover, .canvas-container .btn-select:hover, .canvas-container .btn-download:hover, .canvas-container .btn-restore:hover, .canvas-container .btn-drag:hover {
background: rgb(255, 255, 255, 1);
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15) !important;
}
.canvas-container .btn-cancel {
font-size: 20px;
}
.border-transparent {
border-color: transparent !important;
}
.delete-metadata {
display: none;
cursor: pointer;
position: absolute;
right: 10px;
top: 0;
font-size: 1.2rem;
user-select: none;
}
.input-metadata:hover > .delete-metadata {
display: block;
}
@media (max-width: 480px) {
.subtitle {
font-size: .875em
}
}

BIN
public/favicon-metadata.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,893 +0,0 @@
var canvasEditions = [];
(async function () {
const cache = await caches.open('pdf');
var responsePdf = await cache.match(url);
var pdfBlob = await responsePdf.blob();
url = await URL.createObjectURL(pdfBlob);
var dataTransfer = new DataTransfer();
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf').files = dataTransfer.files;
fabric.Textbox.prototype._wordJoiners = /[]/;
// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window['pdfjs-dist/build/pdf'];
// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
// Asynchronous download of PDF
var loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(function(pdf) {
if(pdf.numPages > maxPage) {
alert("Le PDF de doit pas dépasser "+maxPage+" pages");
document.location = "/";
return;
}
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
}
var fontCaveat = null;
var addLock = forceAddLock;
var forceAddLock = !is_mobile();
var copiedObject = null;
var activeCanvas = null;
var activeCanvasPointer = null;
var pdfRenderTasks = [];
var pdfPages = [];
var svgCollections = [];
var resizeTimeout;
var pdfHistory = {};
var currentScale = 1.5;
var windowWidth = window.innerWidth;
var menu = document.getElementById('offcanvasTop')
var menuOffcanvas = new bootstrap.Offcanvas(menu)
var currentCursor = null;
if(localStorage.getItem('pdfHistory')) {
pdfHistory = JSON.parse(localStorage.getItem('pdfHistory'));
}
var responsiveDisplay = function() {
if(is_mobile()) {
document.body.style.paddingRight = "";
menu.classList.remove('show');
menuOffcanvas.hide();
document.getElementById('container-pages').classList.remove('vh-100');
} else {
menuOffcanvas.show();
document.body.style.paddingRight = "350px";
document.getElementById('container-pages').classList.add('vh-100');
}
menu.classList.remove('d-md-block');
menu.classList.remove('d-none');
}
responsiveDisplay();
if(localStorage.getItem('svgCollections')) {
svgCollections = JSON.parse(localStorage.getItem('svgCollections'));
}
var storeCollections = function () {
localStorage.setItem('svgCollections', JSON.stringify(svgCollections));
}
var getSvgItem = function(svg) {
for (index in svgCollections) {
svgItem = svgCollections[index];
if(svgItem.svg == svg) {
return svgItem;
}
}
return null;
}
opentype.load('/vendor/fonts/Caveat-Regular.ttf', function(err, font) {
fontCaveat = font;
});
var svgClick = function(label, event) {
if(event.detail == 1) {
label.dataset.lock = parseInt(addLock*1);
}
if(event.detail > 1){
stateAddLock(parseInt(label.dataset.lock*1) != 1);
}
if(event.detail > 1) {
return;
}
if(!document.getElementById(label.htmlFor)) {
return;
}
if(!document.getElementById(label.htmlFor).checked) {
return;
}
document.getElementById(label.htmlFor).checked = false;
document.getElementById(label.htmlFor).dispatchEvent(new Event("change"));
event.preventDefault();
}
var svgDblClick = function(label, event) {
if(parseInt(label.dataset.lock*1) == 1) {
return;
}
stateAddLock(true);
}
var svgDragStart = function(label, event) {
document.getElementById(label.htmlFor).checked = true;
document.getElementById(label.htmlFor).dispatchEvent(new Event("change"));
}
var svgChange = function(input, event) {
if(input.checked) {
document.getElementById('btn_svn_select').classList.add('d-none');
document.getElementById('svg_object_actions').classList.add('d-none');
document.getElementById('svg_selected_container').classList.remove('d-none');
if(input.value.match(/^data:/)) {
document.getElementById('svg_selected').src = input.value;
} else {
document.getElementById('svg_selected').src = input.dataset.svg;
}
} else {
document.getElementById('btn_svn_select').classList.remove('d-none');
document.getElementById('svg_object_actions').classList.add('d-none');
document.getElementById('svg_selected_container').classList.add('d-none');
document.getElementById('svg_selected').src = "";
}
stateAddLock(false);
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(input_selected && !input_selected.value.match(/^data:/) && input_selected.value != "text") {
input_selected = null;
}
document.querySelectorAll('.btn-svg').forEach(function(item) {
if(input_selected && item.htmlFor == input_selected.id) {
item.style.setProperty('cursor', 'copy');
} else {
item.style.removeProperty('cursor');
}
});
canvasEditions.forEach(function(canvasEdition, index) {
if(input_selected) {
canvasEdition.defaultCursor = 'copy';
} else {
canvasEdition.defaultCursor = 'default';
}
})
if(is_mobile()) {
menuOffcanvas.hide();
}
}
var getHtmlSvg = function(svg, i) {
var inputRadio = document.createElement('input');
inputRadio.type = "radio";
inputRadio.classList.add("btn-check");
inputRadio.id="radio_svg_"+i;
inputRadio.name = "svg_2_add";
inputRadio.autocomplete = "off";
inputRadio.value = svg.svg;
inputRadio.addEventListener('change', function() {
svgChange(this, event);
});
var svgButton = document.createElement('label');
svgButton.id = "label_svg_"+i;
svgButton.classList.add('position-relative');
svgButton.classList.add('btn');
svgButton.classList.add('btn-svg');
svgButton.classList.add('btn-outline-secondary');
svgButton.htmlFor = "radio_svg_"+i;
if(svg.type == 'signature') {
svgButton.innerHTML += '<i class="bi bi-vector-pen text-black align-middle float-start"></i>';
}
if(svg.type == 'initials') {
svgButton.innerHTML += '<i class="bi bi-type text-black align-middle float-start"></i>';
}
if(svg.type == 'rubber_stamber') {
svgButton.innerHTML += '<i class="bi bi-card-text text-black align-middle float-start"></i>';
}
if(svg.type) {
document.querySelector('.btn-add-svg-type[data-type="'+svg.type+'"]').classList.add('d-none');
}
svgButton.innerHTML += '<a title="Supprimer" data-index="'+i+'" class="btn-svg-list-suppression opacity-50 link-dark position-absolute" style="right: 6px; top: 2px;"><i class="bi bi-trash"></i></a>';
svgButton.draggable = true;
svgButton.addEventListener('dragstart', function(event) {
svgDragStart(this, event);
});
svgButton.addEventListener('click', function(event) {
svgClick(this, event);
});
svgButton.addEventListener('dblclick', function(event) {
svgDblClick(this, event);
});
svgButton.addEventListener('mouseout', function(event) {
this.style.removeProperty('cursor');
})
var svgImg = document.createElement('img');
svgImg.src = svg.svg;
svgImg.draggable = false;
svgImg.style = "max-width: 180px;max-height: 70px;";
svgButton.appendChild(svgImg);
var svgContainer = document.createElement('div');
svgContainer.classList.add('d-grid');
svgContainer.classList.add('gap-2');
svgContainer.appendChild(inputRadio);
svgContainer.appendChild(svgButton);
return svgContainer;
}
document.getElementById('add-lock-checkbox').addEventListener('change', function() {
stateAddLock(this.checked);
});
var stateAddLock = function(state) {
if(forceAddLock) {
state = true;
}
var checkbox = document.getElementById('add-lock-checkbox');
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
addLock = state;
if(!input_selected) {
addLock = false;
checkbox.disabled = true;
} else {
checkbox.disabled = false;
}
/*document.querySelectorAll('.btn-svg').forEach(function(item) {
item.style.borderWidth = "1px";
});*/
if(addLock && input_selected) {
var svgButton = document.querySelector('.btn-svg[for="'+input_selected.id+'"]');
//svgButton.style.borderWidth = "2px";
checkbox.checked = true;
return;
}
checkbox.checked = false;
}
var displaysSVG = function() {
document.getElementById('svg_list').innerHTML = "";
document.getElementById('svg_list_signature').innerHTML = "";
document.getElementById('svg_list_initials').innerHTML = "";
document.getElementById('svg_list_rubber_stamber').innerHTML = "";
document.querySelectorAll('.btn-add-svg-type').forEach(function(item) {
item.classList.remove('d-none');
});
svgCollections.forEach((svg, i) => {
var svgHtmlChild = getHtmlSvg(svg, i);
if(svg.type) {
document.getElementById('svg_list_'+svg.type).appendChild(svgHtmlChild);
return;
}
document.getElementById('svg_list').appendChild(svgHtmlChild);
});
if(svgCollections.length > 0) {
document.getElementById('btn-add-svg').classList.add('btn-light');
document.getElementById('btn-add-svg').classList.remove('btn-primary');
}
if(document.getElementById('btn-add-svg').classList.contains('btn-primary')) {
document.getElementById('btn-add-svg').focus();
}
document.querySelectorAll('.btn-svg-list-suppression').forEach(function(item) {
item.addEventListener('click', function() {
svgCollections.splice(this.dataset.index, 1);
displaysSVG();
storeCollections();
});
});
}
document.querySelectorAll('.btn-add-svg-type').forEach(function(item) {
item.addEventListener('click', function(event) {
document.getElementById('input-svg-type').value = this.dataset.type;
if(this.dataset.modalnav) {
bootstrap.Tab.getOrCreateInstance(document.querySelector('#modalAddSvg #nav-tab '+this.dataset.modalnav)).show();
}
});
});
document.querySelectorAll('label.btn-svg').forEach(function(item) {
item.addEventListener('dragstart', function(event) {
svgDragStart(this, event);
});
item.addEventListener('click', function(event) {
svgClick(this, event);
});
item.addEventListener('dblclick', function(event) {
svgDblClick(this, event);
});
});
document.querySelectorAll('input[name="svg_2_add"]').forEach(function (item) {
item.addEventListener('change', function(event) {
svgChange(this, event);
});
});
displaysSVG();
stateAddLock();
document.getElementById('btn_modal_ajouter').addEventListener('click', function() {
var svgItem = {};
if(document.getElementById('input-svg-type').value) {
svgItem.type = document.getElementById('input-svg-type').value;
}
if(document.getElementById('nav-draw-tab').classList.contains('active')) {
svgItem.svg = document.getElementById('img-upload').src;
}
if(document.getElementById('nav-type-tab').classList.contains('active')) {
var fontPath = fontCaveat.getPath(document.getElementById('input-text-signature').value, 0, 0, 42);
var fabricPath = new fabric.Path(fontPath.toPathData());
fabricPath.top = 0;
fabricPath.left = 0;
fabricPath.height = fabricPath.getScaledHeight();
var textCanvas = document.createElement('canvas');
textCanvas.width = fabricPath.getScaledWidth();
textCanvas.height = fabricPath.getScaledHeight();
var textCanvas = new fabric.Canvas(textCanvas);
textCanvas.add(fabricPath).renderAll();
svgItem.svg = svgToDataUrl(textCanvas.toSVG());
}
if(document.getElementById('nav-import-tab').classList.contains('active')) {
svgItem.svg = document.getElementById('img-upload').src;
}
svgCollections.push(svgItem);
displaysSVG();
localStorage.setItem('svgCollections', JSON.stringify(svgCollections));
var svg_list_id = "svg_list";
if(svgItem.type) {
svg_list_id = svg_list_id + "_" + svgItem.type;
}
document.querySelector('#'+svg_list_id+' label:last-child').click();
});
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {type:mime});
}
function svgToDataUrl(svg) {
return "data:image/svg+xml;base64,"+btoa(svg);
}
function trimSvgWhitespace(svgContent) {
if(!svgContent) {
return null;
}
var svgContainer = document.createElement("div")
svgContainer.classList.add('invisible');
svgContainer.classList.add('position-absolute');
svgContainer.classList.add('top-0');
svgContainer.classList.add('start-0');
svgContainer.style = "z-index: -1;";
svgContainer.innerHTML = svgContent;
document.body.appendChild(svgContainer);
var svg = svgContainer.querySelector('svg');
var box = svg.getBBox();
svg.setAttribute("viewBox", [box.x, box.y, box.width, box.height].join(" "));
svgContent = svgContainer.innerHTML;
document.body.removeChild(svgContainer)
return svgContent = svgContainer.innerHTML;
}
var signaturePad = new SignaturePad(document.getElementById('signature-pad'), {
penColor: 'rgb(0, 0, 0)',
minWidth: 1.25,
maxWidth: 2,
throttle: 0,
onEnd: function() {
const file = new File([dataURLtoBlob(signaturePad.toDataURL())], "draw.png", {
type: 'image/png'
});
var data = new FormData();
data.append('file', file);
uploadSVG(data);
}
});
document.getElementById('signature-pad-reset').addEventListener('click', function(event) {
signaturePad.clear();
event.preventDefault();
})
document.querySelectorAll('#modalAddSvg .nav-link').forEach(function(item) { item.addEventListener('shown.bs.tab', function (event) {
var firstInput = document.querySelector(event.target.dataset.bsTarget).querySelector('input');
if(firstInput) {
firstInput.focus();
}
})});
document.getElementById('modalAddSvg').addEventListener('shown.bs.modal', function (event) {
document.querySelector('#modalAddSvg #nav-tab button:first-child').focus();
var tab = document.querySelector('#modalAddSvg .tab-pane.active');
if(tab.querySelector('input')) {
tab.querySelector('input').focus();
}
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(input_selected) {
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
}
})
document.getElementById('modalAddSvg').addEventListener('hidden.bs.modal', function (event) {
signaturePad.clear();
document.getElementById('btn_modal_ajouter').setAttribute('disabled', 'disabled');
document.getElementById('input-svg-type').value = null;
document.getElementById('input-text-signature').value = null;
document.getElementById('input-image-upload').value = null;
document.getElementById('img-upload').src = "";
document.getElementById('img-upload').classList.add("d-none");
bootstrap.Tab.getOrCreateInstance(document.querySelector('#modalAddSvg #nav-tab button:first-child')).show();
})
document.getElementById('input-text-signature').addEventListener('keydown', function(event) {
document.getElementById('btn_modal_ajouter').removeAttribute('disabled');
if(event.key == 'Enter') {
document.getElementById('btn_modal_ajouter').click()
}
})
document.getElementById('input-image-upload').addEventListener('change', function(event) {
var data = new FormData();
data.append('file', document.getElementById('input-image-upload').files[0]);
uploadSVG(data);
event.preventDefault();
});
var uploadSVG = function(formData) {
document.getElementById('btn_modal_ajouter').setAttribute('disabled', 'disabled');
document.getElementById('btn_modal_ajouter_spinner').classList.remove('d-none');
document.getElementById('btn_modal_ajouter_check').classList.add('d-none');
xhr = new XMLHttpRequest();
xhr.open( 'POST', document.getElementById('form-image-upload').action, true );
xhr.onreadystatechange = function () {
var svgImage = svgToDataUrl(trimSvgWhitespace(this.responseText));
document.getElementById('img-upload').src = svgImage;
document.getElementById('img-upload').classList.remove("d-none");
document.getElementById('btn_modal_ajouter').removeAttribute('disabled');
document.getElementById('btn_modal_ajouter_spinner').classList.add('d-none');
document.getElementById('btn_modal_ajouter_check').classList.remove('d-none');
document.getElementById('btn_modal_ajouter').focus();
};
xhr.send( formData );
}
document.getElementById('save').addEventListener('click', function(event) {
var dataTransfer = new DataTransfer();
canvasEditions.forEach(function(canvasEdition, index) {
dataTransfer.items.add(new File([dataURLtoBlob(svgToDataUrl(canvasEdition.toSVG()))], index+'.svg', {
type: 'image/svg+xml'
}));
})
document.getElementById('input_svg').files = dataTransfer.files;
});
document.getElementById('save_mobile').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('btn-svg-pdf-delete').addEventListener('click', function(event) {
deleteActiveObject();
});
document.getElementById('btn_svg_selected_close').addEventListener('click', function(event) {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
stateAddLock(false);
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
this.blur();
});
document.addEventListener('click', function(event) {
if(event.target.nodeName == "DIV") {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) {
return;
}
stateAddLock(false);
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
}
});
var deleteActiveObject = function() {
canvasEditions.forEach(function(canvasEdition, index) {
canvasEdition.getActiveObjects().forEach(function(activeObject) {
canvasEdition.remove(activeObject);
});
})
}
document.addEventListener('keydown', function(event) {
if(event.key == 'Escape' && (event.target.tagName == "BODY" || event.target.name == "svg_2_add")) {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) {
return;
}
input_selected.checked = false;
stateAddLock(false);
input_selected.dispatchEvent(new Event("change"));
input_selected.blur();
return;
}
if(event.target.tagName != "BODY") {
return;
}
if(event.key == 'Delete') {
deleteActiveObject();
return;
}
if(event.ctrlKey && event.key == 'c') {
if(!activeCanvas || !activeCanvas.getActiveObject()) {
return;
}
copiedObject = fabric.util.object.clone(activeCanvas.getActiveObject());
return;
}
if(event.ctrlKey && event.key == 'v') {
copiedObject = fabric.util.object.clone(copiedObject);
copiedObject.left = activeCanvasPointer.x;
copiedObject.top = activeCanvasPointer.y;
activeCanvas.add(copiedObject).renderAll();
return;
}
if(event.ctrlKey && (event.key == 'à' || event.key == '0')) {
autoZoom();
event.preventDefault() && event.stopPropagation();
return;
}
if(event.ctrlKey && (event.key == '=' || event.key == '+')) {
zoomChange(1);
event.preventDefault() && event.stopPropagation();
return;
}
if(event.ctrlKey && event.key == '-') {
zoomChange(-1);
event.preventDefault() && event.stopPropagation();
return;
}
});
var addObjectInCanvas = function(canvas, item) {
item.on('selected', function(event) {
if(!is_mobile()) {
return;
}
document.getElementById('svg_object_actions').classList.remove('d-none');
document.getElementById('svg_selected_container').classList.add('d-none');
document.getElementById('btn_svn_select').classList.add('d-none');
});
item.on('deselected', function(event) {
if(!is_mobile()) {
return;
}
if(document.querySelector('input[name="svg_2_add"]:checked')) {
document.getElementById('svg_selected_container').classList.remove('d-none');
} else {
document.getElementById('btn_svn_select').classList.remove('d-none');
}
document.getElementById('svg_object_actions').classList.add('d-none');
});
return canvas.add(item);
}
var createAndAddSvgInCanvas = function(canvas, item, x, y, height = null) {
save.removeAttribute('disabled');
save_mobile.removeAttribute('disabled');
if(!height) {
height = 100;
}
if(item == 'text') {
var textbox = new fabric.Textbox('Texte à modifier', {
left: x,
top: y - 20,
fontSize: 20,
fontFamily: 'Monospace'
});
addObjectInCanvas(canvas, textbox).setActiveObject(textbox);
textbox.keysMap[13] = "exitEditing";
textbox.lockScalingFlip = true;
textbox.enterEditing();
textbox.selectAll();
return;
}
fabric.loadSVGFromURL(item, function(objects, options) {
var svg = fabric.util.groupSVGElements(objects, options);
svg.svgOrigin = item;
svg.lockScalingFlip = true;
svg.scaleToHeight(height);
if(svg.getScaledWidth() > 200) {
svg.scaleToWidth(200);
}
var svgItem = getSvgItem(item);
if(svgItem && svgItem.scale) {
svg.scaleToWidth(canvas.width * svgItem.scale);
}
svg.top = y - (svg.getScaledHeight() / 2);
svg.left = x - (svg.getScaledWidth() / 2);
addObjectInCanvas(canvas, svg);
});
}
window.addEventListener('resize', function(event) {
event.preventDefault() && event.stopPropagation();
if(windowWidth == window.innerWidth) {
return;
}
responsiveDisplay();
windowWidth = window.innerWidth;
autoZoom();
});
document.addEventListener('wheel', function(event) {
if(!event.ctrlKey) {
return;
}
event.preventDefault() && event.stopPropagation();
if(event.deltaY > 0) {
zoomChange(-1)
} else {
zoomChange(1)
}
}, { passive: false });
document.getElementById('btn-zoom-decrease').addEventListener('click', function() {
zoomChange(-1)
});
document.getElementById('btn-zoom-increase').addEventListener('click', function() {
zoomChange(1)
});
var autoZoom = function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(resizePDF, 100);
}
var zoomChange = function (inOrOut) {
if(resizeTimeout) {
return;
}
var deltaScale = 0.2 * inOrOut;
if(currentScale + deltaScale < 0) {
return
}
if(currentScale + deltaScale > 3) {
return
}
clearTimeout(resizeTimeout);
currentScale += deltaScale;
resizeTimeout = setTimeout(resizePDF(currentScale), 50);
}
var resizePDF = function (scale = 'auto') {
renderComplete = true;
pdfRenderTasks.forEach(function(renderTask) {
if(!renderTask) {
renderComplete = false;
}
});
if(!renderComplete) {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function(){ resizePDF(scale) }, 50);
return;
}
pdfPages.forEach(function(page, pageIndex) {
var renderTask = pdfRenderTasks[pageIndex];
if(scale == 'auto' && page.getViewport({scale: 1.5}).width > document.getElementById('container-pages').clientWidth - 40) {
scale = (document.getElementById('container-pages').clientWidth - 40) / page.getViewport({scale: 1}).width;
}
if(scale == 'auto') {
scale = 1.5;
}
var viewport = page.getViewport({scale: scale});
currentScale = scale;
var canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
canvasEdition = canvasEditions[pageIndex];
var scaleMultiplier = canvasPDF.width / canvasEdition.width;
var objects = canvasEdition.getObjects();
for (var i in objects) {
objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
objects[i].left = objects[i].left * scaleMultiplier;
objects[i].top = objects[i].top * scaleMultiplier;
objects[i].setCoords();
}
canvasEdition.setWidth(canvasEdition.getWidth() * scaleMultiplier);
canvasEdition.setHeight(canvasEdition.getHeight() * scaleMultiplier);
canvasEdition.renderAll();
canvasEdition.calcOffset();
var renderContext = {
canvasContext: context,
viewport: viewport,
enhanceTextSelection: true
};
renderTask.cancel();
pdfRenderTasks[pageIndex] = null;
renderTask = page.render(renderContext);
renderTask.promise.then(function () {
pdfRenderTasks[pageIndex] = renderTask;
clearTimeout(resizeTimeout);
resizeTimeout = null;
});
});
}
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
var scale = 1.5;
var viewport = page.getViewport({scale: scale});
if(viewport.width > document.getElementById('container-pages').clientWidth - 40) {
viewport = page.getViewport({scale: 1});
scale = (document.getElementById('container-pages').clientWidth - 40) / viewport.width;
viewport = page.getViewport({ scale: scale });
}
currentScale = scale;
var pageIndex = page.pageNumber - 1;
document.getElementById('form_pdf').insertAdjacentHTML('beforeend', '<input name="svg[' + pageIndex + ']" id="data-svg-' + pageIndex + '" type="hidden" value="" />');
document.getElementById('container-pages').insertAdjacentHTML('beforeend', '<div class="position-relative mt-1 ms-1 me-1 d-inline-block" id="canvas-container-' + pageIndex +'"><canvas id="canvas-pdf-'+pageIndex+'" class="shadow-sm canvas-pdf"></canvas><div class="position-absolute top-0 start-0"><canvas id="canvas-edition-'+pageIndex+'"></canvas></div></div>');
var canvasPDF = document.getElementById('canvas-pdf-' + pageIndex);
var canvasEditionHTML = document.getElementById('canvas-edition-' + pageIndex);
// Prepare canvas using PDF page dimensions
var context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
canvasEditionHTML.height = canvasPDF.height;
canvasEditionHTML.width = canvasPDF.width;
var renderContext = {
canvasContext: context,
viewport: viewport,
enhanceTextSelection: true
};
var renderTask = page.render(renderContext);
pdfRenderTasks.push(renderTask);
pdfPages.push(page);
var canvasEdition = new fabric.Canvas('canvas-edition-' + pageIndex, {
selection : false,
allowTouchScrolling: true
});
document.getElementById('canvas-container-' + pageIndex).addEventListener('drop', function(event) {
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(!input_selected) {
return;
}
createAndAddSvgInCanvas(canvasEdition, input_selected.value, event.layerX, event.layerY, input_selected.dataset.height);
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
});
canvasEdition.on('mouse:move', function(event) {
activeCanvas = this;
activeCanvasPointer = event.pointer;
});
canvasEdition.on('mouse:down:before', function(event) {
currentCursor = this.defaultCursor;
});
canvasEdition.on('mouse:down', function(event) {
if(event.target) {
this.defaultCursor = 'default';
return;
}
var input_selected = document.querySelector('input[name="svg_2_add"]:checked');
if(currentCursor == 'default' && input_selected) {
this.defaultCursor = 'copy';
}
if(currentCursor != 'copy') {
return;
}
if(!input_selected) {
return;
}
createAndAddSvgInCanvas(this, input_selected.value, event.pointer.x, event.pointer.y, input_selected.dataset.height);
if(addLock) {
return;
}
input_selected.checked = false;
input_selected.dispatchEvent(new Event("change"));
});
canvasEdition.on('object:scaling', function(event) {
if(event.transform.action == "scaleX") {
event.target.scaleY = event.target.scaleX;
}
if(event.transform.action == "scaleY") {
event.target.scaleX = event.target.scaleY;
}
});
canvasEdition.on('object:scaled', function(event) {
var item = getSvgItem(event.target.svgOrigin);
if(!item) {
return;
}
item.scale = event.target.width * event.target.scaleX / event.target.canvas.width;
storeCollections();
});
canvasEdition.on("text:changed", function(event) {
if (!event.target instanceof fabric.IText) {
return;
}
const textLinesMaxWidth = event.target.textLines.reduce((max, _, i) => Math.max(max, event.target.getLineWidth(i)), 0);
event.target.set({width: textLinesMaxWidth});
});
canvasEditions.push(canvasEdition);
});
}
}, function (reason) {
console.error(reason);
});
})();

300
public/js/metadata.js Normal file
View File

@ -0,0 +1,300 @@
var windowWidth = window.innerWidth;
var menu = null;
var menuOffcanvas = null;
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
};
var responsiveDisplay = function() {
if(is_mobile()) {
menu.classList.remove('show');
menuOffcanvas.hide();
} else {
menuOffcanvas.show();
}
menu.classList.remove('d-md-block');
menu.classList.remove('d-none');
};
var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
var nbPDF = 0;
var pages = [];
var pdfRenderTasks = [];
let filename = null
let pdffile = null
let deletedMetadata = [];
var loadPDF = async function(pdfBlob, filename, pdfIndex) {
let url = await URL.createObjectURL(pdfBlob);
pdffile = pdfBlob
let loadingTask = pdfjsLib.getDocument(url);
document.querySelector('#text_document_name span').innerText = filename;
await loadingTask.promise.then(function(pdf) {
pdf.getMetadata().then(function(metadata) {
console.log(metadata);
for(fieldKey in defaultFields) {
addMetadata(fieldKey, null, defaultFields[fieldKey]['type'], false);
}
for(metaKey in metadata.info) {
if(metaKey == "Custom" || metaKey == "PDFFormatVersion" || metaKey.match(/^Is/) || metaKey == "Trapped") {
continue;
}
addMetadata(metaKey, metadata.info[metaKey], "text", false);
}
for(metaKey in metadata.info.Custom) {
if(metaKey == "sha256") {
continue;
}
addMetadata(metaKey, metadata.info.Custom[metaKey], "text", false);
}
for(let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
let pageIndex = (page.pageNumber - 1);
pages[pageIndex] = page;
pageRender(pageIndex);
});
}
if(document.querySelector('.input-metadata input')) {
document.querySelector('.input-metadata input').focus();
} else {
document.getElementById('input_metadata_key').focus();
}
});
}, function (reason) {
console.error(reason);
});
return loadingTask;
}
var pageRender = async function(pageIndex) {
let page = pages[pageIndex];
let viewport = page.getViewport({scale: 1});
let sizeWidth = document.getElementById('container-pages').offsetWidth;
let scaleWidth = sizeWidth / viewport.width;
let viewportWidth = page.getViewport({scale: scaleWidth });
viewport = viewportWidth;
let canvasPDF = document.createElement('canvas');
canvasPDF.classList.add('shadow-sm');
document.getElementById('container-pages').appendChild(canvasPDF);
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
if(pdfRenderTasks[pageIndex]) {
pdfRenderTasks[pageIndex].cancel();
}
pdfRenderTasks[pageIndex] = await page.render({
canvasContext: context,
viewport: viewport,
});
}
var addMetadata = function(key, value, type, focus) {
let input = document.querySelector('.input-metadata input[name="'+key+'"]');
if(input && !input.value) {
input.value = value;
}
if(input && focus) {
input.focus();
}
if(input) {
return;
}
let div = document.createElement('div');
div.classList.add('form-floating', 'mt-3', 'input-metadata');
input = document.createElement('input');
input.value = value;
input.type = type;
input.name = key;
input.classList.add('form-control');
let label = document.createElement('label');
label.innerText = key;
let deleteButton = document.createElement('div')
deleteButton.title = "Supprimer cette metadonnée"
deleteButton.innerHTML = "×"
deleteButton.classList.add('delete-metadata')
div.appendChild(input);
div.appendChild(label);
div.appendChild(deleteButton);
document.getElementById('form-metadata-container').appendChild(div);
if(focus) {
input.focus();
}
}
const deleteMetadata = function(el) {
if (confirm("Souhaitez-vous supprimer ce champ ?") === false) return;
const input = el.closest('.input-metadata')
const label = input.querySelector('label').innerText
deletedMetadata.push(label)
input.remove()
}
const DL = function (d,f) {
let a = document.createElement("a"),
u = URL.createObjectURL(d);
a.download = f,
a.href = u,
a.click(),
setTimeout(() => URL.revokeObjectURL(u))
}
const save = async function () {
const PDFDocument = window['PDFLib'].PDFDocument
const PDFHexString = window['PDFLib'].PDFHexString
const PDFName = window['PDFLib'].PDFName
const arrayBuffer = await pdffile.arrayBuffer();
const pdf = await PDFDocument.load(arrayBuffer);
deletedMetadata.forEach(function (el) {
pdf.getInfoDict().delete(PDFName.of(el))
});
([...document.getElementsByClassName('input-metadata')] || []).forEach(function (el) {
const label = el.querySelector('label').innerText
const input = el.querySelector('input').value
pdf.getInfoDict().set(PDFName.of(label), PDFHexString.fromText(input));
});
const newPDF = new Blob([await pdf.save()], {type: "application/pdf"});
DL(newPDF, filename)
}
var createEventsListener = function() {
document.getElementById('form_metadata_add').addEventListener('submit', function(e) {
let formData = new FormData(this);
addMetadata(formData.get('metadata_key'), "", "text", true);
this.classList.add('invisible');
setTimeout(function() { document.getElementById('form_metadata_add').classList.remove('invisible'); }, 400);
this.reset();
e.preventDefault();
});
document.getElementById('input_metadata_value').addEventListener('focus', function(e) {
if(document.getElementById('input_metadata_key').value) {
document.querySelector('#form_metadata_add button').click();
}
});
document.addEventListener('click', function (event) {
if (event.target.closest(".delete-metadata")) {
deleteMetadata(event.target)
}
})
document.getElementById('save').addEventListener('click', function (e) {
save()
})
}
async function getPDFBlobFromCache(cacheUrl) {
const cache = await caches.open('pdf');
let responsePdf = await cache.match(cacheUrl);
if(!responsePdf) {
return null;
}
let pdfBlob = await responsePdf.blob();
return pdfBlob;
}
async function uploadFromUrl(url) {
history.replaceState({}, '', '/metadata');
var response = await fetch(url);
if(response.status != 200) {
return;
}
var pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return;
}
let dataTransfer = new DataTransfer();
let filename = url.replace(/^.*\//, '');
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf_upload').files = dataTransfer.files;
document.getElementById('input_pdf_upload').dispatchEvent(new Event("change"));
}
var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-metadata').classList.add('d-none');
document.getElementById('input_pdf_upload').focus();
let cache;
try {
cache = await caches.open('pdf');
} catch (e) {
console.error(e)
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
return;
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
let filename = document.getElementById('input_pdf_upload').files[0].name;
let response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response);
history.pushState({}, '', '/metadata#'+filename);
pageMetadata(urlPdf)
});
}
var pageMetadata = async function(url) {
filename = url.replace('/pdf/', '');
document.title = filename + ' - ' + document.title;
document.querySelector('body').classList.add('bg-light');
document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-metadata').classList.remove('d-none');
menu = document.getElementById('sidebarTools');
menuOffcanvas = new bootstrap.Offcanvas(menu);
responsiveDisplay();
let pdfBlob = await getPDFBlobFromCache(url);
if(!pdfBlob) {
document.location = '/metadata';
return;
}
createEventsListener();
loadPDF(pdfBlob, filename, nbPDF);
};
(function () {
if(window.location.hash && window.location.hash.match(/^\#http/)) {
let hashUrl = window.location.hash.replace(/^\#/, '');
pageUpload();
uploadFromUrl(hashUrl);
} else if(window.location.hash) {
pageMetadata('/pdf/'+window.location.hash.replace(/^\#/, ''));
} else {
pageUpload();
}
window.addEventListener('hashchange', function() {
window.location.reload();
})
})();

786
public/js/organization.js Normal file
View File

@ -0,0 +1,786 @@
var windowWidth = window.innerWidth;
var menu = null;
var menuOffcanvas = null;
var is_mobile = function() {
return !(window.getComputedStyle(document.getElementById('is_mobile')).display === "none");
};
var hasTouch = function() {
return 'ontouchstart' in document.documentElement
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0;
}
var disabledHoverStyle = function() {
try { // prevent exception on browsers not supporting DOM styleSheets properly
for (var si in document.styleSheets) {
var styleSheet = document.styleSheets[si];
if (!styleSheet.rules) continue;
for (var ri = styleSheet.rules.length - 1; ri >= 0; ri--) {
if (!styleSheet.rules[ri].selectorText) continue;
if (styleSheet.rules[ri].selectorText.match(':hover')) {
styleSheet.deleteRule(ri);
}
}
}
} catch (ex) {}
}
var responsiveDisplay = function() {
if(is_mobile()) {
document.getElementById('page-organization').style.paddingRight = "inherit";
menu.classList.remove('show');
menuOffcanvas.hide();
document.getElementById('container-pages').classList.remove('vh-100');
document.getElementById('container-btn-zoom').style.top = '62px';
document.getElementById('container-btn-zoom').style.right = '6px';
document.getElementById('container-btn-zoom').classList.add('d-none');
} else {
menuOffcanvas.show();
document.getElementById('page-organization').style.paddingRight = "350px";
document.getElementById('container-pages').classList.add('vh-100');
document.getElementById('container-btn-zoom').style.top = '6px';
document.getElementById('container-btn-zoom').style.right = '368px';
document.getElementById('container-btn-zoom').classList.remove('d-none');
}
menu.classList.remove('d-md-block');
menu.classList.remove('d-none');
};
var isSelectionMode = function() {
return document.querySelectorAll('.canvas-container .input-select:checked').length > 0;
}
var isDraggedMode = function() {
return document.querySelectorAll('.canvas-container .input-drag:checked').length > 0;
}
var nbPagePerLine = 5;
if(is_mobile()) {
nbPagePerLine = 2;
}
var pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = '/vendor/pdf.worker.js?legacy';
var nbPDF = 0;
var pages = [];
var pdfRenderTasks = [];
var loadPDF = async function(pdfBlob, filename, pdfIndex) {
let url = await URL.createObjectURL(pdfBlob);
let dataTransfer = new DataTransfer();
let i = 0;
for (i = 0; i < document.getElementById('input_pdf').files.length; i++) {
dataTransfer.items.add(document.getElementById('input_pdf').files[i]);
}
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf').files = dataTransfer.files;
updateListePDF();
let pdfLetter = String.fromCharCode(96 + i+1).toUpperCase();
let loadingTask = pdfjsLib.getDocument(url);
await loadingTask.promise.then(function(pdf) {
for(var pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++ ) {
pdf.getPage(pageNumber).then(function(page) {
let pageIndex = pdfLetter + "_" + (page.pageNumber - 1);
pages[pageIndex] = page;
let pageHTML = '<div class="position-relative mt-0 ms-1 me-0 mb-1 canvas-container d-flex align-items-center justify-content-center bg-transparent bg-opacity-25 border border-2 border-transparent" id="canvas-container-' + pageIndex +'" draggable="true">';
pageHTML += '<canvas class="canvas-pdf shadow-sm"></canvas>';
pageHTML += '<div title="Séléctionner cette page" class="position-absolute top-0 start-50 translate-middle-x p-2 ps-3 pe-3 mt-2 rounded-circle btn-select d-none"><i class="bi bi-check-square"></i></div>';
pageHTML += '<div title="Supprimer cette page" class="position-absolute top-50 start-0 translate-middle-y p-2 ps-3 pe-3 ms-2 rounded-circle btn-delete d-none"><i class="bi bi-trash"></i></div>';
pageHTML += '<div title="Restaurer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-restore d-none"><i class="bi bi-recycle"></i></div>';
pageHTML += '<div title="Déplacer cette page" class="position-absolute top-50 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag d-none"><i class="bi bi-arrows-move"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute start-0 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-left bg-white shadow d-none" style="left: -5px !important;"><i class="bi bi-arrow-up-square"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute start-100 top-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here-right bg-white shadow d-none" style="margin-left: 3px !important;"><i class="bi bi-arrow-up-square"></i></div>';
pageHTML += '<div title="Déplacer ici" class="position-absolute top-100 start-50 translate-middle p-2 ps-3 pe-3 rounded-circle container-resize btn-drag-here_mobile bg-white shadow d-none"><i class="bi bi-arrows-collapse"></i></div>';
pageHTML += '<div title="Tourner cette page" class="position-absolute top-50 end-0 translate-middle-y p-2 ps-3 pe-3 me-2 rounded-circle container-rotate btn-rotate d-none"><i class="bi bi-arrow-clockwise"></i></div>';
pageHTML += '<div title="Télécharger cette page" class="position-absolute bottom-0 start-50 translate-middle-x p-2 ps-3 pe-3 mb-3 rounded-circle btn-download d-none"><i class="bi bi-download"></i></div>';
pageHTML += '<p class="page-title position-absolute text-center w-100 ps-2 pe-2 pb-0 pt-0 mb-1 bg-white opacity-75 d-none" style="bottom: -4px; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">Page '+page.pageNumber+' - '+filename+'</p>';
pageHTML += '<input form="form_pdf" class="checkbox-page d-none" role="switch" type="checkbox" checked="checked" value="'+pdfLetter+page.pageNumber+'" />';
pageHTML += '<input type="hidden" class="input-rotate" value="0" id="input_rotate_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-select d-none" value="'+pdfLetter+page.pageNumber+'" id="input_select_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-hover d-none" value="'+pdfLetter+page.pageNumber+'" id="input_select_'+pageIndex+'" />';
pageHTML += '<input type="checkbox" class="input-drag d-none" value="'+pdfLetter+page.pageNumber+'" id="input_drag_'+pageIndex+'" />';
pageHTML += '</div>';
document.getElementById('container-pages').insertAdjacentHTML('beforeend', pageHTML);
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
canvasContainer.addEventListener('click', function(e) {
if(isPageDeleted(this) || isPageDragged(this)) {
return;
}
canvasContainer.querySelector('.btn-select').click();
});
canvasContainer.addEventListener('mouseenter', function(e) {
if(is_mobile()) {
return false;
}
this.querySelector('input[type=checkbox].input-hover').checked = true;
updatePageState(this);
});
canvasContainer.addEventListener('mouseleave', function(e) {
this.querySelector('input[type=checkbox].input-hover').checked = false;
updatePageState(this);
});
canvasContainer.addEventListener('dragstart', function(e) {
if(is_mobile()) {
return false;
}
if(isDraggedMode()) {
return false;
}
if(isSelectionMode()) {
return false;
}
this.querySelector('.container-resize').classList.add('d-none');
this.querySelector('.canvas-pdf').classList.add('shadow-lg');
this.querySelector('.canvas-pdf').style.border = '2px dashed #777';
e.dataTransfer.setData('element', this.id);
this.style.opacity = 0.4;
});
canvasContainer.addEventListener('dragend', function(e) {
this.querySelector('.container-resize').classList.remove('d-none');
this.querySelector('.canvas-pdf').classList.remove('shadow-lg');
this.querySelector('.canvas-pdf').style.removeProperty('border');
this.style.opacity = 1;
updatePageState(this);
});
canvasContainer.addEventListener('dragover', function(e) {
if (e.preventDefault) {
e.preventDefault();
}
let pdfOver = this;
let pdfMoving = document.querySelector('#'+e.dataTransfer.getData('element'));
if(pdfOver.id == pdfMoving.id) {
return;
}
let leftRight = false;
let topBottom = false;
if(pdfOver.offsetTop < pdfMoving.offsetTop) {
topBottom = 'top';
}
if(pdfOver.offsetTop > pdfMoving.offsetTop) {
topBottom = 'bottom';
}
if(pdfOver.offsetLeft > pdfMoving.offsetLeft) {
leftRight = 'right';
}
if(pdfOver.offsetLeft < pdfMoving.offsetLeft) {
leftRight = 'left';
}
if (leftRight == 'left' || topBottom == 'top') {
pdfOver.insertAdjacentElement('beforebegin', pdfMoving);
}
if (leftRight == 'right' || topBottom == 'bottom') {
pdfOver.insertAdjacentElement('afterend', pdfMoving);
}
return false;
});
canvasContainer.querySelector('.btn-delete').addEventListener('click', function(e) {
e.stopPropagation();
toggleDeletePage(this.parentNode);
});
canvasContainer.querySelector('.btn-restore').addEventListener('click', function(e) {
e.stopPropagation();
toggleDeletePage(this.parentNode);
});
canvasContainer.querySelector('.btn-select').addEventListener('click', function(e) {
e.stopPropagation();
toggleSelectPage(this.parentNode);
});
canvasContainer.querySelector('.btn-drag').addEventListener('click', function(e) {
e.stopPropagation();
toggleDragPage(this.parentNode);
});
canvasContainer.querySelector('.btn-drag-here_mobile').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'right');
});
canvasContainer.querySelector('.btn-drag-here-right').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'right');
});
canvasContainer.querySelector('.btn-drag-here-left').addEventListener('click', function(e) {
e.stopPropagation();
movePagesDragged(this.parentNode, 'left');
});
canvasContainer.querySelector('.btn-download').addEventListener('click', function(e) {
e.stopPropagation();
let container = this.parentNode;
let pageValue = container.querySelector('.checkbox-page').value;
let orientation = degreesToOrientation(container.querySelector('.input-rotate').value);
if(orientation) {
pageValue = pageValue + "-" + orientation;
}
document.querySelector('#input_pages').value = pageValue;
document.querySelector('#form_pdf').submit();
});
canvasContainer.querySelector('.btn-rotate').addEventListener('click', function(e) {
e.stopPropagation();
let inputRotate = this.parentNode.querySelector('.input-rotate');
inputRotate.value = (parseInt(inputRotate.value) + 90) % 360;
pageRender(pageIndex);
})
pageRender(pageIndex);
});
}
}, function (reason) {
console.error(reason);
});
return loadingTask;
};
var pageRenderAll = function() {
for(pageIndex in pages) {
pageRender(pageIndex);
}
}
var pageRender = async function(pageIndex) {
let scrollWidth = 12;
if(is_mobile()) {
scrollWidth = -4;
}
let page = pages[pageIndex];
let rotation = parseInt(document.querySelector('#input_rotate_'+pageIndex).value);
let viewport = page.getViewport({scale: 1, rotation: rotation});
let sizeWidth = Math.floor((document.getElementById('container-pages').offsetWidth - (8*(nbPagePerLine+1)) - scrollWidth) / nbPagePerLine);
let sizeHeight = sizeWidth * 1.25;
let scaleWidth = sizeWidth / viewport.width;
let scaleHeight = sizeHeight / viewport.height;
let viewportWidth = page.getViewport({scale: scaleWidth, rotation: rotation});
let viewportHeight = page.getViewport({scale: scaleHeight, rotation: rotation});
if(viewportWidth.height > sizeWidth) {
viewport = viewportHeight;
} else {
viewport = viewportWidth;
}
let canvasContainer = document.getElementById('canvas-container-' + pageIndex);
canvasContainer.style.height = (sizeHeight + 4) + "px";
canvasContainer.style.width = (sizeWidth + 4) + "px";
let canvasPDF = canvasContainer.querySelector('.canvas-pdf');
let context = canvasPDF.getContext('2d');
canvasPDF.height = viewport.height;
canvasPDF.width = viewport.width;
if(pdfRenderTasks[pageIndex]) {
pdfRenderTasks[pageIndex].cancel();
}
pdfRenderTasks[pageIndex] = await page.render({
canvasContext: context,
viewport: viewport,
});
}
var getFileIndex = function(page) {
return page.id.replace('canvas-container-', '').replace(/_.*$/, '');
}
var getFilesStats = function() {
let files = [];
document.querySelectorAll('.canvas-container').forEach(function(page) {
let fileIndex = getFileIndex(page);
if(!files[fileIndex]) {
files[fileIndex] = { nbPage: 0, nbPageSelected: 0, nbPageDeleted: 0};
}
if(isPageDeleted(page)) {
files[fileIndex].nbPageDeleted++;
} else {
files[fileIndex].nbPage++;
}
if(isPageSelected(page)) {
files[fileIndex].nbPageSelected++;
}
});
return files;
}
var updateListePDF = function() {
document.querySelector('#list_pdf').innerHTML = "";
let nbFiles = document.querySelector('#input_pdf').files.length;
for (var i = 0; i < nbFiles; i++) {
let pdfLetter = String.fromCharCode(96 + i+1).toUpperCase();
const pdfFile = document.querySelector('#input_pdf').files.item(i);
document.querySelector('#list_pdf').insertAdjacentHTML('beforeend', '<li id="file_' + pdfLetter + '" class="list-group-item small ps-2 pe-5" title="'+decodeURI(pdfFile.name)+'" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;"><i class="bi bi-files"></i><span class="ms-2">'+decodeURI(pdfFile.name)+'</span> <input class="form-check-input float-end position-absolute" style="right: 10px;" type="checkbox" /> </li>');
let fileItem = document.querySelector('#file_' + pdfLetter);
fileItem.querySelector('input[type=checkbox]').addEventListener('change', function(e) {
document.querySelectorAll('.canvas-container').forEach(function(page) {
if(getFileIndex(page) == pdfLetter && !isPageDeleted(page)) {
selectPage(page, e.target.checked);
}
});
updateGlobalState();
});
document.querySelector('#liste_pdf_titre_mobile').innerText = decodeURI(pdfFile.name);
document.querySelector('#btn_liste_pdf_bar span').innerText = nbFiles;
if(nbFiles > 1) {
document.querySelector('#liste_pdf_titre_mobile').innerText = nbFiles + ' documents PDF';
}
}
updateGlobalState();
}
var getPagesSelected = function() {
let pages = [];
document.querySelectorAll('.canvas-container .input-select:checked').forEach(function(item) {
pages[item.parentNode.id.replace('canvas-container-', '')] = item.parentNode;
});
return pages;
}
var selectPage = function(page, state) {
page.querySelector('input[type=checkbox].input-select').checked = state;
updatePageState(page);
}
var toggleSelectPage = function(page) {
selectPage(page, !isPageSelected(page));
updateGlobalState();
}
var isPageSelected = function(page) {
return page.querySelector('input[type=checkbox].input-select').checked;
}
var dragPage = function(page, state) {
page.querySelector('input[type=checkbox].input-drag').checked = state;
updatePageState(page);
}
var toggleDragPage = function(page) {
dragPage(page, !isPageDragged(page));
updateGlobalState();
document.querySelectorAll('.canvas-container').forEach(function(page) {
updatePageState(page);
});
}
var isPageDragged = function(page) {
return page.querySelector('input[type=checkbox].input-drag').checked;
}
var movePagesDragged = function(pageHere, position) {
document.querySelectorAll('.canvas-container .input-drag:checked').forEach(function(item) {
let page = item.parentNode;
if(position == 'right') {
pageHere.insertAdjacentElement('afterend', page);
} else {
pageHere.insertAdjacentElement('beforebegin', page);
}
});
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalDrag')).hide();
}
var toggleDeletePage = function(page) {
deletePage(page, isPageDeleted(page))
updateGlobalState();
}
var deletePage = function(page, state) {
page.querySelector('input[type=checkbox].checkbox-page').checked = state;
page.querySelector('input[type=checkbox].input-select').checked = false;
updatePageState(page);
}
var isPageDeleted = function(page) {
return !page.querySelector('input[type=checkbox].checkbox-page').checked;
}
var isPageHover = function(page) {
return page.querySelector('input[type=checkbox].input-hover').checked;
}
var updatePageState = function(page) {
page.classList.remove('border-primary', 'shadow-sm', 'bg-primary', 'border-secondary', 'bg-secondary');
page.classList.add('border-transparent', 'bg-transparent');
page.querySelector('.canvas-pdf').style.opacity = '1';
page.querySelector('.canvas-pdf').style.zIndex = 'inherit';
page.querySelector('.canvas-pdf').classList.add('shadow-sm');
page.querySelector('.canvas-pdf').classList.remove('shadow');
page.querySelector('.btn-rotate').classList.add('d-none');
page.querySelector('.btn-download').classList.add('d-none');
page.querySelector('.btn-delete').classList.add('d-none');
page.querySelector('.btn-select').classList.add('d-none');
page.querySelector('.btn-select').classList.remove('text-primary');
page.querySelector('.btn-drag').classList.add('d-none');
page.querySelector('.btn-drag-here-left').classList.add('d-none');
page.querySelector('.btn-drag-here-right').classList.add('d-none');
page.querySelector('.btn-drag-here_mobile').classList.add('d-none');
page.querySelector('.btn-restore').classList.add('d-none');
page.querySelector('.page-title').classList.add('d-none');
page.querySelector('.canvas-pdf').classList.remove('opacity-50');
page.classList.remove('page-dragged');
if(isPageDeleted(page)) {
page.querySelector('.canvas-pdf').style.opacity = '0.15';
}
if(isPageHover(page) && !isPageDeleted(page) && !isPageDragged(page) && !isPageSelected(page) && !isDraggedMode()) {
page.querySelector('.page-title').classList.remove('d-none');
page.classList.add('border-secondary', 'bg-secondary');
page.classList.remove('border-transparent', 'bg-transparent');
page.querySelector('.btn-select').classList.remove('d-none')
}
if(isPageHover(page) && !isPageDeleted(page) && !isPageDragged(page) && !isPageSelected(page) && !isDraggedMode() && !isSelectionMode()) {
page.querySelector('.btn-rotate').classList.remove('d-none');
page.querySelector('.btn-download').classList.remove('d-none');
page.querySelector('.btn-delete').classList.remove('d-none');
page.querySelector('.btn-drag').classList.remove('d-none');
}
if(isPageHover(page) && isPageDeleted(page)) {
page.querySelector('.btn-restore').classList.remove('d-none');
}
if(is_mobile() && isPageDeleted(page)) {
page.querySelector('.btn-restore').classList.remove('d-none');
}
if(isPageSelected(page) && !isDraggedMode()) {
page.querySelector('.page-title').classList.remove('d-none');
page.classList.add('border-primary', 'shadow-sm', 'bg-primary');
page.classList.remove('border-transparent', 'bg-transparent', 'border-secondary', 'bg-secondary');
page.querySelector('.btn-select').classList.add('text-primary');
page.querySelector('.btn-select').classList.remove('d-none');
}
if(isPageDragged(page)) {
page.classList.add('page-dragged');
page.querySelector('.canvas-pdf').classList.remove('shadow-sm');
page.querySelector('.canvas-pdf').classList.add('shadow');
}
if(!isPageDragged(page) && isDraggedMode()) {
page.querySelector('.canvas-pdf').classList.add('opacity-50');
page.querySelector('.btn-drag-here-left').classList.remove('d-none');
page.querySelector('.btn-drag-here-right').classList.remove('d-none');
}
}
var updateFilesState = function() {
let filesStats = getFilesStats();
for(fileIndex in filesStats) {
let checkbox = document.querySelector('#file_'+fileIndex+' input[type=checkbox]');
let fileStat = filesStats[fileIndex];
checkbox.checked = (fileStat.nbPageSelected > 0 && fileStat.nbPageSelected == fileStat.nbPage);
checkbox.indeterminate = (fileStat.nbPageSelected > 0 && fileStat.nbPageSelected < fileStat.nbPage);
document.querySelector('#file_'+fileIndex+' span').classList.remove('text-primary');
if(fileStat.nbPageSelected > 0) {
document.querySelector('#file_'+fileIndex+' span').classList.add('text-primary');
}
}
}
var updateGlobalState = function() {
updateFilesState();
if(!is_mobile()) {
document.querySelector('#container-btn-zoom').classList.remove('d-none');
}
document.querySelector('#container_btn_select').classList.add('opacity-50');
document.querySelector('#container_btn_select').classList.remove('border-primary');
document.querySelector('#container_btn_select .card-header').classList.remove('bg-primary', 'text-white');
document.querySelector('#container_btn_select .card-header').classList.add('text-muted');
document.querySelectorAll('#container_btn_select .card-body button').forEach(function(button) {
button.classList.add('btn-outline-secondary');
button.classList.remove('btn-outline-primary');
button.setAttribute('disabled', 'disabled');
});
document.querySelector('#container_btn_select .card-header span').innerText = "Aucune";
document.querySelector('#container_btn_select .card-footer').classList.add('d-none');
document.querySelector('#top_bar_action').classList.remove('d-none');
document.querySelector('#top_bar_action_selection').classList.add('d-none');
document.querySelector('#bottom_bar_action').classList.remove('d-none');
document.querySelector('#bottom_bar_action_selection').classList.add('d-none');
document.querySelector('#save').removeAttribute('disabled');
if(isSelectionMode()) {
document.querySelector('#container_btn_select .card-header span').innerText = document.querySelectorAll('.canvas-container .input-select:checked').length;
document.querySelector('#top_bar_action_selection_recap_nb_pages').innerText = document.querySelectorAll('.canvas-container .input-select:checked').length;
document.querySelector('#container_btn_select').classList.remove('opacity-50');
document.querySelector('#container_btn_select').classList.add('border-primary');
document.querySelector('#container_btn_select .card-header').classList.remove('text-muted');
document.querySelector('#container_btn_select .card-header').classList.add('bg-primary', 'text-white');
document.querySelectorAll('#container_btn_select .card-body button').forEach(function(button) {
button.classList.add('btn-outline-primary');
button.classList.remove('btn-outline-secondary');
button.removeAttribute('disabled');
});
document.querySelector('#container_btn_select .card-footer').classList.remove('d-none');
document.querySelectorAll('.canvas-container .btn-add').forEach(function(button) {
button.classList.remove('d-none');
});
document.querySelector('#top_bar_action_selection').classList.remove('d-none');
document.querySelector('#top_bar_action').classList.add('d-none');
document.querySelector('#bottom_bar_action_selection').classList.remove('d-none');
document.querySelector('#bottom_bar_action').classList.add('d-none');
document.querySelector('#save').setAttribute('disabled', 'disabled');
}
if(isDraggedMode()) {
document.querySelector('#modalDrag .modal-body').insertAdjacentElement('afterbegin', document.querySelector('#container-pages'));
document.querySelector('#container-pages').style.overflow = 'visible';
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalDrag')).show();
}
}
var degreesToOrientation = function(degrees) {
if(degrees == 90) { return "east"; }
if(degrees == 180) { return "south"; }
if(degrees == 270) { return "west"; }
return null;
}
var uploadAndLoadPDF = async function(input_upload) {
const cache = await caches.open('pdf');
for (let i = 0; i < input_upload.files.length; i++) {
if(input_upload.files[i].size > maxSize) {
alert("Le PDF ne doit pas dépasser " + Math.round(maxSize/1024/1024) + " Mo");
break;
}
let filename = input_upload.files[i].name;
let response = new Response(input_upload.files[i], { "status" : 200, "statusText" : "OK" });
let urlPdf = '/pdf/'+filename;
await cache.put(urlPdf, response);
let pdfBlob = await getPDFBlobFromCache(urlPdf);
nbPDF++;
await loadPDF(pdfBlob, filename, nbPDF);
}
}
var createEventsListener = function() {
document.getElementById('save-select_mobile').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('save-select').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('save').addEventListener('click', function(event) {
let order = [];
let selectionMode = isSelectionMode();
document.querySelectorAll('.canvas-container').forEach(function(canvasContainer) {
let checkbox = canvasContainer.querySelector('.checkbox-page');
if(selectionMode) {
checkbox = canvasContainer.querySelector('.input-select');
}
let inputRotate = canvasContainer.querySelector('.input-rotate');
let pageValue = "";
if(checkbox.checked) {
pageValue = checkbox.value;
}
let orientation = degreesToOrientation(inputRotate.value);
if(pageValue && orientation) {
pageValue = pageValue + "-" + orientation;
}
if(pageValue) {
order.push(pageValue);
}
});
document.querySelector('#input_pages').value = order.join(',');
});
document.getElementById('save_mobile').addEventListener('click', function(event) {
document.getElementById('save').click();
});
document.getElementById('input_pdf_upload_2').addEventListener('change', async function(event) {
await uploadAndLoadPDF(this);
this.value = '';
});
document.getElementById('btn-zoom-decrease').addEventListener('click', function(event) {
nbPagePerLine++;
pageRenderAll();
});
document.getElementById('btn-zoom-increase').addEventListener('click', function(event) {
nbPagePerLine--;
pageRenderAll();
});
document.getElementById('btn_cancel_select_footer').addEventListener('click', function(event) {
document.getElementById('btn_cancel_select').click();
});
document.getElementById('btn_cancel_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_cancel_select').click();
});
document.getElementById('btn_cancel_select').addEventListener('click', function(event) {
document.querySelectorAll('.input-select:checked').forEach(function(input) {
input.parentNode.querySelector('.btn-select').click();
});
});
document.getElementById('btn_delete_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_delete_select').click();
});
document.getElementById('btn_delete_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
deletePage(pages[index]);
}
updateGlobalState();
});
document.getElementById('btn_rotate_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_rotate_select').click();
});
document.getElementById('btn_rotate_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
let inputRotate = pages[index].querySelector('.input-rotate');
inputRotate.value = (parseInt(inputRotate.value) + 90) % 360;
pageRender(index);
}
});
document.getElementById('btn_drag_select').addEventListener('click', function(event) {
let pages = getPagesSelected();
for(index in pages) {
toggleDragPage(pages[index]);
}
});
document.getElementById('btn_drag_select_mobile').addEventListener('click', function(event) {
document.getElementById('btn_drag_select').click();
});
document.querySelector('#modalDrag').addEventListener('shown.bs.modal', event => {
document.querySelector('#modalDrag .modal-body').scrollTop = document.querySelector('.page-dragged').offsetTop;
});
document.querySelector('#modalDrag').addEventListener('hide.bs.modal', event => {
document.querySelector('#container-main').insertAdjacentElement('afterbegin', document.querySelector('#container-pages'));
document.querySelector('#container-pages').style.overflowY = 'scroll';
document.querySelector('#container-pages').style.overflowX = 'hidden';
});
document.querySelector('#modalDrag').addEventListener('hidden.bs.modal', event => {
if(is_mobile()) {
window.scrollTo(0, document.querySelector('.page-dragged').offsetTop);
} else {
document.querySelector('#container-pages').scrollTop = document.querySelector('.page-dragged').offsetTop;
}
document.querySelectorAll('.canvas-container .input-drag:checked').forEach(function(item) {
let page = item.parentNode;
page.querySelector('input[type=checkbox].input-drag').checked = false;
});
document.querySelectorAll('.canvas-container').forEach(function(page) {
page.querySelector('input[type=checkbox].input-hover').checked = false;
updatePageState(page);
});
updateGlobalState();
});
document.querySelector('#btn_liste_pdf').addEventListener('click', function(event) {
bootstrap.Modal.getOrCreateInstance(document.querySelector('#modalFichier')).show();
document.querySelector('#modalFichier .modal-body').insertAdjacentElement('afterbegin', document.querySelector('#list_pdf'));
});
document.querySelector('#btn_liste_pdf_bar').addEventListener('click', function(event) {
document.querySelector('#btn_liste_pdf').click();
});
document.querySelector('#modalDrag').addEventListener('hidden.bs.modal', event => {
document.querySelector('#list_pdf_container').insertAdjacentElement('afterbegin', document.querySelector('#list_pdf'));
});
document.querySelector('body').addEventListener('click', function(event) {
if(!event.originalTarget.classList.contains('offcanvas-header') && !event.originalTarget.classList.contains('offcanvas-body') && event.originalTarget.id != 'container-pages' && event.originalTarget.id != 'sidebarToolsLabel' && event.originalTarget.id != 'btn_container') {
return;
}
document.getElementById('btn_cancel_select').click();
});
}
async function getPDFBlobFromCache(cacheUrl) {
const cache = await caches.open('pdf');
let responsePdf = await cache.match(cacheUrl);
if(!responsePdf) {
return null;
}
let pdfBlob = await responsePdf.blob();
return pdfBlob;
}
async function uploadFromUrl(url) {
history.replaceState({}, '', '/organization');
var response = await fetch(url);
if(response.status != 200) {
return;
}
var pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return;
}
let dataTransfer = new DataTransfer();
let filename = url.replace(/^.*\//, '');
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf_upload').files = dataTransfer.files;
document.getElementById('input_pdf_upload').dispatchEvent(new Event("change"));
}
var pageUpload = async function() {
document.querySelector('body').classList.remove('bg-light');
document.getElementById('input_pdf_upload').value = '';
document.getElementById('page-upload').classList.remove('d-none');
document.getElementById('page-organization').classList.add('d-none');
document.getElementById('input_pdf_upload').focus();
let cache;
try {
cache = await caches.open('pdf');
} catch (e) {
console.error(e)
alert("Erreur d'accès au cache. Cette application ne fonctionne pas en mode de navigation privée");
return;
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
uploadAndLoadPDF(this);
pageOrganization(null);
});
}
var pageOrganization = async function() {
document.querySelector('body').classList.add('bg-light');
document.getElementById('page-upload').classList.add('d-none');
document.getElementById('page-organization').classList.remove('d-none');
menu = document.getElementById('sidebarTools');
menuOffcanvas = new bootstrap.Offcanvas(menu);
responsiveDisplay();
createEventsListener();
};
(function () {
if(window.location.hash && window.location.hash.match(/^\#http/)) {
let hashUrl = window.location.hash.replace(/^\#/, '');
pageUpload();
uploadFromUrl(hashUrl);
} else {
pageUpload();
}
window.addEventListener('hashchange', function() {
window.location.reload();
})
if (hasTouch()) {
disabledHoverStyle();
}
})();

1163
public/js/signature.js Normal file

File diff suppressed because it is too large Load Diff

5
public/logo.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg class="bi bi-vector-pen" width="128" height="128" fill="currentColor" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<path d="m84.48 1.8008a4.4307 4.4307 0 0 1 6.2738 0l35.445 35.445a4.4307 4.4307 0 0 1 0 6.2738l-16.854 16.854-7.3461 29.358a13.292 13.292 0 0 1-9.074 9.5082l-91.671 27.506 27.506-91.68a13.292 13.292 0 0 1 9.4994-9.0652l29.358-7.3372zm-15.95 25.769-28.117 7.0271a4.4307 4.4307 0 0 0-3.1724 3.0306l-22.774 75.898 75.915-22.774a4.4307 4.4307 0 0 0 3.0129-3.1635l7.0359-28.126-31.901-31.901z" fill-rule="evenodd" stroke-width="8.8614"/>
<path d="m15.237 113.29 45.796-37.466a8.8614 8.8614 0 1 0-8.8614-8.8614l-37.466 45.796-0.2304 0.76208z" fill-rule="evenodd" stroke-width="8.8614"/>
</svg>

After

Width:  |  Height:  |  Size: 776 B

View File

@ -1,9 +1,10 @@
@font-face {
font-family: "bootstrap-icons";
src: url("./fonts/bootstrap-icons.woff2?856008caa5eb66df68595e734e59580d") format("woff2"),
url("./fonts/bootstrap-icons.woff?856008caa5eb66df68595e734e59580d") format("woff");
src: url("./fonts/bootstrap-icons.woff2?524846017b983fc8ded9325d94ed40f3") format("woff2"),
url("./fonts/bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff");
}
.bi::before,
[class^="bi-"]::before,
[class*=" bi-"]::before {
display: inline-block;
@ -18,6 +19,7 @@ url("./fonts/bootstrap-icons.woff?856008caa5eb66df68595e734e59580d") format("wof
-moz-osx-font-smoothing: grayscale;
}
.bi-123::before { content: "\f67f"; }
.bi-alarm-fill::before { content: "\f101"; }
.bi-alarm::before { content: "\f102"; }
.bi-align-bottom::before { content: "\f103"; }
@ -1388,3 +1390,315 @@ url("./fonts/bootstrap-icons.woff?856008caa5eb66df68595e734e59580d") format("wof
.bi-translate::before { content: "\f658"; }
.bi-x-lg::before { content: "\f659"; }
.bi-safe::before { content: "\f65a"; }
.bi-apple::before { content: "\f65b"; }
.bi-microsoft::before { content: "\f65d"; }
.bi-windows::before { content: "\f65e"; }
.bi-behance::before { content: "\f65c"; }
.bi-dribbble::before { content: "\f65f"; }
.bi-line::before { content: "\f660"; }
.bi-medium::before { content: "\f661"; }
.bi-paypal::before { content: "\f662"; }
.bi-pinterest::before { content: "\f663"; }
.bi-signal::before { content: "\f664"; }
.bi-snapchat::before { content: "\f665"; }
.bi-spotify::before { content: "\f666"; }
.bi-stack-overflow::before { content: "\f667"; }
.bi-strava::before { content: "\f668"; }
.bi-wordpress::before { content: "\f669"; }
.bi-vimeo::before { content: "\f66a"; }
.bi-activity::before { content: "\f66b"; }
.bi-easel2-fill::before { content: "\f66c"; }
.bi-easel2::before { content: "\f66d"; }
.bi-easel3-fill::before { content: "\f66e"; }
.bi-easel3::before { content: "\f66f"; }
.bi-fan::before { content: "\f670"; }
.bi-fingerprint::before { content: "\f671"; }
.bi-graph-down-arrow::before { content: "\f672"; }
.bi-graph-up-arrow::before { content: "\f673"; }
.bi-hypnotize::before { content: "\f674"; }
.bi-magic::before { content: "\f675"; }
.bi-person-rolodex::before { content: "\f676"; }
.bi-person-video::before { content: "\f677"; }
.bi-person-video2::before { content: "\f678"; }
.bi-person-video3::before { content: "\f679"; }
.bi-person-workspace::before { content: "\f67a"; }
.bi-radioactive::before { content: "\f67b"; }
.bi-webcam-fill::before { content: "\f67c"; }
.bi-webcam::before { content: "\f67d"; }
.bi-yin-yang::before { content: "\f67e"; }
.bi-bandaid-fill::before { content: "\f680"; }
.bi-bandaid::before { content: "\f681"; }
.bi-bluetooth::before { content: "\f682"; }
.bi-body-text::before { content: "\f683"; }
.bi-boombox::before { content: "\f684"; }
.bi-boxes::before { content: "\f685"; }
.bi-dpad-fill::before { content: "\f686"; }
.bi-dpad::before { content: "\f687"; }
.bi-ear-fill::before { content: "\f688"; }
.bi-ear::before { content: "\f689"; }
.bi-envelope-check-1::before { content: "\f68a"; }
.bi-envelope-check-fill::before { content: "\f68b"; }
.bi-envelope-check::before { content: "\f68c"; }
.bi-envelope-dash-1::before { content: "\f68d"; }
.bi-envelope-dash-fill::before { content: "\f68e"; }
.bi-envelope-dash::before { content: "\f68f"; }
.bi-envelope-exclamation-1::before { content: "\f690"; }
.bi-envelope-exclamation-fill::before { content: "\f691"; }
.bi-envelope-exclamation::before { content: "\f692"; }
.bi-envelope-plus-fill::before { content: "\f693"; }
.bi-envelope-plus::before { content: "\f694"; }
.bi-envelope-slash-1::before { content: "\f695"; }
.bi-envelope-slash-fill::before { content: "\f696"; }
.bi-envelope-slash::before { content: "\f697"; }
.bi-envelope-x-1::before { content: "\f698"; }
.bi-envelope-x-fill::before { content: "\f699"; }
.bi-envelope-x::before { content: "\f69a"; }
.bi-explicit-fill::before { content: "\f69b"; }
.bi-explicit::before { content: "\f69c"; }
.bi-git::before { content: "\f69d"; }
.bi-infinity::before { content: "\f69e"; }
.bi-list-columns-reverse::before { content: "\f69f"; }
.bi-list-columns::before { content: "\f6a0"; }
.bi-meta::before { content: "\f6a1"; }
.bi-mortorboard-fill::before { content: "\f6a2"; }
.bi-mortorboard::before { content: "\f6a3"; }
.bi-nintendo-switch::before { content: "\f6a4"; }
.bi-pc-display-horizontal::before { content: "\f6a5"; }
.bi-pc-display::before { content: "\f6a6"; }
.bi-pc-horizontal::before { content: "\f6a7"; }
.bi-pc::before { content: "\f6a8"; }
.bi-playstation::before { content: "\f6a9"; }
.bi-plus-slash-minus::before { content: "\f6aa"; }
.bi-projector-fill::before { content: "\f6ab"; }
.bi-projector::before { content: "\f6ac"; }
.bi-qr-code-scan::before { content: "\f6ad"; }
.bi-qr-code::before { content: "\f6ae"; }
.bi-quora::before { content: "\f6af"; }
.bi-quote::before { content: "\f6b0"; }
.bi-robot::before { content: "\f6b1"; }
.bi-send-check-fill::before { content: "\f6b2"; }
.bi-send-check::before { content: "\f6b3"; }
.bi-send-dash-fill::before { content: "\f6b4"; }
.bi-send-dash::before { content: "\f6b5"; }
.bi-send-exclamation-1::before { content: "\f6b6"; }
.bi-send-exclamation-fill::before { content: "\f6b7"; }
.bi-send-exclamation::before { content: "\f6b8"; }
.bi-send-fill::before { content: "\f6b9"; }
.bi-send-plus-fill::before { content: "\f6ba"; }
.bi-send-plus::before { content: "\f6bb"; }
.bi-send-slash-fill::before { content: "\f6bc"; }
.bi-send-slash::before { content: "\f6bd"; }
.bi-send-x-fill::before { content: "\f6be"; }
.bi-send-x::before { content: "\f6bf"; }
.bi-send::before { content: "\f6c0"; }
.bi-steam::before { content: "\f6c1"; }
.bi-terminal-dash-1::before { content: "\f6c2"; }
.bi-terminal-dash::before { content: "\f6c3"; }
.bi-terminal-plus::before { content: "\f6c4"; }
.bi-terminal-split::before { content: "\f6c5"; }
.bi-ticket-detailed-fill::before { content: "\f6c6"; }
.bi-ticket-detailed::before { content: "\f6c7"; }
.bi-ticket-fill::before { content: "\f6c8"; }
.bi-ticket-perforated-fill::before { content: "\f6c9"; }
.bi-ticket-perforated::before { content: "\f6ca"; }
.bi-ticket::before { content: "\f6cb"; }
.bi-tiktok::before { content: "\f6cc"; }
.bi-window-dash::before { content: "\f6cd"; }
.bi-window-desktop::before { content: "\f6ce"; }
.bi-window-fullscreen::before { content: "\f6cf"; }
.bi-window-plus::before { content: "\f6d0"; }
.bi-window-split::before { content: "\f6d1"; }
.bi-window-stack::before { content: "\f6d2"; }
.bi-window-x::before { content: "\f6d3"; }
.bi-xbox::before { content: "\f6d4"; }
.bi-ethernet::before { content: "\f6d5"; }
.bi-hdmi-fill::before { content: "\f6d6"; }
.bi-hdmi::before { content: "\f6d7"; }
.bi-usb-c-fill::before { content: "\f6d8"; }
.bi-usb-c::before { content: "\f6d9"; }
.bi-usb-fill::before { content: "\f6da"; }
.bi-usb-plug-fill::before { content: "\f6db"; }
.bi-usb-plug::before { content: "\f6dc"; }
.bi-usb-symbol::before { content: "\f6dd"; }
.bi-usb::before { content: "\f6de"; }
.bi-boombox-fill::before { content: "\f6df"; }
.bi-displayport-1::before { content: "\f6e0"; }
.bi-displayport::before { content: "\f6e1"; }
.bi-gpu-card::before { content: "\f6e2"; }
.bi-memory::before { content: "\f6e3"; }
.bi-modem-fill::before { content: "\f6e4"; }
.bi-modem::before { content: "\f6e5"; }
.bi-motherboard-fill::before { content: "\f6e6"; }
.bi-motherboard::before { content: "\f6e7"; }
.bi-optical-audio-fill::before { content: "\f6e8"; }
.bi-optical-audio::before { content: "\f6e9"; }
.bi-pci-card::before { content: "\f6ea"; }
.bi-router-fill::before { content: "\f6eb"; }
.bi-router::before { content: "\f6ec"; }
.bi-ssd-fill::before { content: "\f6ed"; }
.bi-ssd::before { content: "\f6ee"; }
.bi-thunderbolt-fill::before { content: "\f6ef"; }
.bi-thunderbolt::before { content: "\f6f0"; }
.bi-usb-drive-fill::before { content: "\f6f1"; }
.bi-usb-drive::before { content: "\f6f2"; }
.bi-usb-micro-fill::before { content: "\f6f3"; }
.bi-usb-micro::before { content: "\f6f4"; }
.bi-usb-mini-fill::before { content: "\f6f5"; }
.bi-usb-mini::before { content: "\f6f6"; }
.bi-cloud-haze2::before { content: "\f6f7"; }
.bi-device-hdd-fill::before { content: "\f6f8"; }
.bi-device-hdd::before { content: "\f6f9"; }
.bi-device-ssd-fill::before { content: "\f6fa"; }
.bi-device-ssd::before { content: "\f6fb"; }
.bi-displayport-fill::before { content: "\f6fc"; }
.bi-mortarboard-fill::before { content: "\f6fd"; }
.bi-mortarboard::before { content: "\f6fe"; }
.bi-terminal-x::before { content: "\f6ff"; }
.bi-arrow-through-heart-fill::before { content: "\f700"; }
.bi-arrow-through-heart::before { content: "\f701"; }
.bi-badge-sd-fill::before { content: "\f702"; }
.bi-badge-sd::before { content: "\f703"; }
.bi-bag-heart-fill::before { content: "\f704"; }
.bi-bag-heart::before { content: "\f705"; }
.bi-balloon-fill::before { content: "\f706"; }
.bi-balloon-heart-fill::before { content: "\f707"; }
.bi-balloon-heart::before { content: "\f708"; }
.bi-balloon::before { content: "\f709"; }
.bi-box2-fill::before { content: "\f70a"; }
.bi-box2-heart-fill::before { content: "\f70b"; }
.bi-box2-heart::before { content: "\f70c"; }
.bi-box2::before { content: "\f70d"; }
.bi-braces-asterisk::before { content: "\f70e"; }
.bi-calendar-heart-fill::before { content: "\f70f"; }
.bi-calendar-heart::before { content: "\f710"; }
.bi-calendar2-heart-fill::before { content: "\f711"; }
.bi-calendar2-heart::before { content: "\f712"; }
.bi-chat-heart-fill::before { content: "\f713"; }
.bi-chat-heart::before { content: "\f714"; }
.bi-chat-left-heart-fill::before { content: "\f715"; }
.bi-chat-left-heart::before { content: "\f716"; }
.bi-chat-right-heart-fill::before { content: "\f717"; }
.bi-chat-right-heart::before { content: "\f718"; }
.bi-chat-square-heart-fill::before { content: "\f719"; }
.bi-chat-square-heart::before { content: "\f71a"; }
.bi-clipboard-check-fill::before { content: "\f71b"; }
.bi-clipboard-data-fill::before { content: "\f71c"; }
.bi-clipboard-fill::before { content: "\f71d"; }
.bi-clipboard-heart-fill::before { content: "\f71e"; }
.bi-clipboard-heart::before { content: "\f71f"; }
.bi-clipboard-minus-fill::before { content: "\f720"; }
.bi-clipboard-plus-fill::before { content: "\f721"; }
.bi-clipboard-pulse::before { content: "\f722"; }
.bi-clipboard-x-fill::before { content: "\f723"; }
.bi-clipboard2-check-fill::before { content: "\f724"; }
.bi-clipboard2-check::before { content: "\f725"; }
.bi-clipboard2-data-fill::before { content: "\f726"; }
.bi-clipboard2-data::before { content: "\f727"; }
.bi-clipboard2-fill::before { content: "\f728"; }
.bi-clipboard2-heart-fill::before { content: "\f729"; }
.bi-clipboard2-heart::before { content: "\f72a"; }
.bi-clipboard2-minus-fill::before { content: "\f72b"; }
.bi-clipboard2-minus::before { content: "\f72c"; }
.bi-clipboard2-plus-fill::before { content: "\f72d"; }
.bi-clipboard2-plus::before { content: "\f72e"; }
.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
.bi-clipboard2-pulse::before { content: "\f730"; }
.bi-clipboard2-x-fill::before { content: "\f731"; }
.bi-clipboard2-x::before { content: "\f732"; }
.bi-clipboard2::before { content: "\f733"; }
.bi-emoji-kiss-fill::before { content: "\f734"; }
.bi-emoji-kiss::before { content: "\f735"; }
.bi-envelope-heart-fill::before { content: "\f736"; }
.bi-envelope-heart::before { content: "\f737"; }
.bi-envelope-open-heart-fill::before { content: "\f738"; }
.bi-envelope-open-heart::before { content: "\f739"; }
.bi-envelope-paper-fill::before { content: "\f73a"; }
.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
.bi-envelope-paper-heart::before { content: "\f73c"; }
.bi-envelope-paper::before { content: "\f73d"; }
.bi-filetype-aac::before { content: "\f73e"; }
.bi-filetype-ai::before { content: "\f73f"; }
.bi-filetype-bmp::before { content: "\f740"; }
.bi-filetype-cs::before { content: "\f741"; }
.bi-filetype-css::before { content: "\f742"; }
.bi-filetype-csv::before { content: "\f743"; }
.bi-filetype-doc::before { content: "\f744"; }
.bi-filetype-docx::before { content: "\f745"; }
.bi-filetype-exe::before { content: "\f746"; }
.bi-filetype-gif::before { content: "\f747"; }
.bi-filetype-heic::before { content: "\f748"; }
.bi-filetype-html::before { content: "\f749"; }
.bi-filetype-java::before { content: "\f74a"; }
.bi-filetype-jpg::before { content: "\f74b"; }
.bi-filetype-js::before { content: "\f74c"; }
.bi-filetype-jsx::before { content: "\f74d"; }
.bi-filetype-key::before { content: "\f74e"; }
.bi-filetype-m4p::before { content: "\f74f"; }
.bi-filetype-md::before { content: "\f750"; }
.bi-filetype-mdx::before { content: "\f751"; }
.bi-filetype-mov::before { content: "\f752"; }
.bi-filetype-mp3::before { content: "\f753"; }
.bi-filetype-mp4::before { content: "\f754"; }
.bi-filetype-otf::before { content: "\f755"; }
.bi-filetype-pdf::before { content: "\f756"; }
.bi-filetype-php::before { content: "\f757"; }
.bi-filetype-png::before { content: "\f758"; }
.bi-filetype-ppt-1::before { content: "\f759"; }
.bi-filetype-ppt::before { content: "\f75a"; }
.bi-filetype-psd::before { content: "\f75b"; }
.bi-filetype-py::before { content: "\f75c"; }
.bi-filetype-raw::before { content: "\f75d"; }
.bi-filetype-rb::before { content: "\f75e"; }
.bi-filetype-sass::before { content: "\f75f"; }
.bi-filetype-scss::before { content: "\f760"; }
.bi-filetype-sh::before { content: "\f761"; }
.bi-filetype-svg::before { content: "\f762"; }
.bi-filetype-tiff::before { content: "\f763"; }
.bi-filetype-tsx::before { content: "\f764"; }
.bi-filetype-ttf::before { content: "\f765"; }
.bi-filetype-txt::before { content: "\f766"; }
.bi-filetype-wav::before { content: "\f767"; }
.bi-filetype-woff::before { content: "\f768"; }
.bi-filetype-xls-1::before { content: "\f769"; }
.bi-filetype-xls::before { content: "\f76a"; }
.bi-filetype-xml::before { content: "\f76b"; }
.bi-filetype-yml::before { content: "\f76c"; }
.bi-heart-arrow::before { content: "\f76d"; }
.bi-heart-pulse-fill::before { content: "\f76e"; }
.bi-heart-pulse::before { content: "\f76f"; }
.bi-heartbreak-fill::before { content: "\f770"; }
.bi-heartbreak::before { content: "\f771"; }
.bi-hearts::before { content: "\f772"; }
.bi-hospital-fill::before { content: "\f773"; }
.bi-hospital::before { content: "\f774"; }
.bi-house-heart-fill::before { content: "\f775"; }
.bi-house-heart::before { content: "\f776"; }
.bi-incognito::before { content: "\f777"; }
.bi-magnet-fill::before { content: "\f778"; }
.bi-magnet::before { content: "\f779"; }
.bi-person-heart::before { content: "\f77a"; }
.bi-person-hearts::before { content: "\f77b"; }
.bi-phone-flip::before { content: "\f77c"; }
.bi-plugin::before { content: "\f77d"; }
.bi-postage-fill::before { content: "\f77e"; }
.bi-postage-heart-fill::before { content: "\f77f"; }
.bi-postage-heart::before { content: "\f780"; }
.bi-postage::before { content: "\f781"; }
.bi-postcard-fill::before { content: "\f782"; }
.bi-postcard-heart-fill::before { content: "\f783"; }
.bi-postcard-heart::before { content: "\f784"; }
.bi-postcard::before { content: "\f785"; }
.bi-search-heart-fill::before { content: "\f786"; }
.bi-search-heart::before { content: "\f787"; }
.bi-sliders2-vertical::before { content: "\f788"; }
.bi-sliders2::before { content: "\f789"; }
.bi-trash3-fill::before { content: "\f78a"; }
.bi-trash3::before { content: "\f78b"; }
.bi-valentine::before { content: "\f78c"; }
.bi-valentine2::before { content: "\f78d"; }
.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
.bi-wrench-adjustable-circle::before { content: "\f78f"; }
.bi-wrench-adjustable::before { content: "\f790"; }
.bi-filetype-json::before { content: "\f791"; }
.bi-filetype-pptx::before { content: "\f792"; }
.bi-filetype-xlsx::before { content: "\f793"; }

File diff suppressed because one or more lines are too long

1
public/vendor/bootstrap.min.css.map vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

16
public/vendor/pdf-lib.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,81 +0,0 @@
<!doctype html>
<html lang="fr_FR">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<title>Signature PDF</title>
</head>
<body>
<div class="px-4 py-5 my-5 text-center">
<h1 class="display-5 fw-bold"><i class="bi bi-vector-pen"></i> Signer un PDF</h1>
<div class="col-lg-3 mx-auto">
<div class="col-12">
<label for="input_pdf_upload" class="form-label">Choisir un PDF</label>
<input id="input_pdf_upload" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf">
<p class="mt-1 opacity-50"><small class="text-muted">Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo et <?php echo $maxPage ?> pages</small></p>
<a class="btn btn-sm btn-link opacity-75" href="/#https://raw.githubusercontent.com/24eme/signaturepdf/master/tests/files/document.pdf">Tester avec un PDF de démo</a>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom">
<small>Logiciel libre sous license AGPL-3.0 : <a href="https://github.com/24eme/signaturepdf">voir le code source</a></small>
</footer>
<script>
(async function () {
const cache = await caches.open('pdf');
var key = "<?php echo $key ?>";
var urlPdf = '/'+key+'/pdf';
var urlSignature = '/'+key;
var pdfHistory = {};
var maxSize = <?php echo $maxSize ?>;
if(localStorage.getItem('pdfHistory')) {
pdfHistory = JSON.parse(localStorage.getItem('pdfHistory'));
}
document.getElementById('input_pdf_upload').addEventListener('change', async function(event) {
if(document.getElementById('input_pdf_upload').files[0].size > maxSize) {
alert("Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo");
document.getElementById('input_pdf_upload').value = "";
return;
}
var response = new Response(document.getElementById('input_pdf_upload').files[0], { "status" : 200, "statusText" : "OK" });
await cache.put(urlPdf, response);
pdfHistory[key] = { filename: document.getElementById('input_pdf_upload').files[0].name }
localStorage.setItem('pdfHistory', JSON.stringify(pdfHistory));
document.location = urlSignature;
});
async function uploadFromUrl(url) {
var response = await fetch(url);
if(response.status != 200) {
return;
}
var pdfBlob = await response.blob();
if(pdfBlob.type != 'application/pdf' && pdfBlob.type != 'application/octet-stream') {
return;
}
var dataTransfer = new DataTransfer();
var filename = url.replace(/^.*\//, '');
dataTransfer.items.add(new File([pdfBlob], filename, {
type: 'application/pdf'
}));
document.getElementById('input_pdf_upload').files = dataTransfer.files;
document.getElementById('input_pdf_upload').dispatchEvent(new Event("change"));
history.replaceState({}, "Signature de PDF", "/");
}
if(window.location.hash) {
uploadFromUrl(window.location.hash.replace(/^\#/, ''));
}
window.addEventListener('hashchange', function() {
uploadFromUrl(window.location.hash.replace(/^\#/, ''));
})
})();
</script>
</body>
</html>

107
templates/metadata.html.php Normal file
View File

@ -0,0 +1,107 @@
<!doctype html>
<html lang="fr_FR">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="/favicon-metadata.ico">
<title>Édition des métadonnées d'un PDF</title>
</head>
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-tags"></i> Éditer les métadonnées</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Ajouter, modifier ou supprimer les métadonnées d'un PDF</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF</label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
<p class="mt-2 small fw-light text-dark">&nbsp;</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-metadata" class="d-none">
<div id="div-margin-top" style="height: 88px;" class="d-md-none"></div>
<div style="width: 60%; overflow: auto;" class="vh-100" id="container-main">
<div id="form-metadata" class="mx-auto w-75 pt-3 pb-5">
<h3>Liste des métadonnées du PDF</h3>
<div id="form-metadata-container">
</div>
<form id="form_metadata_add" class="position-relative">
<hr class="text-muted mt-4 mb-3" />
<div class="mb-3">
<label class="form-label text-muted" for="input_metadata_key">Ajouter une nouvelle métadonnée</label>
<div class="form-floating">
<input id="input_metadata_key" name="metadata_key" type="text" class="form-control" required value="" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
<label>Clé</label>
</div>
<input id="input_metadata_value" readonly="readonly" style="border-top: 0; border-top-right-radius: 0; border-top-left-radius: 0;" name="metadata_value" type="text" class="form-control bg-light opacity-50" value="" placeholder="Valeur" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;">
</div>
<button type="submit" type="button" class="btn btn-outline-secondary float-end"><i class="bi bi-plus-circle"></i> Ajouter</button>
</form>
</div>
</div>
<div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
<div style="width: 40%;" class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/metadata"></a>
<div class="offcanvas-header d-block mb-0 pb-0 border-bottom">
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Édition des métadonnées<span class="float-end me-2"><i class="bi bi-tags"></i></span></h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
<p id="text_document_name" class="text-muted mb-2" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;" title=""><i class="bi bi-files"></i> <span></span></p>
</div>
<div class="offcanvas-body bg-light" style="padding-bottom: 60px;">
<div id="container-pages">
</div>
</div>
<div class="position-absolute bg-white bottom-0 pb-2 ps-2 pe-2 w-100 border-top shadow-lg">
<div id="btn_container" class="d-grid gap-2 mt-2">
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Enregistrer et télécharger le PDF</button>
</div>
</div>
</div>
<div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div id="bottom_bar_action" class="d-grid gap-2">
<button class="btn btn-primary" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF</button>
</div>
</div>
</div>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
<script src="/vendor/pdf.js?legacy"></script>
<script src="/vendor/pdf-lib.min.js?1.17.1"></script>
<script>
var defaultFields = <?php echo json_encode(isset($METADATA_DEFAULT_FIELDS) ? $METADATA_DEFAULT_FIELDS : array()); ?>;
</script>
<script src="/js/metadata.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/metadata.js") ?>"></script>
</body>
</html>

View File

@ -0,0 +1,162 @@
<!doctype html>
<html lang="fr_FR">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="/favicon-organization.ico">
<title>Organiser un PDF</title>
</head>
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-ui-checks-grid"></i> Organiser des PDF</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Fusionner, trier, pivoter, supprimer, extraire des pages</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo"><i class="bi bi-info-circle"></i></small></label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" multiple="true" />
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-organization" style="padding-right: 350px;" class="d-none">
<div id="div-margin-top" style="height: 88px;" class="d-md-none"></div>
<div style="top: 62px;" class="w-100 position-absolute text-center text-muted opacity-50 d-md-none"><em>Toucher une page pour la sélectionner</em></div>
<div id="container-main">
<div id="container-pages" class="col-12 pt-1 vh-100 d-flex align-content-start flex-wrap position-relative" style="overflow-y: scroll; overflow-x: hidden;">
</div>
</div>
<div id="container-btn-zoom" class="btn-group-vertical position-fixed" style="top: 6px; right: 368px;">
<button id="btn-zoom-increase" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-in"></i></button>
<button id="btn-zoom-decrease" class="btn btn-outline-dark bg-white text-dark"><i class="bi bi-zoom-out"></i></button>
</div>
<div id="div-margin-bottom" style="height: 55px;" class="d-md-none"></div>
<div class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/organization"></a>
<div class="offcanvas-header mb-0 pb-0">
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Organisation de PDF <span class="float-end me-2" title="Ce PDF est stocké sur votre ordinateur pour être signé par vous uniquement"><i class="bi-ui-checks-grid"></i></span></h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body pt-3" style="padding-bottom: 60px;">
<div id="list_pdf_container">
<ul id="list_pdf" class="list-group">
</ul>
</div>
<div class="d-grid gap-2 mt-2">
<button type="button" class="btn btn-sm btn-outline-dark" onclick="document.getElementById('input_pdf_upload_2').click();"><i class="bi bi-plus-circle"></i> Ajouter un PDF</button>
<input id="input_pdf_upload_2" class="form-control d-none" type="file" accept=".pdf,application/pdf" multiple="true">
</div>
<hr />
<div id="container_btn_select" class="opacity-50 card">
<div class="card-header small text-center p-1"><span>Aucune</span> page(s) sélectionnée(s) <button id="btn_cancel_select" type="button" class="btn-close btn-close-white float-end" aria-label="Close"></button></div>
<div class="card-body d-grid gap-2 p-2">
<button id="btn_rotate_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-clockwise"></i> Tourner de 90°</button>
<button id="btn_drag_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrows-move"></i> Déplacer</button>
<button id="btn_delete_select" disabled="disabled" type="button" class="btn btn-sm btn-outline-secondary"><i class="bi bi-trash"></i> Supprimer</button>
<button id="save-select" class="btn btn-sm btn-outline-secondary" disabled="disabled" form="form_pdf" type="submit"><i class="bi bi-download"></i> Extraire et télécharger</button>
</div>
<div class="card-footer d-none small text-center p-1 border-primary bg-primary bg-opacity-25"><a id="btn_cancel_select_footer" type="button" aria-label="Close" style="text-decoration: none;" class="text-primary"><i class="bi bi-x-lg"></i> Annuler la sélection</a></div>
</div>
<div class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100">
<form id="form_pdf" action="/organize" method="post" enctype="multipart/form-data">
<input id="input_pdf" name="pdf[]" type="file" class="d-none" />
<input id="input_pages" type="hidden" value="" name="pages" />
<div id="btn_container" class="d-grid gap-2 mt-2">
<button class="btn btn-primary" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
</div>
</form>
</div>
</div>
</div>
<div id="top_bar" class="position-fixed top-0 start-0 bg-white w-100 shadow-sm d-md-none p-2">
<div id="top_bar_action">
<div class="d-flex" role="group">
<button id="btn_liste_pdf" type="button" data-bs-toggle="modal" data-bs-target="#modalFichier" class="btn btn-dark flex-grow-1 me-2" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<i class="bi bi-files"></i> <span id="liste_pdf_titre_mobile"></span>
</button>
<button type="button" class="btn btn-outline-dark position-relative" style="padding-left: 30px;" onclick="document.getElementById('input_pdf_upload_2').click(); this.blur();"><i class="bi bi-plus-circle position-absolute" style="left: 10px;"></i>Ajouter&nbsp;un&nbsp;PDF</button>
</div>
</div>
<div id="top_bar_action_selection" class="d-none">
<div id="top_bar_action_selection_recap" class="bg-primary text-white text-center rounded-top p-1 position-relative"><button id="btn_liste_pdf_bar" type="button" style="text-decoration: none;left: 0px; top:0px;" class="btn bg-white bg-opacity-50 text-primary position-absolute p-0 ps-1 pe-1 mt-1 ms-1"><i class="bi bi-files"></i>&nbsp;<span></span> PDF</button><span id="top_bar_action_selection_recap_nb_pages">Aucune</span> page(s)<button id="btn_cancel_select_mobile" type="button" style="text-decoration: none;right: 0px; top:0px;" class="btn bg-white bg-opacity-50 text-primary position-absolute p-0 ps-1 pe-1 mt-1 me-1"><i class="bi bi-x-lg"></i>&nbsp;Annuler</button></div>
<div class="btn-group w-100">
<button id="btn_rotate_select_mobile" type="button" class="btn btn-outline-primary" style="border-top-left-radius: 0 !important;"><i class="bi bi-arrow-clockwise"></i> Tourner</button>
<button id="btn_drag_select_mobile" type="button" class="btn btn-outline-primary"><i class="bi bi-arrows-move"></i> Déplacer</button>
<button id="btn_delete_select_mobile" type="button" class="btn btn-outline-primary" style="border-top-right-radius: 0 !important;"><i class="bi bi-trash"></i> Supprimer</button>
</div>
</div>
</div>
<div id="bottom_bar" class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div id="bottom_bar_action" class="d-grid gap-2">
<button class="btn btn-primary" type="submit" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF complet</button>
</div>
<div id="bottom_bar_action_selection" class="d-grid gap-2 d-none">
<button id="save-select_mobile" class="btn btn-outline-primary" type="submit" form="form_pdf"><i class="bi bi-download"></i> Télécharger la sélection</button>
</div>
</div>
</div>
<div class="modal fade" id="modalDrag" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable modal-xl modal-fullscreen-md-down">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Déplacement de page(s)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body bg-light ps-5">
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalFichier" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Documents PDF</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
<script src="/vendor/pdf.js?legacy"></script>
<script>
var maxSize = <?php echo $maxSize ?>;
</script>
<script src="/js/organization.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/organization.js") ?>"></script>
</body>
</html>

View File

@ -1,164 +0,0 @@
<!doctype html>
<html lang="fr_FR">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.5.0" rel="stylesheet">
<style>
@font-face {
font-family: 'Caveat';
font-style: normal;
font-weight: 400;
src: url(/vendor/fonts/Caveat-Regular.ttf) format('truetype');
}
.offcanvas .list-item-add label:hover {
background: #e8ebed;
border: 1px solid #505050;
}
.offcanvas .list-item-add label:active, .offcanvas .list-item-add label.active, .offcanvas .list-item-add .btn-check:active + .btn-outline-secondary, .offcanvas .list-item-add .btn-check:checked + .btn-outline-secondary {
background: #c9d1d8;
border: 1px solid #000;
box-shadow: 0 .25rem .5rem rgba(0,0,0,.075) !important;
}
</style>
<title>Signature PDF</title>
</head>
<body class="bg-light" style="padding-right: 350px;">
<div style="height: 65px;" class="d-md-none"></div>
<div id="container-pages" style="overflow: auto" class="col-12 pt-1 pb-1 text-center vh-100">
</div>
<div style="height: 55px;" class="d-md-none"></div>
<div class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="offcanvasTop" aria-labelledby="offcanvasTopLabel" style="width: 350px;">
<div class="offcanvas-header mb-0 pb-0">
<h5 id="offcanvasTopLabel">Signature du PDF</h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="form-check form-switch mb-2 small d-none">
<input class="form-check-input" type="checkbox" id="add-lock-checkbox" disabled="disabled">
<label style="cursor: pointer;" class="form-check-label" for="add-lock-checkbox"> Garder la séléction active</label>
</div>
<div id="svg_list_signature" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_signature_add" name="svg_2_add" autocomplete="off" value="signature">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="signature" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_signature_add" id="label_svg_signature_add"><i class="bi bi-vector-pen"></i> Signature <small class="text-muted float-end">Ajouter</small></label>
</div>
<div id="svg_list_initials" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_initials_add" name="svg_2_add" autocomplete="off" value="intials">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="initials" data-modalnav="#nav-type-tab" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_initials_add" id="label_svg_initials_add"><i class="bi bi-type"></i> Paraphe <small class="text-muted float-end">Ajouter</small></label>
</div>
<div id="svg_list_rubber_stamber" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_rubber_stamber_add" name="svg_2_add" autocomplete="off" value="rubber_stamber">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="rubber_stamber" data-modalnav="#nav-import-tab" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_rubber_stamber_add" id="label_svg_rubber_stamber_add"><i class="bi bi-card-text"></i> Tampon <small class="text-muted float-end">Ajouter</small></label>
</div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_text" data-svg="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktdGV4dGFyZWEtdCIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBkPSJNMS41IDIuNUExLjUgMS41IDAgMCAxIDMgMWgxMGExLjUgMS41IDAgMCAxIDEuNSAxLjV2My41NjNhMiAyIDAgMCAxIDAgMy44NzRWMTMuNUExLjUgMS41IDAgMCAxIDEzIDE1SDNhMS41IDEuNSAwIDAgMS0xLjUtMS41VjkuOTM3YTIgMiAwIDAgMSAwLTMuODc0VjIuNXptMSAzLjU2M2EyIDIgMCAwIDEgMCAzLjg3NFYxMy41YS41LjUgMCAwIDAgLjUuNWgxMGEuNS41IDAgMCAwIC41LS41VjkuOTM3YTIgMiAwIDAgMSAwLTMuODc0VjIuNUEuNS41IDAgMCAwIDEzIDJIM2EuNS41IDAgMCAwLS41LjV2My41NjN6TTIgN2ExIDEgMCAxIDAgMCAyIDEgMSAwIDAgMCAwLTJ6bTEyIDBhMSAxIDAgMSAwIDAgMiAxIDEgMCAwIDAgMC0yeiIvPjxwYXRoIGQ9Ik0xMS40MzQgNEg0LjU2Nkw0LjUgNS45OTRoLjM4NmMuMjEtMS4yNTIuNjEyLTEuNDQ2IDIuMTczLTEuNDk1bC4zNDMtLjAxMXY2LjM0M2MwIC41MzctLjExNi42NjUtMS4wNDkuNzQ4VjEyaDMuMjk0di0uNDIxYy0uOTM4LS4wODMtMS4wNTQtLjIxLTEuMDU0LS43NDhWNC40ODhsLjM0OC4wMWMxLjU2LjA1IDEuOTYzLjI0NCAyLjE3MyAxLjQ5NmguMzg2TDExLjQzNCA0eiIvPjwvc3ZnPgo=" name="svg_2_add" autocomplete="off" value="text">
<label draggable="true" id="label_svg_text" class="btn btn-outline-secondary text-black text-start btn-svg" for="radio_svg_text"><i class="bi bi-textarea-t"></i> Texte</label>
</div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_check" data-height="18" name="svg_2_add" autocomplete="off" value="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktY2hlY2stbGciIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTEyLjczNiAzLjk3YS43MzMuNzMzIDAgMCAxIDEuMDQ3IDBjLjI4Ni4yODkuMjkuNzU2LjAxIDEuMDVMNy44OCAxMi4wMWEuNzMzLjczMyAwIDAgMS0xLjA2NS4wMkwzLjIxNyA4LjM4NGEuNzU3Ljc1NyAwIDAgMSAwLTEuMDYuNzMzLjczMyAwIDAgMSAxLjA0NyAwbDMuMDUyIDMuMDkzIDUuNC02LjQyNWEuMjQ3LjI0NyAwIDAgMSAuMDItLjAyMloiLz4KPC9zdmc+Cg==">
<label draggable="true" id="label_svg_check" class="btn btn-outline-secondary text-black text-start btn-svg" for="radio_svg_check"><i class="bi bi-check-square"></i> Case à cocher</label>
</div>
<div id="svg_list" class="d-grid gap-2 mt-2 mb-2 list-item-add"></div>
<div class="d-grid gap-2 mt-2">
<button type="button" id="btn-add-svg" class="btn btn-sm btn-light" data-bs-toggle="modal" data-bs-target="#modalAddSvg"><i class="bi bi-plus-circle"></i> Ajouter un élément</button>
</div>
<form class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100 d-none d-sm-none d-md-block" id="form_pdf" action="/sign" method="post" enctype="multipart/form-data">
<input id="input_pdf" name="pdf" type="file" class="d-none" />
<input id="input_svg" name="svg[]" type="file" class="d-none" />
<div class="d-grid gap-2 mt-2">
<button class="btn btn-primary" disabled="disabled" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF Signé</button>
</div>
</form>
</div>
</div>
<div class="position-fixed top-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div class="d-grid gap-2">
<button id="btn_svn_select" class="btn btn-light btn-lg" data-bs-toggle="offcanvas" data-bs-target="#offcanvasTop" aria-controls="offcanvasTop"><i class="bi bi-hand-index"></i> Séléctionner une signature</button>
</div>
<div id="svg_selected_container" class="text-center d-none position-relative">
<img id="svg_selected" src="" style="height: 48px;" class="img-fluid"/>
<button type="button" id="btn_svg_selected_close" class="btn-close text-reset position-absolute" style="top: 9px; right: 9px;"></button>
</div>
<div id="svg_object_actions" class="d-none">
<button id="btn-svg-pdf-delete" class="btn btn-lg btn-light"><i class="bi bi-trash"></i></button>
</div>
</div>
<div class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow d-md-none">
<div class="btn-group position-absolute opacity-25" style="top: -46px;">
<button id="btn-zoom-decrease" class="btn btn-secondary"><i class="bi bi-dash"></i></button>
<button id="btn-zoom-increase" class="btn btn-secondary"><i class="bi bi-plus"></i></button>
</div>
<div class="d-grid gap-2">
<button class="btn btn-primary" disabled="disabled" type="submit" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF Signé</button>
</div>
</div>
<div class="modal fade" id="modalAddSvg" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-draw-tab" data-bs-toggle="tab" data-bs-target="#nav-draw" type="button" role="tab" aria-controls="nav-draw" aria-selected="true"><i class="bi bi-vector-pen"></i> Dessiner</button>
<button class="nav-link" id="nav-type-tab" data-bs-toggle="tab" data-bs-target="#nav-type" type="button" role="tab" aria-controls="nav-type" aria-selected="false"><i class="bi bi-fonts"></i> Saisir</button>
<button class="nav-link" id="nav-import-tab" data-bs-toggle="tab" data-bs-target="#nav-import" type="button" role="tab" aria-controls="nav-import" aria-selected="false"><i class="bi bi-image"></i> Importer</button>
</div>
</nav>
<div class="tab-content mt-3" id="nav-svg-add">
<div class="tab-pane fade show active" id="nav-draw" role="tabpanel" aria-labelledby="nav-draw-tab">
<small id="signature-pad-reset" class="text-muted opacity-75 position-absolute" style="right: 25px; bottom: 25px; cursor: pointer;" title="Effacer la signature"><i class="bi bi-trash"></i></small>
<canvas id="signature-pad" class="border bg-light" width="462" height="200"></canvas>
</div>
<div class="tab-pane fade" id="nav-type" role="tabpanel" aria-labelledby="nav-type-tab">
<input id="input-text-signature" type="text" class="form-control form-control-lg" placeholder="Ma signature" style="font-family: Caveat; font-size: 48px;" />
</div>
<div class="tab-pane fade" id="nav-import" role="tabpanel" aria-labelledby="nav-import-tab">
<div class="text-center">
<img id="img-upload" class="d-none" style="max-width: 460px; max-height: 200px;" src="" />
</div>
<form id="form-image-upload" action="/image2svg" method="POST" enctype="multipart/form-data">
<input id="input-image-upload" class="form-control" name="image" type="file">
</form>
</div>
</div>
<input id="input-svg-type" type="hidden" />
</div>
<div class="modal-footer">
<button tabindex="-1" type="button" class="btn btn-light" data-bs-dismiss="modal">Annuler</button>
<button id="btn_modal_ajouter" type="button" disabled="disabled" class="btn btn-primary" data-bs-dismiss="modal"><span id="btn_modal_ajouter_spinner" class="spinner-border spinner-border-sm d-none"></span><span id="btn_modal_ajouter_check" class="bi bi-check-circle"></span> Ajouter</button>
</div>
</div>
</div>
</div>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.1"></script>
<script src="/vendor/pdf.js?legacy"></script>
<script src="/vendor/fabric.min.js?4.6.0"></script>
<script src="/vendor/signature_pad.umd.min.js?3.0.0-beta.3"></script>
<script src="/vendor/opentype.min.js?1.3.3"></script>
<script>
var url = '/<?php echo $key ?>/pdf';
var pdfHistory = {};
var maxPage = <?php echo $maxPage ?>;
var filename = null;
if(localStorage.getItem('pdfHistory')) {
pdfHistory = JSON.parse(localStorage.getItem('pdfHistory'));
}
if(pdfHistory["<?php echo $key ?>"]) {
filename = pdfHistory["<?php echo $key ?>"].filename;
}
</script>
<script src="/js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,282 @@
<!doctype html>
<html lang="fr">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Logiciel libre de signature de PDF en ligne">
<link href="/vendor/bootstrap.min.css?5.1.1" rel="stylesheet">
<link href="/vendor/bootstrap-icons.css?1.8.1" rel="stylesheet">
<link href="/css/app.css?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/css/app.css") ?>" rel="stylesheet">
<title>Signature PDF</title>
</head>
<body>
<noscript>
<div class="alert alert-danger text-center" role="alert">
<i class="bi bi-exclamation-triangle"></i> Site non fonctionnel sans JavaScript activé
</div>
</noscript>
<div id="page-upload">
<?php if(!$disableOrganization): ?>
<ul class="nav justify-content-center nav-tabs mt-2">
<li class="nav-item">
<a class="nav-link active" href="/signature"><i class="bi bi-vector-pen"></i> Signer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/organization"><i class="bi bi-ui-checks-grid"></i> Organiser</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/metadata"><i class="bi bi-tags"></i> Metadonnées</a>
</li>
</ul>
<?php endif; ?>
<div class="px-4 py-4 text-center">
<h1 class="display-5 fw-bold mb-0 mt-3"><i class="bi bi-vector-pen"></i> Signer un PDF</h1>
<p class="fw-light mb-3 subtitle text-dark text-nowrap" style="overflow: hidden; text-overflow: ellipsis;">Signer, parapher, tamponner, compléter un document</p>
<div class="col-md-6 col-lg-5 col-xl-4 col-xxl-3 mx-auto">
<div class="col-12">
<label class="form-label mt-3" for="input_pdf_upload">Choisir un PDF <small class="opacity-75" style="cursor: help" title="Le PDF ne doit pas dépasser <?php echo round($maxSize / 1024 / 1024) ?> Mo et <?php echo $maxPage ?> pages"><i class="bi bi-info-circle"></i></small></label>
<input id="input_pdf_upload" placeholder="Choisir un PDF" class="form-control form-control-lg" type="file" accept=".pdf,application/pdf" />
<p class="mt-2 small fw-light text-dark">Le PDF sera traité par le serveur sans être conservé ni stocké</p>
<?php if($PDF_DEMO_LINK): ?>
<a class="btn btn-sm btn-link opacity-75" href="#<?php echo $PDF_DEMO_LINK ?>">Tester avec un PDF de démo</a>
<?php endif; ?>
</div>
</div>
</div>
<footer class="text-center text-muted mb-2 fixed-bottom opacity-75">
<small>Logiciel libre <span class="d-none d-md-inline">sous license AGPL-3.0</span> : <a href="https://github.com/24eme/signaturepdf">voir le code source </a><?php if($COMMIT): ?> <span class="d-none d-md-inline small">[<a href="https://github.com/24eme/signaturepdf/tree/<?php echo $COMMIT ?>"><?php echo $COMMIT ?></a>]</span><?php endif; ?></small>
</footer>
</div>
<div id="page-signature" style="padding-right: 350px;" class="d-none">
<?php if(isset($hash)): ?>
<div id="alert-signature-help" class="position-relative d-none">
<div class="alert alert-primary alert-dismissible position-absolute top-0 start-50 translate-middle-x text-center mt-4 pb-2 w-50 opacity-100" style="z-index: 100;" role="alert">
<h5 class="alert-heading">Comment signer ?</h5>
<strong>En cliquant directement sur la page du document</strong> pour insérer l'élément séléctionné dans la colonne de droite <small>(signature, paraphe, texte, tampon, etc ...)</small>
<div class="mt-1 fs-3"><i class="bi bi-box-arrow-down"></i></div>
<button type="button" class="btn-close btn-sm" aria-label="Close"></button>
</div>
</div>
<?php endif; ?>
<div style="height: 65px;" class="d-md-none"></div>
<div id="container-pages" class="col-12 pt-1 pb-1 text-center vh-100">
</div>
<div style="height: 55px;" class="d-md-none"></div>
<div class="offcanvas offcanvas-end show d-none d-md-block shadow-sm" data-bs-backdrop="false" data-bs-scroll="true" data-bs-keyboard="false" tabindex="-1" id="sidebarTools" aria-labelledby="sidebarToolsLabel">
<a class="btn btn-close btn-sm position-absolute opacity-25 d-none d-sm-none d-md-block" title="Fermer ce PDF et retourner à l'accueil" style="position: absolute; top: 2px; right: 2px; font-size: 10px;" href="/signature"></a>
<div class="offcanvas-header mb-0 pb-0">
<h5 class="mb-1 d-block w-100" id="sidebarToolsLabel">Signature du PDF <?php if(isset($hash)): ?><span class="float-end small me-2" title="Ce PDF est partagé avec d'autres personnes pour être signé à plusieurs"><span class="nblayers"></span> <i class="bi bi-people-fill"></i></span><?php else: ?><span class="float-end me-2" title="Ce PDF est stocké sur votre ordinateur pour être signé par vous uniquement"><i class="bi bi-person-workspace"></i></span><?php endif; ?></h5>
<button type="button" class="btn-close text-reset d-md-none" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body pt-0">
<p id="text_document_name" class="text-muted" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden;" title=""><i class="bi bi-files"></i> <span></span></p>
<div class="form-check form-switch mb-2 small d-none">
<input class="form-check-input" type="checkbox" id="add-lock-checkbox" disabled="disabled">
<label style="cursor: pointer;" class="form-check-label" for="add-lock-checkbox"> Garder la séléction active</label>
</div>
<div id="svg_list_signature" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_signature_add" name="svg_2_add" autocomplete="off" value="signature">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="signature" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_signature_add" id="label_svg_signature_add"><i class="bi bi-vector-pen"></i> Signature <small class="text-muted float-end">Créer</small></label>
</div>
<div id="svg_list_initials" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_initials_add" name="svg_2_add" autocomplete="off" value="intials">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="initials" data-modalnav="#nav-type-tab" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_initials_add" id="label_svg_initials_add"><i class="bi bi-type"></i> Paraphe <small class="text-muted float-end">Créer</small></label>
</div>
<div id="svg_list_rubber_stamber" class="list-item-add"></div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_rubber_stamber_add" name="svg_2_add" autocomplete="off" value="rubber_stamber">
<label data-bs-toggle="modal" data-bs-target="#modalAddSvg" data-type="rubber_stamber" data-modalnav="#nav-import-tab" class="btn btn-outline-secondary text-black text-start btn-add-svg-type" for="radio_svg_rubber_stamber_add" id="label_svg_rubber_stamber_add"><i class="bi bi-card-text"></i> Tampon <small class="text-muted float-end">Créer</small></label>
</div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_text" data-svg="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktdGV4dGFyZWEtdCIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBkPSJNMS41IDIuNUExLjUgMS41IDAgMCAxIDMgMWgxMGExLjUgMS41IDAgMCAxIDEuNSAxLjV2My41NjNhMiAyIDAgMCAxIDAgMy44NzRWMTMuNUExLjUgMS41IDAgMCAxIDEzIDE1SDNhMS41IDEuNSAwIDAgMS0xLjUtMS41VjkuOTM3YTIgMiAwIDAgMSAwLTMuODc0VjIuNXptMSAzLjU2M2EyIDIgMCAwIDEgMCAzLjg3NFYxMy41YS41LjUgMCAwIDAgLjUuNWgxMGEuNS41IDAgMCAwIC41LS41VjkuOTM3YTIgMiAwIDAgMSAwLTMuODc0VjIuNUEuNS41IDAgMCAwIDEzIDJIM2EuNS41IDAgMCAwLS41LjV2My41NjN6TTIgN2ExIDEgMCAxIDAgMCAyIDEgMSAwIDAgMCAwLTJ6bTEyIDBhMSAxIDAgMSAwIDAgMiAxIDEgMCAwIDAgMC0yeiIvPjxwYXRoIGQ9Ik0xMS40MzQgNEg0LjU2Nkw0LjUgNS45OTRoLjM4NmMuMjEtMS4yNTIuNjEyLTEuNDQ2IDIuMTczLTEuNDk1bC4zNDMtLjAxMXY2LjM0M2MwIC41MzctLjExNi42NjUtMS4wNDkuNzQ4VjEyaDMuMjk0di0uNDIxYy0uOTM4LS4wODMtMS4wNTQtLjIxLTEuMDU0LS43NDhWNC40ODhsLjM0OC4wMWMxLjU2LjA1IDEuOTYzLjI0NCAyLjE3MyAxLjQ5NmguMzg2TDExLjQzNCA0eiIvPjwvc3ZnPgo=" name="svg_2_add" autocomplete="off" value="text">
<label draggable="true" id="label_svg_text" class="btn btn-outline-secondary text-black text-start btn-svg" for="radio_svg_text"><i class="bi bi-textarea-t"></i> Texte</label>
</div>
<div class="d-grid gap-2 mb-2 list-item-add">
<input type="radio" class="btn-check" id="radio_svg_check" data-height="18" name="svg_2_add" autocomplete="off" value="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktY2hlY2stbGciIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTEyLjczNiAzLjk3YS43MzMuNzMzIDAgMCAxIDEuMDQ3IDBjLjI4Ni4yODkuMjkuNzU2LjAxIDEuMDVMNy44OCAxMi4wMWEuNzMzLjczMyAwIDAgMS0xLjA2NS4wMkwzLjIxNyA4LjM4NGEuNzU3Ljc1NyAwIDAgMSAwLTEuMDYuNzMzLjczMyAwIDAgMSAxLjA0NyAwbDMuMDUyIDMuMDkzIDUuNC02LjQyNWEuMjQ3LjI0NyAwIDAgMSAuMDItLjAyMloiLz4KPC9zdmc+Cg==">
<label draggable="true" id="label_svg_check" class="btn btn-outline-secondary text-black text-start btn-svg" for="radio_svg_check"><i class="bi bi-check-square"></i> Case à cocher</label>
</div>
<div id="svg_list" class="d-grid gap-2 mt-2 mb-2 list-item-add"></div>
<div class="d-grid gap-2 mt-2">
<button type="button" id="btn-add-svg" class="btn btn-sm btn-light" data-bs-toggle="modal" data-bs-target="#modalAddSvg"><i class="bi bi-plus-circle"></i> Créer un élément</button>
</div>
<div id="form_block" class="position-absolute bottom-0 pb-2 ps-0 pe-4 w-100">
<?php if(!isset($hash)): ?>
<?php if(!isset($noSharingMode)): ?>
<button class="btn btn-outline-dark w-100" type="button" data-bs-toggle="modal" data-bs-target="#modal-start-share"><i class="bi bi-share"></i> Partager pour signer <i class="bi bi-people-fill"></i> à plusieurs</button>
<?php endif; ?>
<form id="form_pdf" action="/sign" method="post" enctype="multipart/form-data" class="d-none d-sm-none d-md-block">
<input id="input_pdf" name="pdf" type="file" class="d-none" />
<input id="input_svg" name="svg[]" type="file" class="d-none" />
<button class="btn btn-primary w-100 mt-2" disabled="disabled" type="submit" id="save"><i class="bi bi-download"></i> Télécharger le PDF signé</button>
</form>
<?php elseif(!isset($noSharingMode)): ?>
<div class="d-none d-sm-none d-md-block position-relative">
<a id="btn-signature-help" class="position-absolute top-0 end-0 text-dark" href="" style="z-index: 5;"><i class="bi bi-question-circle"></i></a>
<p id="nblayers_text" class="small d-none mb-2 opacity-75">Vous êtes <span class="badge rounded-pill border border-dark text-dark"><span class="nblayers">0</span> <i class="bi bi-people-fill"></i></span> à avoir signé ce PDF</p>
</div>
<div class="btn-group w-100">
<a id="btn_download" class="btn btn-outline-dark w-100" href="/signature/<?php echo $hash ?>/pdf"><i class="bi bi-download"></i> Télécharger le PDF</a>
<button class="btn btn-outline-dark" type="button" id="btn_share" data-bs-toggle="modal" data-bs-target="#modal-share-informations"><i class="bi bi-share"></i></button>
</div>
<form id="form_pdf" action="/signature/<?php echo $hash ?>/save" method="post" enctype="multipart/form-data" class="d-none d-sm-none d-md-block">
<input id="input_svg" name="svg[]" type="file" class="d-none" />
<button class="btn btn-primary w-100 mt-2" disabled="disabled" type="submit" id="save"><i class="bi bi-cloud-upload"></i> Transmettre ma signature</button>
</form>
<?php endif; ?>
</div>
</div>
</div>
<div class="position-fixed top-0 start-0 bg-white w-100 p-2 shadow-sm d-md-none">
<div class="d-grid gap-2">
<button id="btn_svn_select" class="btn btn-light btn-lg" data-bs-toggle="offcanvas" data-bs-target="#sidebarTools" aria-controls="sidebarTools"><i class="bi bi-hand-index"></i> Séléctionner une signature</button>
</div>
<div id="svg_selected_container" class="text-center d-none position-relative">
<img id="svg_selected" src="" style="height: 48px;" class="img-fluid"/>
<button type="button" id="btn_svg_selected_close" class="btn-close text-reset position-absolute" style="top: 9px; right: 9px;"></button>
</div>
<div id="svg_object_actions" class="d-none">
<button id="btn-svg-pdf-delete" class="btn btn-lg btn-light"><i class="bi bi-trash"></i></button>
</div>
</div>
<div class="position-fixed bottom-0 start-0 bg-white w-100 p-2 shadow d-md-none">
<div class="btn-group position-absolute opacity-25" style="top: -46px;">
<button id="btn-zoom-decrease" class="btn btn-secondary"><i class="bi bi-dash"></i></button>
<button id="btn-zoom-increase" class="btn btn-secondary"><i class="bi bi-plus"></i></button>
</div>
<div class="d-grid gap-2">
<?php if(isset($hash)): ?>
<button class="btn btn-primary" disabled="disabled" type="submit" id="save_mobile"><i class="bi bi-cloud-upload"></i> Transmettre ma signature</button>
<?php else: ?>
<button class="btn btn-primary" disabled="disabled" type="submit" id="save_mobile"><i class="bi bi-download"></i> Télécharger le PDF signé</button>
<?php endif; ?>
</div>
</div>
<div class="modal fade" id="modalAddSvg" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<nav class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active ps-2 ps-md-3 pe-2 pe-md-3" id="nav-draw-tab" data-bs-toggle="tab" data-bs-target="#nav-draw" type="button" role="tab" aria-controls="nav-draw" aria-selected="true"><i class="bi bi-vector-pen"></i> Dessiner<br /><small>à main levée</small></button>
<button class="nav-link ps-2 ps-md-3 pe-2 pe-md-3" id="nav-type-tab" data-bs-toggle="tab" data-bs-target="#nav-type" type="button" role="tab" aria-controls="nav-type" aria-selected="false"><i class="bi bi-fonts"></i> Saisir<br /><small>du texte</small></button>
<button class="nav-link ps-2 ps-md-3 pe-2 pe-md-3" id="nav-import-tab" data-bs-toggle="tab" data-bs-target="#nav-import" type="button" role="tab" aria-controls="nav-import" aria-selected="false"><i class="bi bi-image"></i> Importer<br /><small>une image</small></button>
</nav>
<div class="tab-content mt-3" id="nav-svg-add">
<div class="tab-pane fade show active" id="nav-draw" role="tabpanel" aria-labelledby="nav-draw-tab">
<small id="signature-pad-reset" class="text-muted opacity-75 position-absolute" style="right: 25px; bottom: 25px; cursor: pointer;" title="Effacer la signature"><i class="bi bi-trash"></i></small>
<canvas id="signature-pad" class="border bg-light" width="462" height="200"></canvas>
</div>
<div class="tab-pane fade" id="nav-type" role="tabpanel" aria-labelledby="nav-type-tab">
<input id="input-text-signature" type="text" class="form-control form-control-lg" placeholder="Ma signature" />
</div>
<div class="tab-pane fade" id="nav-import" role="tabpanel" aria-labelledby="nav-import-tab">
<div class="text-center">
<img id="img-upload" class="d-none" src="" />
</div>
<form id="form-image-upload" action="/image2svg" method="POST" enctype="multipart/form-data">
<input id="input-image-upload" class="form-control" name="image" type="file">
</form>
</div>
</div>
<input id="input-svg-type" type="hidden" />
</div>
<div class="modal-footer d-block">
<button tabindex="-1" type="button" class="btn btn-light col-4" data-bs-dismiss="modal">Annuler</button>
<button id="btn_modal_ajouter" type="button" disabled="disabled" data-bs-dismiss="modal" class="btn btn-primary float-end col-4"><span id="btn_modal_ajouter_spinner" class="spinner-border spinner-border-sm d-none"></span><span id="btn_modal_ajouter_check" class="bi bi-check-circle"></span> Créer</button>
</div>
</div>
</div>
</div>
</div>
<?php if(!isset($hash) && !isset($noSharingMode)): ?>
<div id="modal-start-share" class="modal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-share"></i> Partager ce PDF pour le signer à plusieurs </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>En activant le partage de ce PDF vous allez pouvoir proposer un lien aux personnes de votre choix pour qu'elles puissent signer ce PDF.</p>
<p><i class="bi bi-hdd-network"></i> Ce partage nécessite que le PDF soit transféré et stocké sur le serveur afin d'être accessible aux futurs signataires.</p>
<p class="mb-0"><i class="bi bi-hourglass-split"></i> Le PDF sera conservé <select name="duration" form="form_sharing"><option value="+1 year">un an</option><option value="+6 month">six mois</option><option value="+1 month" selected="selected">un mois</option><option value="+1 week">une semaine</option><option value="+1 day">un jour</option><option value="+1 hour">une heure</option></select> après la dernière signature.</p>
</div>
<div class="modal-footer text-center d-block">
<form id="form_sharing" clas action="/share" method="post" enctype="multipart/form-data">
<input id="input_pdf_share" name="pdf" type="file" class="d-none" />
<input id="input_svg_share" name="svg[]" type="file" class="d-none" />
<button class="btn col-9 col-md-6 btn-primary" type="submit" id="save_share"><i class="bi bi-cloud-upload"></i> Démarrer le partage</button>
</form>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if(isset($hash)): ?>
<div id="modal-share-informations" class="modal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-people-fill"></i> Signer ce PDF à plusieurs</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Plusieurs personnes peuvent signer ce PDF en même temps.</p>
<p>Pour cela il vous suffit de partager avec les personnes de votre choix le lien vers cette page :</p>
<div class="input-group mb-3">
<span class="input-group-text">Lien à partager</span>
<input id="input-share-link" type="text" onclick="this.select(); this.setSelectionRange(0, 99999);" readonly="readonly" class="form-control bg-light font-monospace" value="">
<button onclick="navigator.clipboard.writeText(document.getElementById('input-share-link').value); this.innerText = 'Copié !';" autofocus="autofocus" class="btn btn-primary" type="button" id="btn-copy-share-link"><i class="bi bi-clipboard"></i> Copier</button>
<script>document.querySelector('#input-share-link').value = document.location.href.replace(/#.*/, '');</script>
</div>
<p class="mb-0">Chacun des signataires pourra à tout moment télécharger la dernière version du PDF signé.</p>
</div>
<div class="modal-footer text-start">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if(isset($hash)): ?>
<div id="modal-signed" class="modal" tabindex="-1">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-file-earmark-check"></i> PDF signé</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="mb-1"><i class="bi bi-check-circle text-success"></i> Votre signature a bien été prise en compte&nbsp;!</p>
</div>
<div class="modal-footer text-center d-block">
<a class="btn btn-outline-dark" href="/signature/<?php echo $hash ?>/pdf"><i class="bi bi-download"></i> Télécharger le PDF</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
<span id="is_mobile" class="d-md-none"></span>
<script src="/vendor/bootstrap.min.js?5.1.3"></script>
<script src="/vendor/pdf.js?legacy"></script>
<script src="/vendor/fabric.min.js?4.6.0"></script>
<script src="/vendor/signature_pad.umd.min.js?3.0.0-beta.3"></script>
<script src="/vendor/opentype.min.js?1.3.3"></script>
<script>
var maxSize = <?php echo $maxSize ?>;
var maxPage = <?php echo $maxPage ?>;
var sharingMode = <?php echo intval(!isset($noSharingMode)) ?>;
var hash = null;
<?php if(isset($hash)): ?>
hash = "<?php echo $hash ?>";
<?php endif; ?>
</script>
<script src="/js/signature.js?<?php echo ($COMMIT) ? $COMMIT : filemtime($ROOT."/public/js/signature.js") ?>"></script>
</body>
</html>

View File

@ -157,10 +157,10 @@ describe("Signature d'un pdf", () => {
it("Ajout de texte au pdf", async () => {
await page.click("#label_svg_text");
await page.mouse.click(originX + 150, originY + 100);
await page.keyboard.type('Bon pour un logiciel libre !');
await page.keyboard.type('Bon pour un logiciel libre épatant !');
await page.mouse.click(originX + 150, originY + 50);
expect(await page.evaluate(() => { return canvasEditions[0].getObjects().length; })).toBe(5);
expect(await page.evaluate(() => { return canvasEditions[0].getObjects()[4].text; })).toBe('Bon pour un logiciel libre !');
expect(await page.evaluate(() => { return canvasEditions[0].getObjects()[4].text; })).toBe('Bon pour un logiciel libre épatant !');
expect(await page.evaluate(() => { return document.querySelector('#radio_svg_text').checked; })).toBe(true);
});
it("Suppression de tous les éléments ajoutés à la liste", async () => {

View File

@ -1,2 +0,0 @@
/tmp/
/.idea/

View File

@ -1,16 +0,0 @@
# Enable rewrite engine and route requests to framework
RewriteEngine On
# Some servers require you to specify the `RewriteBase` directive
# In such cases, it should be the path (relative to the document root)
# containing this .htaccess file
#
# RewriteBase /
RewriteRule ^(app|tmp)\/|\.ini$ - [R=404]
RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L,QSA]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

View File

@ -1,6 +1,18 @@
CHANGELOG
3.7.3
3.8.0 (15 Feb 2022)
* Feat: allow access to previous session data in cache-based session handler
* Feat: pass session information to onSuspect Session handler
* Fix: PHP 8.1 compatibility fixes [#332](https://github.com/bcosca/fatfree-core/issues/332) [#333](https://github.com/bcosca/fatfree-core/issues/333)
* Fix: check for critical schemes in url validation
* Fix: plural format syntax with empty param, [#325](https://github.com/bcosca/fatfree-core/issues/325)
* Fix: DB mapper not able to fetch field scheme in sqlite views
* Fix: capitalization of array key X-Http-Method-Override in headers [#327](https://github.com/bcosca/fatfree-core/issues/327)
* Fix SMTP: allow RFC2047 encoded words in From/To/Cc/Bcc headers
* Fix: use correct ternary value, [#323](https://github.com/bcosca/fatfree-core/issues/323)
* Fix: trace not present in error handler when in CLI mode and !DEBUG, [#323](https://github.com/bcosca/fatfree-core/issues/323)
3.7.3 (13 Dec 2020)
* NEW: added auto_increment detection, [bcosca/fatfree#1192](https://github.com/bcosca/fatfree/issues/1192), [bcosca/fatfree#1093](https://github.com/bcosca/fatfree/issues/1093), [bcosca/fatfree#1175](https://github.com/bcosca/fatfree/issues/1175), [#290](https://github.com/bcosca/fatfree-core/issues/290)
* added SMTP dialog error handling, [#317](https://github.com/bcosca/fatfree-core/issues/317)
* Fix: Check active transaction before rollback/commit (PHP8 issue)

0
vendor/fatfree/lib/COPYING → vendor/fatfree/COPYING vendored Executable file → Normal file
View File

29
vendor/fatfree/README.md vendored Normal file
View File

@ -0,0 +1,29 @@
# fatfree-core
Fat-Free Framework core library
### Usage:
First make sure to add a proper url rewrite configuration to your server, see https://fatfreeframework.com/3.6/routing-engine#DynamicWebSites
**without composer:**
```php
$f3 = require('lib/base.php');
```
**with composer:**
```
composer require bcosca/fatfree-core
```
```php
require("vendor/autoload.php");
$f3 = \Base::instance();
```
---
For the main repository (demo package), see https://github.com/bcosca/fatfree
For the test bench and unit tests, see https://github.com/f3-factory/fatfree-dev
For the user guide, see https://fatfreeframework.com/user-guide
For the documentation, see https://fatfreeframework.com/api-reference

View File

@ -36,7 +36,8 @@ class Audit extends Prefab {
* @param $str string
**/
function url($str) {
return is_string(filter_var($str,FILTER_VALIDATE_URL));
return is_string(filter_var($str,FILTER_VALIDATE_URL))
&& !preg_match('/^(javascript|php):\/\/.*$/i', $str);
}
/**

View File

@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
//@{ Framework details
const
PACKAGE='Fat-Free Framework',
VERSION='3.7.3-Release';
VERSION='3.8.1-Dev';
//@}
//@{ HTTP status codes (RFC 2616)
@ -159,7 +159,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
private function cut($key) {
return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
$key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
$key,-1,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
}
/**
@ -221,11 +221,11 @@ final class Base extends Prefab implements ArrayAccess {
* @return mixed
*/
function cast($val) {
if (preg_match('/^(?:0x[0-9a-f]+|0[0-7]+|0b[01]+)$/i',$val))
if ($val && preg_match('/^(?:0x[0-9a-f]+|0[0-7]+|0b[01]+)$/i',$val))
return intval($val,0);
if (is_numeric($val))
return $val+0;
$val=trim($val);
$val=trim($val?:'');
if (preg_match('/^\w+$/i',$val) && defined($val))
return constant($val);
return $val;
@ -389,18 +389,18 @@ final class Base extends Prefab implements ArrayAccess {
if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
unset($jar['expire']);
if (isset($_COOKIE[$parts[1]]))
setcookie($parts[1],NULL,['expires'=>0]+$jar);
setcookie($parts[1],'',['expires'=>0]+$jar);
if ($ttl)
$jar['expires']=$time+$ttl;
setcookie($parts[1],$val,$jar);
setcookie($parts[1],$val?:'',$jar);
} else {
unset($jar['samesite']);
if (isset($_COOKIE[$parts[1]]))
call_user_func_array('setcookie',
array_merge([$parts[1],NULL],['expire'=>0]+$jar));
array_merge([$parts[1],''],['expire'=>0]+$jar));
if ($ttl)
$jar['expire']=$time+$ttl;
call_user_func_array('setcookie',[$parts[1],$val]+$jar);
call_user_func_array('setcookie',[$parts[1],$val?:'']+$jar);
}
$_COOKIE[$parts[1]]=$val;
return $val;
@ -501,11 +501,11 @@ final class Base extends Prefab implements ArrayAccess {
if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
$jar['expires']=$jar['expire'];
unset($jar['expire']);
setcookie($parts[1],NULL,$jar);
setcookie($parts[1],'',$jar);
} else {
unset($jar['samesite']);
call_user_func_array('setcookie',
array_merge([$parts[1],NULL],$jar));
array_merge([$parts[1],''],$jar));
}
unset($_COOKIE[$parts[1]]);
}
@ -713,7 +713,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function split($str,$noempty=TRUE) {
return array_map('trim',
preg_split('/[,;|]/',$str,0,$noempty?PREG_SPLIT_NO_EMPTY:0));
preg_split('/[,;|]/',$str?:'',0,$noempty?PREG_SPLIT_NO_EMPTY:0));
}
/**
@ -829,7 +829,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function hash($str) {
return str_pad(base_convert(
substr(sha1($str),-16),16,36),11,'0',STR_PAD_LEFT);
substr(sha1($str?:''),-16),16,36),11,'0',STR_PAD_LEFT);
}
/**
@ -968,10 +968,11 @@ final class Base extends Prefab implements ArrayAccess {
isset($prop)?$prop:null
]
);
$php81=version_compare(PHP_VERSION, '8.1.0')>=0;
switch ($type) {
case 'plural':
preg_match_all('/(?<tag>\w+)'.
'(?:\s*\{\s*(?<data>.+?)\s*\})/',
'(?:\s*\{\s*(?<data>.*?)\s*\})/',
$mod,$matches,PREG_SET_ORDER);
$ord=['zero','one','two'];
foreach ($matches as $match) {
@ -1051,19 +1052,42 @@ final class Base extends Prefab implements ArrayAccess {
($frac?strlen($frac)-2:0),
$decimal_point,$thousands_sep);
case 'date':
if (empty($mod) || $mod=='short')
$prop='%x';
elseif ($mod=='full')
$prop='%A, %d %B %Y';
elseif ($mod!='custom')
$prop='%d %B %Y';
return strftime($prop,$args[$pos]);
if ($php81) {
$lang = $this->split($this->LANGUAGE);
// requires intl extension
$formatter = new IntlDateFormatter($lang[0],
(empty($mod) || $mod=='short')
? IntlDateFormatter::SHORT :
($mod=='full' ? IntlDateFormatter::LONG : IntlDateFormatter::MEDIUM),
IntlDateFormatter::NONE);
return $formatter->format($args[$pos]);
} else {
if (empty($mod) || $mod=='short')
$prop='%x';
elseif ($mod=='full')
$prop='%A, %d %B %Y';
elseif ($mod!='custom')
$prop='%d %B %Y';
return strftime($prop,$args[$pos]);
}
case 'time':
if (empty($mod) || $mod=='short')
$prop='%X';
elseif ($mod!='custom')
$prop='%r';
return strftime($prop,$args[$pos]);
if ($php81) {
$lang = $this->split($this->LANGUAGE);
// requires intl extension
$formatter = new IntlDateFormatter($lang[0],
IntlDateFormatter::NONE,
(empty($mod) || $mod=='short')
? IntlDateFormatter::SHORT :
($mod=='full' ? IntlDateFormatter::LONG : IntlDateFormatter::MEDIUM),
IntlTimeZone::createTimeZone($this->hive['TZ']));
return $formatter->format($args[$pos]);
} else {
if (empty($mod) || $mod=='short')
$prop='%X';
elseif ($mod!='custom')
$prop='%r';
return strftime($prop,$args[$pos]);
}
default:
return $expr[0];
}
@ -1089,7 +1113,7 @@ final class Base extends Prefab implements ArrayAccess {
* @param $code string
**/
function language($code) {
$code=preg_replace('/\h+|;q=[0-9.]+/','',$code);
$code=preg_replace('/\h+|;q=[0-9.]+/','',$code?:'');
$code.=($code?',':'').$this->fallback;
$this->languages=[];
foreach (array_reverse(explode(',',$code)) as $lang)
@ -1225,7 +1249,7 @@ final class Base extends Prefab implements ArrayAccess {
$time=microtime(TRUE);
header_remove('Pragma');
header('Cache-Control: max-age='.$secs);
header('Expires: '.gmdate('r',$time+$secs));
header('Expires: '.gmdate('r',round($time+$secs)));
header('Last-Modified: '.gmdate('r'));
}
else {
@ -1342,7 +1366,7 @@ final class Base extends Prefab implements ArrayAccess {
$loggable=$this->split($loggable);
foreach ($loggable as $status)
if ($status=='*' ||
preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',(string) $code)) {
error_log($text);
foreach (explode("\n",$trace) as $nexus)
if ($nexus)
@ -1376,7 +1400,7 @@ final class Base extends Prefab implements ArrayAccess {
if ($this->hive['CLI'])
echo PHP_EOL.'==================================='.PHP_EOL.
'ERROR '.$error['code'].' - '.$error['status'].PHP_EOL.
$error['text'].PHP_EOL.PHP_EOL.$error['trace'];
$error['text'].PHP_EOL.PHP_EOL.(isset($error['trace']) ? $error['trace'] : '');
else
echo $this->hive['AJAX']?
json_encode($error):
@ -1424,7 +1448,7 @@ final class Base extends Prefab implements ArrayAccess {
if (empty($parts[4]))
user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
$url=parse_url($parts[4]);
parse_str(@$url['query'],$GLOBALS['_GET']);
parse_str(isset($url['query'])?$url['query']:'',$GLOBALS['_GET']);
if (preg_match('/GET|HEAD/',$verb))
$GLOBALS['_GET']=array_merge($GLOBALS['_GET'],$args);
$GLOBALS['_POST']=$verb=='POST'?$args:[];
@ -1775,7 +1799,7 @@ final class Base extends Prefab implements ArrayAccess {
++$ctr;
if ($ctr/$kbps>($elapsed=microtime(TRUE)-$now) &&
!connection_aborted())
usleep(1e6*($ctr/$kbps-$elapsed));
usleep(round(1e6*($ctr/$kbps-$elapsed)));
echo $part;
}
}
@ -2234,6 +2258,7 @@ final class Base extends Prefab implements ArrayAccess {
* @return mixed
* @param $key string
**/
#[\ReturnTypeWillChange]
function offsetexists($key) {
return $this->exists($key);
}
@ -2244,6 +2269,7 @@ final class Base extends Prefab implements ArrayAccess {
* @param $key string
* @param $val mixed
**/
#[\ReturnTypeWillChange]
function offsetset($key,$val) {
return $this->set($key,$val);
}
@ -2253,6 +2279,7 @@ final class Base extends Prefab implements ArrayAccess {
* @return mixed
* @param $key string
**/
#[\ReturnTypeWillChange]
function &offsetget($key) {
$val=&$this->ref($key);
return $val;
@ -2262,6 +2289,7 @@ final class Base extends Prefab implements ArrayAccess {
* Convenience method for removing hive key
* @param $key string
**/
#[\ReturnTypeWillChange]
function offsetunset($key) {
$this->clear($key);
}
@ -2378,7 +2406,7 @@ final class Base extends Prefab implements ArrayAccess {
$req.='?'.$query;
}
$_SERVER['REQUEST_URI']=$req;
parse_str($query,$GLOBALS['_GET']);
parse_str($query?:'',$GLOBALS['_GET']);
}
elseif (function_exists('getallheaders')) {
foreach (getallheaders() as $key=>$val) {
@ -2400,8 +2428,8 @@ final class Base extends Prefab implements ArrayAccess {
$headers[strtr(ucwords(strtolower(strtr(
substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
}
if (isset($headers['X-HTTP-Method-Override']))
$_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
if (isset($headers['X-Http-Method-Override']))
$_SERVER['REQUEST_METHOD']=$headers['X-Http-Method-Override'];
elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
$_SERVER['REQUEST_METHOD']=strtoupper($_POST['_method']);
$scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
@ -2533,9 +2561,11 @@ final class Base extends Prefab implements ArrayAccess {
if (PHP_SAPI=='cli-server' &&
preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
$this->reroute('/');
if (ini_get('auto_globals_jit'))
if (ini_get('auto_globals_jit')) {
// Override setting
$GLOBALS+=['_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST];
$GLOBALS['_ENV']=$_ENV;
$GLOBALS['_REQUEST']=$_REQUEST;
}
// Sync PHP globals with corresponding hive keys
$this->init=$this->hive;
foreach (explode('|',self::GLOBALS) as $global) {
@ -2699,7 +2729,7 @@ class Cache extends Prefab {
if (!$this->dsn)
return TRUE;
$regex='/'.preg_quote($this->prefix.'.','/').'.*'.
preg_quote($suffix,'/').'/';
preg_quote($suffix?:'','/').'/';
$parts=explode('=',$this->dsn,2);
switch ($parts[0]) {
case 'apc':

View File

@ -148,7 +148,7 @@ class Basket extends Magic {
**/
function save() {
if (!$this->id)
$this->id=uniqid(NULL,TRUE);
$this->id=uniqid('',TRUE);
$_SESSION[$this->key][$this->id]=$this->item;
return $this->item;
}

0
vendor/fatfree/lib/code.css → vendor/fatfree/code.css vendored Executable file → Normal file
View File

View File

@ -1,18 +1,12 @@
{
"name": "bcosca/fatfree",
"name": "bcosca/fatfree-core",
"description": "A powerful yet easy-to-use PHP micro-framework designed to help you build dynamic and robust Web applications - fast!",
"homepage": "http://fatfreeframework.com/",
"license": "GPL-3.0",
"require": {
"php": ">=5.4"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/bcosca/fatfree"
}
],
"autoload": {
"files": ["lib/base.php"]
"classmap": ["."]
}
}

View File

@ -1,4 +0,0 @@
[globals]
DEBUG=3
UI=ui/

View File

@ -107,6 +107,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
* Causes a fatal error in PHP 5.3.5 if uncommented
* return ArrayIterator
**/
#[\ReturnTypeWillChange]
abstract function getiterator();

View File

@ -325,7 +325,7 @@ class Mapper extends \DB\Cursor {
if (!array_key_exists($col,$val2))
$val2[$col]=NULL;
list($v1,$v2)=[$val1[$col],$val2[$col]];
if ($out=strnatcmp($v1,$v2)*
if ($out=strnatcmp($v1?:'',$v2?:'')*
(($order==SORT_ASC)*2-1))
return $out;
}
@ -383,7 +383,7 @@ class Mapper extends \DB\Cursor {
return $this->update();
$db=$this->db;
$now=microtime(TRUE);
while (($id=uniqid(NULL,TRUE)) &&
while (($id=uniqid('',TRUE)) &&
($data=&$db->read($this->file)) && isset($data[$id]) &&
!connection_aborted())
usleep(mt_rand(0,100));

View File

@ -150,7 +150,7 @@ class Mapper extends \DB\Cursor {
);
$tmp=$this->db->selectcollection(
$fw->HOST.'.'.$fw->BASE.'.'.
uniqid(NULL,TRUE).'.tmp'
uniqid('',TRUE).'.tmp'
);
$tmp->batchinsert($grp['retval'],['w'=>1]);
$filter=[];

View File

@ -339,7 +339,7 @@ class SQL {
$cmd=[
'sqlite2?'=>[
'SELECT * FROM pragma_table_info('.$this->quote($table).') JOIN ('.
'SELECT sql FROM sqlite_master WHERE type=\'table\' AND '.
'SELECT sql FROM sqlite_master WHERE (type=\'table\' OR type=\'view\') AND '.
'name='.$this->quote($table).')',
'name','type','dflt_value','notnull',0,'pk',TRUE,'sql',
'/\W(%s)\W+[^,]+?AUTOINCREMENT\W/i'],

View File

@ -427,6 +427,7 @@ class Mapper extends \DB\Cursor {
$values='';
$filter='';
$pkeys=[];
$aikeys=[];
$nkeys=[];
$ckeys=[];
$inc=NULL;
@ -449,6 +450,9 @@ class Mapper extends \DB\Cursor {
unset($field);
}
foreach ($this->fields as $key=>&$field) {
if ($field['auto_inc']) {
$aikeys[] = $key;
}
if ($field['pkey']) {
$field['previous']=$field['value'];
if (!$inc && empty($field['value']) &&
@ -469,7 +473,7 @@ class Mapper extends \DB\Cursor {
}
unset($field);
}
if ($fields) {
if ($fields) {
$add=$aik='';
if ($this->engine=='pgsql' && !empty($pkeys)) {
$names=array_keys($pkeys);
@ -478,7 +482,7 @@ class Mapper extends \DB\Cursor {
}
$lID=$this->db->exec(
(preg_match('/mssql|dblib|sqlsrv/',$this->engine) &&
array_intersect(array_keys($pkeys),$ckeys)?
array_intersect(array_keys($aikeys),$ckeys)?
'SET IDENTITY_INSERT '.$this->table.' ON;':'').
'INSERT INTO '.$this->table.' ('.$fields.') '.
'VALUES ('.$values.')'.$add,$args

View File

@ -258,12 +258,12 @@ class Image {
if ($width/$ratio<=$height) {
$cropw=round($origh*$width/$height);
imagecopyresampled($tmp,$this->data,
0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
0,0,round(($origw-$cropw)/2),0,$width,$height,$cropw,$origh);
}
else {
$croph=round($origw*$height/$width);
imagecopyresampled($tmp,$this->data,
0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
0,0,0,round(($origh-$croph)/2),$width,$height,$origw,$croph);
}
}
else
@ -309,13 +309,13 @@ class Image {
if ($align & self::POS_Left)
$posx=0;
if ($align & self::POS_Center)
$posx=($imgw-$ovrw)/2;
$posx=round(($imgw-$ovrw)/2);
if ($align & self::POS_Right)
$posx=$imgw-$ovrw;
if ($align & self::POS_Top)
$posy=0;
if ($align & self::POS_Middle)
$posy=($imgh-$ovrh)/2;
$posy=round(($imgh-$ovrh)/2);
if ($align & self::POS_Bottom)
$posy=$imgh-$ovrh;
if (empty($posx))
@ -374,10 +374,14 @@ class Image {
$block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr];
for ($k=0,$pts=count($block);$k<$pts;++$k)
$block[$k]*=$dim;
imagefilledpolygon($sprite,$block,$pts/2,$fg);
if (version_compare(PHP_VERSION, '8.1.0') >= 0) {
imagefilledpolygon($sprite,$block,$fg);
} else {
imagefilledpolygon($sprite,$block,$pts/2,$fg);
}
for ($k=0;$k<4;++$k) {
imagecopyresampled($this->data,$sprite,
$i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim);
round($i*$dim/2),round($j*$dim/2),0,0,round($dim/2),round($dim/2),$dim,$dim);
$this->data=imagerotate($this->data,90,
imagecolorallocatealpha($this->data,0,0,0,127));
}
@ -424,25 +428,25 @@ class Image {
$char=imagecreatetruecolor($block,$block);
imagefill($char,0,0,$bg);
imagettftext($char,$size*2,0,
($block-$w)/2,$block-($block-$h)/2,
round(($block-$w)/2),round($block-($block-$h)/2),
$fg,$path,$seed[$i]);
$char=imagerotate($char,mt_rand(-30,30),
imagecolorallocatealpha($char,0,0,0,127));
// Reduce to normal size
$tmp[$i]=imagecreatetruecolor(
($w=imagesx($char))/2,($h=imagesy($char))/2);
round(($w=imagesx($char))/2),round(($h=imagesy($char))/2));
imagefill($tmp[$i],0,0,IMG_COLOR_TRANSPARENT);
imagecopyresampled($tmp[$i],
$char,0,0,0,0,$w/2,$h/2,$w,$h);
$char,0,0,0,0,round($w/2),round($h/2),$w,$h);
imagedestroy($char);
$width+=$i+1<$len?$block/2:$w/2;
$height=max($height,$h/2);
}
$this->data=imagecreatetruecolor($width,$height);
$this->data=imagecreatetruecolor(round($width),round($height));
imagefill($this->data,0,0,IMG_COLOR_TRANSPARENT);
for ($i=0;$i<$len;++$i) {
imagecopy($this->data,$tmp[$i],
$i*$block/2,($height-imagesy($tmp[$i]))/2,0,0,
round($i*$block/2),round(($height-imagesy($tmp[$i]))/2),0,0,
imagesx($tmp[$i]),imagesy($tmp[$i]));
imagedestroy($tmp[$i]);
}

View File

@ -1,89 +0,0 @@
<?php
// Kickstart the framework
$f3=require('lib/base.php');
$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');
// Load configuration
$f3->config('config.ini');
$f3->route('GET /',
function($f3) {
$classes=array(
'Base'=>
array(
'hash',
'json',
'session',
'mbstring'
),
'Cache'=>
array(
'apc',
'apcu',
'memcache',
'memcached',
'redis',
'wincache',
'xcache'
),
'DB\SQL'=>
array(
'pdo',
'pdo_dblib',
'pdo_mssql',
'pdo_mysql',
'pdo_odbc',
'pdo_pgsql',
'pdo_sqlite',
'pdo_sqlsrv'
),
'DB\Jig'=>
array('json'),
'DB\Mongo'=>
array(
'json',
'mongo'
),
'Auth'=>
array('ldap','pdo'),
'Bcrypt'=>
array(
'openssl'
),
'Image'=>
array('gd'),
'Lexicon'=>
array('iconv'),
'SMTP'=>
array('openssl'),
'Web'=>
array('curl','openssl','simplexml'),
'Web\Geo'=>
array('geoip','json'),
'Web\OpenID'=>
array('json','simplexml'),
'Web\OAuth2'=>
array('json'),
'Web\Pingback'=>
array('dom','xmlrpc'),
'CLI\WS'=>
array('pcntl')
);
$f3->set('classes',$classes);
$f3->set('content','welcome.htm');
echo View::instance()->render('layout.htm');
}
);
$f3->route('GET /userref',
function($f3) {
$f3->set('content','userref.htm');
echo View::instance()->render('layout.htm');
}
);
$f3->run();

View File

@ -57,6 +57,7 @@ abstract class Magic implements ArrayAccess {
* @return mixed
* @param $key string
**/
#[\ReturnTypeWillChange]
function offsetexists($key) {
return Base::instance()->visible($this,$key)?
isset($this->$key):
@ -69,6 +70,7 @@ abstract class Magic implements ArrayAccess {
* @param $key string
* @param $val mixed
**/
#[\ReturnTypeWillChange]
function offsetset($key,$val) {
return Base::instance()->visible($this,$key)?
($this->$key=$val):$this->set($key,$val);
@ -79,6 +81,7 @@ abstract class Magic implements ArrayAccess {
* @return mixed
* @param $key string
**/
#[\ReturnTypeWillChange]
function &offsetget($key) {
if (Base::instance()->visible($this,$key))
$val=&$this->$key;
@ -92,6 +95,7 @@ abstract class Magic implements ArrayAccess {
* @return NULL
* @param $key string
**/
#[\ReturnTypeWillChange]
function offsetunset($key) {
if (Base::instance()->visible($this,$key))
unset($this->$key);

2616
vendor/fatfree/readme.md vendored

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
*/
//! Cache-based session handler
class Session {
class Session extends Magic {
protected
//! Session ID
@ -35,7 +35,9 @@ class Session {
//! Suspect callback
$onsuspect,
//! Cache instance
$_cache;
$_cache,
//! Session meta data
$_data=[];
/**
* Open session
@ -53,6 +55,7 @@ class Session {
**/
function close() {
$this->sid=NULL;
$this->_data=[];
return TRUE;
}
@ -65,6 +68,7 @@ class Session {
$this->sid=$id;
if (!$data=$this->_cache->get($id.'.@'))
return '';
$this->_data = $data;
if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
$fw=Base::instance();
if (!isset($this->onsuspect) ||
@ -193,4 +197,29 @@ class Session {
$this->_ip=$fw->IP;
}
/**
* check latest meta data existence
* @param string $key
* @return bool
*/
function exists($key) {
return isset($this->_data[$key]);
}
/**
* get meta data from latest session
* @param string $key
* @return mixed
*/
function &get($key) {
return $this->_data[$key];
}
function set($key,$val) {
trigger_error('Unable to set data on previous session');
}
function clear($key) {
trigger_error('Unable to clear data on previous session');
}
}

View File

@ -256,7 +256,7 @@ class SMTP extends Magic {
foreach ($headers as $key=>&$val) {
if (in_array($key,['From','To','Cc','Bcc'])) {
$email='';
preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/',
preg_match_all('/(?:".+?" |=\?.+?\?= )?(?:<.+?>|[^ ,]+)/',
$val,$matches,PREG_SET_ORDER);
foreach ($matches as $raw)
$email.=($email?', ':'').
@ -283,7 +283,7 @@ class SMTP extends Magic {
unset($headers['Content-Type']);
$enc=$headers['Content-Transfer-Encoding'];
unset($headers['Content-Transfer-Encoding']);
$hash=uniqid(NULL,TRUE);
$hash=uniqid('',TRUE);
// Send mail headers
$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol;
foreach ($headers as $key=>$val)
@ -352,7 +352,7 @@ class SMTP extends Magic {
'Content-Type'=>'text/plain; '.
'charset='.Base::instance()->ENCODING
];
$this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'?
$this->host=strtolower((($this->scheme=strtolower($scheme?:''))=='ssl'?
'ssl':'tcp').'://'.$host);
$this->port=$port;
$this->user=$user;

View File

@ -43,7 +43,7 @@ class Template extends Preview {
$out='';
foreach ($node['@attrib'] as $key=>$val)
$out.='$'.$key.'='.
(preg_match('/\{\{(.+?)\}\}/',$val)?
(preg_match('/\{\{(.+?)\}\}/',$val?:'')?
$this->token($val):
Base::instance()->stringify($val)).'; ';
return '<?php '.$out.'?>';

View File

@ -1,6 +0,0 @@
/* Reset */
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,dir,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}ol{list-style:decimal}ul{list-style:disc}ul ul{list-style:circle}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}ins{text-decoration:underline}mark{background:none}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select,a img{vertical-align:middle}
/* Typography */
*{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;max-width:100%}html{height:100%;font-size:100%;font-family:serif;overflow-y:scroll;-webkit-text-size-adjust:100%}body{margin:0;min-height:100%;overflow:hidden}body,pre,label,input,button,select,textarea{font:normal 100%/1.25 serif;vertical-align:top}a{display:inline-block}p,ul,ol{margin:1.25em 0}h1{font-size:2em;line-height:1.25em;margin:0.625em 0}h2{font-size:1.5em;line-height:1.6667em;margin:0.8333em 0}h3{font-size:1.25em;line-height:1em;margin:1em 0}h4{font-size:1em;line-height:1.25em;margin:1.25em 0}h5{font-size:0.8125em;line-height:1.5385em;margin:1.5385em 0}h6{font-size:0.6875em;line-height:1.8182em;margin:1.8182em 0}blockquote{margin:0 3em}caption{font-weight:bold}ul,ol,dir,menu,dd{margin-left:3em}ul,dir,menu{list-style:disc}ol{list-style:decimal}sub,sup{font-size:75%;line-height:0;vertical-align:baseline;position:relative}sub{top:0.5em}sup{top:-0.5em}label{display:inline-block}input[type="text"],input[type="password"],input[type="file"]{padding:1px;border:1px solid #999;margin:-4px 0 0 0}select,textarea{padding:0;border:1px solid #999;margin:-4px 0 0 0}fieldset{padding:0.625em;border:1px solid #ccc;margin-bottom:0.625em}input[type="radio"],input[type="checkbox"]{height:1em;vertical-align:top;margin:0.125em}div,table{overflow:hidden}
/* Fluid Fonts */
@media screen and (max-width:960px){body{font-size:0.81255em}}

View File

@ -1,165 +0,0 @@
body {
font-family:Ubuntu,sans-serif;
font-size:1.2em;
}
h1 {
color:#faa;
}
h2 {
color:#e88;
}
h3 {
color:#b66;
}
h4 {
color:#944;
}
h5 {
color:#722;
}
h6 {
color:#500;
}
small {
font-size:0.75em
}
a {
text-decoration:none;
color:#66f;
}
a:hover {
color:#666;
}
img {
max-width:100%
}
table {
font-size:.8em;
color:#666 !important;
background:#eee;
width:100%;
border-radius:.5em;
}
table code {
background:transparent;
padding:0
}
tr {
border-bottom:1px solid #fff;
}
tr:last-child {
border-bottom:none;
}
th,td {
font-size:1em;
line-height:1.25em;
margin:0;
padding:1em;
white-space:nowrap;
}
th {
font-weight:bold;
text-align:left;
text-transform:uppercase;
color:#fff;
background:#999;
}
th a {
color:#fff;
}
th:first-child,
td:first-child {
width:50%;
}
pre {
background:#efefef;
padding:0.75em;
border-radius:0.75em;
}
ul,p {
color:#666;
line-height:1.5em;
}
p code,ul code {
padding:.25em .75em;
border-radius:.75em;
white-space:nowrap
}
blockquote pre,blockquote code {
color:#666;
background:#fff;
}
code {
background:#eee;
}
.center {
text-align:center;
}
.right {
text-align:right;
}
.content {
padding:0 20px;
max-width:768px;
margin:0 auto;
}
.header {
background:#eee;
}
.header img {
width:90%;
max-width:768px;
padding:0 5%;
}
.footer {
font-size:0.9em;
background:#333;
}
.footer p {
color:#eee;
padding:20px;
max-width:768px;
margin:0 auto;
}
.footer .stats {
font-size:.9em;
}
@media screen and (max-width:48em) {
body {
font-size:1em;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?php echo $ENCODING; ?>" />
<title>Powered by <?php echo $PACKAGE; ?></title>
<base href="<?php echo $SCHEME.'://'.$HOST.':'.$PORT.$BASE.'/'; ?>" />
<link rel="stylesheet" href="lib/code.css" type="text/css" />
<link rel="stylesheet" href="ui/css/base.css" type="text/css" />
<link rel="stylesheet" href="ui/css/theme.css" type="text/css" />
</head>
<body>
<?php echo $this->render(Base::instance()->get('content')); ?>
</body>
</html>

View File

@ -1,4 +0,0 @@
<div class="content">
<?php echo Markdown::instance()->
convert(Base::instance()->read('readme.md')); ?>
</div>

View File

@ -1,62 +0,0 @@
<div class="header center">
<p><img src="ui/images/logo.png"></p>
</div>
<div class="content">
<h2>Version <?php echo $VERSION; ?></h2>
<p>The first thing you might want to do is visualize your directory structures. Fat-Free gives you total control over your Web site. Organize your folders in any way that pleases you (or your development team if you're part of a group). Decide where you want to store the following:</p>
<ul>
<li>Application and code libraries</li>
<li>HTML templates</li>
<li>Graphics and media files</li>
<li>Javascript and CSS files</li>
<li>Database (if you plan to use an embedded DB like SQLite)</li>
<li>Configuration files</li>
<li>Uploads/Downloads</li>
</ul>
<p>For security reasons, consider relocating the <code>lib/</code> folder to a path that's not Web-accessible. If you decide to move this folder, just change the line in <code>index.php</code> containing <code>require 'lib/base.php';</code> so it points to the new location. The <code>lib/</code> folder also contains framework plug-ins that extend F3's capabilities. You can change the default location of all plug-ins by moving the files to your desired subdirectory. Then, it's just a matter of pointing the <code>PLUGINS</code> global variable to the new location. You may delete the plug-ins that you don't need. You can reinstate them later as you find necessary.</p>
<p>F3 can autoload OOP classes for you. Just add the path to the <code>AUTOLOAD</code> variable.</p>
<p>When you're ready to write your F3-enabled site, you can start editing the rest of the code contained in the <code>index.php</code> file that displayed this Web page. Developing PHP applications will never be the same!</p>
<h2>PHP Dependencies</h2>
<p>Some framework features in this version will not be available if PHP is not configured with the modules needed by your application.</p>
<table>
<tr>
<th>Class/Plug-in</th>
<th>PHP Module</th>
</tr>
<?php foreach ($classes as $class=>$modules): ?>
<tr>
<td><?php echo $class; ?></td>
<td>
<?php foreach ($modules as $module): ?>
<input type="checkbox" <?php echo extension_loaded($module)?'checked':''?> onclick="return false"> <?php echo $module; ?><br>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<ul>
<li>The <code>Base</code> class requires all listed PHP modules enabled to function properly.</li>
<li>The <code>Cache</code> class will use any available module in the list. If none can be found, it will use the filesystem as fallback.</li>
<li>The <code>DB\SQL</code> class requires the <code>pdo</code> module and a PDO driver relevant to your application.</li>
<li>The <code>Bcrypt</code> class will use the <code>mcrypt</code> or <code>openssl</code> module for entropy generation. Otherwise, it employs a custom random function.</li>
<li>The <code>Web</code> class will use the <code>curl</code> module for HTTP requests to another server. If this is not detected, it will use other transports available, such as the HTTP stream wrapper or native sockets.</li>
<li>The <code>geoip</code> module listed in the <code>Web\Geo</code> class is optional; the class will use an alternative Web service for geo-location.</li>
<li>Other framework classes in the list need all its listed modules enabled.</li>
</ul>
<h2>Need Help?</h2>
<p>If you have any questions regarding the framework, technical support is available at <code><a href="https://groups.google.com/forum/#!forum/f3-framework">https://groups.google.com/forum/?fromgroups#!forum/f3-framework</a></code></p>
<p>You can also join our <a href="https://fatfreeframework-slack.herokuapp.com/">Slack Channel</a> to get support</p>
<p>Need live support? You can talk to the development team and the rest of the Fat-Free community via IRC. We're on the FreeNode (<code>chat.freenode.net</code>) <code>#fatfree</code> channel. If the channel appears quiet, the development team might just be busy with the next great release, or it's probably due to time zone differences. Just hang around.</p>
<p>The <strong><a href="<?php echo $BASE; ?>/userref">User Reference</a></strong> is designed to serve as a handbook and programming guide. However, the online documentation at <a href="https://github.com/bcosca/fatfree" onclick="window.open(this.href); return false;"><code>https://github.com/bcosca/fatfree</code></a> provides the latest and most comprehensive information about the framework.</p>
<h2>Fair Licensing</h2>
<p><b>Fat-Free Framework is free software covered by the terms of the GNU Public License (GPL v3).</b> You may not use the software, documentation, and samples except in compliance with the license. If the terms and conditions of this license are too restrictive for your use, alternative licensing is available for a very reasonable fee.</p>
<p>If you feel that this software is one great weapon to have in your programming arsenal, it saves you a lot of time and money, use it for commercial gain or in your business organization, please consider making a donation to the project. A significant amount of time, effort, and money has been spent on this project. Your donations help keep this project alive and the development team motivated. Donors and sponsors get priority support commensurate to your contribution (24-hour response time on business days).</p>
<h2>Support F3</h2>
<p>F3 is community-driven software. Support the development of the Fat-Free Framework. Your contributions help keep this project alive.</p>
<p class="center"><a href="https://www.paypal.me/fatfree" target="_blank"><img src="ui/images/paypal.png"></a></p>
</div>
<div class="footer center">
<p>Fat-Free Framework is licensed under the terms of the GPL, either v3 or later<br>
Copyright &copy; 2009-2017 F3::Factory/Bong Cosca &lt;bong&#46;cosca&#64;yahoo&#46;com&gt;</p>
<p class="stats"><code><?php echo Base::instance()->format('Page rendered in {0} msecs / Memory usage {1} Kibytes',round(1e3*(microtime(TRUE)-$TIME),2),round(memory_get_usage(TRUE)/1e3,1)); ?></code></p>
</div>

View File

@ -232,7 +232,7 @@ class Web extends Prefab {
// Throttle output
++$ctr;
if ($ctr/$kbps>$elapsed=microtime(TRUE)-$start)
usleep(1e6*($ctr/$kbps-$elapsed));
usleep(round(1e6*($ctr/$kbps-$elapsed)));
}
// Send 1KiB and reset timer
echo fread($handle,1024);
@ -1006,7 +1006,7 @@ if (!function_exists('gzdecode')) {
if (!is_dir($tmp=$fw->TEMP))
mkdir($tmp,Base::MODE,TRUE);
file_put_contents($file=$tmp.'/'.$fw->SEED.'.'.
$fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX);
$fw->hash(uniqid('',TRUE)).'.gz',$str,LOCK_EX);
ob_start();
readgzfile($file);
$out=ob_get_clean();