Compare commits
295 Commits
Author | SHA1 | Date |
---|---|---|
Vincent LAURENT | c4c2183132 | |
Gabriel Poma | c375b43b14 | |
Vincent LAURENT | 06bac144dd | |
Vincent LAURENT | d5f4eca0ff | |
Vincent LAURENT | f4bab4ac5f | |
Vincent LAURENT | 26aac0af94 | |
Vincent LAURENT | f9c413d85a | |
Gabriel | 00bf081319 | |
Gabriel | 1023ebd347 | |
Simon C | 5f703890ac | |
xgaia | 37a678e9db | |
Vincent LAURENT | 357bffc55f | |
Vincent LAURENT | fee27a0311 | |
Étienne Deparis | a1509a554f | |
Vincent LAURENT | 30e7ffef89 | |
Vincent LAURENT | ada82de930 | |
Vincent LAURENT | 4646ad35a7 | |
Vincent LAURENT | c19f1af646 | |
Vincent LAURENT | d6b6b43ac2 | |
xgaia | 7d4ae2907f | |
Gabriel Poma | 96b21fb850 | |
Gabriel Poma | 5925d4e4c6 | |
xgaia | c119d8d1cd | |
Vincent LAURENT | de66c05f2a | |
Vincent LAURENT | 9b36ae02e5 | |
Vincent LAURENT | e488e20fad | |
Vincent LAURENT | a04e26d2ce | |
Gabriel Poma | c89c8d53dd | |
Gabriel Poma | 60044ee371 | |
Gabriel Poma | c30c89ad60 | |
Gabriel Poma | 96c663493c | |
Vincent LAURENT | 841cc18ed9 | |
Gabriel Poma | 2653effbc8 | |
Vincent LAURENT | 9abd4e848b | |
Vincent LAURENT | 4b0ca82652 | |
Vincent LAURENT | 4965d7878b | |
Vincent LAURENT | b30be5b344 | |
Gabriel Poma | 8d9f49a3e8 | |
Gabriel Poma | 4f4d3a0583 | |
Vincent LAURENT | 49bf6e50ab | |
Vincent LAURENT | 6cc0461975 | |
Gabriel Poma | 784e15f252 | |
Gabriel Poma | 6bed0fdc1d | |
Vincent LAURENT | 75434aee71 | |
Vincent LAURENT | 6c28618fbe | |
Vincent LAURENT | a2843a329c | |
Vincent LAURENT | 9bdd28c7df | |
Gabriel Poma | eab2e0c127 | |
Gabriel Poma | 011c9094e1 | |
Gabriel Poma | cbea7de840 | |
Gabriel Poma | b037685c57 | |
Gabriel Poma | f9af7f969e | |
Vincent LAURENT | f0eb416ced | |
Vincent LAURENT | e2f9637426 | |
Vincent LAURENT | 186a9cc225 | |
Gabriel Poma | eec274bf0f | |
Vincent LAURENT | 9a63402a2a | |
Vincent LAURENT | 68be39ed23 | |
xgaia | c9f0d25027 | |
Vincent LAURENT | 7d7e666bc7 | |
Vincent LAURENT | f27abba511 | |
Gabriel Poma | 818f08d24c | |
Vincent LAURENT | 5a4309fcf3 | |
Vincent LAURENT | 1eb0f5101b | |
Jonas Chopin-Revel | 66cc24abd1 | |
Vincent LAURENT | 3ee536cff3 | |
Vincent LAURENT | 4f9697d409 | |
Vincent LAURENT | e9bd3f9704 | |
Vincent LAURENT | b3087129a8 | |
Gabriel Poma | 50ed5b5973 | |
Vincent LAURENT | 9cbf0107a2 | |
Gabriel Poma | 4efb3d9ad1 | |
Gabriel Poma | b979fd3d02 | |
Gabriel Poma | ea354ca8cb | |
Vincent LAURENT | a04123b846 | |
Vincent LAURENT | e7dacf528c | |
Vincent LAURENT | 36d0e0df4b | |
Vincent LAURENT | cf418043f2 | |
Vincent LAURENT | 25fdbbba58 | |
Vincent LAURENT | 9a70988f78 | |
Vincent LAURENT | c48ca163d0 | |
Vincent LAURENT | d1431afbae | |
Vincent LAURENT | c66126b865 | |
Vincent LAURENT | 7a91ab5ef5 | |
Vincent LAURENT | bf849a4d30 | |
Vincent LAURENT | 4d55c69c91 | |
Vincent LAURENT | 89a7e24e59 | |
Vincent LAURENT | f3ff57dd00 | |
Vincent LAURENT | e112050623 | |
Vincent LAURENT | 233c9a8e1e | |
Vincent LAURENT | 653036f28d | |
Vincent LAURENT | a848163e2d | |
Vincent LAURENT | ccf0afd5e2 | |
Vincent LAURENT | 692245f763 | |
Vincent LAURENT | ed36de8bd6 | |
Vincent LAURENT | 7ae60e41a8 | |
Vincent LAURENT | 3f9d8f380f | |
Vincent LAURENT | 96172423c6 | |
Vincent LAURENT | 1da99c5c29 | |
Vincent LAURENT | e45aaa4eb3 | |
Vincent LAURENT | 2e0044eb64 | |
Vincent LAURENT | 6588ff91b7 | |
Vincent LAURENT | 6c5a1f7006 | |
Vincent LAURENT | e1d6e5bdad | |
Vincent LAURENT | 3c87f0fb3d | |
Vincent LAURENT | 90def30fef | |
Vincent LAURENT | ad7439a419 | |
Vincent LAURENT | 3bc1343701 | |
Vincent LAURENT | 8fb39606b6 | |
Vincent LAURENT | 0a4cce8d56 | |
Vincent LAURENT | b193b022d7 | |
Vincent LAURENT | 09e5985258 | |
Vincent LAURENT | 7219519f0c | |
Vincent LAURENT | eb758a1b9a | |
Vincent LAURENT | ebc92a17e5 | |
Vincent LAURENT | c300673ab1 | |
Vincent LAURENT | 6aa3bfe804 | |
Vincent LAURENT | 8216041378 | |
Vincent LAURENT | e5dd867883 | |
Vincent LAURENT | 8d25b8cafa | |
battosai30 | 9cdb4bcf6b | |
Vincent LAURENT | f63d5ee0a5 | |
Vincent LAURENT | 91d526ae01 | |
Vincent LAURENT | d888334299 | |
Vincent LAURENT | 0739e2e7c3 | |
Vincent LAURENT | bc4bded932 | |
Vincent LAURENT | a3236c8368 | |
Vincent LAURENT | 9c07ef9355 | |
Vincent LAURENT | 62c5bbab91 | |
Vincent LAURENT | f0e86c61da | |
Vincent LAURENT | 8b8aa16fa7 | |
Vincent LAURENT | 60522a1c18 | |
Vincent LAURENT | 02bad421bd | |
Vincent LAURENT | ec797574eb | |
Vincent LAURENT | cfcd34829e | |
Vincent LAURENT | eb170fcd63 | |
Vincent LAURENT | 5bb3ef0f64 | |
Vincent LAURENT | 6ffcd6a346 | |
Vincent LAURENT | 8fa6b5e459 | |
Vincent LAURENT | 492e20dda0 | |
Vincent LAURENT | 02e8007ec1 | |
Vincent LAURENT | 3f2b002804 | |
Vincent LAURENT | 17b0b22f8e | |
Vincent LAURENT | a7ab90c698 | |
Vincent LAURENT | 9199e27d9d | |
Vincent LAURENT | a694d62be4 | |
Vincent LAURENT | abda679784 | |
Vincent LAURENT | 9671751ef3 | |
Vincent LAURENT | 156859993f | |
Vincent LAURENT | 9b25ef4c6e | |
Vincent LAURENT | 6198c55d3a | |
Vincent LAURENT | d842917573 | |
Vincent LAURENT | b1e7f02215 | |
Vincent LAURENT | 24437112b8 | |
Vincent LAURENT | 89a5b58961 | |
Vincent LAURENT | e6a5c45d92 | |
Vincent LAURENT | f48e78642b | |
Vincent LAURENT | 36a692e34b | |
Vincent LAURENT | abb63e8f90 | |
Vincent LAURENT | 14e8765b6e | |
Vincent LAURENT | 5b8b4872e9 | |
Vincent LAURENT | eca93fb359 | |
Vincent LAURENT | b2ca7e81f3 | |
Vincent LAURENT | b2c7b8986b | |
Vincent LAURENT | ebf7e0e889 | |
Vincent LAURENT | 3327a64774 | |
Vincent LAURENT | 39380bd641 | |
Vincent LAURENT | 4964e98a56 | |
Vincent LAURENT | f703d61f24 | |
Vincent LAURENT | 992081a882 | |
Vincent LAURENT | 8ca0115391 | |
Vincent LAURENT | a787b82d45 | |
Vincent LAURENT | f8ee51f1e9 | |
Vincent LAURENT | 62d6a7c0c3 | |
Vincent LAURENT | 8c4adb0216 | |
Vincent LAURENT | a6e9b444ce | |
Vincent LAURENT | ac8279e43e | |
Vincent LAURENT | 48107c16fd | |
Vincent LAURENT | ad93b7ccef | |
Vincent LAURENT | c7b7a3e2ee | |
Simon Chabot | 05550b03c9 | |
Vincent LAURENT | 7106b79298 | |
Vincent LAURENT | 152d54b397 | |
Vincent LAURENT | 43992abcff | |
Vincent LAURENT | 75813eefaa | |
Vincent LAURENT | 720c0ba34d | |
Vincent LAURENT | c7811b437b | |
Vincent LAURENT | 5332629032 | |
Vincent LAURENT | 4051264f78 | |
Vincent LAURENT | 4a261db73e | |
Vincent LAURENT | 0e891e1307 | |
Vincent LAURENT | 5b6d30a870 | |
Vincent LAURENT | 8ae7d12a63 | |
Vincent LAURENT | ebbad9a7e0 | |
Vincent LAURENT | 38b1dcebba | |
Vincent LAURENT | 7df937668d | |
Vincent LAURENT | 8a3ea4a4e2 | |
Vincent LAURENT | d1f41cb099 | |
Vincent LAURENT | 8fb7d1745f | |
Vincent LAURENT | a085b68904 | |
Vincent LAURENT | 52d8cf5506 | |
Vincent LAURENT | 07dd82558a | |
Vincent LAURENT | e2327a0edc | |
Vincent LAURENT | f950dd1cd8 | |
Vincent LAURENT | 915808243e | |
Vincent LAURENT | 3e4aebac44 | |
Vincent LAURENT | beb2313366 | |
Vincent LAURENT | b57bad56e2 | |
Vincent LAURENT | 35c31d3d8b | |
Jb Lm | 487434e8b9 | |
Jb Lm | 62615bd649 | |
Jb Lm | cf41808878 | |
Vincent LAURENT | a3d72959f7 | |
Vincent LAURENT | 5faf1f2a65 | |
Vincent LAURENT | 0c34e442cd | |
Jb Lm | 12ad0806da | |
Jb Lm | 0158023433 | |
Vincent LAURENT | 5a7d9f2a6a | |
Vincent LAURENT | 621e8b8145 | |
Jb Lm | fce2b8e4c3 | |
Jb Lm | 23728f11b7 | |
Jb Lm | a0505e545a | |
Jb Lm | c4bbffd3bf | |
Vincent LAURENT | 85d9250d1b | |
Jb Lm | 5e0c1abf45 | |
Jb Lm | f82ec19809 | |
Jb Lm | f884eb73ae | |
Vincent LAURENT | 7f2730decb | |
Vincent LAURENT | 3169425f4a | |
Vincent LAURENT | 1e925a38a0 | |
Vincent LAURENT | 0cc5ba6439 | |
Vincent LAURENT | c47c7728c8 | |
Vincent LAURENT | b598f1f35c | |
Vincent LAURENT | f9e64c0169 | |
Vincent LAURENT | 12e6fd770f | |
Vincent LAURENT | c0108da53a | |
Vincent LAURENT | 3665f31c11 | |
Vincent LAURENT | 5a36382e27 | |
Jb Lm | 2f2d0d09aa | |
Jb Lm | 8943efaf08 | |
Jb Lm | 7732ed4447 | |
Jb Lm | 1b4c730f40 | |
Vincent LAURENT | 82e04e5116 | |
Vincent LAURENT | 175555a06e | |
Vincent LAURENT | 9a225a261a | |
Vincent LAURENT | 8d3b494309 | |
Vincent LAURENT | 6537d1ae1c | |
Jb Lm | bb48a9acd8 | |
Jb Lm | 2f7c1f5156 | |
Jb Lm | c1c86fb881 | |
Jb Lm | 69781d8e73 | |
Jb Lm | 6f8c2fb4ed | |
Jb Lm | be2d9a794c | |
Jb Lm | b432256ccc | |
Jb Lm | 0d81740d09 | |
Jb Lm | 6c109acdbe | |
Vincent LAURENT | 814793f847 | |
Vincent LAURENT | b5d3b990fb | |
Jb Lm | 0ca295070d | |
Jb Lm | c452c32527 | |
Vincent LAURENT | b6dad1dcf5 | |
Vincent LAURENT | a41155c950 | |
Vincent LAURENT | b3c7c186a8 | |
Vincent LAURENT | eb42a1547f | |
Vincent LAURENT | 7787d46bc7 | |
Vincent LAURENT | db58c63ac3 | |
Vincent LAURENT | a31eb7852a | |
Vincent LAURENT | d51f31109f | |
Vincent LAURENT | 2bca27fd42 | |
Vincent LAURENT | 6f8f20ea08 | |
Vincent LAURENT | b5a1115cd4 | |
Vincent LAURENT | 7fe764d915 | |
Vincent LAURENT | 933f5b8e42 | |
Vincent LAURENT | 1996c661b2 | |
Vincent LAURENT | afdcbf952c | |
Vincent LAURENT | 30aa95f6ce | |
Vincent LAURENT | 2c8c9a7f8e | |
Vincent LAURENT | 253cc31d73 | |
Vincent LAURENT | 61fc267e93 | |
Vincent LAURENT | 9fc73054ab | |
Vincent LAURENT | b8f216d166 | |
Vincent LAURENT | 8495642ab8 | |
Vincent LAURENT | d2f773a91d | |
Vincent LAURENT | 97e994a1be | |
xgaia | 6af056f847 | |
xgaia | 3ec94a0ae3 | |
xgaia | 2d76302d8b | |
xgaia | 1f0022b6e1 | |
Vincent LAURENT | 2f834f6ba6 | |
Vincent LAURENT | da76ec041b | |
Vincent LAURENT | 4e47ecf18a | |
Vincent LAURENT | f54f3b6c02 | |
Vincent LAURENT | 9b34a76e54 | |
Vincent LAURENT | c20b244dba | |
Vincent LAURENT | f65f77b4aa |
|
@ -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 }}
|
|
@ -2,3 +2,4 @@ package-lock.json
|
|||
package.json
|
||||
node_modules
|
||||
tests/downloads/
|
||||
config/config.ini
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
|
@ -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}
|
|
@ -0,0 +1,3 @@
|
|||
upload_max_filesize = $UPLOAD_MAX_FILESIZE
|
||||
post_max_size = $POST_MAX_SIZE
|
||||
max_file_uploads = $MAX_FILE_UPLOADS
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
FallbackResource /index.php
|
|
@ -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
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
893
public/js/app.js
|
@ -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);
|
||||
});
|
||||
})();
|
|
@ -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();
|
||||
})
|
||||
})();
|
|
@ -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();
|
||||
}
|
||||
})();
|
|
@ -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 |
|
@ -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"; }
|
||||
|
|
|
@ -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>
|
|
@ -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"> </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>
|
|
@ -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 un 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> <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> 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>
|
|
@ -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>
|
|
@ -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 !</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>
|
|
@ -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 () => {
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
/tmp/
|
||||
/.idea/
|
|
@ -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]
|
|
@ -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,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
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
|
@ -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':
|
|
@ -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;
|
||||
}
|
|
@ -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": ["."]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[globals]
|
||||
|
||||
DEBUG=3
|
||||
UI=ui/
|
|
@ -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();
|
||||
|
||||
|
|
@ -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));
|
|
@ -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=[];
|
|
@ -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'],
|
|
@ -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
|
|
@ -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]);
|
||||
}
|
|
@ -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();
|
|
@ -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);
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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.'?>';
|
|
@ -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}}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 7.5 KiB |
|
@ -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>
|
|
@ -1,4 +0,0 @@
|
|||
<div class="content">
|
||||
<?php echo Markdown::instance()->
|
||||
convert(Base::instance()->read('readme.md')); ?>
|
||||
</div>
|
|
@ -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 © 2009-2017 F3::Factory/Bong Cosca <bong.cosca@yahoo.com></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>
|
|
@ -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();
|